Linux设备模型之platform设备

  • 1. Platform模块的软件架构
  • 2. Platform设备
    • 2.1 platform_device原型
    • 2.2 注册添加device
      • 2.2.1 platform_device_register
      • 2.2.2 platform_device_add
      • 2.2.3 device卸载过程
  • 3. platform驱动
    • 3.1 platform_driver原型
    • 3.2 注册添加driver
    • 3.3 driver注册移除
  • 4. platform总线
    • 4.1 platform_bus_type原型
  • 5. platform驱动和设备的匹配
    • 5.1 driver_match_device
    • 5.2 platform_match
    • 5.3 通过match表匹配
  • 6 实例解析platform
    • 6.1 platform bus 注册
    • 6.2 platform device 注册
    • 6.3 platform driver 注册
    • 6.4 驱动初始化
  • 附录:API接口
    • Platform Device提供的API
    • Platform Driver提供的API

在Linux设备模型的抽象中,存在着一类称作“ Platform Device”的设备,内核是这样描述它们的( Documentation/driver-api/driver-model/platform.rst):

Platform devices
~~~~~~~~~~~~~~~~
Platform devices are devices that typically appear as autonomous
entities in the system. This includes legacy port-based devices and
host bridges to peripheral buses, and most controllers integrated
into system-on-chip platforms.  What they usually have in common
is direct addressing from a CPU bus.  Rarely, a platform_device will
be connected through a segment of some other kind of bus; but its
registers will still be directly addressable.Platform devices are given a name, used in driver binding, and a
list of resources such as addresses and IRQs::struct platform_device {const char  *name;u32        id;struct device    dev;u32      num_resources;struct resource   *resource;};
Platform drivers
~~~~~~~~~~~~~~~~
Platform drivers follow the standard driver model convention, where
discovery/enumeration is handled outside the drivers, and drivers
provide probe() and remove() methods.  They support power management
and shutdown notifications using the standard conventions::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 (*suspend_late)(struct platform_device *, pm_message_t state);int (*resume_early)(struct platform_device *);int (*resume)(struct platform_device *);struct device_driver driver;};Note that probe() should in general verify that the specified device hardware
actually exists; sometimes platform setup code can't be sure.  The probing
can use device resources, including clocks, and device platform_data.Platform drivers register themselves the normal way::int platform_driver_register(struct platform_driver *drv);Or, in common situations where the device is known not to be hot-pluggable,
the probe() routine can live in an init section to reduce the driver's
runtime memory footprint::int platform_driver_probe(struct platform_driver *drv,int (*probe)(struct platform_device *))Kernel modules can be composed of several platform drivers. The platform core
provides helpers to register and unregister an array of drivers::int __platform_register_drivers(struct platform_driver * const *drivers,unsigned int count, struct module *owner);void platform_unregister_drivers(struct platform_driver * const *drivers,unsigned int count);If one of the drivers fails to register, all drivers registered up to that
point will be unregistered in reverse order. Note that there is a convenience
macro that passes THIS_MODULE as owner parameter::#define platform_register_drivers(drivers, count)

概括来说,Platform设备包括:

  • 基于端口的设备(已不推荐使用,保留下来只为兼容旧设备,legacy);
  • 连接物理总线的桥设备;
  • 集成在SOC平台上面的控制器;
  • 连接在其它bus上的设备(很少见)。
  • 等等。

这些设备有一个基本的特征:可以通过CPU bus直接寻址(例如在嵌入式系统常见的“寄存器”)。因此,由于这个共性,内核在设备模型的基础上(devicedevice_driver),对这些设备进行了更进一步的封装,抽象出paltform busplatform deviceplatform driver,以便驱动开发人员可以方便的开发这类设备的驱动。

可以说,paltform设备对Linux驱动工程师是非常重要的,因为我们编写的大多数设备驱动,都是为了驱动plaftom设备。本文我们就来看看Platform设备在内核中的实现。

1. Platform模块的软件架构

内核中Platform设备有关的实现位于include/linux/platform_device.hdrivers/base/platform.c两个文件中,它的软件架构如下:

由图片可知,Platform设备在内核中的实现主要包括三个部分:

  • Platform Bus,基于底层bus模块,抽象出一个虚拟的Platform bus,用于挂载Platform设备;
  • Platform Device,基于底层device模块,抽象出Platform Device,用于表示Platform设备;
  • Platform Driver,基于底层device_driver模块,抽象出Platform Driver,用于驱动Platform设备。

其中Platform DevicePlatform Driver会会其它Driver提供封装好的API,具体可参考后面的描述。

2. Platform设备

Platform提供的接口包括:Platform DevicePlatform Driver两个数据结构,以及它们的操作函数。

