Linux设备模型(三) 总线、设备和设备驱动

前面是设备模型的底层基础,接下来的就是对这些底层的东西的使用,构建上层容器,就像之前说的kobject很少单独使用,都是被嵌入在其他的更上层的结构中使用。


总线bus

总线是处理器和一个或多个设备之间的通道,在设备模型中, 所有的设备都通过总线相连,总线可以相互插入。设备模型展示了总线和它们所控制的设备之间的实际连接。
在 Linux 设备模型中, 总线由 struct bus_type 结构表示,这里注意,随着Linux版本的更新,struct bus_type 也在不断完善,这个结构体会随着内核变化而增减一些成员,以下是Linux3.0版本的Kernel源码。

/* include/linux/device.h */
struct bus_type {
    const char      *name;/*总线类型名称*/
    struct bus_attribute    *bus_attrs;/*总线属性*/
    struct device_attribute *dev_attrs;/*设备属性,为每个加入总线的设备建立属性链表,在注册设备时添加*/
    struct driver_attribute *drv_attrs;/*驱动属性,为每个加入总线的驱动建立属性链表,在注册驱动时添加*/

    int (*match)(struct device *dev, struct device_driver *drv);
    int (*uevent)(struct device *dev, struct kobj_uevent_env *env);
    int (*probe)(struct device *dev);
    int (*remove)(struct device *dev);
    void (*shutdown)(struct device *dev);

    int (*suspend)(struct device *dev, pm_message_t state);
    int (*resume)(struct device *dev);

    const struct dev_pm_ops *pm;

    struct subsys_private *p;/*bus的私有成员*/
};
  • name 设备总线的名称,会在sysfs中以目录出现/sys/bus/i2c

  • bus_attributedevice_attributedriver_attribute 这三个结构体是将attribute和对应的读写操作封装一起的专门针对一类的属性这个在前面举例说过kobj_attribute

  • bus_attrsdev_attrsdrv_attrs,一些默认的attribute,可以在bus、device或者device_driver添加到内核时,自动为它们添加相应的attribute

  • int (match)(struct device dev, struct device_driver * drv);
    一个由bus driver实现的回调函数,当一个新设备或者驱动被添加到这个总线时,这个方法会被调用一次或多次,若指定的驱动程序能够处理指定的设备,则返回非零值。
    必须在总线层使用这个函数, 因为那里存在正确的逻辑,内核不知道如何为每个总线类型匹配设备和驱动程序

  • int (*uevent)(struct device *dev, struct kobj_uevent_env *env);
    一个由bus driver实现的回调函数,在为用户空间产生热插拔事件之前,这个方法允许总线添加环境变量(参数和 kset 的uevent方法相同)

  • probe、remove,这两个回调函数,用于初始化和移除总线,对有些总线是必须的,但是有些总线的probe在driver中直接来实现,如i2c_bus_type,有些总线是自己做的初始化

注:并不是所有的bus都需要probe和remove接口的,因为对有些bus来说(例如platform bus),它本身就是一个虚拟的总线,无所谓初始化,直接就能使用,因此这些bus的driver就可以将这两个回调函数留空。

  • shutdown、suspend、resume,和probe、remove的原理类似,电源管理相关的实现

  • pm 电源管理相关的逻辑

  • p 一个struct subsys_private类型的指针

bus_type 的私有数据结构体 struct subsys_private

/* drivers/base/base.h  */
struct subsys_private {
    struct kset subsys;//bus内嵌的kset
    struct kset *devices_kset;

    struct kset *drivers_kset;
    struct klist klist_devices;
    struct klist klist_drivers;
    struct blocking_notifier_head bus_notifier;
    unsigned int drivers_autoprobe:1;//自动初始化驱动标志
    struct bus_type *bus;

    struct list_head class_interfaces;
    struct kset glue_dirs;
    struct mutex class_mutex;
    struct class *class;
};
  • subsys 注册进内核的kset,在sysfs中就是 在/sys/bus/目录下的新总线

  • klist_devices 指向bus下所有的device的指针

  • klist_drivers 指向bus下所有的device_driver的指针

  • class_interfaces 是一个list head,用于保存该bus下所有的interface

bus总线的操作:

之前分析过,每个总线注册会在/sys/bus/下生成一个目录,那这个sys/bus/目录已经被内核创建好了,新创建的总线只需要加载这个kset之下,在内核初始化时有一个
在kernel初始化时加载 kernel_init() --> do_basic_setup() --> driver_init() --> buses_init()

/* drivers/base/bus.c */
int __init buses_init(void)
{
    bus_kset = kset_create_and_add("bus", &bus_uevent_ops, NULL);
    if (!bus_kset)
        return -ENOMEM;
    return 0;
}

