总线驱动设备

Linux设备模型使用了三个数据结构分别来描述总线、设备和驱动。所有的设备和对应的驱动都必须挂载在某一个总线上,通过总线,可以绑定设备和驱动。
这个属于分离的思想,将设备和驱动分开管理。

1、bus 总线

总线是处理器和设备之间的通道。总线有多种类型,每种总线可以挂载多个设备。
在设备模型中,所有的设备都通过总线相连,以总线来管理设备和驱动函数。总线有bus_type结构表示。

/*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);
总线属性添加和删除:

设置总线的属性后,会在对应的总线目录下增加了一个新的文件,通过对该文件的读写访问,触发相应的函数操作,从而实现/sys/的文件接口与内核设备模型的数据交互。

 struct attribute {
 const char *name; //设定该文件的名字
 struct module *owner; //设定该文件的属主
 mode_t mode; //设定该文件的文件操作权限
 };
/*linux/device.h*/
 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);
 };

bus_attribute中有两个函数指针,show和store。

设置总线属性有两个步骤:
1、创建并初始化bus_attribute结构

使用宏BUS_ATTR
BUS_ATTR(_name, _mode, _show, _store)
该宏会定义一个名叫bus_attr__name的bus_attibute的结构,并且成员name设置为_name,文件权限mode设置为_mode,两个函数调用分别人show和store。

2、将bus_attibute添加到指定的总线上

使用以下调用:

 int bus_create_file(struct bus_type *bus, struct bus_attribute *attr)

该函数失败时返回错误号。
一旦调用该函数,会就在指定bus总线的目录下新建一个名叫_name的文件

2、driver

驱动程序是在CPU运行时,提供操作的软件接口。所有的设备必须有与之配套驱动程序才能正常工作。一个驱动程序可以驱动多个类似或者完全不同的设备。

/*linux/device.h*/
struct device_driver {
 const char *name; //驱动函数的名字,在对应总线的driver目录下显示
 struct bus_type *bus; //指定该驱动程序所操作的总线类型,必须设置,不然会注册失败

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

 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);
 struct attribute_group **groups;

 struct dev_pm_ops *pm;

 struct driver_private *p;
 };

和设备不一样的是,在注册驱动函数是必须指定该驱动函数对应的总线,因为驱动函数注册成功后,会存放在对应总线的driver目录下,如果没有总线,注册当然会失败。

注册驱动

1、定义结构体device_driver。

2、调用注册函数:

 int driver_register(struct device_driver *drv)
函数失败返回非零,需要判断返回值来检查注册是否成功。

驱动函数属性:

 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);
 };
3、device

设备就是连接在总线上的物理实体。设备是有功能之分的。具有相同功能的设备被归到一个类,如输入设备(鼠标,键盘,游戏杆等)。

linux系统中每个设备都用一个device结构的表示

struct device {
 struct klist klist_children;
 struct klist_node knode_parent; /* node in sibling list */
 struct klist_node knode_driver;
 struct klist_node knode_bus;
 struct device *parent; //指定该设备的父设备,如果不指定(NULL),注册后的设备目录
 ///在sys/device下
 struct kobject kobj;
 char bus_id[BUS_ID_SIZE]; /* position on parent bus */ //在总线生识别设备的字符串,
 //同时也是设备注册后的目录名字。
 struct bus_type *bus; /* type of bus device is on */ //指定该设备连接的总线
 struct device_driver *driver; /* which driver has allocated this
 device */ //管理该设备的驱动函数
 void *driver_data; /* data private to the driver */ //驱动程序的私有数据
 struct dev_pm_info power;
 void (*release)(struct device *dev); //当给设备的最后一个引用被删除时,调用该函数
 };
注册设备

注册一个完整的device结构前,至少定义parrent、bus_id、bus和release成员

1、定义结构体device。

2、调用注册函数:

int device_register(struct device *dev)
函数失败返回非零,需要判断返回值来检查注册是否成功。
设备属性:
/*linux/device.h*/
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);
 };
1. 创建并初始化device_attribute结构

使用宏DEVICE_ATTR
DRIVER_ATTR(_name, _mode, _show, _store)
该宏会定义一个名叫driver_attr__name的driver_attibute的结构,并且成员name设置为_name,文件权限mode设置为_mode,两个函数调用分别人show和store。

2、将device_attibute添加到指定的设备上,使用以下调用:
/*drivers/base/core.c*/
int device_create_file(struct device *dev, struct device_attribute *attr)
该函数失败时返回错误号。

一旦调用该函数,会就在指定dev设备的目录下新建一个名叫_name的文件.

sysfs文件系统

设备模型是2.6内核新引入的特征。设备模型提供了一个独立的机制专门来表示设备,并描述其在系统中的拓扑结构。
sys下有如下目录

1.block

block:用于管理块设备,系统中的每一个块设备会在该目录下对应一个子目录。

2.bus

bus:用于管理总线,每注册一条总线,在该目录下有一个对应的子目录。

    platform , i2c ,spi ,usb, sdio ...