2.1 platform_device原型

  1. 用于抽象Platform设备的数据结构----struct platform_device

    /* include/linux/platform_device.h*/
    struct platform_device {/* 分配id的方式,决定了name的值 */const char  *name;    int     id;bool        id_auto;//真正的设备,通过 container_of ,就能找到整个platform_device ,访问其它成员,如后面要提到的 resourcestruct device   dev;/*num_resources、resource,该设备的资源描述,由struct resource(include/linux/ioport.h)结构抽象。 在Linux中, 系统资源包括I/O、Memory、Register、IRQ、DMA、Bus等多种类型。这些资源大多具有独占性,不允许多个设备同时使用,因此Linux内核提供了一些API,用于分配、管理这些资源。 当某个设备需要使用某些资源时,只需利用struct resource组织这些资源(如名称、类型、起始、结束地址等),并保存在该设备的resource指针中即可。然后在设备probe时,设备需求会调用资源管理接口,分配、使用这些资源。而内核的资源管理逻辑,可以判断这些资源是否已被使用、是否可被使用等等。*/u32     num_resources;struct resource *resource;/*记录和驱动的匹配表id_table中匹配的哪一个表项指针*/const struct platform_device_id *id_entry;/* MFD cell pointer */struct mfd_cell *mfd_cell;/* arch specific additions */// 这个参数一般都指向这个结构体实体本身地址struct pdev_archdata    archdata;
    };
    

    该结构的解释如下:

    • dev,真正的设备(Platform设备只是一个特殊的设备,因此其核心逻辑还是由底层的模块实现)。

    • name,设备的名称,和struct device结构中的init_name("Linux设备模型之device和device driver”)意义相同。实际上,该名称在设备注册时,会拷贝到dev.init_name中。

    • id,用于标识该设备的ID
      在“Linux设备模型之Bus”中有提过,内核允许存在多个名称相同的设备。而设备驱动的probe,依赖于名称,Linux采取的策略是:在bus的设备链表中查找device,和对应的device_driver比对name,如果相同,则查看该设备是否已经绑定了driver(查看其dev->driver指针是否为空),如果已绑定,则不会执行probe动作,如果没有绑定,则以该device的指针为参数,调用driverprobe接口。
      因此,在driverprobe接口中,通过判断设备的ID,可以知道此次驱动的设备是哪个。

    • id_auto,指示在注册设备时,是否自动赋予ID值(不需要人为指定啦,可以懒一点啦)。

    • num_resourcesresource,该设备的资源描述,由struct resourceinclude/linux/ioport.h)结构抽象。
      在Linux中,系统资源包括I/OMemoryRegisterIRQDMABus等多种类型。这些资源大多具有独占性,不允许多个设备同时使用,因此Linux内核提供了一些API,用于分配、管理这些资源。
      当某个设备需要使用某些资源时,只需利用struct resource组织这些资源(如名称、类型、起始、结束地址等),并保存在该设备的resource指针中即可。然后在设备probe时,设备需求会调用资源管理接口,分配、使用这些资源。而内核的资源管理逻辑,可以判断这些资源是否已被使用、是否可被使用等等。

    • id_entry,和内核模块相关的内容,暂不说明。

    • mfd_cell,和MFD设备相关的内容,暂不说明。

    • archdata,一个奇葩的存在!!它的目的是为了保存一些architecture相关的数据,去看看arch/arm/include/asm/device.hstruct pdev_archdata结构的定义

有时还需要指定dev.platform_data,这一部分数据常常被驱动使用,这也是Linux 驱动和设备分离的一部分体现。