bus_kset 是内核的全局变量,之后注册的每个总线都要以这个为父节点,添加在它之下

  1. 注册总线
    接下来就是用注册总线,开始去组建设备模型这座大厦,让我们看看内核是如何一步一步创建设备模型的

    /* /include/linux/device.h

    • /drivers/base/bus.c
      */
      int bus_register(struct bus_type *bus)
      {
      int retval;
      struct subsys_private *priv;

      /* 1.为bus_type中struct subsys_private类型的指针分配空间 */
      priv = kzalloc(sizeof(struct subsys_private), GFP_KERNEL);
      if (!priv)
      return -ENOMEM;
      /* 更新priv->bus和bus->p两个指针的值 */
      priv->bus = bus;
      bus->p = priv;

      BLOCKING_INIT_NOTIFIER_HEAD(&priv->bus_notifier);
      /* 2.设置该bus的subsys->kobject的名字 ,为第1步创建的kset赋予与bus相同的name */
      retval = kobject_set_name(&priv->subsys.kobj, “%s”, bus->name);
      if (retval)
      goto out;
      /* 设置该bus的kset->kobject的父对象为 bus_kset */
      priv->subsys.kobj.kset = bus_kset;
      priv->subsys.kobj.ktype = &bus_ktype;
      priv->drivers_autoprobe = 1;//设置该标志,当有driver注册时,会自动匹配devices上的设备并用probe初始化,当有device注册时也同样找到driver并会初始化

      /* 3.完成bus的subsys的注册,即创建一个name的新目录在/sys/bus/下 */
      retval = kset_register(&priv->subsys);//调用了 kobject_uevent
      if (retval)
      goto out;
      /* 4.总线的目录下添加一个uevent属性(如/sys/bus/spi/uevent) */
      retval = bus_create_file(bus, &bus_attr_uevent);
      if (retval)
      goto bus_uevent_fail;
      /* 5.向内核添加devices和device_drivers的kset,并指定其父对象为subsys.kobj */
      priv->devices_kset = kset_create_and_add(“devices”, NULL,
      &priv->subsys.kobj);
      if (!priv->devices_kset) {
      retval = -ENOMEM;
      goto bus_devices_fail;
      }

      priv->drivers_kset = kset_create_and_add(“drivers”, NULL,
      &priv->subsys.kobj);
      if (!priv->drivers_kset) {
      retval = -ENOMEM;
      goto bus_drivers_fail;
      }
      /* */
      klist_init(&priv->klist_devices, klist_devices_get, klist_devices_put);
      klist_init(&priv->klist_drivers, NULL, NULL);
      /* 6.在bus下添加drivers_probe和drivers_autoprobe两个文件 */
      retval = add_probe_files(bus);
      if (retval)
      goto bus_probe_files_fail;
      /* 7.添加由bus_attrs指针定义的bus的默认attribute */
      retval = bus_add_attrs(bus);
      if (retval)
      goto bus_attrs_fail;

      pr_debug(“bus: ‘%s’: registered\n”, bus->name);
      return 0;

    bus_attrs_fail:
    remove_probe_files(bus);
    bus_probe_files_fail:
    kset_unregister(bus->p->drivers_kset);
    bus_drivers_fail:
    kset_unregister(bus->p->devices_kset);
    bus_devices_fail:
    bus_remove_file(bus, &bus_attr_uevent);
    bus_uevent_fail:
    kset_unregister(&bus->p->subsys);
    out:
    kfree(bus->p);
    bus->p = NULL;
    return retval;
    }
    其中kobj_type 提供了对bus类型的统一的sysfs处理接口

    static const struct sysfs_ops bus_sysfs_ops = {
    .show = bus_attr_show,
    .store = bus_attr_store,
    };
    static struct kobj_type bus_ktype = {
    .sysfs_ops = &bus_sysfs_ops,
    };
    注册一个新的总线到系统中,必须检查返回值,返回 0 表示成功创建并添加总线到系统,会在/sys/bus/目录下得到一个name的新总线

  2. 删除总线
void bus_unregister(struct bus_type *bus);

将添加的总线从系统中删除,会删除整个路径下的所有目录和文件

  1. 列举设备和驱动
int bus_for_each_dev(struct bus_type *bus, struct device *start, void *data, int (*fn)(struct device *, void *));

int bus_for_each_drv(struct bus_type *bus, struct device_driver *start, void *data, int (*fn)(struct device_driver *, void *));

这两个函数列举总线上的每个设备或驱动程序, 将关联的 device 或 device_driver 传递给 fn, 同时传递 data 值。
若 start 为 NULL, 则从第一个设备开始; 否则从 start 之后的第一个设备开始。若 fn 返回非零值, 迭代停止并且那个值从 bus_for_each_dev 或bus_for_each_drv 返回。

  1. 总线属性