其中,每个总线子目录下会有两个子目录:devices和drivers。 devices包含里系统中所有属于该总线的的设备。drivers包含里系统中所有属于该总线的的驱动。

3.class

class:将系统中的设备按功能分类。

     leds, misc,input,...
4.devices

devices:该目录提供了系统中设备拓扑结构图。

5.dev

dev:该目录已注册的设备节点的视图。


clipboard.png
root root 0 2011-01-31 10:11 class/net/eth0 -> ../../devices/pci0000:00/0000:00:1c.5/0000:86:00.0/net/eth0/

class/net/eth0的路径其实就是devices目录中一个网卡设备的软连接。

sys中的其他目录都是将device目录下的数据加以转换加工而得。上面的图中,将usb设备归类到bus总线上,又把它归类到class。正是在sys中有很多这样的结构,内核就有一个完整而且复杂的拓扑结构图。
而维护这些关系的结构体就包括kobject、kset、ktype和subsystem等数据结构

kobject ,kset和ktype

kobject

kobject是一个对象的抽象,它用于管理对象。每个kobject对应着sysfs中的一个目录。
kobject用struct kobject来描述。

kset

kset是一些kobject的集合,这些kobject可以有相同的ktype,也可以不同。同时,kset自己也包含一个kobject。在sysfs中,kset也是对应这一个目录,但是目录下面包含着其他的kojbect。

ktype

每个kobject对象都内嵌有一个ktype,该结构定义了kobject在创建和删除时所采取的行为。

clipboard1.png

匹配过程

device和driver里面都有一个成员变量bus,表示它们归哪个总线管理;

bus里面则有两个链表,device链表和driver链表。

当有新的设备加入的时候,就会将它加入它对应的bus的device链表,然后在它的驱动链表中寻找是否有驱动driver和该device匹配成功,如果匹配成功设备就可以正常使用了,否则,不好意思继续等待。

当有新的驱动加入的时候,就会将它加入它对应的bus的driver链表,然后在它的设备链表中寻找是否有设备device和该driver匹配成功,如果成功设备就可以正常使用了

device_add
int device_add(struct device *dev)  
        dev = get_device(dev);
        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->kobj->name 的名字  
            dev->init_name = NULL;  
        }  
      
        if (!dev_name(dev) && dev->bus && dev->bus->dev_name)       
            dev_set_name(dev, "%s%u", dev->bus->dev_name, dev->id);//使用bus以及设备id来初始化设备内部kobject名字,一般dev->init_name 
       //设置成功后,dev_name(dev)返回dev->kobj->name,if条件不成立,不执行
        if (!dev_name(dev)) {//获得设备的名字  
            error = -EINVAL;  
            goto name_error;  
        }  
      
        parent = get_device(dev->parent);增加设备父设备 ,例如:csid的设备节点节v4l-subdev4的父设备是fd8c0000.qcom,msm-cam
        kobj = get_device_parent(dev, parent);  
       
        if (kobj)  
            dev->kobj.parent = kobj;//在kobject层实现设备父子关系  
      
        if (parent)  
            set_dev_node(dev, dev_to_node(parent));  //设置该设备节点为-1,一般未注册前默认为-1
        error = kobject_add(&dev->kobj, dev->kobj.parent, NULL);//把内嵌的kobject注册到设备模型中将设备加入到kobject模型中,创建sys相关目录 ,目录名字为kobj->name 
          
        /* notify platform of device entry */  
        if (platform_notify)  
            platform_notify(dev);  
      
        error = device_create_file(dev, &uevent_attr);//创建sys目录下设备的uevent属性文件,通过它可以查看设备的uevent事件  在/sys/devices/.../中添加dev的uevent属性文件



        if (MAJOR(dev->devt)) {    
            error = device_create_file(dev, &devt_attr);//创建sys目录下设备的设备号属性,即major和minor /主要是在sys/devices/...中添加dev属性文件


            error = device_create_sys_dev_entry(dev);  //在/sys/dev/char/或者/sys/dev/block/创建devt的属性的连接文件,形如10:45,由主设备号和次设备号构成,指向/sys/devices/.../的具体设备目录,该链接文件只具备读属性,显示主设备号:次设备号,如10:45,用户空间udev相应uevent事件时,将根据设备号在/dev下创建节点文件
            devtmpfs_create_node(dev);  
        }  
        error = device_add_class_symlinks(dev);  //创建类符号链接//相互创建dev和class之间的链接文件

   
 r = device_add_attrs(dev);//创建sys目录下设备其他属性文件  //添加设备属性文件

        error = bus_add_device(dev);//将设备加入到管理它的bus总线的设备连表上  创建subsystem链接文件,链接class下的具体的子系统文件夹
        error = dpm_sysfs_add(dev);//电源管理相关  
        device_pm_add(dev);   //添加设备到激活设备列表中,用于电源管理
      
        /* Notify clients of device addition.  This call must come 
         * after dpm_sysfs_add() and before kobject_uevent(). 
         */  
        if (dev->bus)  
            blocking_notifier_call_chain(&dev->bus->p->bus_notifier,BUS_NOTIFY_ADD_DEVICE, dev);//通知注册监听该总线的设备,有新设备加入  
    执行bus通知链上的注册函数,由设备注册上来
        kobject_uevent(&dev->kobj, KOBJ_ADD);//产生一个内核uevent事件,该事件可以被内核以及应用层捕获,属于linux设备模型中热插拔机制  
    //产生一个KOBJ_ADD的uevent事件,通过netlink机制和用户空间通信
        bus_probe_device(dev);//------------给设备探测相应的驱动开始寻找设备所对应的驱动------------  去bus上找dev对应的drv,主要执行__device_attach,主要进行match,sys_add,执行probe函数和绑定等操作
        if (parent)  
            klist_add_tail(&dev->p->knode_parent, &parent->p->klist_children);////把设备添加到父设备的children列表中
    建立设备与总线间的父子关系, 如果设备有父设备,将它加入parent的子设备链中 */  
        if (dev->class) {//如果设备的属于某个设备类,比如Mass storage,HID等等  
            mutex_lock(&dev->class->p->mutex);  如果改dev有所属类,则将dev的添加到类的设备列表里面
            /* 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->interfaces, node)  
                if (class_intf->add_dev)  
                    class_intf->add_dev(dev, class_intf);//通知有新设备加入  ,//执行改dev的class_intf->add_dev(),这个有个好处,就是只有设备匹配注册成功了,才进行其它的注册工作(如字符设备的注册,生成/dev/***节点文件)以及部分初始化工作。
            mutex_unlock(&dev->class->p->mutex);  
        }  

bus_add_device()

 int bus_add_device(struct device *dev)      {  
             struct bus_type *bus =bus_get(dev->bus);  
       
             if (bus) {  
                       /* 创建相应的属性文件 */  
                       error = device_add_attrs(bus,dev);  
    /* 在sys/bus/总线类型/devices/dev_name()dev  在devices目录下创建名字为devname(d)
    指向sys/devices/相同设备名字的 符号链接*/  
                       error =sysfs_create_link(&bus->p->devices_kset->kobj,
     &dev->kobj,dev_name(dev));  
    /* 在sys/devices/设备名字/目录下创建目录名字为 subsystem 并且指向在sys/bus/总线类型/devices/
    de符号链接*/
                       error =sysfs_create_link(&dev->kobj,  
                                         &dev->bus->p->subsys.kobj,"subsystem");  
                       /* 把设备加入到总线的设备链表中,这步才是重点*/  
                       klist_add_tail(&dev->p->knode_bus,&bus->p->klist_devices);  
             }  
    }  

