device_add

八、device_add
強烈建議閱讀原文:https://blog.csdn.net/qq_20678703/article/details/52841706
展開
1.總體框架

linux設備模型:設備device,驅動driver,總線bus。

設備代表物理設備,驅動代表了設備操作方法,bus則是用來管理和匹配它們。

device和driver裏面都有一個成員變量bus,表示它們歸哪個總線管理;

bus裏面則有兩個鏈表,device鏈表和driver鏈表

當有新的設備加入的時候,就會將它加入它對應的bus的device鏈表,然後在它的驅動鏈表中尋找是否有驅動driver和該device匹配成功,如果匹配成功設備就可以正常使用了,否則,不好意思繼續等待。

當有新的驅動加入的時候,就會將它加入它對應的bus的driver鏈表,然後在它的設備鏈表中尋找是否有設備device和該driver匹配成功,如果成功設備就可以正常使用了。

device_add就是將設備加入到Linux設備模型的關鍵,它的內部將找到它的bus,然後讓它的bus給它找到它的driver,其調用順序爲:

                                                                                 

platform_driver_register(&csid_driver);

driver_sysfs_add

2.1 device_add

[cpp] view plain copy
 
int device_add(struct device *dev)  
    dev = get_device(dev);//增加該設備的引用計數  到底還是增加dev->kobj->kref的計數
    if (!dev->p) {  
        error = device_private_init(dev);//初始化設備的私有成員p  
        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);  這步非常重要 獲取v4l-subdev4設備目錄的父目錄是video4linux,video4linux的父目錄是fd8c0000.qcom,msm-cam

   
    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事件  在driver_register中已經分析,主要是在/sys/devices/.../中添加dev的uevent屬性文件

    if (MAJOR(dev->devt)) {  //如果定義了devt,則產生dev屬性,並在/dev目錄下產生設備節點文件  
        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);//電源管理相關  
添加設備的電源管理屬性,截止這裏,我們的/sys/devices/.../具體設備目錄下至少生成有以下四個屬性文件:uevent,dev,subsystem,power,你找到了嗎?
    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機制和用戶空間通信,這個driver_register中已經分析過了
    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);  
    }  

device_add創建的文件如下:


bus_add_device()
[cpp] view plain copy
 
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);  
         }  
}  

2.2  bus_probe_device


[cpp] view plain copy
 
//爲設備找到一個驅動  
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);  
}  

2.3 device_attach


[cpp] view plain copy
 
/** 
 * 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;  
}  

2.4 bus_for_each_drv

[cpp] view plain copy
 
/** 
 * bus_for_each_drv - driver迭代器 
 * @bus: 設備隸屬的總線 
 * @start: 迭代器輪訓的起始元素 
 * @data: 傳遞給回調函數的參數 
 * @fn: 回調函數 
 */  
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()->driver_probe_device()->really_probe()->bus->probe()->drv->probe()  總線中定義的probe函數會優先執行,如果總線中沒有定義probe纔會執行驅動中定義的probe


2.5  __device_attach

[cpp] view plain copy
 
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函數-------  
}-  

2.6 driver_probe_device
[cpp] view plain copy
 
int driver_probe_device(struct device_driver *drv, struct device *dev)  
{  
    int ret = 0;  
  
    if (!device_is_registered(dev))//如果設備已經被註冊過了,直接退出  
        return -ENODEV;  
  
    pr_debug("bus: '%s': %s: matched device %s with driver %s\n",  
         drv->bus->name, __func__, dev_name(dev), drv->name);  
  
    pm_runtime_get_noresume(dev);  
    pm_runtime_barrier(dev);  
    ret = really_probe(dev, drv);//-------繼續調用really_probe函數-------  
    pm_runtime_put_sync(dev);  
    return ret;  
}  

2.7 really_probe
[cpp] view plain copy
 
static int really_probe(struct device *dev, struct device_driver *drv)  
{  
    int ret = 0;  
  
    atomic_inc(&probe_count);  
    pr_debug("bus: '%s': %s: probing driver %s with device %s\n",  
         drv->bus->name, __func__, drv->name, dev_name(dev));  
    WARN_ON(!list_empty(&dev->devres_head));  
  
    dev->driver = drv;//匹配好後,將驅動信息記錄到設備內部  
    if (driver_sysfs_add(dev)) {  
        printk(KERN_ERR "%s: driver_sysfs_add(%s) failed\n",  
            __func__, dev_name(dev));  
        goto probe_failed;  
    }  
  
    if (dev->bus->probe) {//如果總線存在probe函數,則調用總線的probe函數  
        ret = dev->bus->probe(dev);  
        if (ret)  
            goto probe_failed;  
    } else if (drv->probe) {  
        ret = drv->probe(dev);//如果總線中沒有probe函數,則調用驅動的probe函數  
        if (ret)  
            goto probe_failed;  
    }  
  
    driver_bound(dev);//將設備加入到驅動支持的設備鏈表中,一個設備需要一個驅動,一個驅動支持多個設備  
    ret = 1;  
    atomic_dec(&probe_count);  
    wake_up(&probe_waitqueue);  
    return ret;  
}  

在驅動或者總線的probe函數中,一般會在/dev/目錄先創建相應的設備節點,這樣應用程序就可以通過該設備節點來使用設備了。
 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章