在bus_type结构体中含有总线属性、设备属性、驱动属性,这其中的总线属性结构如下:

struct bus_attribute {
    struct attribute    attr;
    ssize_t (*show)(struct bus_type *bus, char *buf);
    ssize_t (*store)(struct bus_type *bus, const char *buf, size_t count);
};

struct bus_attributestruct attribute 很相似,就是对其再封装了一层,可以通过bus_attribute定义一个属性并制定单独的处理回调函数,在前面举例说过kobj_attribute

在内核源码bus_register() 中有

retval = bus_create_file(bus, &bus_attr_uevent);//添加 bus_attribute
static BUS_ATTR(uevent, S_IWUSR, NULL, bus_uevent_store);

/* 设备属性文件的实际处理函数: */
BUS_ATTR(_name,_mode,_show,_store)/*这个宏声明一个结构, 将 bus_attr_ 作为给定 _name 的前缀来创建总线的真正名称*/

/*总线的属性必须显式调用 bus_create_file 来创建:*/
int bus_create_file(struct bus_type *bus, struct bus_attribute *attr); 
/*删除总线的属性调用:*/
void bus_remove_file(struct bus_type *bus, struct bus_attribute *attr);

示例:创建一个version属性:

static ssize_t show_bus_version(struct bus_type *bus, char *buf)
{
    return snprintf(buf, PAGE_SIZE, "%s\n", Version);
}

static BUS_ATTR(version, S_IRUGO, show_bus_version, NULL); 

//创建属性文件在模块加载时完成:
if (bus_create_file(&ldd_bus_type, &bus_attr_version))
    printk(KERN_NOTICE "Unable to create version attribute\n");

对于bus_create_file这个函数,它其实是对 sysfs_create_file() 的一层封装,指明是bus所用的接口

总线的属性的添加是在 bus_register() 中完成添加的,即在总线模块创建时添加

设备device

在最底层, Linux 系统中的每个设备由一个 struct device 代表:

/* include/linux/device.h */
struct device {
    struct device       *parent;

    struct device_private   *p;

    struct kobject kobj;
    const char      *init_name; /* initial name of the device */
    const struct device_type *type;

    struct mutex        mutex;  /* mutex to synchronize calls to
                     * its driver.
                     */

    struct bus_type *bus;       /* type of bus device is on */
    struct device_driver *driver;   /* which driver has allocated this
                       device */
    void        *platform_data; /* Platform specific data, device
                       core doesn't touch it */
    struct dev_pm_info  power;
    struct dev_power_domain *pwr_domain;

#ifdef CONFIG_NUMA
    int     numa_node;  /* NUMA node this device is close to */
#endif
    u64     *dma_mask;  /* dma mask (if dma'able device) */
    u64     coherent_dma_mask;/* Like dma_mask, but for
                         alloc_coherent mappings as
                         not all hardware supports
                         64 bit addresses for consistent
                         allocations such descriptors. */

    struct device_dma_parameters *dma_parms;

    struct list_head    dma_pools;  /* dma pools (if dma'ble) */

    struct dma_coherent_mem *dma_mem; /* internal for coherent mem
                         override */
    /* arch specific additions */
    struct dev_archdata archdata;

    struct device_node  *of_node; /* associated device tree node */

    dev_t           devt;   /* dev_t, creates the sysfs "dev" */

    spinlock_t      devres_lock;
    struct list_head    devres_head;

    struct klist_node   knode_class;
    struct class        *class;
    const struct attribute_group **groups;  /* optional groups */

    void    (*release)(struct device *dev);
};
  • parent 设备的 “父” 设备,即该设备所属的设备,通常一个父设备是某种总线或者主控制器. 如果 parent 是 NULL, 则该设备是顶层设备,较少见
    在Linux内核里,都是先以某个总线命名来定义一个总线设备,与总线同名,但是其结构体不同,而是一个struct device ,如前面的 platform_bus
  • p 用于struct device的私有数据结构指针,该指针中会保存子设备链表、用于添加到bus/driver/prent等设备中的链表头等等,具体可查看源代码。

  • kobj 该数据结构对应的struct kobject,代表该设备并将其连接到结构体系中的 kobject
    注意:作为通用的规则, device->kobj->parent 应等于 device->parent->kobj

  • init_name 该设备的名称
    注意:在设备模型中,名称是一个非常重要的变量,任何注册到内核中的设备,都必须有一个合法的名称,可以在初始化时给出,也可以由内核根据”bus name + device ID”的方式创造

  • struct device_type *type 表述了与struct device之间的关系

  • bus 该device属于哪个总线
  • driver 哪个驱动程序分配了这个设备
  • platform_data 存具体的平台相关的数据,可传入一些私有数据,设备模型并不用关心该指针
  • power 电源管理相关的逻辑
  • pm_domain 电源管理相关的逻辑
  • pins 关于 “PINCTRL” 的功能
  • numa_node “NUMA”功能
  • dma_mask DMA相关的功能,如果是DMA设备
  • coherent_dma_mask DMA相关的功能
  • devt dev_t是一个32位的整数类型,表示设备号,由主设备号和次设备号组成
  • class 该设备属于那个class
  • groups 该设备的默认attribute集合。将会在设备注册时自动在sysfs中创建对应的文件
  • release() 当这个设备的最后引用被删除时,内核调用该方法; 它从被嵌入的 kobject 的 release 方法中调用。
    所有注册到核心的设备结构必须有一个 release 方法, 否则内核将打印错误信息。