/*include/linux/device.h*/
/*** struct device - The basic device structure* @parent:   The device's "parent" device, the device to which it is attached.*      In most cases, a parent device is some sort of bus or host*         controller. If parent is NULL, the device, is a top-level device,*      which is not usually what you want.* @p:        Holds the private data of the driver core portions of the device.*      See the comment of the struct device_private for detail.* @kobj:    A top-level, abstract class from which other classes are derived.* @init_name:  Initial name of the device.* @type: The type of device.*        This identifies the device type and carries type-specific*      information.* @mutex:   Mutex to synchronize calls to its driver.* @bus:    Type of bus device is on.* @driver: Which driver has allocated this* @platform_data: Platform data specific to the device.*         Example: For devices on custom boards, as typical of embedded*      and SOC based hardware, Linux often uses platform_data to point*        to board-specific structures describing devices and how they*       are wired.  That can include what ports are available, chip*        variants, which GPIO pins act in what additional roles, and so*         on.  This shrinks the "Board Support Packages" (BSPs) and*      minimizes board-specific #ifdefs in drivers.* @driver_data: Private pointer for driver specific info.* @links:  Links to suppliers and consumers of this device.* @power:   For device power management.*       See Documentation/driver-api/pm/devices.rst for details.* @pm_domain:   Provide callbacks that are executed during system suspend,*         hibernation, system resume and during runtime PM transitions*       along with subsystem-level and driver-level callbacks.* @em_pd: device's energy model performance domain* @pins:    For device pin management.*     See Documentation/driver-api/pin-control.rst for details.* @msi:    MSI related data* @numa_node:   NUMA node this device is close to.* @dma_ops:    DMA mapping operations for this device.* @dma_mask:    Dma mask (if dma'ble device).* @coherent_dma_mask: Like dma_mask, but for alloc_coherent mapping as not all*        hardware supports 64-bit addresses for consistent allocations*      such descriptors.* @bus_dma_limit: Limit of an upstream bridge or bus which imposes a smaller*      DMA limit than the device itself supports.* @dma_range_map: map for DMA memory ranges relative to that of RAM* @dma_parms:  A low level driver may set these to teach IOMMU code about*         segment limitations.* @dma_pools:   Dma pools (if dma'ble device).* @dma_mem:   Internal for coherent mem override.* @cma_area: Contiguous memory area for dma allocations* @dma_io_tlb_mem: Pointer to the swiotlb pool used.  Not for driver use.* @archdata: For arch-specific additions.* @of_node: Associated device tree node.* @fwnode:  Associated device node supplied by platform firmware.* @devt:   For creating the sysfs "dev".* @id:     device instance* @devres_lock: Spinlock to protect the resource of the device.* @devres_head: The resources list of the device.* @knode_class: The node used to add the device to the class list.* @class:  The class of the device.* @groups:  Optional attribute groups.* @release:   Callback to free the device after all references have*      gone away. This should be set by the allocator of the*      device (i.e. the bus driver that discovered the device).* @iommu_group: IOMMU group the device belongs to.* @iommu: Per device generic IOMMU runtime data* @removable:  Whether the device can be removed from the system. This*              should be set by the subsystem / bus driver that discovered*              the device.** @offline_disabled: If set, the device is permanently online.* @offline:   Set after successful invocation of bus type's .offline().* @of_node_reused: Set if the device-tree node is shared with an ancestor*              device.* @state_synced: The hardware state of this device has been synced to match*          the software state of this device by calling the driver/bus*        sync_state() callback.* @can_match:   The device has matched with a driver at least once or it is in*     a bus (like AMBA) which can't check for matching drivers until*     other devices probe successfully.* @dma_coherent: this particular device is dma coherent, even if the*      architecture supports non-coherent devices.* @dma_ops_bypass: If set to %true then the dma_ops are bypassed for the*        streaming DMA operations (->map_* / ->unmap_* / ->sync_*),*        and optionall (if the coherent mask is large enough) also*      for dma allocations.  This flag is managed by the dma ops*      instance from ->dma_supported.** At the lowest level, every device in a Linux system is represented by an* instance of struct device. The device structure contains the information* that the device model core needs to model the system. Most subsystems,* however, track additional information about the devices they host. As a* result, it is rare for devices to be represented by bare device structures;* instead, that structure, like kobject structures, is usually embedded within* a higher-level representation of the device.*/
struct device {struct kobject kobj;struct device       *parent;struct device_private   *p;const char      *init_name; /* initial name of the device */const struct device_type *type;struct bus_type    *bus;        /* type of bus device is on */struct device_driver *driver;  /* which driver has allocated thisdevice */void       *platform_data;  /* Platform specific data, devicecore doesn't touch it */void     *driver_data;    /* Driver data, set and get withdev_set_drvdata/dev_get_drvdata */struct mutex      mutex;   /* mutex to synchronize calls to* its driver.*/struct dev_links_info    links;struct dev_pm_info    power;struct dev_pm_domain  *pm_domain;#ifdef CONFIG_ENERGY_MODELstruct em_perf_domain   *em_pd;
#endif#ifdef CONFIG_PINCTRLstruct dev_pin_info *pins;
#endifstruct dev_msi_info msi;
#ifdef CONFIG_DMA_OPSconst struct dma_map_ops *dma_ops;
#endifu64      *dma_mask;   /* dma mask (if dma'able device) */u64       coherent_dma_mask;/* Like dma_mask, but foralloc_coherent mappings asnot all hardware supports64 bit addresses for consistentallocations such descriptors. */u64      bus_dma_limit;   /* upstream dma constraint */const struct bus_dma_region *dma_range_map;struct device_dma_parameters *dma_parms;struct list_head dma_pools;   /* dma pools (if dma'ble) */#ifdef CONFIG_DMA_DECLARE_COHERENTstruct dma_coherent_mem    *dma_mem; /* internal for coherent memoverride */
#endif
#ifdef CONFIG_DMA_CMAstruct cma *cma_area;       /* contiguous memory area for dmaallocations */
#endif
#ifdef CONFIG_SWIOTLBstruct io_tlb_mem *dma_io_tlb_mem;
#endif/* arch specific additions */struct dev_archdata archdata;struct device_node *of_node; /* associated device tree node */struct fwnode_handle  *fwnode; /* firmware device node */#ifdef CONFIG_NUMAint        numa_node;   /* NUMA node this device is close to */
#endifdev_t          devt;    /* dev_t, creates the sysfs "dev" */u32          id;  /* device instance */spinlock_t        devres_lock;struct list_head    devres_head;struct class        *class;const struct attribute_group **groups; /* optional groups */void (*release)(struct device *dev);struct iommu_group  *iommu_group;struct dev_iommu   *iommu;enum device_removable    removable;bool           offline_disabled:1;bool            offline:1;bool         of_node_reused:1;bool          state_synced:1;bool            can_match:1;
#if defined(CONFIG_ARCH_HAS_SYNC_DMA_FOR_DEVICE) ||\defined(CONFIG_ARCH_HAS_SYNC_DMA_FOR_CPU) ||\defined(CONFIG_ARCH_HAS_SYNC_DMA_FOR_CPU_ALL)bool         dma_coherent:1;
#endif
#ifdef CONFIG_DMA_OPS_BYPASSbool         dma_ops_bypass : 1;
#endif
};
  1. 用于抽象Platform设备驱动的数据结构----struct platform_driver

    /* include/linux/platform_device.h*/
    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;/** For most device drivers, no need to care about this flag as long as* all DMAs are handled through the kernel DMA API. For some special* ones, for example VFIO drivers, they know how to manage the DMA* themselves and set this flag so that the IOMMU layer will allow them* to setup and manage their own I/O address space.*/bool driver_managed_dma;
    };
    
    • struct platform_driver结构和struct device_driver非常类似,无非就是提供proberemovesuspendresume等回调函数,这里不再细说。
    • 另外这里有一个id_table的指针,该指针和"Linux设备模型之device和device driver”所描述的of_match_tableacpi_match_table的功能类似:提供其它方式的设备probe

    在"Linux设备模型之device和device driver”讲过,内核会在合适的时机检查devicedevice_driver的名字,如果匹配,则执行probe。其实除了名称之外,还有一些宽泛的匹配方式,例如这里提到的各种match table

