八、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/目錄先創建相應的設備節點,這樣應用程序就可以通過該設備節點來使用設備了。