天天看点

Linux设备驱动模型浅析2-- 设备及总线的注册

linux 设备驱动体系结构中,Kset和Kobject结构体,都是用来表示 sys下的目录结构的,用来形成完整的sys文件系统结构,仅此而已。在驱动体系中,最重要的三个要素无非是设备, 驱动 ,总线,三者紧密的联系在了一起,分别用device,device_driver,bus_type三个数据结构来形容,在这三个数据结构中,分别嵌套了kobject或者是kset,在我们注册设备,驱动,总线的过程中,通过这些kobject,kset形成了我们可视的sys文件系统,而在device, device_driver,bus_type中,使用其他的数据元素比如klist,knode,进一步构成了三个数据结构之间的关系。

下面来讲驱动中封装这些结构的容器。

1总线bus

struct bus_type {

     const char        *name;       //总线的名称,这个名字理论上并不是sys/bus/下的那些目录的目录名。那些目录的目录名应该是在下面变量  subsys_private p.sbusys的name变量中。但是往往那个name是由这个name赋值的,所以就一样的。但这里要明白的是(还是上面的老生常谈),表示目录是由Kset.Kobject这个东西来表示的。

     struct bus_attribute    *bus_attrs;  //根据后面的bus_add_attrs函数分析,这些个属性可能是数组

     struct device_attribute    *dev_attrs;

     struct driver_attribute    *drv_attrs;  //bus device driver的属性,一些操作导入导出的属性,等后面再分析

     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;           //power manage 的operations

     struct subsys_private *p;  //记录总线的一些私有信息,

 };

struct subsys_private {  

 struct kset subsys;  //代表该总线类型在在/sys/bus/目录下所对应的目录项    

 struct kset *devices_kset; //指向在该总线类型目录下面的devices目录项

 struct kset *drivers_kset;//指向在该总线类型目录下面的drivers目录项

 struct klist klist_devices; //下挂属于该bus的设备

 struct klist klist_drivers;//下挂属于该bus的设备驱动

 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;

 };

    这个结构体用来描述比如:/sys/bus/pci pci总线,/sys/bus/platform platform总线等。

另外:从这个结构体分析下来,整个bus的目录结构都很清楚了。

eg:

    找到总线下的设备目录: bus_type bus ---> subsys_private p---->Kset devices_kset

    找到总线下的设备驱动目录: bus_type bus ---> subsys_private p---->Kset driver_kset

另外,找到的也只是目录,因为找到的仅仅是Kset结构。

2设备device

    首先明白,device这个结构并不是直接挂在bus下的,可以到/sys/bus/platform/device下随便看一下,发现里面的都是link文件,link到/sys/device/下。所以真正的device结构体的在/sys/device下的。

struct device {

struct device *parent; //指向父设备

struct device_private *p;//device的私有数据,里面包含了该设备子设备链表的头部,以及挂入bus, driver结构设备链表的节点,类似于listhead

struct kobject kobj; //通过该kobj,device将被挂入到sys文件系统,形成一个目录

const char *init_name; //设备名

struct device_type *type;  //设备类型

struct mutex mutex;

struct bus_type *bus; //指向设备的bus type

struct device_driver *driver; //指向设备所用的driver

void *platform_data;

struct dev_pm_info power;

u64 *dma_mask;

u64 coherent_dma_mask;

struct device_dma_parameters *dma_parms;

struct list_head dma_pools;

struct dma_coherent_mem *dma_mem;

struct dev_archdata archdata;

dev_t devt;

spinlock_t devres_lock;

struct list_head devres_head;

struct klist_node knode_class;  //通过该klist_node挂入对应的class设备链表

struct class *class;//指向该klist_node所属的class

const struct attribute_group **groups;

void (*release)(struct device *dev);

};

3设备driver

struct device_driver {

const char *name;//驱动名称

struct bus_type *bus;//驱动所属总线类型

struct module *owner;

const char *mod_name;

bool suppress_bind_attrs;

#if defined(CONFIG_OF)

const struct of_device_id *of_match_table;

#endif

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;//私有数据结构,里面存放了属于该driver的设备的链表头,

     //并提供了可以将driver挂入bus_type driver链表的klist_node节点

};

   下面讲述总线和设备的注册:

1总线的注册:

       用户可以自己注册一个总线,然后将自己喜欢的设备和驱动挂载到下面。但是linux 2.6中,有个默认的总线,platform总线。我们就分析一下这个总线。

int __init platform_bus_init(void)

{

int error;

early_platform_cleanup();

error = device_register(&platform_bus);

//设备注册。哦,linux将platform也当成了一个设备,他在/sys/device目录下。当然,以后会在platform这个设备下再建立其他的设备,回顾刚才介绍device结构体时候有个parent变量,应该就是用在这里的。具体device_register这个函数,后面再介绍  

if (error)

return error;

    //总线注册

error =  bus_register(&platform_bus_type);

if (error)

device_unregister(&platform_bus);

return error;

}

int bus_register(struct bus_type *bus)