注:在注册 struct device 前,最少要设置parent, bus_id, bus, 和 release 成员

struct device的私有数据结构
/* driver/base/base.h */
struct device_private {
    struct klist klist_children;//子集结构 
    struct klist_node knode_parent;//父级挂接点
    struct klist_node knode_driver;//driver挂接点
    struct klist_node knode_bus;//bus挂接点
    void *driver_data;//自定义数据
    struct device *device;//回指向所属的device
};

struct device 的设备类型结构体
/* include/linux/device.h */
struct device_type {
    const char *name;
    const struct attribute_group **groups;
    int (*uevent)(struct device *dev, struct kobj_uevent_env *env);
    char *(*devnode)(struct device *dev, mode_t *mode);
    void (*release)(struct device *dev);

    const struct dev_pm_ops *pm;
};

device_type的功能包括:
- name 表示该类型的名称,当该类型的设备添加到内核时,内核会发出 “DEVTYPE=‘name’” 类型的uevent,告知用户空间某个类型的设备available了
- groups 该类型设备的公共attribute集合。设备注册时,会同时注册这些attribute。这就是面向对象中“继承”的概念
- uevent 同理,所有相同类型的设备,会有一些共有的uevent需要发送,由该接口实现
- devnode 与devtmpfs有关的内容
- release 如果device结构没有提供release接口,就要查询它所属的type是否提供。用于释放device变量所占的空间,当这个设备的最后引用被删除时,内核调用该方法(具体在哪调用?怎么调用?怎么删除?); 它从被嵌入的 kobject 的 release 方法中调用。所有注册到核心的设备结构必须有一个 release 方法, 否则内核将打印错误信息

  1. 注册和注销设备
    /* driver/base/core.c */
    int device_register(struct device *dev);
    void device_unregister(struct device *dev);

    一个实际的总线也是一个设备,所以必须单独注册,看一下platform平台设备的注册

    struct device platform_bus = {
    .init_name = “platform”,//这里的platform_bus是虚拟总线,不需要release
    };

    error = device_register(&platform_bus);
    if (error)
    return error;

    //注册一个设备的代码
    int device_register(struct device *dev)
    {
    device_initialize(dev);//先初始化相关的结构体和内部关联
    return device_add(dev);//添加设备到系统,调用kobject_uevent 触发一个设备添加事件
    }

    void device_initialize(struct device *dev)
    {
    dev->kobj.kset = devices_kset;//devices_kset是在内核初始化时已经创建的devices,即指定其上一级目录
    kobject_init(&dev->kobj, &device_ktype);
    INIT_LIST_HEAD(&dev->dma_pools);
    mutex_init(&dev->mutex);
    lockdep_set_novalidate_class(&dev->mutex);
    spin_lock_init(&dev->devres_lock);
    INIT_LIST_HEAD(&dev->devres_head);
    device_pm_init(dev);
    set_dev_node(dev, -1);
    }
    /**

    • device_add - add device to device hierarchy.
    • @dev: device.
      */
      int device_add(struct device *dev)
      {
      struct device *parent = NULL;
      struct class_interface *class_intf;
      int error = -EINVAL;

      dev = get_device(dev);
      if (!dev)
      goto done;

      /* 1.device设备的私有数据private申请空间并回指device */
      if (!dev->p) {
      error = device_private_init(dev);
      if (error)
      goto done;
      }

      /*

      • for statically allocated devices, which should all be converted
      • some day, we need to initialize the name. We prevent reading back
      • the name, and force the use of dev_name()
        */
        if (dev->init_name) {
        dev_set_name(dev, “%s”, dev->init_name);//设置设备的name
        dev->init_name = NULL;
        }

      if (!dev_name(dev)) {
      error = -EINVAL;
      goto name_error;
      }

      pr_debug(“device: ‘%s’: %s\n”, dev_name(dev), func);
      /* 2.获取device的父节点 */
      parent = get_device(dev->parent);
      setup_parent(dev, parent);

      /* use parent numa_node */
      if (parent)
      set_dev_node(dev, dev_to_node(parent));//dev->numa_node = dev->parent->numa_node

      /* first, register with generic layer. */
      /* we require the name to be set before, and pass NULL */
      /* 3.初始化dev的kobj并与其父节点连接 */
      error = kobject_add(&dev->kobj, dev->kobj.parent, NULL);
      if (error)
      goto Error;

      /* notify platform of device entry */
      if (platform_notify)
      platform_notify(dev);
      /* 4.创建uevent属性文件 */
      error = device_create_file(dev, &uevent_attr);
      if (error)
      goto attrError;

      if (MAJOR(dev->devt)) {
      error = device_create_file(dev, &devt_attr);//创建dev属性文件
      if (error)
      goto ueventattrError;

      /* 5.创建在/dev/char/下的链接 */
      error = device_create_sys_dev_entry(dev);
      if (error)
          goto devtattrError;
      
      devtmpfs_create_node(dev);
      

      }

      /* 6.创建在/sys/class/下的链接 */
      error = device_add_class_symlinks(dev);
      if (error)
      goto SymlinkError;
      /* 7.添加属性文件 */
      error = device_add_attrs(dev);
      if (error)
      goto AttrsError;
      /* 8.在相关总线下创建设备的链接文件 */
      error = bus_add_device(dev);
      if (error)
      goto BusError;
      /* 9.电源管理相关 */
      error = dpm_sysfs_add(dev);
      if (error)
      goto DPMError;
      device_pm_add(dev);

      /* Notify clients of device addition. This call must come

      • after dpm_sysf_add() and before kobject_uevent().
        */
        if (dev->bus)
        blocking_notifier_call_chain(&dev->bus->p->bus_notifier,
        BUS_NOTIFY_ADD_DEVICE, dev);
        /* 10.添加设备uevent事件 */
        kobject_uevent(&dev->kobj, KOBJ_ADD);

      /* 11.为新添加的设备自动匹配driver */
      bus_probe_device(dev);
      if (parent)
      klist_add_tail(&dev->p->knode_parent,
      &parent->p->klist_children);//把该设备的节点挂到其父节点的链表

      if (dev->class) {
      mutex_lock(&dev->class->p->class_mutex);
      /* tie the class to the device */
      klist_add_tail(&dev->knode_class,
      &dev->class->p->klist_devices);//把该设备的节点挂到class的设备链表

      /* notify any interfaces that the device is here */
      list_for_each_entry(class_intf,
                  &dev->class->p->class_interfaces, node)
          if (class_intf->add_dev)
              class_intf->add_dev(dev, class_intf);
      mutex_unlock(&dev->class->p->class_mutex);
      

      }
      done:
      put_device(dev);
      return error;
      DPMError:
      bus_remove_device(dev);
      BusError:
      device_remove_attrs(dev);
      AttrsError:
      device_remove_class_symlinks(dev);
      SymlinkError:
      if (MAJOR(dev->devt))
      devtmpfs_delete_node(dev);
      if (MAJOR(dev->devt))
      device_remove_sys_dev_entry(dev);
      devtattrError:
      if (MAJOR(dev->devt))
      device_remove_file(dev, &devt_attr);
      ueventattrError:
      device_remove_file(dev, &uevent_attr);
      attrError:
      kobject_uevent(&dev->kobj, KOBJ_REMOVE);
      kobject_del(&dev->kobj);
      Error:
      cleanup_device_parent(dev);
      if (parent)
      put_device(parent);
      name_error:
      kfree(dev->p);
      dev->p = NULL;
      goto done;
      }
      /* 自动匹配设备驱动 */
      void bus_probe_device(struct device *dev)
      {
      struct bus_type *bus = dev->bus;
      int ret;

      if (bus && bus->p->drivers_autoprobe) {//设置了自动匹配初始化标志就会开始自动匹配
      ret = device_attach(dev);
      WARN_ON(ret < 0);
      }
      }
      /* 为设备连接驱动 */
      int device_attach(struct device *dev)
      {
      int ret = 0;

      device_lock(dev);
      if (dev->driver) {
      if (klist_node_attached(&dev->p->knode_driver)) {
      ret = 1;
      goto out_unlock;
      }
      ret = device_bind_driver(dev);
      if (ret == 0)
      ret = 1;
      else {
      dev->driver = NULL;
      ret = 0;
      }
      } else {
      pm_runtime_get_noresume(dev);
      ret = bus_for_each_drv(dev->bus, NULL, dev, __device_attach);//这个函数就是迭代匹配__device_attach这个函数的内容
      pm_runtime_put_sync(dev);
      }
      out_unlock:
      device_unlock(dev);
      return ret;
      }

    __device_attach 正好就是回调了之前在创建dev时初始化的match probe 回调函数

    static int __device_attach(struct device_driver *drv, void *data)
    {
    struct device *dev = data;

    if (!driver_match_device(drv, dev))//回调drv->bus->match 函数判断是否设备名和驱动名是否匹配
        return 0;
    
    return driver_probe_device(drv, dev);//回调drv->bus->probe 函数执行初始化
    

    }

    这部分的代码需要结合device_driver部分的内容,不过在后边也会对设备和设备驱动的匹配做完整分析

    device_register() 的特殊之处是先初始化且指定了创建的设备的kobj->kset为内核初始化时已经创建的全局设备节点 devices_kset
    所以如果是创建一个总线设备,直接使用这个函数创建很方便和直接
    如果是在为其他总线添加一个设备,则需要手动初始化并使用device_add手动指定从属关系并添加

  2. 设备属性

    /* include/linux/device.h */
    /* interface for exporting device attributes */
    struct device_attribute {
    struct attribute attr;
    ssize_t (*show)(struct device *dev, struct device_attribute *attr,
    char *buf);
    ssize_t (*store)(struct device *dev, struct device_attribute *attr,
    const char *buf, size_t count);
    };