2.2 注册添加device

这里只是简单罗列函数调用过程,这一部分实际上是Device注册的过程的一个封装,具体内部操作可以参考Linux设备注册。

platform_device_registerdevice_initialize(&pdev->dev);arch_setup_pdev_archdata(空函数)platform_device_addpdev->dev.parent = &platform_bus;指定父设备为platform设备总线pdev->dev.bus = &platform_bus_type;设定设备名称三种情况(-1 -2(申请ID) other)设备资源管理 调用device_add(pdev->dev)

2.2.1 platform_device_register

/*drivers/base/platform.c*//*** platform_device_register - add a platform-level device* @pdev: platform device we're adding** NOTE: _Never_ directly free @pdev after calling this function, even if it* returned an error! Always use platform_device_put() to give up the* reference initialised in this function instead.*/
int platform_device_register(struct platform_device *pdev)
{device_initialize(&pdev->dev);// device 初始化setup_pdev_dma_masks(pdev);return platform_device_add(pdev);
}
EXPORT_SYMBOL_GPL(platform_device_register);

2.2.2 platform_device_add

/*drivers/base/platform.c*//*** platform_device_add - add a platform device to device hierarchy* @pdev: platform device we're adding** This is part 2 of platform_device_register(), though may be called* separately _iff_ pdev was allocated by platform_device_alloc().*/
int platform_device_add(struct platform_device *pdev)
{u32 i;int ret;if (!pdev)return -EINVAL;// 指定父设备 即 依托的总线 为 platform_busif (!pdev->dev.parent)pdev->dev.parent = &platform_bus;// 指定bus 涉及到后面的驱动匹配// (因为设备添加过程会拿这个设备所属的总线总线上由注册的驱动list)pdev->dev.bus = &platform_bus_type;// 根据ID的不同值以不同的策略初始化设备name字段switch (pdev->id) {default:dev_set_name(&pdev->dev, "%s.%d", pdev->name,  pdev->id);break;case PLATFORM_DEVID_NONE: // platform_device.name 的 值dev_set_name(&pdev->dev, "%s", pdev->name);break;case PLATFORM_DEVID_AUTO:// 自动分配platform设备ID/** 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_alloc(&platform_devid_ida, GFP_KERNEL);if (ret < 0)goto err_out;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);if (ret) {dev_err(&pdev->dev, "failed to claim resource %d: %pR\n", i, r);goto failed;}}}pr_debug("Registering platform device '%s'. Parent at %s\n",dev_name(&pdev->dev), dev_name(pdev->dev.parent));// 最关键的就是device_add,把设备添加到总线上。ret = device_add(&pdev->dev);if (ret == 0)return ret;failed:if (pdev->id_auto) {ida_free(&platform_devid_ida, pdev->id);pdev->id = PLATFORM_DEVID_AUTO;}while (i--) {struct resource *r = &pdev->resource[i];if (r->parent)release_resource(r);}err_out:return ret;
}
EXPORT_SYMBOL_GPL(platform_device_add);

2.2.3 device卸载过程

这一部分内容也是上面的操作的一个逆向操作,其实核心的内容还是设备删除的操作,同样可以参考上面给出的联接查看设备的注销过程,就能明白platform框架只是在原有的驱动和设备驱动模型上的更高一层的封装。所以这里还是简单的罗列一下调用过程。

platform_device_unregisterplatform_device_del释放platform_device iddevice_del(pdev->dev)platform_device_putput_device

3. platform驱动

3.1 platform_driver原型

platform_driver 也是一个包含了device_driver 的结构。

/*include/linux/platform_device.h*/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;/** For most device drivers, no need to care about this flag as long as* all DMAs are handled through the kernel DMA API. For some special* ones, for example VFIO drivers, they know how to manage the DMA* themselves and set this flag so that the IOMMU layer will allow them* to setup and manage their own I/O address space.*/bool driver_managed_dma;
};

从结构体可以看出,平台设备驱动提供了一些操作接口和一个platform_device_id 类型的兼容性匹配表。