{

int retval;

struct subsys_private *priv;

//分配bus_type的私有数据

priv = kzalloc(sizeof(struct subsys_private), GFP_KERNEL);

if (!priv)

return -ENOMEM;

//建立私有数据和bus_type的关联

priv->bus = bus;

bus->p = priv;

BLOCKING_INIT_NOTIFIER_HEAD(&priv->bus_notifier);

//设置kset的name ,即总线目录的名字

retval = kobject_set_name(&priv->subsys.kobj, "%s", bus->name);

if (retval)

goto out;

//设置该总线类型目录的父目录

priv->subsys.kobj.kset = bus_kset;

//设置该总线类型目录的类型

priv->subsys.kobj.ktype = &bus_ktype;

//当设备添加时,自动检测匹配的驱动

priv->drivers_autoprobe = 1;

//将该bus_type对应的kset注册到sys文件系统中

retval = kset_register(&priv->subsys);

if (retval)

goto out;

//创建总线类型目录的属性文件

retval = bus_create_file(bus, &bus_attr_uevent);

if (retval)

goto bus_uevent_fail;

//创建属于该总线类型的devices kset,并将device kset

//

priv->devices_kset = kset_create_and_add("devices", NULL,

&priv->subsys.kobj);

if (!priv->devices_kset) {

retval = -ENOMEM;

goto bus_devices_fail;

}

//创建属于该总线类型的driver kset

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);

//在bus的目录下添加bus_attr_drivers_probe 和 bus_attr_drivers_autoprobe文件。应该是probe的时候使用。

retval = add_probe_files(bus);

if (retval)

goto bus_probe_files_fail;

//将bus的属性都建成一个文件

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;

}

到此,bus_register解释完成。

2设备的注册

int device_register(struct device *dev)

{

device_initialize(dev);

return device_add(dev);

}

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;

if (!dev->p) {

error = device_private_init(dev);

if (error)

goto done;

}

if (dev->init_name) {

dev_set_name(dev, "%s", dev->init_name);

dev->init_name = NULL;

}

if (!dev_name(dev)) {

error = -EINVAL;

goto name_error;

}

parent = get_device(dev->parent);//找到该设备的parent,该操作主要是

      //是增加父设备的引用计数

setup_parent(dev, parent);//对dev的obj中的parent赋值,赋值为父设备的

//obj,以便后续通过obj进行组织

if (parent)

set_dev_node(dev, dev_to_node(parent));

//将该设备通过kobj嵌入到了vsys系统中

error = kobject_add(&dev->kobj, dev->kobj.parent, NULL);

if (error)

goto Error;

if (platform_notify)

platform_notify(dev);

//创建设备的属性文件

error = device_create_file(dev, &uevent_attr);

if (error)

goto attrError;

if (MAJOR(dev->devt)) {

error = device_create_file(dev, &devt_attr);

if (error)

goto ueventattrError;

error = device_create_sys_dev_entry(dev);

if (error)

goto devtattrError;

devtmpfs_create_node(dev);

}

//在其他文件夹 建立link文件,这就是为什么在class目录下也能看到device的目录和文件了  

error = device_add_class_symlinks(dev);

if (error)

goto SymlinkError;

error = device_add_attrs(dev);

if (error)

goto AttrsError;

//在bus目录下 建立link文件,所以在/sys/bus/platform/device下回看到n多个link文件

error = bus_add_device(dev);

if (error)

goto BusError;

error = dpm_sysfs_add(dev);

if (error)

goto DPMError;

device_pm_add(dev);

if (dev->bus)

blocking_notifier_call_chain(&dev->bus->p->bus_notifier,

    BUS_NOTIFY_ADD_DEVICE, dev);

//产生object热插拔事件

kobject_uevent(&dev->kobj, KOBJ_ADD);

//对设备寻找相关的设备驱动程序

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);

//将dev加入到/sys/class/xxx对应的设备链表上

klist_add_tail(&dev->knode_class,

      &dev->class->p->klist_devices);

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;

}

static int device_add_class_symlinks(struct device *dev)

{

int error;

if (!dev->class)

return 0;

//在/sys/device/XXX的目录下,创建一个符号链接,链接到

//该device所属的class,for example,subsystem -> ../../../../class/net

error = sysfs_create_link(&dev->kobj,

 &dev->class->p->subsys.kobj,

 "subsystem");

if (error)

goto out;

//在/sys/device/XXX的目录下,创建一个符号链接,指向其父设备目录,for example,[email protected]:/sys/devices/pci0000:00/0000:00:11.0/0000:02:01.0/net/eth0> ls -l device

//lrwxrwxrwx 1 root root 0 Jul  5 02:11 device -> ../../../0000:02:01.0

//0000:02:01:0其实应该是其真正的设备ID

if (dev->parent && device_is_not_partition(dev)) {

error = sysfs_create_link(&dev->kobj, &dev->parent->kobj,

 "device");

if (error)

goto out_subsys;

}

#ifdef CONFIG_BLOCK

if (sysfs_deprecated && dev->class == &block_class)

return 0;

#endif

//在/sys/class/XXX的目录下,创建一个符号链接,指向该device

error = sysfs_create_link(&dev->class->p->subsys.kobj,

 &dev->kobj, dev_name(dev));

if (error)

goto out_device;

return 0;

out_device:

sysfs_remove_link(&dev->kobj, "device");

out_subsys:

sysfs_remove_link(&dev->kobj, "subsystem");

out:

return error;

}

    当然还有 drive_register的函数,其实和device_register差不多,另外,driver_register也会在最后进行probe,看有没有相应的设备。driver_register会先check这个drvier所在的bus上有没有probe函数,如果有就运行这个函数进行probe,如果没有,就运行自己的probe进行probe,这就是我们在驱动中经常看到的probe函数。

    所以,在驱动中,先运行drive_register和先运行device_register都是一样的。

继续阅读