设备属性文件的实际处理函数:

int device_create_file(struct device *device, struct device_attribute * entry);
void device_remove_file(struct device * dev, struct device_attribute * attr);

创建属性文件在模块加载时完成,所以设备的属性是在设备模块被创建时完成的

int device_add(struct device *dev)
{
    ...

}

这个device_add()是创建一个设备所必须的接口,所以设备属性的创建相应的也就会在这个接口里来完成创建。

注:设备和总线在这里就有些许差别,总线也就算是一种特殊的设备,它只被注册一次,就是在系统初始化的时候,或一个新的总线被创建时,之后都是在这个总线上添加设备
所以这个设备添加就是每创建一个新的设备时都会创建相应的目录和文件在设备目录下

  1. 接口
    device_create(struct class *cls, struct device *parent,
    dev_t devt, void *drvdata,
    const char *fmt, …)

用已经创建的类来创建一个设备,这种创建接口需要的前置条件比较多,主要是cls

创建一个设备

kalloc()
device_register();
device_add();

移除通过device_create创建的设备

void device_destroy(struct class *class, dev_t devt);

示例代码:

#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/device.h>
#include <linux/err.h>
#include <linux/slab.h>

static struct device *my_device;

static my_device_attr_show(struct device *dev, struct device_attribute *attr,
        char *buf)
{
    printk(KERN_ALERT "%s\n",__FUNCTION__);
    sprintf(buf, "%s\n", "T20180203");
    return 0;
}
#if 1
const struct device_attribute dev_attr_dev = {
    .attr = {.name = "version",
             .mode = 0666,
    },
    .show = my_device_attr_show,
    .store = NULL,
};
#else
static DEVICE_ATTR(dev, S_IRUGO, my_device_attr_show, NULL);
#endif
static void my_device_release(struct device *dev)
{
    printk(KERN_EMERG "%s\n",__FUNCTION__);
    return;
}
const static struct device_type dev_type = {
    .release = my_device_release,
};
static int __init my_device_init(void)
{
    int ret = 0;
    printk(KERN_EMERG "%s\n",__FUNCTION__);

    my_device = kzalloc(sizeof(struct device),GFP_KERNEL);
    if(IS_ERR(my_device))
    {
        printk(KERN_EMERG "device_kzalloc error\n");
        goto device_kzalloc_fail;
    }
    my_device->init_name = "my_device";
    my_device->type = &dev_type;

    ret = device_register(my_device);
    if(ret)
    {
        printk(KERN_ALERT "device_register error\n");
        goto device_register_fail;

    }

    ret = device_create_file(my_device,&dev_attr_dev);
    if(ret)
    {
        printk(KERN_ALERT "device_create_file error");
        goto device_create_file_fail;

    }

    printk(KERN_ALERT "device_register OK!\n");
    return 0;
device_create_file_fail:
    device_unregister(my_device);
device_register_fail:
    kfree(my_device);
device_kzalloc_fail:
    return ret;
}
static void __exit my_device_exit(void)
{
    printk(KERN_EMERG "%s\n",__FUNCTION__);
    device_remove_file(my_device,&dev_attr_dev);
    device_unregister(my_device);
    kfree(my_device);
}