其次是结构体中的操作接口函数,其在内部的device_driver结构体内也是有一套类似的操作接口;

其他的电源管理现在已经很少用平台设备驱动中的接口了,而是转而使用内涵的device_driver驱动中的dev_pm_ops结构体中的接口来实现。

3.2 注册添加driver

__platform_driver_register(drv, THIS_MODULE)drv->driver.bus = platform_bus_type;设置probe、remove、shutdown。driver_register
/*./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;// 指定为 platform_bus_type,也是为了到时候的probedrv->driver.bus = &platform_bus_type;/*如果platform驱动中的xx_probe或xx_remove等为空则指定drv->driver.同名接口指针为platform驱动默认行为(仅支持acpi方式匹配)*/if (drv->probe)drv->driver.probe = platform_drv_probe;if (drv->remove)drv->driver.remove = platform_drv_remove;if (drv->shutdown)drv->driver.shutdown = platform_drv_shutdown;return driver_register(&drv->driver);
}
EXPORT_SYMBOL_GPL(__platform_driver_register);
/*./include/linux/platform_device.h*//** use a macro to avoid include chaining to get THIS_MODULE*/
#define platform_driver_register(drv)\__platform_driver_register(drv, THIS_MODULE)
extern int __platform_driver_register(struct platform_driver *,struct module *);
extern void platform_driver_unregister(struct platform_driver *);

通过上面的代码我们很清楚的看到platform_driver实际上也是对通用驱动的注册流程的一个高层级的封装

3.3 driver注册移除

移除过程同样很简单就是设备驱动的删除操作同上参考设备驱动的注销过程,这里也是仅仅简单的罗列API的调用层级和过程。

platform_device_unregisterplatform_driver_unregisterdriver_unregister

4. platform总线

可能有人会好奇,为什么没有platform_bus,实际上platform_bus_type是一个bus_type的实例;而不是基于bus_type的封装。

因为在一般情况下,内核中已经初始化并挂载了一条platform总线在sysfs文件系统中。

4.1 platform_bus_type原型

/*drivers/base/platform.c*/struct bus_type platform_bus_type = {.name     = "platform",.dev_groups    = platform_dev_groups,// sysfs属性.match     = platform_match,.uevent      = platform_uevent,.probe      = platform_probe,.remove      = platform_remove,.shutdown   = platform_shutdown,.dma_configure    = platform_dma_configure,.dma_cleanup = platform_dma_cleanup,.pm        = &platform_dev_pm_ops,
};
EXPORT_SYMBOL_GPL(platform_bus_type);

结合前面的分析,Linux下的设备都应该(但不是必须)有所属的bus_typedev.bus)这个bus_type就抽象了他们共通的一些“属性”和“方法”。

5. platform驱动和设备的匹配

无论上面的注册设备还是注册驱动,最后都是要调用总线类型的mach函数进行驱动和设备的匹配,这也是platform 驱动框架中比较重要核心的部分。

5.1 driver_match_device

无论是driver_register还是device_register,都会调用到driver_match_device。具体详细内容可以查看另外一篇文章:Linux设备驱动和设备匹配过程,这里结合文章内容再次介绍一下。
例如driver_register的调用过程:

/*** 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;}//检测总线的操作函数和驱动的操作函数是否同时存在//三种可能都会被考虑进行注册的动作,//drv的总线probe和drv的probe都已定义//drv的总线remove和drv的remove都已经定义//drv的总线shutdown和drv的shutdown都已经进行定义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);//去已经注册的driver链表上查看,该driver是否已经被注册,防止重复注册同一个driverother = driver_find(drv->name, drv->bus);//这里查找drv->name="hub"驱动是否已经在drv->bus总线上注册,并增加引用计数,若已经注册,则返回提示信息if (other) {printk(KERN_ERR "Error: Driver '%s' is already registered, ""aborting...\n", drv->name);return -EBUSY;}/把这个driver添加到该总线维护的driver链表中ret = bus_add_driver(drv);//如果之前没注册,就在此注册if (ret)return ret;ret = driver_add_groups(drv, drv->groups);if (ret) {bus_remove_driver(drv);return ret;}kobject_uevent(&drv->p->kobj, KOBJ_ADD);return ret;
}
EXPORT_SYMBOL_GPL(driver_register);
driver_register(drv) [core.c]bus_add_driver(drv) [bus.c]if (drv->bus->p->drivers_autoprobe)driver_attach(dev)[dd.c]bus_for_each_dev(dev->bus, NULL, drv,__driver_attach)__driver_attach(dev, drv) [dd.c]driver_match_device(drv, dev) [base.h]// 执行 bus->matchdrv-bus->match ? drv->bus->match(dev, drv) : 1if false, return;driver_probe_device(drv, dev) [dd.c]really_probe(dev, drv) [dd.c]dev-driver = drv;if (dev-bus->probe)dev->bus->probe(dev);else if (drv->probe)drv-aprobe(dev);probe_failed:dev->-driver = NULL;

结合刚刚介绍的platform_bus_type,我们知道,待会就会调用platform_match进行设备与驱动的匹配。

/*drivers/base/platform.c*/struct bus_type platform_bus_type = {.name     = "platform",.dev_groups    = platform_dev_groups,// sysfs属性.match     = platform_match,.uevent      = platform_uevent,.probe      = platform_probe,.remove      = platform_remove,.shutdown   = platform_shutdown,.dma_configure    = platform_dma_configure,.dma_cleanup = platform_dma_cleanup,.pm        = &platform_dev_pm_ops,
};
EXPORT_SYMBOL_GPL(platform_bus_type);