bus_probe_device

    //为设备找到一个驱动  
    void bus_probe_device(struct device *dev)  
    {  
        struct bus_type *bus = dev->bus;//获得设备的隶属的总线,该值在设备初始化时设置  
        struct subsys_interface *sif;  
        int ret;  
        if (!bus)  
            return;  
        if (bus->p->drivers_autoprobe) {  
            ret = device_attach(dev);//-------尝试为该设备找一个driver-------  
            WARN_ON(ret < 0);  
        }  
        mutex_lock(&bus->p->mutex);  
        list_for_each_entry(sif, &bus->p->interfaces, node)  
            if (sif->add_dev)  
                sif->add_dev(dev, sif);  
        mutex_unlock(&bus->p->mutex);  
    }  

device_attach

    /** 
     * device_attach - 尝试为设备寻找到一个驱动 
     *遍历设备隶属的总线上所有的driver,然后调用driver_probe_device匹配设备和驱动,成功就结束循环退出 
     *成功返回值为1,失败为0,-ENODEV表示设备没有被注册 
     */  
    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);-------遍历总线上的driver链表-------  
            pm_runtime_put_sync(dev);  
        }  
    out_unlock:  
        device_unlock(dev);  
        return ret;  
    }  
int bus_for_each_drv(struct bus_type *bus, struct device_driver *start,  
             void *data, int (*fn)(struct device_driver *, void *))  
{  
    struct klist_iter i;  
    struct device_driver *drv;  
    int error = 0;  
  
    if (!bus)  
        return -EINVAL;  
  
    klist_iter_init_node(&bus->p->klist_drivers, &i,start ? &start->p->knode_bus : NULL);  
    while ((drv = next_driver(&i)) && !error)  
        error = fn(drv, data);//-------对于总线中的每个driver调用fn函数进行匹配,fn为__device_attach-------  
    klist_iter_exit(&i);  
    return error;  
}  

__device_attach

    static int __device_attach(struct device_driver *drv, void *data)  
    {  
        struct device *dev = data;  
      
        if (!driver_match_device(drv, dev))//设备和驱动是否匹配函数,成功就继续下面,否则退出,将调用总线的match函数进行匹配  
            return 0;  
      
        return driver_probe_device(drv, dev);//-------设备和驱动匹配成功,调用probe函数-------  
    }  
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,723评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,003评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,512评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,825评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,874评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,841评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,812评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,582评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,033评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,309评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,450评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,158评论 5 341
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,789评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,409评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,609评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,440评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,357评论 2 352

推荐阅读更多精彩内容