<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          底層軟件 | Linux設(shè)備驅(qū)動模型和sysfs文件系統(tǒng)

          共 33713字,需瀏覽 68分鐘

           ·

          2024-09-07 10:03

                    

          原文:https://blog.csdn.net/i37193429/article/details/136491517

          Linux設(shè)備驅(qū)動模型和sysfs文件系統(tǒng)

          Linux內(nèi)核在2.6版本中引入設(shè)備驅(qū)動模型,簡化了驅(qū)動程序的編寫。Linux設(shè)備驅(qū)動模型包含設(shè)備(device)總線(bus)、類(class)驅(qū)動(driver),它們之間相互關(guān)聯(lián)。其中設(shè)備(device)驅(qū)動(driver)通過總線(bus)綁定在一起。

          Linux內(nèi)核中,分別用bus_type、device_driverdevice結(jié)構(gòu)來描述總線、驅(qū)動和設(shè)備,結(jié)構(gòu)體定義詳見linux/device.h。設(shè)備和對應(yīng)的驅(qū)動必須依附于同一種總線,因此device_driverdevice結(jié)構(gòu)中都包含struct bus_type指針。

          Linux sysfs是一個虛擬的文件系統(tǒng),它把連接在系統(tǒng)上的設(shè)備和總線組織成為一個分級的文件,可以由用戶空間存取,向用戶空間導(dǎo)出內(nèi)核數(shù)據(jù)結(jié)構(gòu)以及它們的屬性。
          sysfs展示出設(shè)備驅(qū)動模型中各個組件的層次關(guān)系,某個系統(tǒng)上的sysfs頂層目錄展示如下:

          /sys$ ll
          total 0
          drwxr-xr-x 2 root root 0 Aug 20 15:27 block/
          drwxr-xr-x 29 root root 0 Aug 20 15:27 bus/
          drwxr-xr-x 61 root root 0 Aug 20 15:27 class/
          drwxr-xr-x 4 root root 0 Aug 20 15:27 dev/
          drwxr-xr-x 14 root root 0 Aug 20 15:27 devices/
          drwxr-xr-x 4 root root 0 Aug 20 15:27 firmware/
          drwxr-xr-x 8 root root 0 Aug 20 15:27 fs/
          drwxr-xr-x 2 root root 0 Sep 2 17:08 hypervisor/
          drwxr-xr-x 8 root root 0 Aug 20 15:27 kernel/
          drwxr-xr-x 147 root root 0 Aug 20 15:27 module/
          drwxr-xr-x 2 root root 0 Aug 20 15:27 power/

          重要子目錄介紹:

          • block:  包含所有的塊設(shè)備,如ram,sda
          • bus:    包含系統(tǒng)中所有的總線類型,如pci,usb,i2c
          • class:  包含系統(tǒng)中的設(shè)備類型,如input,pci_busmmc_host
          • dev:    包含兩個子目錄:charblock,分別存放字符設(shè)備和塊設(shè)備的主次設(shè)備號(major:minor),指向/sys/devices目錄下的設(shè)備
          • devices:包含系統(tǒng)所有的設(shè)備

          sysfs中顯示的每一個對象都對應(yīng)一個kobject結(jié)構(gòu)(完整定義位于linux/kobject.h,結(jié)構(gòu)內(nèi)部包含一個parent指針),而另一個相聯(lián)系的結(jié)構(gòu)為kset。kset是嵌入相同類型結(jié)構(gòu)的kobject對象的集合。 內(nèi)核用kobject、ksetparent之間的關(guān)系將各個對象連接起來組成一個分層的結(jié)構(gòu)體系,從而與模型化的子系統(tǒng)相匹配。(有機(jī)會詳細(xì)介紹)

          sysfs中能清晰地看出device、driverbus的相互聯(lián)系,以某系統(tǒng)上pci總線上的igb驅(qū)動為例。/sys/bus/pci/下存在devicesdrivers兩個目錄,分別包含了依附于pci總線上的設(shè)備和驅(qū)動。進(jìn)入igb驅(qū)動目錄,可以發(fā)現(xiàn)存在指向設(shè)備的鏈接。

          /sys/bus/pci/drivers/igb$ ll
          total 0
          ... 0 Sep 2 17:08 0000:07:00.0 -> ../../../../devices/pci0000:00/0000:00:1c.4/0000:07:00.0/
          ... 0 Sep 2 17:08 0000:07:00.1 -> ../../../../devices/pci0000:00/0000:00:1c.4/0000:07:00.1/
          ...

          對應(yīng)地,在/sys/devices/目錄下,可以看到設(shè)備存在一個指向igbdriver項:

          /sys/devices/pci0000:00/0000:00:1c.4/0000:07:00.0$ ll
          total 0
          ...
          lrwxrwxrwx 1 root root 0 Aug 20 15:27 driver -> ../../../../bus/pci/drivers/igb/
          ...

          同樣地,/sys/bus/pci/devices目錄下可以找到指向同樣設(shè)備的一個鏈接:

          /sys/bus/pci/devices$ ll
          total 0
          ...
          ... 0 Aug 20 15:27 0000:07:00.0 -> ../../../devices/pci0000:00/0000:00:1c.4/0000:07:00.0/
          ... 0 Aug 20 15:27 0000:07:00.1 -> ../../../devices/pci0000:00/0000:00:1c.4/0000:07:00.1/
          ...

          對于早期的Linux內(nèi)核(2.6版本以前)來說,通常在驅(qū)動代碼中xxx_driver注冊過程中調(diào)用probe()函數(shù)來對設(shè)備進(jìn)行初始化。
          引入Linux設(shè)備驅(qū)動模型下,設(shè)備和驅(qū)動可以分開注冊,依賴總線完成相互綁定。系統(tǒng)每注冊一個設(shè)備的時候,會尋找與之匹配的驅(qū)動;相反,系統(tǒng)每注冊一個驅(qū)動的時候,會尋找與之匹配的設(shè)備。這個過程中,設(shè)備和驅(qū)動的匹配工作由總線完成。

          下文中將會用關(guān)鍵的內(nèi)核源碼(基于linux 5.2.14 Kernel)說明驅(qū)動和設(shè)備間匹配機(jī)制的實現(xiàn),分析的過程中以platform總線為例。
          platform總線是一種虛擬的總線,與之相對應(yīng)的是PCI、I2C、SPI等實體總線。引入虛擬platform總線是為了解決某些設(shè)備無法直接依附在現(xiàn)有實體總線上的問題,例如SoC系統(tǒng)中集成的獨立外設(shè)控制器,掛接在SoC內(nèi)存空間的外設(shè)等等。

          platform總線的注冊

          platform總線作為Linux的基礎(chǔ)總線,在內(nèi)核啟動階段便完成了注冊,注冊的入口函數(shù)為platform_bus_init()。內(nèi)核啟動階段調(diào)用該函數(shù)的路徑為:

          start_kernel()                  --> arch_call_rest_init()[last step in start_kernel] 
          --> rest_init() --> kernel_init()
          --> kernel_init_freeable() --> do_basic_setup()
          --> driver_init() --> platform_bus_init()

          Linux內(nèi)核中定義了platform_bus_type結(jié)構(gòu)體來描述platform總線,同時也定義了設(shè)備platform_bus,用于管理所有掛載在platform總線下的設(shè)備,定義如下:

          struct bus_type platform_bus_type = {
          .name = 'platform',
          .dev_groups = platform_dev_groups,
          .match = platform_match,
          .uevent = platform_uevent,
          .dma_configure = platform_dma_configure,
          .pm = &platform_dev_pm_ops,
          };

          struct device platform_bus = {
          .init_name = 'platform',
          };

          platform_bus_init()platform總線的注冊主要分為兩步:

          • device_register(&platform_bus)
          • bus_register(&platform_bus_type)。
          int __init platform_bus_init(void)
          {
          int error;

          /* Clear up early_platform_device_list, then only remain head_list */
          early_platform_cleanup();

          /* register platform_bus device (platform_bus is also regarded as a device) */
          error = device_register(&platform_bus);
          if (error) {
          put_device(&platform_bus);
          return error;
          }
          /* Main process to register platform_bus */
          error = bus_register(&platform_bus_type);
          if (error)
          device_unregister(&platform_bus);
          of_platform_register_reconfig_notifier();
          return error;
          }

          device_register(&platform_bus)

          /*****  drivers/base/core.c  *****/
          int device_register(struct device *dev)
          {
          device_initialize(dev); // init device structure
          return device_add(dev); // add device to device hierarchy
          }
          • device_initialize():對struct device中基本成員進(jìn)行初始化,包括kobject、struct device_privatestruct mutex等。
          • device_add(dev):將platform總線也作為一個設(shè)備platform_bus注冊到驅(qū)動模型中,重要的函數(shù)包括device_create_file()device_add_class_symlinks()、bus_add_device()、bus_probe_device()等,下文中對設(shè)備注冊的介紹一節(jié),將對這個函數(shù)做更詳細(xì)的介紹。device_add(&platform_bus)主要功能是完成/sys/devices/platform目錄的建立。

          bus_register(&platform_bus_type)

          /*****  drivers/base/bus.c  *****/
          int bus_register(struct bus_type *bus)
          {
          struct subsys_private *priv;
          struct lock_class_key *key = &bus->lock_key;

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

          priv->bus = bus;
          bus->p = priv;

          BLOCKING_INIT_NOTIFIER_HEAD(&priv->bus_notifier);

          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;

          /* Register kset (subsys) */
          retval = kset_register(&priv->subsys);

          retval = bus_create_file(bus, &bus_attr_uevent);

          /* Setup 'devices' and 'drivers' subfolder under 'platform' */
          priv->devices_kset = kset_create_and_add('devices', NULL,
          &priv->subsys.kobj);

          priv->drivers_kset = kset_create_and_add('drivers', NULL,
          &priv->subsys.kobj);

          INIT_LIST_HEAD(&priv->interfaces);
          __mutex_init(&priv->mutex, 'subsys mutex', key);
          klist_init(&priv->klist_devices, klist_devices_get, klist_devices_put);
          klist_init(&priv->klist_drivers, NULL, NULL);

          /* bus_create_file(bus, &bus_attr_drivers_probe); BUS_ATTR_WO(drivers_probe)
          * bus_create_file(bus, &bus_attr_drivers_autoprobe); BUS_ATTR_RW(drivers_autoprobe)
          * Add two attribute files for current bus /sys/bus/platform
          */
          retval = add_probe_files(bus);

          retval = bus_add_groups(bus, bus->bus_groups);

          return 0;
          }

          bus_register(&platform_bus_type)將總線platform注冊到Linux的總線系統(tǒng)中,主要完成了subsystem的注冊,對struct subsys_private結(jié)構(gòu)進(jìn)行了初始化,具體包括:

          • platform_bus_type->p->drivers_autoprobe = 1
          • struct kset類型成員subsys進(jìn)行初始化,作為子系統(tǒng)中kobject對象的parent。kset本身也包含kobject對象,在sysfs中也表現(xiàn)為一個目錄,即/sys/bus/platform
          • 建立struct kset類型的drivers_ksetdevices_kset,作為總線下掛載的所有驅(qū)動和設(shè)備的集合,sysfs中表現(xiàn)為/sys/bus/platform/drivers/sys/bus/platform/devices。
          • 初始化鏈表klist_driversklist_devices,將總線下的驅(qū)動和設(shè)備分別鏈接在一起。
          • 增加probe文件,對應(yīng)/sys/bus/platform目錄的文件drivers_autoprobedrivers_probe。

          注冊完成后platform_bus_type結(jié)構(gòu)重要的成員列舉如下:

          struct bus_type platform_bus_type = {
          .name = 'platform',
          .dev_groups = platform_dev_groups,
          .match = platform_match,
          .uevent = platform_uevent,
          .dma_configure = platform_dma_configure,
          .pm = &platform_dev_pm_ops,
          .p (struct subsys_private) = {
          .bus = &platform_bus_type,
          .subsys (struct kset) = {
          .kobj = {
          .name = “platform”
          .kref->refcount->refs = 1, // kset_init()
          INIT_LIST_HEAD(.entry),
          .state_in_sysfs = 0,
          .state_add_uevent_sent = 1, // kset_register()
          .state_remove_uevent_sent = 0,
          .state_initialized = 1,
          .kset = bus_kset, // attached to /sys/bus/
          .ktype= bus_ktype,

          .parent = bus_kset->kobj,
          .sd (kernfs_node) = { // create_dir, kobject_add_internal
          .parent = bus_kset->kobj->sd,
          .dir.root = bus_kset->kobj->sd->dir.root,
          .ns = NULL,
          .priv = .kobj
          }
          }
          INIT_LIST_HEAD(&k->list);
          spin_lock_init(&k->list_lock);
          }
          /* key point for driver to autoprobe device, set in bus_register() */
          . drivers_autoprobe = 1
          klist_init(&priv->klist_devices, klist_devices_get, klist_devices_put);
          klist_init(&priv->klist_drivers, NULL, NULL);
          .devices_kset = kset_create_and_add('devices', NULL, &.p->subsys.kobj);
          /* .drivers_kset = kset_create_and_add('drivers', NULL, &.p->subsys.kobj) */
          .drivers_kset = {
          .kobj = {
          .name = “drivers”,
          .parent = &.subsys.kobj,
          .ktype = &kset_ktype,
          .kset = NULL,

          .kref->refcount->refs = 1, // kset_init
          INIT_LIST_HEAD(.entry),
          .state_in_sysfs = 0,
          .state_add_uevent_sent = 1, // kset_register
          .state_remove_uevent_sent = 0,
          .state_initialized = 1,

          .sd = { // create_dir: /sys/bus/platform/drivers
          /* kobject_add_internal */
          .parent = &.subsys.kobj.sd,
          .dir.root = = &.subsys.kobj.sd->dir.root
          .ns = NULL,
          .priv = .kobj
          }
          }
          INIT_LIST_HEAD(.list);
          spin_lock_init(.list_lock);
          .uevent_ops = NULL,
          }
          }
          };

          platform驅(qū)動的注冊

          Linux內(nèi)核中對依賴于platform總線的驅(qū)動定義了platform_driver結(jié)構(gòu)體,內(nèi)部封裝了前述的struct device_driver。

          struct platform_driver {
          int (*probe)(struct platform_device *);
          int (*remove)(struct platform_device *);
          void (*shutdown)(struct platform_device *);
          int (*suspend)(struct platform_device *, pm_message_t state);
          int (*resume)(struct platform_device *);
          struct device_driver driver;
          const struct platform_device_id *id_table;
          bool prevent_deferred_probe;
          };

          為了更好地說明platform驅(qū)動的注冊過程,以驅(qū)動globalfifo_driver為實例,globalfifo_driver結(jié)構(gòu)成員定義如下:

          static struct platform_driver globalfifo_driver = {
          .driver = {
          .name = 'globalfifo_platform',
          .owner = THIS_MODULE,
          },
          .probe = globalfifo_probe,
          .remove = globalfifo_remove,
          };

          globalfifo_driver注冊的入口函數(shù)為platform_driver_register(&globalfifo_driver),具體實現(xiàn)為__platform_driver_register(&globalfifo_driver, THIS_MODULE)
          該函數(shù)會對struct device_driverbus、probe、remove等回調(diào)函數(shù)進(jìn)行初始化,緊接著調(diào)用driver_register(&globalfifo_driver->driver)。

          /*****  drivers/base/platform.c  *****/
          /**
          * __platform_driver_register - register a driver for platform-level devices
          * @drv: platform driver structure
          * @owner: owning module/driver
          */
          int __platform_driver_register(struct platform_driver *drv,
          struct module *owner)
          {
          drv->driver.owner = owner;
          drv->driver.bus = &platform_bus_type;
          drv->driver.probe = platform_drv_probe;
          drv->driver.remove = platform_drv_remove;
          drv->driver.shutdown = platform_drv_shutdown;

          return driver_register(&drv->driver);
          }

          driver_register(&(globalfifo_driver.driver))

          /*****  drivers/base/driver.c  *****/
          /**
          * driver_register - register driver with bus
          * @drv: driver to register
          *
          * We pass off most of the work to the bus_add_driver() call,
          * since most of the things we have to do deal with the bus
          * structures.
          */
          int driver_register(struct device_driver *drv)
          {
          int ret;
          struct device_driver *other;

          if (!drv->bus->p) {
          pr_err('Driver '%s' was unable to register with bus_type '%s' because the bus was not initialized.\n',
          drv->name, drv->bus->name);
          return -EINVAL;
          }

          if ((drv->bus->probe && drv->probe) ||
          (drv->bus->remove && drv->remove) ||
          (drv->bus->shutdown && drv->shutdown))
          printk(KERN_WARNING 'Driver '%s' needs updating - please use '
          'bus_type methods\n', drv->name);

          other = driver_find(drv->name, drv->bus);

          ret = bus_add_driver(drv);

          ret = driver_add_groups(drv, drv->groups);

          kobject_uevent(&drv->p->kobj, KOBJ_ADD);

          return ret;
          }

          driver_register(&(globalfifo_driver.driver))主要的工作包括:

          • 確認(rèn)驅(qū)動依附的總線platform_bus已經(jīng)被注冊并初始化(必要條件)。
          • proberemove、shutdown等回調(diào)函數(shù)初始化進(jìn)行判斷,保證總線和驅(qū)動上相應(yīng)的函數(shù)只能存在一個。
          • driver_find()查找總線上是否已存在當(dāng)前驅(qū)動的同名驅(qū)動。
          • bus_add_driver(&(globalfifo_driver.driver)),將驅(qū)動注冊到總線上,下文詳述。
          • 發(fā)起KOBJ_ADD類型uevent,指示驅(qū)動已經(jīng)添加完成,TODO。

          bus_add_driver(&(globalfifo_driver.driver))

          /*****  drivers/base/bus.c  *****/
          /**
          * bus_add_driver - Add a driver to the bus.
          * @drv: driver.
          */
          int bus_add_driver(struct device_driver *drv)
          {
          struct bus_type *bus;
          struct driver_private *priv;
          int error = 0;

          bus = bus_get(drv->bus);

          priv = kzalloc(sizeof(*priv), GFP_KERNEL);

          klist_init(&priv->klist_devices, NULL, NULL);
          priv->driver = drv;
          drv->p = priv;
          priv->kobj.kset = bus->p->drivers_kset;
          error = kobject_init_and_add(&priv->kobj, &driver_ktype, NULL,
          '%s', drv->name);

          klist_add_tail(&priv->knode_bus, &bus->p->klist_drivers);

          /* Entrance to match device: try to bind driver to devices */
          if (drv->bus->p->drivers_autoprobe) {
          error = driver_attach(drv);
          }
          module_add_driver(drv->owner, drv);

          error = driver_create_file(drv, &driver_attr_uevent);
          error = driver_add_groups(drv, bus->drv_groups);

          if (!drv->suppress_bind_attrs) {
          error = add_bind_files(drv);
          }
          return 0;
          }

          bus_add_driver(&(globalfifo_driver.driver))的主要工作包括:

          • struct device_driver中結(jié)構(gòu)struct driver_private動態(tài)分配空間,并完成后者kobject對象初始化。對應(yīng)地,在/sys/bus/platform/drivers下建立目錄globalfifo_platform。
          • 初始化klist_devices鏈表,用來維護(hù)驅(qū)動相關(guān)聯(lián)的設(shè)備。對應(yīng)sysfs中在每個驅(qū)動目錄下關(guān)聯(lián)的設(shè)備。
          • klist_add_tail()將當(dāng)前驅(qū)動加入到總線對應(yīng)的klist_drivers鏈表中。
          • 如果總線使能drivers_autoprobe,將調(diào)用driver_attach()嘗試匹配設(shè)備。下文中將詳述此過程。
          • 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設(shè)置。

          初始化后globalfifo_driver結(jié)構(gòu)主要的成員列舉如下:

          static struct platform_driver globalfifo_driver = {
          .driver = {
          .name = 'globalfifo_platform',
          .owner = THIS_MODULE,
          .bus = &platform_bus_type,
          .probe = platform_drv_probe
          .remove = platform_drv_remove,
          .shutdown = platform_drv_shutdown,
          .p (struct driver_private) = {
          .driver = & globalfifo_driver.driver,
          klist_init(&.klist_devices, NULL, NULL);
          klist_add_tail(&priv->knode_bus, &bus->p->klist_drivers);

          .kobj = {
          .kset = platform_bus_type->p->drivers_kset,
          .ktype = driver_ktype,

          .kref->refcount->refs = 1, // kset_init
          INIT_LIST_HEAD(.entry),
          .state_in_sysfs = 1,
          .state_add_uevent_sent = 0,
          .state_remove_uevent_sent = 0,
          .state_initialized = 1,
          .name = 'globalfifo_platform',
          .parent = platform_bus_type->p->drivers_kset->kobj,
          }
          }
          },
          .probe = globalfifo_probe,
          .remove = globalfifo_remove,
          };

          driver_attach(&(globalfifo_driver.driver))

          /*****  drivers/base/dd.c  *****/
          /**
          * driver_attach - try to bind driver to devices.
          * @drv: driver.
          */
          int driver_attach(struct device_driver *drv)
          {
          return bus_for_each_dev(drv->bus, NULL, drv, __driver_attach);
          }

          /***** drivers/base/bus.c *****/
          /**
          * bus_for_each_dev - device iterator.
          * @bus: bus type.
          * @start: device to start iterating from.
          * @data: data for the callback.
          * @fn: function to be called for each device.
          */
          int bus_for_each_dev(struct bus_type *bus, struct device *start,
          void *data, int (*fn)(struct device *, void *))
          {
          struct klist_iter i;
          struct device *dev;
          int error = 0;

          if (!bus || !bus->p)
          return -EINVAL;

          klist_iter_init_node(&bus->p->klist_devices, &i,
          (start ? &start->p->knode_bus : NULL));
          while (!error && (dev = next_device(&i)))
          error = fn(dev, data);
          klist_iter_exit(&i);
          return error;
          }

          driver_attach()函數(shù)找到驅(qū)動依附的總線信息,遍歷總線上鏈表klist_devices得到當(dāng)前總線上存在的設(shè)備,然后調(diào)用__driver_attach(dev, drv)函數(shù),嘗試將驅(qū)動和設(shè)備綁定。
          __driver_attach(dev, drv)函數(shù)包含兩個主要的部分:

          • driver_match_device(drv, dev) : 嘗試將驅(qū)動和設(shè)備匹配,返回值指示是否能匹配。
          • device_driver_attach(drv, dev): 將驅(qū)動和設(shè)備綁定。
          static int __driver_attach(struct device *dev, void *data)
          {
          struct device_driver *drv = data;
          int ret;

          /*
          * Lock device and try to bind to it. We drop the error
          * here and always return 0, because we need to keep trying
          * to bind to devices and some drivers will return an error
          * simply if it didn't support the device.
          *
          * driver_probe_device() will spit a warning if there
          * is an error.
          */
          ret = driver_match_device(drv, dev);
          if (ret == 0) {
          /* no match */
          return 0;
          } else if (ret == -EPROBE_DEFER) {
          dev_dbg(dev, 'Device match requests probe deferral\n');
          driver_deferred_probe_add(dev);
          } else if (ret < 0) {
          dev_dbg(dev, 'Bus failed to match device: %d', ret);
          return ret;
          } /* ret > 0 means positive match */

          ... ...

          device_driver_attach(drv, dev);
          return 0;
          }

          driver_match_device(drv, dev)

          static inline int driver_match_device(struct device_driver *drv,
          struct device *dev)
          {
          return drv->bus->match ? drv->bus->match(dev, drv) : 1;
          }

          driver_match_device(drv, dev)回調(diào)drv->bus->match()函數(shù),對于platform_busplatform_match()
          platform_match()函數(shù)會依次嘗試如下幾種方式:

          • driver_override有效時,嘗試將驅(qū)動名字和driver_override匹配
          • 基于設(shè)備樹風(fēng)格的匹配
          • 基于ACPI風(fēng)格的匹配
          • 匹配ID
          • 匹配platform_device設(shè)備名和驅(qū)動的名字
          /**
          * platform_match - bind platform device to platform driver.
          * @dev: device.
          * @drv: driver.
          */
          static int platform_match(struct device *dev, struct device_driver *drv)
          {
          struct platform_device *pdev = to_platform_device(dev);
          struct platform_driver *pdrv = to_platform_driver(drv);

          /* When driver_override is set, only bind to the matching driver */
          if (pdev->driver_override)
          return !strcmp(pdev->driver_override, drv->name);

          /* Attempt an OF style match first */
          if (of_driver_match_device(dev, drv))
          return 1;

          /* Then try ACPI style match */
          if (acpi_driver_match_device(dev, drv))
          return 1;

          /* Then try to match against the id table */
          if (pdrv->id_table)
          return platform_match_id(pdrv->id_table, pdev) != NULL;

          /* fall-back to driver name match */
          return (strcmp(pdev->name, drv->name) == 0);
          }

          device_driver_attach(drv, dev)

          /**
          * device_driver_attach - attach a specific driver to a specific device
          * @drv: Driver to attach
          * @dev: Device to attach it to
          */
          int device_driver_attach(struct device_driver *drv, struct device *dev)
          {
          int ret = 0;

          __device_driver_lock(dev, dev->parent);

          /*
          * If device has been removed or someone has already successfully
          * bound a driver before us just skip the driver probe call.
          */
          if (!dev->p->dead && !dev->driver)
          ret = driver_probe_device(drv, dev);

          __device_driver_unlock(dev, dev->parent);

          return ret;
          }

          在驅(qū)動和設(shè)備匹配成功之后,便將驅(qū)動和設(shè)備進(jìn)行綁定。調(diào)用driver_probe_device(drv, dev)完成此工作,進(jìn)一步調(diào)用really_probe(dev, drv)。

          /**
          * driver_probe_device - attempt to bind device & driver together
          * @drv: driver to bind a device to
          * @dev: device to try to bind to the driver
          */
          int driver_probe_device(struct device_driver *drv, struct device *dev)
          {
          int ret = 0;

          if (!device_is_registered(dev))
          return -ENODEV;

          ... ...
          if (initcall_debug)
          ret = really_probe_debug(dev, drv);
          else
          ret = really_probe(dev, drv);
          ... ...
          return ret;
          }

          really_probe(dev, drv)

          /*****  drivers/base/dd.c  *****/
          static int really_probe(struct device *dev, struct device_driver *drv)
          {
          dev->driver = drv;
          ... ...
          driver_sysfs_add(dev);
          ... ...
          /* Routine to probe device */
          if (dev->bus->probe) {
          ret = dev->bus->probe(dev);
          if (ret)
          goto probe_failed;
          } else if (drv->probe) {
          ret = drv->probe(dev);
          if (ret)
          goto probe_failed;
          }
          ... ...
          driver_bound(dev);
          }

          really_probe(dev, drv)主要完成的工作包括:

          • 將設(shè)備struct devicedriver指針指向globalfifo_driver->driver。
          • driver_sysfs_add(dev)完成sysfs中設(shè)備和驅(qū)動的鏈接,包括在驅(qū)動目錄下建立到設(shè)備的鏈接,和在設(shè)備目錄下建立到驅(qū)動的鏈接。
          • 設(shè)備probe函數(shù)的調(diào)用:優(yōu)先使用platform_device->bus->probe函數(shù),其次使用platform_driver->probe函數(shù)。對于globalfifo_driver,會回調(diào)globalfifo_probe(),完成設(shè)備的初始化。
          • driver_bound(dev)將設(shè)備添加到驅(qū)動維護(hù)的設(shè)備鏈表中,并發(fā)起KOBJ_BIND事件。

          platform設(shè)備的注冊

          最后,對設(shè)備的注冊過程進(jìn)行簡要梳理。
          和驅(qū)動類似,Linux內(nèi)核中對依賴于platform總線的設(shè)備也定義了特有的結(jié)構(gòu):platform_device,內(nèi)部封裝了struct device結(jié)構(gòu)。

          struct platform_device {
          const char *name;
          int id;
          bool id_auto;
          struct device dev;
          u32 num_resources;
          struct resource *resource;

          const struct platform_device_id *id_entry;
          char *driver_override; /* Driver name to force a match */

          /* MFD cell pointer */
          struct mfd_cell *mfd_cell;

          /* arch specific additions */
          struct pdev_archdata archdata;
          };

          globalfifo_driver相對應(yīng),同樣定義globalfifo_device結(jié)構(gòu)體,成員定義如下:

          static struct platform_device globalfifo_device = {
          .name = 'globalfifo_platform',
          .id = -1,
          };

          對設(shè)備globalfifo_device進(jìn)行注冊的入口函數(shù)為platform_device_register(&globalfifo_device)。

          int platform_device_register(struct platform_device *pdev)
          {
          device_initialize(&pdev->dev);
          arch_setup_pdev_archdata(pdev);
          return platform_device_add(pdev);
          }

          其中device_initialize(&pdev->dev)在第一節(jié)platform_bus注冊中也提到過,主要對struct device中基本成員進(jìn)行初始化,包括kobject、struct device_private、struct mutex等。著重介紹platform_device_add(pdev)

          platform_device_add(&globalfifo_device)

          int platform_device_add(struct platform_device *pdev)
          {
          int i, ret;

          if (!pdev->dev.parent)
          pdev->dev.parent = &platform_bus;

          pdev->dev.bus = &platform_bus_type;

          switch (pdev->id) {
          default:
          dev_set_name(&pdev->dev, '%s.%d', pdev->name, pdev->id);
          break;
          case PLATFORM_DEVID_NONE:
          dev_set_name(&pdev->dev, '%s', pdev->name);
          break;
          case PLATFORM_DEVID_AUTO:
          /*
          * Automatically allocated device ID. We mark it as such so
          * that we remember it must be freed, and we append a suffix
          * to avoid namespace collision with explicit IDs.
          */
          ret = ida_simple_get(&platform_devid_ida, 0, 0, GFP_KERNEL);
          pdev->id = ret;
          pdev->id_auto = true;
          dev_set_name(&pdev->dev, '%s.%d.auto', pdev->name, pdev->id);
          break;
          }
          for (i = 0; i < pdev->num_resources; i++) {
          struct resource *p, *r = &pdev->resource[i];
          if (r->name == NULL)
          r->name = dev_name(&pdev->dev);

          p = r->parent;
          if (!p) {
          if (resource_type(r) == IORESOURCE_MEM)
          p = &iomem_resource;
          else if (resource_type(r) == IORESOURCE_IO)
          p = &ioport_resource;
          }

          if (p) {
          ret = insert_resource(p, r);
          }
          }

          ret = device_add(&pdev->dev);
          if (ret == 0)
          return ret;
          ... ...
          }

          platform_device_add(&globalfifo_device)主要工作如下:

          • globalfifo_device.dev.parentglobalfifo_device->dev.bus初始化,分別指向platform_busplatform_bus_type。
          • globalfifo_device.dev.kobj->name初始化為globalfifo_device.name ('globalfifo_platform')。
          • 調(diào)用device_add(&globalfifo_device.dev)添加設(shè)備。

          device_add(&globalfifo_device.dev)

          int device_add(struct device *dev)
          {
          struct device *parent;
          struct kobject *kobj;
          int error = -EINVAL;

          /* This will incr the ref_count */
          dev = get_device(dev);

          /* Init dev->p->device = dev */
          if (!dev->p)
          error = device_private_init(dev);

          /* if init_name exists, use it to initialize dev.kobj->name */
          if (dev->init_name) {
          dev_set_name(dev, '%s', dev->init_name);
          dev->init_name = NULL;
          }

          /* subsystems can specify simple device enumeration */
          if (!dev_name(dev) && dev->bus && dev->bus->dev_name)
          dev_set_name(dev, '%s%u', dev->bus->dev_name, dev->id);

          /* Return ERROR if dev name is not specified */
          if (!dev_name(dev)) {
          error = -EINVAL;
          goto name_error;
          }

          ... ...
          parent = get_device(dev->parent);
          /* get_device_parent(dev, parent) --> platform_bus.kobj */
          kobj = get_device_parent(dev, parent);
          if (kobj)
          dev->kobj.parent = kobj;

          ... ...
          /* first, register with generic layer. */
          /* we require the name to be set before, and pass NULL */
          error = kobject_add(&dev->kobj, dev->kobj.parent, NULL);

          /* notify platform of device entry */
          error = device_platform_notify(dev, KOBJ_ADD);

          error = device_create_file(dev, &dev_attr_uevent);

          error = device_add_class_symlinks(dev);
          error = device_add_attrs(dev);

          /* Main Entrance to add device into existing bus */
          error = bus_add_device(dev);

          error = dpm_sysfs_add(dev);
          device_pm_add(dev);

          /* Create related node in devfs */
          if (MAJOR(dev->devt)) {
          error = device_create_file(dev, &dev_attr_dev);

          error = device_create_sys_dev_entry(dev);

          devtmpfs_create_node(dev);
          }
          ... ...

          kobject_uevent(&dev->kobj, KOBJ_ADD);

          /* Try to find driver to bind this device */
          bus_probe_device(dev);

          ... ...

          }

          主要工作如下:

          • globalfifo_device.dev.kobj.parent初始化為&platform_bus.kobj
          • kobject_add()函數(shù)初始化globalfifo_device.dev.kobj對象,在sysfs中建立相關(guān)的目錄,例如/sys/devices/platform/globalfifo_platform
          • bus_add_device(&globalfifo_device.dev):將globalfifo_device注冊到總線系統(tǒng)里,并建立sysfs的相關(guān)目錄:總線系統(tǒng)中建立到設(shè)備的鏈接,同時也在設(shè)備目錄下建立到總線的subsystem鏈接。
          • bus_probe_device(dev):嘗試在總線上尋找可以綁定的驅(qū)動。下文詳細(xì)介紹。

          globalfifo_devices初步初始化后主要成員列舉如下:

          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;
          },
          };

          bus_probe_device(&globalfifo_device.dev)

          /*****  drivers/base/bus.c  *****/
          /**
          * bus_probe_device - probe drivers for a new device
          * @dev: device to probe
          *
          * - Automatically probe for a driver if the bus allows it.
          */
          void bus_probe_device(struct device *dev)
          {
          struct bus_type *bus = dev->bus;

          if (!bus)
          return;

          if (bus->p->drivers_autoprobe)
          device_initial_probe(dev);
          ... ...
          }

          /***** drivers/base/dd.c *****/
          void device_initial_probe(struct device *dev)
          {
          __device_attach(dev, true);
          }

          static int __device_attach(struct device *dev, bool allow_async)
          {
          ... ...
          ret = bus_for_each_drv(dev->bus, NULL, &data,
          __device_attach_driver);
          ... ...
          }

          static int __device_attach_driver(struct device_driver *drv, void *_data)
          {
          struct device_attach_data *data = _data;
          struct device *dev = data->dev;
          bool async_allowed;
          int ret;

          ret = driver_match_device(drv, dev);
          if (ret == 0) {
          /* no match */
          return 0;
          } else if (ret == -EPROBE_DEFER) {
          dev_dbg(dev, 'Device match requests probe deferral\n');
          driver_deferred_probe_add(dev);
          } else if (ret < 0) {
          dev_dbg(dev, 'Bus failed to match device: %d', ret);
          return ret;
          } /* ret > 0 means positive match */

          async_allowed = driver_allows_async_probing(drv);

          if (async_allowed)
          data->have_async = true;

          if (data->check_async && async_allowed != data->want_async)
          return 0;

          return driver_probe_device(drv, dev);
          }

          bus_probe_device(&globalfifo_device.dev)的執(zhí)行函數(shù)路線分析如下所示,經(jīng)過層層調(diào)用,最終又調(diào)用到driver_match_device()driver_probe_device()函數(shù),查找總線上能和當(dāng)前設(shè)備匹配的驅(qū)動,并將驅(qū)動和設(shè)備綁定在了一起。

              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)

          總結(jié)

          綜上述分析,可以看到驅(qū)動注冊的過程中,會嘗試尋找總線上可以與之匹配的設(shè)備;同樣地,設(shè)備注冊的過程中,也會嘗試尋找總線上可以與之綁定的驅(qū)動。整個過程中,總線、設(shè)備、驅(qū)動的關(guān)鍵注冊函數(shù)分別為:

          • 總線注冊:bus_register()
          • 驅(qū)動注冊:platform_driver_register() --> driver_register() --> bus_add_driver()
          • 設(shè)備注冊:platform_device_add() --> device_add() --> bus_add_device() / bus_probe_device()

          sysfs的角度,可以清楚地看到platform_device、platform_driver、platform_bus之間的聯(lián)系:

          /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設(shè)備驅(qū)動開發(fā)詳解(基于最新的Linux4.0內(nèi)核),宋寶華編著,2016年
          [2] 知識整理–linux設(shè)備驅(qū)動模型:https://blog.csdn.net/TongxinV/article/details/54853122[1]
          [3] linux設(shè)備驅(qū)動模型:https://blog.csdn.net/qq_40732350/article/details/82992904[2]

          原文鏈接

          • Linux設(shè)備驅(qū)動模型簡述(源碼剖析)[3]

          參考資料

          [1]

          2] 知識整理–linux設(shè)備驅(qū)動模型:[https://blog.csdn.net/TongxinV/article/details/54853122: https://blog.csdn.net/TongxinV/article/details/54853122

          [2]

          3] linux設(shè)備驅(qū)動模型:[https://blog.csdn.net/qq_40732350/article/details/82992904: https://blog.csdn.net/qq_40732350/article/details/82992904

          [3]

          Linux設(shè)備驅(qū)動模型簡述(源碼剖析): https://hughesxu.github.io/posts/Linux_device_and_driver_model/



          招已經(jīng)開始啦,大家如果不做好充足準(zhǔn)備的話,招很難找到好工作。


          送大家一份就業(yè)大禮包,大家可以突擊一下春招,找個好工作!


          瀏覽 98
          點贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  一级免费卜片 | 一级一级爱爱 | 五月天黄色片 | 一级成人片免费看 | www.操逼网 |