5.2 platform_match

/*** platform_match - bind platform device to platform driver.* @dev: device.* @drv: driver.** Platform device IDs are assumed to be encoded like this:* "<name><instance>", where <name> is a short description of the type of* device, like "pci" or "floppy", and <instance> is the enumerated* instance of the device, like '0' or '42'.  Driver IDs are simply* "<name>".  So, extract the <name> from the platform_device structure,* and compare it against the name of the driver. Return whether they match* or not.*/
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 *//* 采用ACPI的方式匹配驱动和设备 略*/if (acpi_driver_match_device(dev, drv))return 1;/* Then try to match against the id table *//* 通过驱动和设备的match表来匹配驱动和设备 */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);
}

从这个函数我们可以知道platformdriverdevice的匹配就是通过四种规则来进行匹配的,按优先级划分:

  • 通过设备树匹配
  • 通过ACPI匹配
  • 通过match表匹配
  • 通过两者的名称来匹配

5.3 通过match表匹配

其中兼容ID的匹配表格式是:

/*include/linux/mod_devicetable.h*/#define PLATFORM_NAME_SIZE    20
#define PLATFORM_MODULE_PREFIX  "platform:"struct platform_device_id {char name[PLATFORM_NAME_SIZE];kernel_ulong_t driver_data;
};

具体的匹配规则也很简单,就是使用ID表內的名称来和设备名比较。

/*./drivers/base/platform.c*/static const struct platform_device_id *platform_match_id(const struct platform_device_id *id,struct platform_device *pdev)
{while (id->name[0]) {if (strcmp(pdev->name, id->name) == 0) {pdev->id_entry = id;return id;}id++;}return NULL;
}

需要注意的是,这里还将匹配的id 表的句柄保存在platform deviceid_entry项上,id_table里常常带一个long型的driver_data数据保存驱动数据。

显然,platform_match_id 的作用就是遍历整个 id_table 数组,寻找是否有与 platform_device->name 同名的,如果有,则返回这个 Platform_device_id ,使用id_table 打破了原本设备总线驱动模型,一个 device 只能用与一个 device_driver 配对的局限性。

现在一个platform_device_driver可以与多个platform_device配对。

6 实例解析platform

6.1 platform bus 注册

总线作为管理媒介,必须先注册。platform_busplatform_bus_init 函数中注册。

struct device platform_bus = {.init_name = "platform",
};
EXPORT_SYMBOL_GPL(platform_bus);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,
};
EXPORT_SYMBOL_GPL(platform_bus_type);int __init platform_bus_init(void)
{int error;early_platform_cleanup();error = device_register(&platform_bus);if (error) {put_device(&platform_bus);return error;}error =  bus_register(&platform_bus_type);if (error)device_unregister(&platform_bus);of_platform_register_reconfig_notifier();return error;
}
platform_bus_init
-> device_register(&platform_bus)
-> bus_register(&platform_bus_type)

注:上面的 platform_bus 是一个 deviceplatform_bus_type 才是真正的 bus

可以看到先注册 platform_bus 设备,再注册 platform busplatform_bus_type)。 其中 platform_bus 设备对应 /sys/devices/platform 目录,作为所有 platform_device 的父设备。

Bus 的本质:

  1. 父亲。
    platform_bus 设备表示所有 platform device 的父设备。
  2. 桥梁。
    platform_bus_type 承担 platform_bus 总线下设备与驱动的匹配工作。

6.2 platform device 注册

dts 描述设备硬件组成,被编译成内核可识别的二进制文件。系统启动初期,解析设备树,将设备树描述的设备注册到系统,调用栈如下:

[    0.768872] [<ffffffff807dc558>] of_device_add+0x58/0x78
[    0.774181] [<ffffffff807dca9c>] of_platform_device_create_pdata+0x8c/0xa8
[    0.781056] [<ffffffff807dcbb8>] of_platform_bus_create+0x100/0x1c8
[    0.787322] [<ffffffff807dccf8>] of_platform_populate+0x78/0xd8
[    0.793242] [<ffffffff80200498>] do_one_initcall+0x98/0x1c0

重要信息:

  1. 这里注册的 platform_device 都属于 platform bus

    of_platform_device_create_pdata-> platform_device->dev.bus = &platform_bus_type;
    
  2. 这里注册的 platform_device 会加入到 platform bus 下的设备链表中
     of_device_add-> device_add--> bus_add_device---> klist_add_tail(&dev->p->knode_bus, &bus->p->klist_devices)
    

6.3 platform driver 注册

driver 的注册都在具体的驱动模块中实现。只需要一个 platform_driver_register API 函数。

