Linux设备模型之platform设备


在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 ,访问其它成员,如后面要提到的 resource  
        struct 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 this
					   device */
	void		*platform_data;	/* Platform specific data, device
					   core doesn't touch it */
	void		*driver_data;	/* Driver data, set and get with
					   dev_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_MODEL
	struct em_perf_domain	*em_pd;
#endif

#ifdef CONFIG_PINCTRL
	struct dev_pin_info	*pins;
#endif
	struct dev_msi_info	msi;
#ifdef CONFIG_DMA_OPS
	const struct dma_map_ops *dma_ops;
#endif
	u64		*dma_mask;	/* dma mask (if dma'able device) */
	u64		coherent_dma_mask;/* Like dma_mask, but for
					     alloc_coherent mappings as
					     not all hardware supports
					     64 bit addresses for consistent
					     allocations 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_COHERENT
	struct dma_coherent_mem	*dma_mem; /* internal for coherent mem
					     override */
#endif
#ifdef CONFIG_DMA_CMA
	struct cma *cma_area;		/* contiguous memory area for dma
					   allocations */
#endif
#ifdef CONFIG_SWIOTLB
	struct 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_NUMA
	int		numa_node;	/* NUMA node this device is close to */
#endif
	dev_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_BYPASS
	bool			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_register
    device_initialize(&pdev->dev);
    arch_setup_pdev_archdata(空函数)
    platform_device_add
        pdev->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_bus
	if (!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_unregister
    platform_device_del
        释放platform_device id
        device_del(pdev->dev)
    platform_device_put
      put_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,也是为了到时候的probe
    drv->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_unregister
  platform_driver_unregister
    driver_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是否已经被注册,防止重复注册同一个driver
	other = 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->match
            drv-bus->match ? drv->bus->match(dev, drv) : 1
            if 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 设备的 ID PCIE 驱动支持的 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)平台设备驱动

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值