Linux裝置驅動模型簡述(原始碼剖析)
1. Linux裝置驅動模型和sysfs檔案系統
Linux核心在2.6版本中引入裝置驅動模型,簡化了驅動程式的編寫。Linux裝置驅動模型包含裝置(device)、匯流排(bus)、類(class)和驅動(driver),它們之間相互關聯。其中裝置(device)和驅動(driver)通過匯流排(bus)繫結在一起。
Linux核心中,分別用bus_type、device_driver和device結構來描述匯流排、驅動和裝置,結構體定義詳見linux/device.h。裝置和對應的驅動必須依附於同一種匯流排,因此device_driver和device結構中都包含struct bus_type
Linuxsysfs是一個虛擬的檔案系統,它把連線在系統上的裝置和匯流排組織成為一個分級的檔案,可以由使用者空間存取,向用戶空間匯出核心資料結構以及它們的屬性。
sysfs展示出裝置驅動模型中各個元件的層次關係,某個系統上的sysfs頂層目錄展示如下:
1 /sys$ ll 2 total 0 3 drwxr-xr-x 2 root root 0 Aug 20 15:27 block/ 4 drwxr-xr-x 29 root root 0 Aug 20 15:27 bus/ 5 drwxr-xr-x 61 root root 0 Aug 20 15:27 class/ 6 drwxr-xr-x 4root root 0 Aug 20 15:27 dev/ 7 drwxr-xr-x 14 root root 0 Aug 20 15:27 devices/ 8 drwxr-xr-x 4 root root 0 Aug 20 15:27 firmware/ 9 drwxr-xr-x 8 root root 0 Aug 20 15:27 fs/ 10 drwxr-xr-x 2 root root 0 Sep 2 17:08 hypervisor/ 11 drwxr-xr-x 8 root root 0 Aug 20 15:27 kernel/ 12 drwxr-xr-x 147 root root 0 Aug 2015:27 module/ 13 drwxr-xr-x 2 root root 0 Aug 20 15:27 power/
重要子目錄介紹:
- block: 包含所有的塊裝置,如ram,sda等
- bus: 包含系統中所有的匯流排型別,如pci,usb,i2c等
- class: 包含系統中的裝置型別,如input,pci_bus,mmc_host等
- dev: 包含兩個子目錄:char和block,分別存放字元裝置和塊裝置的主次裝置號(major:minor),指向/sys/devices目錄下的裝置
- devices: 包含系統所有的裝置
sysfs中顯示的每一個物件都對應一個kobject結構(完整定義位於linux/kobject.h,結構內部包含一個parent指標),而另一個相聯絡的結構為kset。kset是嵌入相同型別結構的kobject物件的集合。 核心用kobject、kset和parent之間的關係將各個物件連線起來組成一個分層的結構體系,從而與模型化的子系統相匹配。(有機會詳細介紹)
sysfs中能清晰地看出device、driver和bus的相互聯絡,以某系統上PCI總線上的igb驅動為例。/sys/bus/pci/下存在devices和drivers兩個目錄,分別包含了依附於PCI總線上的裝置和驅動。進入igb驅動目錄,可以發現存在指向裝置的連結。
1 /sys/bus/pci/drivers/igb$ ll 2 total 0 3 ... 0 Sep 2 17:08 0000:07:00.0 -> ../../../../devices/pci0000:00/0000:00:1c.4/0000:07:00.0/ 4 ... 0 Sep 2 17:08 0000:07:00.1 -> ../../../../devices/pci0000:00/0000:00:1c.4/0000:07:00.1/ 5 ...
對應地,在/sys/devices/目錄下,可以看到裝置存在一個指向igb的driver項:
1 /sys/devices/pci0000:00/0000:00:1c.4/0000:07:00.0$ ll 2 total 0 3 ... 4 lrwxrwxrwx 1 root root 0 Aug 20 15:27 driver -> ../../../../bus/pci/drivers/igb/ 5 ...
同樣地,/sys/bus/pci/devices目錄下可以找到指向同樣裝置的一個連結:
1 /sys/bus/pci/devices$ ll 2 total 0 3 ... 4 ... 0 Aug 20 15:27 0000:07:00.0 -> ../../../devices/pci0000:00/0000:00:1c.4/0000:07:00.0/ 5 ... 0 Aug 20 15:27 0000:07:00.1 -> ../../../devices/pci0000:00/0000:00:1c.4/0000:07:00.1/ 6 ...
對於早期的Linux核心(2.6版本以前)來說,通常在驅動程式碼中xxx_driver
註冊過程中呼叫probe()函式來對裝置進行初始化。
引入Linux裝置驅動模型下,裝置和驅動可以分開註冊,依賴匯流排完成相互繫結。系統每註冊一個裝置的時候,會尋找與之匹配的驅動;相反,系統每註冊一個驅動的時候,會尋找與之匹配的裝置。這個過程中,裝置和驅動的匹配工作由匯流排完成。
下文中將會用關鍵的核心原始碼說明驅動和裝置間匹配機制的實現,分析的過程中以platform匯流排為例。
platform匯流排是一種虛擬的匯流排,與之相對應的是PCI、I2C、SPI等實體匯流排。引入虛擬platform匯流排是為了解決某些裝置無法直接依附在現有實體總線上的問題,例如SoC系統中整合的獨立外設控制器,掛接在SoC記憶體空間的外設等等。
2. platform匯流排的註冊
platform匯流排作為Linux的基礎匯流排,在核心啟動階段便完成了註冊,註冊的入口函式為platform_bus_init()。核心啟動階段呼叫該函式的路徑為:
1 start_kernel() --> arch_call_rest_init()[last step in start_kernel] 2 --> rest_init() --> kernel_init() 3 --> kernel_init_freeable() --> do_basic_setup() 4 --> driver_init() --> platform_bus_init()
Linux核心中定義了platform_bus_type結構體來描述platform匯流排,同時也定義了裝置platform_bus,用於管理所有掛載在platform匯流排下的裝置,定義如下:
1 struct bus_type platform_bus_type = { 2 .name = "platform", 3 .dev_groups = platform_dev_groups, 4 .match = platform_match, 5 .uevent = platform_uevent, 6 .dma_configure = platform_dma_configure, 7 .pm = &platform_dev_pm_ops, 8 }; 9 10 struct device platform_bus = { 11 .init_name = "platform", 12 };
platform_bus_init()對platform匯流排的註冊主要分為兩步:
device_register(&platform_bus)
bus_register(&platform_bus_type)
1 int __init platform_bus_init(void) 2 { 3 int error; 4 5 /* Clear up early_platform_device_list, then only remain head_list */ 6 early_platform_cleanup(); 7 8 /* register platform_bus device (platform_bus is also regarded as a device) */ 9 error = device_register(&platform_bus); 10 if (error) { 11 put_device(&platform_bus); 12 return error; 13 } 14 /* Main process to register platform_bus */ 15 error = bus_register(&platform_bus_type); 16 if (error) 17 device_unregister(&platform_bus); 18 of_platform_register_reconfig_notifier(); 19 return error; 20 }
2.1 device_register(&platform_bus)
1 /***** drivers/base/core.c *****/ 2 int device_register(struct device *dev) 3 { 4 device_initialize(dev); // init device structure 5 return device_add(dev); // add device to device hierarchy 6 }
- device_initialize():對struct device中基本成員進行初始化,包括kobject、struct device_private、struct mutex等。
- device_add(dev):將platform匯流排也作為一個裝置platform_bus註冊到驅動模型中,重要的函式包括device_create_file()、device_add_class_symlinks()、bus_add_device()、bus_probe_device()等,下文中對設備註冊的介紹一節,將對這個函式做更詳細的介紹。device_add(&platform_bus)主要功能是完成/sys/devices/platform目錄的建立。
2.2 bus_register(&platform_bus_type)
1 /***** drivers/base/bus.c *****/ 2 int bus_register(struct bus_type *bus) 3 { 4 struct subsys_private *priv; 5 struct lock_class_key *key = &bus->lock_key; 6 7 priv = kzalloc(sizeof(struct subsys_private), GFP_KERNEL); 8 9 priv->bus = bus; 10 bus->p = priv; 11 12 BLOCKING_INIT_NOTIFIER_HEAD(&priv->bus_notifier); 13 14 retval = kobject_set_name(&priv->subsys.kobj, "%s", bus->name); 15 if (retval) 16 goto out; 17 18 priv->subsys.kobj.kset = bus_kset; 19 priv->subsys.kobj.ktype = &bus_ktype; 20 priv->drivers_autoprobe = 1; 21 22 /* Register kset (subsys) */ 23 retval = kset_register(&priv->subsys); 24 25 retval = bus_create_file(bus, &bus_attr_uevent); 26 27 /* Setup "devices" and "drivers" subfolder under "platform" */ 28 priv->devices_kset = kset_create_and_add("devices", NULL, 29 &priv->subsys.kobj); 30 31 priv->drivers_kset = kset_create_and_add("drivers", NULL, 32 &priv->subsys.kobj); 33 34 INIT_LIST_HEAD(&priv->interfaces); 35 __mutex_init(&priv->mutex, "subsys mutex", key); 36 klist_init(&priv->klist_devices, klist_devices_get, klist_devices_put); 37 klist_init(&priv->klist_drivers, NULL, NULL); 38 39 /* bus_create_file(bus, &bus_attr_drivers_probe); BUS_ATTR_WO(drivers_probe) 40 * bus_create_file(bus, &bus_attr_drivers_autoprobe); BUS_ATTR_RW(drivers_autoprobe) 41 * Add two attribute files for current bus /sys/bus/platform 42 */ 43 retval = add_probe_files(bus); 44 45 retval = bus_add_groups(bus, bus->bus_groups); 46 47 return 0; 48 }
bus_register(&platform_bus_type)將匯流排 platform 註冊到 Linux 的匯流排系統中,主要完成了 subsystem 的註冊,對struct subsys_private結構進行了初始化,具體包括:
- platform_bus_type->p->drivers_autoprobe = 1
- 對struct kset型別成員sysfs進行初始化,作為子系統中kobject物件的parent。kset本身也包含kobject物件,在sysfs中也表現為一個目錄,即/sys/bus/platform。
- 建立struct kset型別的drivers_kset和devices_kset,作為匯流排下掛載的所有驅動和裝置的集合,sysfs中表現為/sys/bus/platform/drivers和/sys/bus/platform/devices。
- 初始化連結串列klist_drivers和klist_devices,將匯流排下的驅動和裝置分別連結在一起。
- 增加probe檔案,對應/sys/bus/platform目錄的檔案drivers_autoprobe和drivers_probe。
註冊完成後platform_bus_type結構重要的成員列舉如下:
1 struct bus_type platform_bus_type = { 2 .name = "platform", 3 .dev_groups = platform_dev_groups, 4 .match = platform_match, 5 .uevent = platform_uevent, 6 .dma_configure = platform_dma_configure, 7 .pm = &platform_dev_pm_ops, 8 .p (struct subsys_private) = { 9 .bus = &platform_bus_type, 10 .subsys (struct kset) = { 11 .kobj = { 12 .name = “platform” 13 .kref->refcount->refs = 1, // kset_init() 14 INIT_LIST_HEAD(.entry), 15 .state_in_sysfs = 0, 16 .state_add_uevent_sent = 1, // kset_register() 17 .state_remove_uevent_sent = 0, 18 .state_initialized = 1, 19 .kset = bus_kset, // attached to /sys/bus/ 20 .ktype= bus_ktype, 21 22 .parent = bus_kset->kobj, 23 .sd (kernfs_node) = { // create_dir, kobject_add_internal 24 .parent = bus_kset->kobj->sd, 25 .dir.root = bus_kset->kobj->sd->dir.root, 26 .ns = NULL, 27 .priv = .kobj 28 } 29 } 30 INIT_LIST_HEAD(&k->list); 31 spin_lock_init(&k->list_lock); 32 } 33 /* key point for driver to autoprobe device, set in bus_register() */ 34 . drivers_autoprobe = 1 35 klist_init(&priv->klist_devices, klist_devices_get, klist_devices_put); 36 klist_init(&priv->klist_drivers, NULL, NULL); 37 .devices_kset = kset_create_and_add("devices", NULL, &.p->subsys.kobj); 38 /* .drivers_kset = kset_create_and_add("drivers", NULL, &.p->subsys.kobj) */ 39 .drivers_kset = { 40 .kobj = { 41 .name = “drivers”, 42 .parent = &.subsys.kobj, 43 .ktype = &kset_ktype, 44 .kset = NULL, 45 46 .kref->refcount->refs = 1, // kset_init 47 INIT_LIST_HEAD(.entry), 48 .state_in_sysfs = 0, 49 .state_add_uevent_sent = 1, // kset_register 50 .state_remove_uevent_sent = 0, 51 .state_initialized = 1, 52 53 .sd = { // create_dir: /sys/bus/platform/drivers 54 /* kobject_add_internal */ 55 .parent = &.subsys.kobj.sd, 56 .dir.root = = &.subsys.kobj.sd->dir.root 57 .ns = NULL, 58 .priv = .kobj 59 } 60 } 61 INIT_LIST_HEAD(.list); 62 spin_lock_init(.list_lock); 63 .uevent_ops = NULL, 64 } 65 } 66 };
3. platform驅動的註冊
Linux核心中對依賴於platform匯流排的驅動定義了platform_driver結構體,內部封裝了前述的struct device_driver。
1 struct platform_driver { 2 int (*probe)(struct platform_device *); 3 int (*remove)(struct platform_device *); 4 void (*shutdown)(struct platform_device *); 5 int (*suspend)(struct platform_device *, pm_message_t state); 6 int (*resume)(struct platform_device *); 7 struct device_driver driver; 8 const struct platform_device_id *id_table; 9 bool prevent_deferred_probe; 10 };
為了更好地說明platform驅動的註冊過程,以驅動globalfifo_driver為例項,globalfifo_driver結構成員定義如下:
1 static struct platform_driver globalfifo_driver = { 2 .driver = { 3 .name = "globalfifo_platform", 4 .owner = THIS_MODULE, 5 }, 6 .probe = globalfifo_probe, 7 .remove = globalfifo_remove, 8 };
globalfifo_driver註冊的入口函式為platform_driver_register(&globalfifo_driver),具體實現為__platform_driver_register(&globalfifo_driver, THIS_MODULE)。
該函式會對struct device_driver的bus、probe、remove等回撥函式進行初始化,緊接著呼叫driver_register(&globalfifo_driver->driver)。
1 /***** drivers/base/platform.c *****/ 2 /** 3 * __platform_driver_register - register a driver for platform-level devices 4 * @drv: platform driver structure 5 * @owner: owning module/driver 6 */ 7 int __platform_driver_register(struct platform_driver *drv, 8 struct module *owner) 9 { 10 drv->driver.owner = owner; 11 drv->driver.bus = &platform_bus_type; 12 drv->driver.probe = platform_drv_probe; 13 drv->driver.remove = platform_drv_remove; 14 drv->driver.shutdown = platform_drv_shutdown; 15 16 return driver_register(&drv->driver); 17 }
3.1 driver_register(&(globalfifo_driver.driver))
1 /***** drivers/base/driver.c *****/ 2 /** 3 * driver_register - register driver with bus 4 * @drv: driver to register 5 * 6 * We pass off most of the work to the bus_add_driver() call, 7 * since most of the things we have to do deal with the bus 8 * structures. 9 */ 10 int driver_register(struct device_driver *drv) 11 { 12 int ret; 13 struct device_driver *other; 14 15 if (!drv->bus->p) { 16 pr_err("Driver '%s' was unable to register with bus_type '%s' because the bus was not initialized.\n", 17 drv->name, drv->bus->name); 18 return -EINVAL; 19 } 20 21 if ((drv->bus->probe && drv->probe) || 22 (drv->bus->remove && drv->remove) || 23 (drv->bus->shutdown && drv->shutdown)) 24 printk(KERN_WARNING "Driver '%s' needs updating - please use " 25 "bus_type methods\n", drv->name); 26 27 other = driver_find(drv->name, drv->bus); 28 29 ret = bus_add_driver(drv); 30 31 ret = driver_add_groups(drv, drv->groups); 32 33 kobject_uevent(&drv->p->kobj, KOBJ_ADD); 34 35 return ret; 36 }
driver_register(&(globalfifo_driver.driver))主要的工作包括:
- 確認驅動依附的匯流排platform_bus已經被註冊並初始化(必要條件)。
- 對bus、probe、remove等回撥函式初始化進行判斷,保證匯流排和驅動上相應的函式只能存在一個。
- driver_find()查詢總線上是否已存在當前驅動的同名驅動。
- bus_add_driver(&(globalfifo_driver.driver)),將驅動註冊到總線上,下文詳述。
- 發起KOBJ_ADD型別uevent,指示驅動已經新增完成,TODO。
3.2 bus_add_driver(&(globalfifo_driver.driver))
1 /***** drivers/base/bus.c *****/ 2 /** 3 * bus_add_driver - Add a driver to the bus. 4 * @drv: driver. 5 */ 6 int bus_add_driver(struct device_driver *drv) 7 { 8 struct bus_type *bus; 9 struct driver_private *priv; 10 int error = 0; 11 12 bus = bus_get(drv->bus); 13 14 priv = kzalloc(sizeof(*priv), GFP_KERNEL); 15 16 klist_init(&priv->klist_devices, NULL, NULL); 17 priv->driver = drv; 18 drv->p = priv; 19 priv->kobj.kset = bus->p->drivers_kset; 20 error = kobject_init_and_add(&priv->kobj, &driver_ktype, NULL, 21 "%s", drv->name); 22 23 klist_add_tail(&priv->knode_bus, &bus->p->klist_drivers); 24 25 /* Entrance to match device: try to bind driver to devices */ 26 if (drv->bus->p->drivers_autoprobe) { 27 error = driver_attach(drv); 28 } 29 module_add_driver(drv->owner, drv); 30 31 error = driver_create_file(drv, &driver_attr_uevent); 32 error = driver_add_groups(drv, bus->drv_groups); 33 34 if (!drv->suppress_bind_attrs) { 35 error = add_bind_files(drv); 36 } 37 return 0; 38 }
bus_add_driver(&(globalfifo_driver.driver))的主要工作包括:
- 為struct device_driver中結構struct driver_private動態分配空間,並完成後者kobject物件初始化。對應地,在/sys/bus/platform/drivers下建立目錄globalfifo_platform。
- 初始化klist_devices連結串列,用來維護驅動相關聯的裝置。對應sysfs中在每個驅動目錄下關聯的裝置。
- klist_add_tail()將當前驅動加入到匯流排對應的klist_drivers連結串列中。
- 如果匯流排使能drivers_autoprobe,將呼叫driver_attach()嘗試匹配裝置。下文中將詳述此過程。
- module_add_driver(drv->owner, drv)通過sysfs_create_link(),在globalfifo_platform目錄下新建module項指向/sys/module/globalfifo_platform。同時,也在/sys/module/globalfifo_platform/目錄下新建driver目錄,建立bus->name:drv->name連結到/sys/bus/platform/drivers/globalfifo_platform。
- uevent設置。
初始化後globalfifo_driver結構主要的成員列舉如下:
1 static struct platform_driver globalfifo_driver = { 2 .driver = { 3 .name = "globalfifo_platform", 4 .owner = THIS_MODULE, 5 .bus = &platform_bus_type, 6 .probe = platform_drv_probe 7 .remove = platform_drv_remove, 8 .shutdown = platform_drv_shutdown, 9 .p (struct driver_private) = { 10 .driver = & globalfifo_driver.driver, 11 klist_init(&.klist_devices, NULL, NULL); 12 klist_add_tail(&priv->knode_bus, &bus->p->klist_drivers); 13 14 .kobj = { 15 .kset = platform_bus_type->p->drivers_kset, 16 .ktype = driver_ktype, 17 18 .kref->refcount->refs = 1, // kset_init 19 INIT_LIST_HEAD(.entry), 20 .state_in_sysfs = 1, 21 .state_add_uevent_sent = 0, 22 .state_remove_uevent_sent = 0, 23 .state_initialized = 1, 24 .name = "globalfifo_platform", 25 .parent = platform_bus_type->p->drivers_kset->kobj, 26 } 27 } 28 }, 29 .probe = globalfifo_probe, 30 .remove = globalfifo_remove, 31 };
3.3 driver_attach(&(globalfifo_driver.driver))
1 /***** drivers/base/dd.c *****/ 2 /** 3 * driver_attach - try to bind driver to devices. 4 * @drv: driver. 5 */ 6 int driver_attach(struct device_driver *drv) 7 { 8 return bus_for_each_dev(drv->bus, NULL, drv, __driver_attach); 9 } 10 11 /***** drivers/base/bus.c *****/ 12 /** 13 * bus_for_each_dev - device iterator. 14 * @bus: bus type. 15 * @start: device to start iterating from. 16 * @data: data for the callback. 17 * @fn: function to be called for each device. 18 */ 19 int bus_for_each_dev(struct bus_type *bus, struct device *start, 20 void *data, int (*fn)(struct device *, void *)) 21 { 22 struct klist_iter i; 23 struct device *dev; 24 int error = 0; 25 26 if (!bus || !bus->p) 27 return -EINVAL; 28 29 klist_iter_init_node(&bus->p->klist_devices, &i, 30 (start ? &start->p->knode_bus : NULL)); 31 while (!error && (dev = next_device(&i))) 32 error = fn(dev, data); 33 klist_iter_exit(&i); 34 return error; 35 }
driver_attach()函式找到驅動依附的匯流排資訊,遍歷總線上連結串列klist_devices得到當前總線上存在的裝置,然後呼叫__driver_attach(dev, drv)函式,嘗試將驅動和裝置繫結。
__driver_attach(dev, drv)函式包含兩個主要的部分:
- driver_match_device(drv, dev): 嘗試將驅動和裝置匹配,返回值指示是否能匹配。
- device_driver_attach(drv, dev): 將驅動和裝置繫結。
1 static int __driver_attach(struct device *dev, void *data) 2 { 3 struct device_driver *drv = data; 4 int ret; 5 6 /* 7 * Lock device and try to bind to it. We drop the error 8 * here and always return 0, because we need to keep trying 9 * to bind to devices and some drivers will return an error 10 * simply if it didn't support the device. 11 * 12 * driver_probe_device() will spit a warning if there 13 * is an error. 14 */ 15 ret = driver_match_device(drv, dev); 16 if (ret == 0) { 17 /* no match */ 18 return 0; 19 } else if (ret == -EPROBE_DEFER) { 20 dev_dbg(dev, "Device match requests probe deferral\n"); 21 driver_deferred_probe_add(dev); 22 } else if (ret < 0) { 23 dev_dbg(dev, "Bus failed to match device: %d", ret); 24 return ret; 25 } /* ret > 0 means positive match */ 26 27 ... ... 28 29 device_driver_attach(drv, dev); 30 return 0; 31 }
3.4 driver_match_device(drv, dev)
1 static inline int driver_match_device(struct device_driver *drv, 2 struct device *dev) 3 { 4 return drv->bus->match ? drv->bus->match(dev, drv) : 1; 5 }
driver_match_device(drv, dev)回撥drv->bus->match()函式,對於platform_bus為platform_match()。
platform_match()函式會依次嘗試如下幾種方式:
- driver_override有效時,嘗試將驅動名字和driver_override匹配
- 基於裝置樹風格的匹配
- 基於 ACPI 風格的匹配
- 匹配 ID 表
- 匹配platform_device裝置名和驅動的名字
1 /** 2 * platform_match - bind platform device to platform driver. 3 * @dev: device. 4 * @drv: driver. 5 */ 6 static int platform_match(struct device *dev, struct device_driver *drv) 7 { 8 struct platform_device *pdev = to_platform_device(dev); 9 struct platform_driver *pdrv = to_platform_driver(drv); 10 11 /* When driver_override is set, only bind to the matching driver */ 12 if (pdev->driver_override) 13 return !strcmp(pdev->driver_override, drv->name); 14 15 /* Attempt an OF style match first */ 16 if (of_driver_match_device(dev, drv)) 17 return 1; 18 19 /* Then try ACPI style match */ 20 if (acpi_driver_match_device(dev, drv)) 21 return 1; 22 23 /* Then try to match against the id table */ 24 if (pdrv->id_table) 25 return platform_match_id(pdrv->id_table, pdev) != NULL; 26 27 /* fall-back to driver name match */ 28 return (strcmp(pdev->name, drv->name) == 0); 29 }
3.5 device_driver_attach(drv, dev)
1 /** 2 * device_driver_attach - attach a specific driver to a specific device 3 * @drv: Driver to attach 4 * @dev: Device to attach it to 5 */ 6 int device_driver_attach(struct device_driver *drv, struct device *dev) 7 { 8 int ret = 0; 9 10 __device_driver_lock(dev, dev->parent); 11 12 /* 13 * If device has been removed or someone has already successfully 14 * bound a driver before us just skip the driver probe call. 15 */ 16 if (!dev->p->dead && !dev->driver) 17 ret = driver_probe_device(drv, dev); 18 19 __device_driver_unlock(dev, dev->parent); 20 21 return ret; 22 }
在驅動和裝置匹配成功之後,便將驅動和裝置進行繫結。呼叫driver_probe_device(drv, dev)完成此工作,進一步呼叫really_probe(dev, drv)。
1 /** 2 * driver_probe_device - attempt to bind device & driver together 3 * @drv: driver to bind a device to 4 * @dev: device to try to bind to the driver 5 */ 6 int driver_probe_device(struct device_driver *drv, struct device *dev) 7 { 8 int ret = 0; 9 10 if (!device_is_registered(dev)) 11 return -ENODEV; 12 13 ... ... 14 if (initcall_debug) 15 ret = really_probe_debug(dev, drv); 16 else 17 ret = really_probe(dev, drv); 18 ... ... 19 return ret; 20 }
3.6 really_probe(dev, drv)
1 /***** drivers/base/dd.c *****/ 2 static int really_probe(struct device *dev, struct device_driver *drv) 3 { 4 dev->driver = drv; 5 ... ... 6 driver_sysfs_add(dev); 7 ... ... 8 /* Routine to probe device */ 9 if (dev->bus->probe) { 10 ret = dev->bus->probe(dev); 11 if (ret) 12 goto probe_failed; 13 } else if (drv->probe) { 14 ret = drv->probe(dev); 15 if (ret) 16 goto probe_failed; 17 } 18 ... ... 19 driver_bound(dev); 20 }
really_probe(dev, drv)主要完成的工作包括:
- 將裝置struct device中driver指標指向globalfifo_driver->driver。
- driver_sysfs_add(dev)完成sysfs中裝置和驅動的連結,包括在驅動目錄下建立到裝置的連結,和在裝置目錄下建立到驅動的連結。
- 裝置probe函式的呼叫:優先使用platform_device->bus->probe函式,其次使用platform_driver->probe函式。對於globalfifo_driver,會回撥globalfifo_probe(),完成裝置的初始化。
- driver_bound(dev)將裝置新增到驅動維護的裝置連結串列中,併發起KOBJ_BIND事件。
4. platform裝置的註冊
最後,對裝置的註冊過程進行簡要梳理。
和驅動類似,Linux核心中對依賴於platform匯流排的裝置也定義了特有的結構:platform_device,內部封裝了struct device結構。
1 struct platform_device { 2 const char *name; 3 int id; 4 bool id_auto; 5 struct device dev; 6 u32 num_resources; 7 struct resource *resource; 8 9 const struct platform_device_id *id_entry; 10 char *driver_override; /* Driver name to force a match */ 11 12 /* MFD cell pointer */ 13 struct mfd_cell *mfd_cell; 14 15 /* arch specific additions */ 16 struct pdev_archdata archdata; 17 };
與globalfifo_driver相對應,同樣定義globalfifo_device結構體,成員定義如下:
1 static struct platform_device globalfifo_device = { 2 .name = "globalfifo_platform", 3 .id = -1, 4 };
對裝置globalfifo_device進行註冊的入口函式為platform_device_register(&globalfifo_device)。
1 int platform_device_register(struct platform_device *pdev) 2 { 3 device_initialize(&pdev->dev); 4 arch_setup_pdev_archdata(pdev); 5 return platform_device_add(pdev); 6 }
其中device_initialize(&pdev->dev)在第一節platform_bus註冊中也提到過,主要對struct device中基本成員進行初始化,包括kobject、struct device_private、struct mutex等。著重介紹platform_device_add(pdev)。
4.1 platform_device_add(&globalfifo_device)
1 int platform_device_add(struct platform_device *pdev) 2 { 3 int i, ret; 4 5 if (!pdev->dev.parent) 6 pdev->dev.parent = &platform_bus; 7 8 pdev->dev.bus = &platform_bus_type; 9 10 switch (pdev->id) { 11 default: 12 dev_set_name(&pdev->dev, "%s.%d", pdev->name, pdev->id); 13 break; 14 case PLATFORM_DEVID_NONE: 15 dev_set_name(&pdev->dev, "%s", pdev->name); 16 break; 17 case PLATFORM_DEVID_AUTO: 18 /* 19 * Automatically allocated device ID. We mark it as such so 20 * that we remember it must be freed, and we append a suffix 21 * to avoid namespace collision with explicit IDs. 22 */ 23 ret = ida_simple_get(&platform_devid_ida, 0, 0, GFP_KERNEL); 24 pdev->id = ret; 25 pdev->id_auto = true; 26 dev_set_name(&pdev->dev, "%s.%d.auto", pdev->name, pdev->id); 27 break; 28 } 29 for (i = 0; i < pdev->num_resources; i++) { 30 struct resource *p, *r = &pdev->resource[i]; 31 if (r->name == NULL) 32 r->name = dev_name(&pdev->dev); 33 34 p = r->parent; 35 if (!p) { 36 if (resource_type(r) == IORESOURCE_MEM) 37 p = &iomem_resource; 38 else if (resource_type(r) == IORESOURCE_IO) 39 p = &ioport_resource; 40 } 41 42 if (p) { 43 ret = insert_resource(p, r); 44 } 45 } 46 47 ret = device_add(&pdev->dev); 48 if (ret == 0) 49 return ret; 50 ... ... 51 }
platform_device_add(&globalfifo_device)主要工作如下:
- 對globalfifo_device.dev.parent和globalfifo_device->dev.bus初始化,分別指向platform_bus和platform_bus_type。
- globalfifo_device.dev.kobj->name初始化為globalfifo_device.name(“globalfifo_platform”)。
- 呼叫device_add(&globalfifo_device.dev)device_add(&globalfifo_device.dev)新增裝置。
4.2 device_add(&globalfifo_device.dev)
1 int device_add(struct device *dev) 2 { 3 struct device *parent; 4 struct kobject *kobj; 5 int error = -EINVAL; 6 7 /* This will incr the ref_count */ 8 dev = get_device(dev); 9 10 /* Init dev->p->device = dev */ 11 if (!dev->p) 12 error = device_private_init(dev); 13 14 /* if init_name exists, use it to initialize dev.kobj->name */ 15 if (dev->init_name) { 16 dev_set_name(dev, "%s", dev->init_name); 17 dev->init_name = NULL; 18 } 19 20 /* subsystems can specify simple device enumeration */ 21 if (!dev_name(dev) && dev->bus && dev->bus->dev_name) 22 dev_set_name(dev, "%s%u", dev->bus->dev_name, dev->id); 23 24 /* Return ERROR if dev name is not specified */ 25 if (!dev_name(dev)) { 26 error = -EINVAL; 27 goto name_error; 28 } 29 30 ... ... 31 parent = get_device(dev->parent); 32 /* get_device_parent(dev, parent) --> platform_bus.kobj */ 33 kobj = get_device_parent(dev, parent); 34 if (kobj) 35 dev->kobj.parent = kobj; 36 37 ... ... 38 /* first, register with generic layer. */ 39 /* we require the name to be set before, and pass NULL */ 40 error = kobject_add(&dev->kobj, dev->kobj.parent, NULL); 41 42 /* notify platform of device entry */ 43 error = device_platform_notify(dev, KOBJ_ADD); 44 45 error = device_create_file(dev, &dev_attr_uevent); 46 47 error = device_add_class_symlinks(dev); 48 error = device_add_attrs(dev); 49 50 /* Main Entrance to add device into existing bus */ 51 error = bus_add_device(dev); 52 53 error = dpm_sysfs_add(dev); 54 device_pm_add(dev); 55 56 /* Create related node in devfs */ 57 if (MAJOR(dev->devt)) { 58 error = device_create_file(dev, &dev_attr_dev); 59 60 error = device_create_sys_dev_entry(dev); 61 62 devtmpfs_create_node(dev); 63 } 64 ... ... 65 66 kobject_uevent(&dev->kobj, KOBJ_ADD); 67 68 /* Try to find driver to bind this device */ 69 bus_probe_device(dev); 70 71 ... ... 72 73 }
主要工作如下:
- globalfifo_device.dev.kobj.parent初始化為&platform_bus.kobj。
- kobject_add()函式初始化globalfifo_device.dev.kobj物件,在sysfs中建立相關的目錄,例如/sys/devices/platform/globalfifo_platform。
- bus_add_device(&globalfifo_device.dev):將globalfifo_device註冊到匯流排系統裡,並建立sysfs的相關目錄:匯流排系統中建立到裝置的連結,同時也在裝置目錄下建立到匯流排的subsystem連結。
- bus_probe_device(dev):嘗試在總線上尋找可以繫結的驅動。下文詳細介紹。
globalfifo_device初步初始化後主要成員列舉如下:
static struct platform_device globalfifo_device = { .name = "globalfifo_platform", .id = -1, .dev = { .parent = &platform_bus, .bus = &platform_bus_type, .p = { .device = & globalfifo_device.dev, INIT_LIST_HEAD(.klist_children->k_list), spin_lock_init(.klist_children->k_lock), .klist_children->get = klist_children_get, .klist_children->put = klist_children_put, INIT_LIST_HEAD(&.deferred_probe) }, .kobj = { .name = "globalfifo_platform", .kref->refcount->refs = 1, INIT_LIST_HEAD(.entry), .state_in_sysfs = 0, .state_add_uevent_sent = 0, .state_remove_uevent_sent = 0, .state_initialized = 1, .kset = devices_kset, .ktype = device_ktype, .name = "globalfifo_platform", .parent = & platform_bus.kobj, .sd = { //create_dir: /sys/devices/platform/globalfifo_platform .parent = platform_bus.kobj.sd, .dir.root = platform_bus.kobj.sd->dir.root, .ns = NULL, .priv = .kobj } }, INIT_LIST_HEAD(.dma_pools), Mutex_init(.mutex), spin_lock_init(.devres_lock), INIT_LIST_HEAD(&dev->devres_head), device_pm_init(.), .numa_node = -1, INIT_LIST_HEAD(&dev->msi_list), INIT_LIST_HEAD(&dev->links.consumers); INIT_LIST_HEAD(&dev->links.suppliers); dev->links.status = DL_DEV_NO_DRIVER; }, };
4.3 bus_probe_device(&globalfifo_device.dev)
1 /***** drivers/base/bus.c *****/ 2 /** 3 * bus_probe_device - probe drivers for a new device 4 * @dev: device to probe 5 * 6 * - Automatically probe for a driver if the bus allows it. 7 */ 8 void bus_probe_device(struct device *dev) 9 { 10 struct bus_type *bus = dev->bus; 11 12 if (!bus) 13 return; 14 15 if (bus->p->drivers_autoprobe) 16 device_initial_probe(dev); 17 ... ... 18 } 19 20 /***** drivers/base/dd.c *****/ 21 void device_initial_probe(struct device *dev) 22 { 23 __device_attach(dev, true); 24 } 25 26 static int __device_attach(struct device *dev, bool allow_async) 27 { 28 ... ... 29 ret = bus_for_each_drv(dev->bus, NULL, &data, 30 __device_attach_driver); 31 ... ... 32 } 33 34 static int __device_attach_driver(struct device_driver *drv, void *_data) 35 { 36 struct device_attach_data *data = _data; 37 struct device *dev = data->dev; 38 bool async_allowed; 39 int ret; 40 41 ret = driver_match_device(drv, dev); 42 if (ret == 0) { 43 /* no match */ 44 return 0; 45 } else if (ret == -EPROBE_DEFER) { 46 dev_dbg(dev, "Device match requests probe deferral\n"); 47 driver_deferred_probe_add(dev); 48 } else if (ret < 0) { 49 dev_dbg(dev, "Bus failed to match device: %d", ret); 50 return ret; 51 } /* ret > 0 means positive match */ 52 53 async_allowed = driver_allows_async_probing(drv); 54 55 if (async_allowed) 56 data->have_async = true; 57 58 if (data->check_async && async_allowed != data->want_async) 59 return 0; 60 61 return driver_probe_device(drv, dev); 62 }
bus_probe_device(&globalfifo_device.dev)的執行函式路線分析如下所示,經過層層呼叫,最終又呼叫到driver_match_device()和driver_probe_device()函式,查詢總線上能和當前裝置匹配的驅動,並將驅動和裝置繫結在了一起。
struct device *dev = &globalfifo_device.dev; struct device_attach_data *data = { .dev = dev, .check_async = allow_async, .want_async = false, }; struct device_driver *drv; --------------------------------------------------- bus_probe_device(dev) | V device_initial_probe(dev) | V __device_attach(dev, true) | V bus_for_each_drv(dev->bus, NULL, &data, __device_attach_driver) | V __device_attach_driver(drv, data) | V driver_match_device(drv, dev) / driver_probe_device(drv, dev)
5. 總結
綜上述分析,可以看到驅動註冊的過程中,會嘗試尋找總線上可以與之匹配的裝置;同樣地,設備註冊的過程中,也會嘗試尋找總線上可以與之繫結的驅動。整個過程中,匯流排、裝置、驅動的關鍵註冊函式分別為:
- 匯流排註冊:bus_register()
- 驅動註冊:platform_driver_register()
--> driver_register() -->bus_add_driver()
- 裝置註冊:platform_device_add()
--> device_add() -->bus_add_device() /bus_probe_device()
從sysfs的角度,可以清楚地看到platform_device、platform_driver、platform_bus之間的聯絡:
/sys/bus/platform/drivers/globalfifo_platform$ ll total 0 bind globalfifo_platform -> ../../../../devices/platform/globalfifo_platform/ module -> ../../../../module/globalfifo_platform/ uevent unbind /sys/bus/platform/devices$ ll total 0 ... ... globalfifo_platform -> ../../../devices/platform/globalfifo_platform/ /sys/devices/platform/globalfifo_platform$ ll total 0 driver -> ../../../bus/platform/drivers/globalfifo_platform/ modalias power/ subsystem -> ../../../bus/platform/ uevent /sys/module/globalfifo_platform/drivers$ ll total 0 platform:globalfifo_platform -> ../../../bus/platform/drivers/globalfifo_platform/
參考資料
[1] Linux裝置驅動開發詳解(基於最新的Linux4.0核心),宋寶華編著,2016年
[2] 知識整理–linux裝置驅動模型:https://blog.csdn.net/TongxinV/article/details/54853122
[3] Linux裝置驅動模型:https://blog.csdn.net/qq_40732350/article/details/82992904