/*** platform_device_register - add a platform-level device* @pdev: platform device we're adding*/
int platform_device_register(struct platform_device *pdev)
{device_initialize(&pdev->dev);setup_pdev_dma_masks(pdev);return platform_device_add(pdev);
}
EXPORT_SYMBOL_GPL(platform_device_register);

重要信息:
驱动注册后,驱动信息会加入到 platform bus 下的驱动链表中

6.4 驱动初始化

设备/驱动均已注册,现在万事具备,只欠东风。谁来调用驱动初始化函数?

驱动最重要的函数是probe,调用流程如下:

[    1.343296] [<ffffffff807c0168>] ls_i2c_probe+0x38/0x440
[    1.348604] [<ffffffff804fa848>] driver_probe_device+0xc8/0x270
[    1.354528] [<ffffffff804f853c>] bus_for_each_drv+0x64/0xc8
[    1.360091] [<ffffffff804fac0c>] device_attach+0xac/0xd0
[    1.365410] [<ffffffff804f8b88>] bus_probe_device+0xa0/0xe0
[    1.370975] [<ffffffff804f7a2c>] device_add+0x554/0x7c0
[    1.376197] [<ffffffff807dcaac>] of_platform_device_create_pdata+0x8c/0xa8
[    1.383071] [<ffffffff807dcbc8>] of_platform_bus_create+0x100/0x1c8
[    1.389338] [<ffffffff807dcc28>] of_platform_bus_create+0x160/0x1c8
[    1.395604] [<ffffffff807dcd08>] of_platform_populate+0x78/0xd8
[    1.401522] [<ffffffff80200498>] do_one_initcall+0x98/0x1c0

可见驱动初始化流程被融合到了设备/驱动注册流程中。

上面的例子是注册设备完成后,通过 device_attach 函数扫描总线上注册的驱动是否与当前设备匹配,如果匹配就执行驱动初始化函数。 还有一种情况是注册驱动完成后,通过 driver_attach 函数扫描总线上注册的设备是否与当前驱动匹配,如果匹配就执行驱动初始化函数。

Bus 桥梁作用的体现:

  1. 集合:
    bus->p->klist_devices 收集总线下设备。
    bus->p->klist_drivers 收集总线下驱动。

  2. 匹配:
    bus->match 函数会检查设备与驱动是否匹配,不同总线判断方法不同。比如 PCIE 总线是判断 PCIE 设备的 IDPCIE 驱动支持的 id_table 是否匹配,platform 总线是判断 platform_devicecompatible 字段是否与 platform_drivercompatible 匹配。

附录:API接口

Platform Device提供的API

Platform Device主要提供设备的分配、注册等接口,供其它driver使用,具体包括:

/* include/linux/platform_device.h*/
extern int platform_device_register(struct platform_device *);
extern void platform_device_unregister(struct platform_device *);extern void arch_setup_pdev_archdata(struct platform_device *);
extern struct resource *platform_get_resource(struct platform_device *, unsigned int, unsigned int);
extern int platform_get_irq(struct platform_device *, unsigned int);
extern struct resource *platform_get_resource_byname(struct platform_device *, unsigned int, const char *);
extern int platform_get_irq_byname(struct platform_device *, const char *);
extern int platform_add_devices(struct platform_device **, int);extern struct platform_device *platform_device_register_full(const struct platform_device_info *pdevinfo);static inline struct platform_device *platform_device_register_resndata(struct device *parent, const char *name, int id,const struct resource *res, unsigned int num,const void *data, size_t size)static inline struct platform_device *platform_device_register_simple(const char *name, int id,const struct resource *res, unsigned int num)static inline struct platform_device *platform_device_register_data(struct device *parent, const char *name, int id,const void *data, size_t size)extern struct platform_device *platform_device_alloc(const char *name, int id);
extern int platform_device_add_resources(struct platform_device *pdev,const struct resource *res,unsigned int num);
extern int platform_device_add_data(struct platform_device *pdev,const void *data, size_t size);
extern int platform_device_add(struct platform_device *pdev);
extern void platform_device_del(struct platform_device *pdev);
extern void platform_device_put(struct platform_device *pdev);
platform_device_register、platform_device_unregister,Platform设备的注册/注销接口,和底层的device_register等接口类似。
arch_setup_pdev_archdata,设置platform_device变量中的archdata指针。
platform_get_resource、platform_get_irq、platform_get_resource_byname、platform_get_irq_byname,通过这些接口,可以获取platform_device变量中的resource信息,以及直接获取IRQ的number等等。
platform_device_register_full、platform_device_register_resndata、platform_device_register_simple、platform_device_register_data,其它形式的设备注册。调用者只需要提供一些必要的信息,如name、ID、resource等,Platform模块就会自动分配一个struct platform_device变量,填充内容后,注册到内核中。
platform_device_alloc,以name和id为参数,动态分配一个struct platform_device变量。
platform_device_add_resources,向platform device中增加资源描述。
platform_device_add_data,向platform device中添加自定义的数据(保存在pdev->dev.platform_data指针中)。
platform_device_add、platform_device_del、platform_device_put,其它操作接口。