module_init(my_device_init);
module_exit(my_device_exit);

MODULE_LICENSE("Dual BSD/GPL");

设备驱动

设备模型跟踪所有对系统已知的驱动. 这个跟踪的主要原因是使驱动核心能匹配驱动和新设备。
一旦驱动在系统中是已知的对象, 但是, 许多其他的事情变得有可能. 设备驱动可输出和任何特定设备无关的信息和配置变量, 
struct device_driver {
    const char      *name;
    struct bus_type     *bus;

    struct module       *owner;
    const char      *mod_name;  /* used for built-in modules */

    bool suppress_bind_attrs;   /* disables bind/unbind via sysfs */

    const struct of_device_id   *of_match_table;

    int (*probe) (struct device *dev);
    int (*remove) (struct device *dev);
    void (*shutdown) (struct device *dev);
    int (*suspend) (struct device *dev, pm_message_t state);
    int (*resume) (struct device *dev);
    const struct attribute_group **groups;

    const struct dev_pm_ops *pm;

    struct driver_private *p;
};

感觉 device_driver 结构体简单了许多,在早期的内核版本中driver的数据结构为 “struct driver”,不知道从哪个版本开始,就改成”struct device_driver”了,可能是为了表明是设备驱动

  • name 该driver的名称,和device结构一样,该名称非常重要,后面会再详细说明
  • bus 该driver所驱动设备的总线类型。为什么driver需要记录总线设备的指针呢?因为内核要保证在driver运行前,设备所依赖的总线能够正确初始化
  • owner 內核module相关的变量,表明其所属者
  • mod_name 编译时模块的name
  • suppress_bind_attrs 是否在sysfs中启用bind和unbind attribute属性,如果使能,则在sysfs中对应驱动下创建 bind 和 unbind 属性文件
    在kernel中,bind/unbind是从用户空间手动的为driver绑定/解绑定指定的设备的机制。这种机制是在bus.c中完成的
  • probe probe 是一个函数被调用来查询一个特定设备的存在(以及这个驱动是否可以使用它)
  • remove 当设备从系统中去除时被调用
  • shutdown 在关闭时被调用来关闭设备
  • suspend、resume、pm 电源管理相关的内容
  • groups 和struct device 结构中的同名变量类似,driver也可以定义一些默认attribute,这样在将driver注册到内核中时,会自动将这些attribute添加到sysfs中。
  • driver_private *p driver的私有数据指针

    struct device_driver的私有数据结构
    /* driver/base/base.h */
    struct driver_private {
    struct kobject kobj;//device_driver内嵌的kobject
    struct klist klist_devices;//可以操控的设备链表
    struct klist_node knode_bus;//挂接到bus的节点
    struct module_kobject *mkobj;//模块相关
    struct device_driver *driver;//回指该driver
    };

    1. 注册

    /* driver/base/driver.c */
    int driver_register(struct device_driver *drv)
    {
    int ret;
    struct device_driver *other;

    BUG_ON(!drv->bus->p);
    
    if ((drv->bus->probe && drv->probe) ||
        (drv->bus->remove && drv->remove) ||
        (drv->bus->shutdown && drv->shutdown))
        printk(KERN_WARNING "Driver '%s' needs updating - please use "
            "bus_type methods\n", drv->name);
    /* 查找驱动是否已经被注册 */
    other = driver_find(drv->name, drv->bus);
    if (other) {
        put_driver(other);
        printk(KERN_ERR "Error: Driver '%s' is already registered, "
            "aborting...\n", drv->name);
        return -EBUSY;
    }
    /* 将驱动加入到对应的bus */
    ret = bus_add_driver(drv);
    if (ret)
        return ret;
    ret = driver_add_groups(drv, drv->groups);
    if (ret)
        bus_remove_driver(drv);
    return ret;
    

    }
    /**

    • bus_add_driver - Add a driver to the bus.
    • @drv: driver.
      */
      int bus_add_driver(struct device_driver *drv)
      {
      struct bus_type *bus;
      struct driver_private *priv;
      int error = 0;

      bus = bus_get(drv->bus);//取得其所在bus的指针
      if (!bus)
      return -EINVAL;

      pr_debug(“bus: ‘%s’: add driver %s\n”, bus->name, drv->name);

      /* 为驱动的kobject申请空间 */
      priv = kzalloc(sizeof(*priv), GFP_KERNEL);
      if (!priv) {
      error = -ENOMEM;
      goto out_put_bus;
      }
      klist_init(&priv->klist_devices, NULL, NULL);//设备操作函数清空,设备链表初始化
      /* 建立层次结构 */
      priv->driver = drv;
      drv->p = priv;
      priv->kobj.kset = bus->p->drivers_kset;
      /* 初始化并添加drv的kobject */
      error = kobject_init_and_add(&priv->kobj, &driver_ktype, NULL,
      “%s”, drv->name);
      if (error)
      goto out_unregister;
      /* 判断bus是否设置了自动匹配初始化标志,开始自动匹配,与前边设备注册类似 */
      if (drv->bus->p->drivers_autoprobe) {
      error = driver_attach(drv);
      if (error)
      goto out_unregister;
      }
      /* 把driver接入到bus的klist_drivers */
      klist_add_tail(&priv->knode_bus, &bus->p->klist_drivers);
      module_add_driver(drv->owner, drv);//模块添加

      /* 添加driver的uevent属性文件 */
      error = driver_create_file(drv, &driver_attr_uevent);
      if (error) {
      printk(KERN_ERR “%s: uevent attr (%s) failed\n”,
      func, drv->name);
      }
      /* 添加bus的driver相关属性文件 */
      error = driver_add_attrs(bus, drv);
      if (error) {
      /* How the hell do we get out of this pickle? Give up */
      printk(KERN_ERR “%s: driver_add_attrs(%s) failed\n”,
      func, drv->name);
      }
      /* 根据bind标志,添加bind 相关的属性 */
      if (!drv->suppress_bind_attrs) {
      error = add_bind_files(drv);
      if (error) {
      /* Ditto */
      printk(KERN_ERR “%s: add_bind_files(%s) failed\n”,
      func, drv->name);
      }
      }
      //触发uevent上报
      kobject_uevent(&priv->kobj, KOBJ_ADD);
      return 0;

    out_unregister:
    kobject_put(&priv->kobj);
    kfree(drv->p);
    drv->p = NULL;
    out_put_bus:
    bus_put(bus);
    return error;
    }

    /* 判断bus是否设置了自动匹配初始化标志,开始自动匹配,与前边设备注册类似 */
    int driver_attach(struct device_driver *drv)
    {
    return bus_for_each_dev(drv->bus, NULL, drv, __driver_attach);
    }

    /* 这部分的代码和之前在设备注册的一样的,都会调用初始化总线的probe去初始化驱动 */
    static int __driver_attach(struct device *dev, void *data)
    {
    struct device_driver *drv = data;

    /*
     * Lock device and try to bind to it. We drop the error
     * here and always return 0, because we need to keep trying
     * to bind to devices and some drivers will return an error
     * simply if it didn't support the device.
     *
     * driver_probe_device() will spit a warning if there
     * is an error.
     */
    
    if (!driver_match_device(drv, dev))//回调drv->bus->match 函数判断是否设备名和驱动名是否匹配
        return 0;
    
    if (dev->parent)    /* Needed for USB */
        device_lock(dev->parent);
    device_lock(dev);
    if (!dev->driver)
        driver_probe_device(drv, dev);//回调drv->bus->probe 函数执行初始化
    device_unlock(dev);
    if (dev->parent)
        device_unlock(dev->parent);
    
    return 0;
    

    }

    1. 设备驱动属性

    struct driver_attribute {
    struct attribute attr;
    ssize_t (*show)(struct device_driver *driver, char *buf);
    ssize_t (*store)(struct device_driver *driver, const char *buf,
    size_t count);
    };

    设备驱动属性结构可在编译时建立, 使用以下宏

    define DRIVER_ATTR(_name, _mode, _show, _store);

    其实对于这个宏,也就内核喜欢使用,如果在平时开发中,基本都是直接赋值结构体

    设备驱动属性文件的实际处理使用以下函数:
    int driver_create_file(struct device_driver *drv, const struct driver_attribute *attr);
    void driver_remove_file(struct device_driver *drv, const struct driver_attribute *attr);

    1. 设备驱动的 probe

所谓的”probe”,是指在Linux内核中,如果存在相同名称的device和device_driver(注:还存在其它方式),内核就会执行device_driver中的probe回调函数。
而该函数就是所有driver的入口,可以执行诸如硬件设备初始化、字符设备注册、设备文件操作ops注册等动作
“remove”是它的反操作,发生在device或者device_driver任何一方从内核注销时,其原理类似。

那么问题来了,这个 probe 回调函数是在什么时候被调用的???
    1.将struct device类型的变量注册到内核中时 device_register 中就有一次被调用
    2.将struct device_driver类型的变量注册到内核中时 device_register 中就有一次被调用
    3.手动查找同一bus下的所有device_driver,如果有和指定device同名的driver,执行probe操作(device_attach)
    4.手动查找同一bus下的所有device,如果有和指定driver同名的device,执行probe操作(driver_attach)
    5.自行调用driver的probe接口,并在该接口中将该driver绑定到某个device结构中----即设置 dev->driver ( device_bind_driver )

每个bus都有一个drivers_autoprobe变量,用于控制是否在device或者driver注册时,自动probe。
该变量默认为1(即自动probe),bus模块将它开放到sysfs中了,因而可在用户空间修改,进而控制probe行为。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值