Platform Driver提供的API

  • platform_driver_registeplatform_driver_unregisterplatform driver的注册、注销接口。
  • platform_driver_probe,主动执行probe动作。
  • platform_set_drvdataplatform_get_drvdata,设置或者获取driver保存在device变量中的私有数据。

refer to
Linux 内核:设备驱动模型 学习总结
Linux 内核学习(3)---- platform driver模型
Linux 内核:设备驱动模型(5)平台设备驱动

Linux设备模型之platform设备相关推荐

  1. linux设备模型(8)_platform设备,Linux设备模型之platform总线

    Linux设备模型之platform总线- 一:前言 Platform总线是kernel中最近加入的一种虚拟总线.在近版的2.6kernel中,很多驱动都用platform改写了.只有在分析完plat ...

  2. 学习《Linux设备模型浅析之设备篇》笔记(一)

    最近在学习Linux设备模型,前面几篇文章也是读这篇的时候遇到问题,然后为了搞清楚先转去摸索才写出来的. 当然了,刚开始是先读到<Linux那些事儿之我是Sysfs>,搞不清楚才去读的&l ...

  3. Linux 设备驱动开发 —— platform设备驱动应用实例解析

    前面我们已经学习了platform设备的理论知识Linux 设备驱动开发 -- platform 设备驱动 ,下面将通过一个实例来深入我们的学习. 一.platform 驱动的工作过程 platfor ...

  4. Linux 设备驱动开发 —— 设备树在platform设备驱动中的使用

    关与设备树的概念,我们在Exynos4412 内核移植(六)-- 设备树解析 里面已经学习过,下面看一下设备树在设备驱动开发中起到的作用 Device Tree是一种描述硬件的数据结构,设备树源(De ...

  5. RT-Thread记录(十三、I/O 设备模型之PIN设备)

    讲完UART设备之后,我们已经熟悉RT-Thread I/O 设备模型了,回头看看基本的 PIN 设备. 目录 前言 一.PIN 设备模型解析 1.1 初识 GPIO 操作函数 1.2 PIN 设备框 ...

  6. RT-Thread记录(十二、I/O 设备模型之UART设备 — 使用测试)

    从 UART 设备开始学会使用 RT-Thread I/O 设备模型 . 目录 前言 一.UART 设备操作 1.1 UART 设备控制块 1.2 UART 操作函数 1.2.1 查找 UART 设备 ...

  7. Linux 设备驱动开发 —— platform 设备驱动

    一.platform总线.设备与驱动         在Linux 2.6 的设备驱动模型中,关心总线.设备和驱动3个实体,总线将设备和驱动绑定.在系统每注册一个设备的时候,会寻找与之匹配的驱动:相反 ...

  8. Linux设备模型、平台设备驱动、设备树(device tree)、GPIO子系统以及pinctrl子系统介绍

    文章目录 一.Linux设备模型介绍 (1)设备驱动模型总体介绍 (2)设备驱动模型文件表现 (3)设备驱动模型工作原理 [1]总线 [2]设备 [3]驱动 [4]注册流程 二.平台设备驱动介绍 (1 ...

  9. linux 驱动开发之platform设备驱动一(4)

    前言 Linux 设备和驱动通常都需要挂接在一种总线上,例如PCI.USB.I2C.SPI 等的设备存在真实的总线,这自然不是问题,但是SOC上的外设控制器.挂接在SoC内存空间的外设等却不依附于此类 ...

最新文章

  1. 2022-2028年中国橡胶带行业市场运营格局及未来前景分析报告
  2. redis的两种持久化方式详解
  3. 机器学习之过拟合与欠拟合以及偏差-方差分解
  4. codevs 2924 数独挑战
  5. 如何通过OpenFace实现人脸识别框架
  6. 家用电器如何计算功率和消耗的度数
  7. CSS3及JS媒体查询教程
  8. iQOO Neo6现身安兔兔数据库:高导热稀土散热加入 跑分轻松破百万
  9. 【连载】【STM32神舟III号实验例程】SysTick实验(11)
  10. 安全架构--13--企业资产管理体系建设总结
  11. 微信小程序-音乐播放器总结
  12. 办理房产证,重要的三张纸
  13. MPU和MMU、MPU和MCU的区别
  14. Vim内同时对多行增加或删除相同的内容
  15. 计算机软件类杂志,软件技术类的核心期刊有哪些
  16. 网络工程师速记100条知识点(一)
  17. 在低谷期应该做点什么
  18. task_struct结构
  19. 字符串比较大小(C语言)
  20. 炫界 (978) -(建工发现应用克隆漏)_铁岭在建工地扬尘监测价格,便携粉尘浓度检测,服务靠谱...

热门文章

  1. 中国大学MOOC体育保健学考试试题及答案
  2. 【UER #7】套路
  3. 谷歌浏览器批量删除书签
  4. 2021.11.10 - 145.提莫攻击
  5. 广东省数字经济行业发展动态及十四五前景预测分析报告2022-2027年
  6. 常见编程代码命名风格
  7. 鸡兔同笼python程序怎么写_鸡(土从)
  8. Ubuntu22.04.01Desktop桌面版安装记录221109
  9. 【WSL2】ubuntu22.04 安装docker
  10. grafana的前端二次开发初体验