平台总线---深入分析
阅读引言:本文会从平台总线的介绍,注册平台设备和驱动, 源码分析, 总结五个部分展开, 源码分析platform放在了最后面。
目录
一、平台总线介绍
二、平台总线如何使用
三、平台总线是如何工作的
四、注册platform设备
五、注册platform驱动
六、编写probe函数
七、平台总线源码分析
一、平台总线介绍
平台总线模型也叫platform总线模型。平台总线是Linux系统虚拟出来的总线。所谓虚拟出来的总线其实就是使用软件来模拟物理总线的一些性质, linux各种子系统还是别的, 都特别爱玩匹配这套东西, 挺好。
二、平台总线如何使用
平台总线模型将一个驱动分成了俩个部分,分别是device.c和driver.c,用来描述device.c硬件,driver. c 用来控制硬件。这样做的目的主要是为了提高代码的重复利用性, 降低耦合度。
三、平台总线是如何工作的
平台总线通过字符串比较,将name相同的device.c和driver.c匹配到一起来控制硬件。平台总线原则:先分离,后搭档。
note:源码分析这个位置放在后面些
四、注册platform设备
platform设备驱动(device.c)里面写的是硬件资源。这里的硬件资源指的是寄存器地址,中断号以及其他硬件资源等。在linux内核里面用struct platform_device结构体来描述硬件资源。这个结构体定义在include/linux/platform_device.h文件当中,结构体原型如下:
struct platform_device {
const char *name;
int id;
bool id_auto;
struct device dev;
u64 platform_dma_mask;
struct device_dma_parameters dma_parms;
u32 num_resources;
struct resource *resource;
const struct platform_device_id *id_entry;
/*
* Driver name to force a match. Do not set directly, because core
* frees it. Use driver_set_override() to set or clear it.
*/
const char *driver_override;
/* MFD cell pointer */
struct mfd_cell *mfd_cell;
/* arch specific additions */
struct pdev_archdata archdata;
};
name可以匹配, 也可以在sys/bus/devices下展示。
/*
* Resources are tree-like, allowing
* nesting etc..
*/
struct resource {
resource_size_t start;
resource_size_t end;
const char *name;
unsigned long flags;
unsigned long desc;
struct resource *parent, *sibling, *child;
};
资源的种类:ioport.h文件中
使用struct resource描述硬件资源信息示例:
struct resource my_dev_resource[] = {
[0] = {.start = 0x1000, .end = 0x1000, .flags = IORESOURCE_MEM},
[1] = {.start = 13, .end = 13, .flags = IORESOURCE_IRQ}
};
在my_dev_resource这个结构体数字中包含了俩组资源,第一组资源的类型是IORESOURCE_MEM,表示这组资源是一组内存类型的资源。起始地址是0xFDD60000,终止地址是0xFDD60004。第二组资源的类型是IORESOURCE_IRQ,表示这是一组中断资源, 中断很号是13。
platform设备加载和卸载函数
platform设备加载函数: 函数原型:int platform_device_register(struct platform_device *device) 函数作用:加载platform设备。
platform设备卸载函数: 函数原型:void platform_device_unregister(struct platform_device *device) 函数作用:卸载platform设备。
代码结构:
void my_dev_release(struct device *dev)
{
printk("%s is called",__func__);
}
struct resource my_dev_resource[] = {
[0] = {.start = 0xFDD60000, .end = 0xFDD60004, .flags = IORESOURCE_MEM},
[1] = {.start = 13, .end = 13, .flags = IORESOURCE_IRQ}
};
struct platform_device my_dev = {
.name = "mydev",
.id = -1,
.resource = my_dev_resource,
.num_resources = ARRAY_SIZE(my_dev_resource),
.dev = {
.release = my_dev_release
},
};
int __init my_dev_init(void)
{
platform_device_register(&my_dev);
}
void __exit my_dev_exit(void)
{
platform_device_unregister(&my_dev);
}
五、注册platform驱动
关键数据结构, platform设备驱动(driver.c)里面写的软件驱动。在driver.c文件中首先需要定义一个platform_driver结构体。然后去实现这个结构体中的各个成员变量。当driver.c和device.c匹配成功以后,会执行driver.c里面的probe函数。platform_driver这个结构体定义在include/linux/platform_device.h文件当中,结构体原型如下:
匹配过程和调用驱动中的probe函数后面会再说。
platform设备加载和卸载函数
1.platform设备加载函数:
函数原型:intIlatform_driver_registepr(struct platform_driver *driver)
函数作用:加载platform设备。
2. platform设备卸载函数:
函数原型:void platform_driver_register(struct platform_driver *driver)
函数作用:卸载platform设备。
int my_probe(struct platform_device *)
{
}
int my_remove(struct platform_device *)
{
}
const struct platform_device_id my_id_table = {
.name = "my_driver",
};
static struct platform_driver my_driver = {
.probe = my_probe,
.remove = my_remove,
.driver = {
.name = "my_driver",
.owner = THIS_MODULE,
},
.id_table = &my_id_table,
};
int __init my_input_init(void)
{
platform_driver_register(&my_driver);
return ret;
}
void __exit my_input_exit(void)
{
platform_driver_unregister(&my_driver);
}
注册平台驱动框架如上。
三种匹配方式, 名称匹配, id匹配, 设备树匹配。优先级由低到高。
- 设备与驱动的名称匹配
- 设备与驱动的ID匹配
- 设备树匹配
设备树匹配是不需要设备端的代码的, 由系统启动的时候解析设备树, 得到的device_node,满足条件的再转为平台设备。后面可能还会再出设备树, 设备模型相关的文章。
六、编写probe函数
驱动是要控制硬件的。但是平台总线模型对硬件的描述是在设备(device)中的。所以在驱动(driver)中,我们需要得到设备(device)中的硬件资源。当设备(device)和驱动(driver)匹配成功以后,会执行驱动(driver)中的probe函数,所以我们要在probe函数中拿到设备(device)中的硬件资源。所以,重点是怎么拿到这些硬件资源呢?
函数原型:extern struct resource *platform_get_resource(struct platform_device *,unsigned int,
unsigned int);
函数作用:获取device中的硬件资源。
函数参数:第一个参数platform_device结构体。第二个参数:资源的类型。第三个参数:索引号。资源处在同类资源的哪个位置上。同类资源指的是flags是一样的同类资源。
返回资源数组中的指定的哪一个的地址。
int my_probe(struct platform_device *dev)
{
if (dev == NULL)
{
goto err_probe;
}
struct resource dev_source;
dev_source = platform_get_resource(dev, IORESOURCE_MEM, 0)
if (dev_source == NULL) {
printk("platform_get_resource failed\r\n");
return -2;
}
/* use dev_source */
err_probe:
return -1;
}
拿到resource结构体之后, 使用gpio, 内存, 中断都是有相应的函数的, 根据不同的功能去选择。
七、平台总线源码分析
先从注册设备端的源码入手。设备基类数据结构如下
/**
* 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: Software IO TLB allocator. Not for driver use.
* @dma_io_tlb_pools: List of transient swiotlb memory pools.
* @dma_io_tlb_lock: Protects changes to the list of active pools.
* @dma_uses_io_tlb: %true if device has used the software IO TLB.
* @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
* @physical_location: Describes physical location of the device connection
* point in the system housing.
* @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;
const 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
#ifdef CONFIG_SWIOTLB_DYNAMIC
struct list_head dma_io_tlb_pools;
spinlock_t dma_io_tlb_lock;
bool dma_uses_io_tlb;
#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;
const struct class *class;
const struct attribute_group **groups; /* optional groups */
void (*release)(struct device *dev);
struct iommu_group *iommu_group;
struct dev_iommu *iommu;
struct device_physical_location *physical_location;
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
};
注册平台设备调用了三个函数, 按照顺序查看。
这里涉及到了很多的sys, 也就是设备模型的东西, 后面单独出文章讲讲这个sys, 也随便我自己梳理一下这个sys设备模型这块的东西。重点还是看这个函数platform_device_add, 文件路径drivers\base\platform.c
/**
* platform_device_add - 将平台设备添加到设备层次结构中
* @pdev: 要添加的平台设备
*
* 这是 platform_device_register() 的第二部分,可以单独调用,但前提是 pdev 是通过 platform_device_alloc() 分配的。
*/
int platform_device_add(struct platform_device *pdev)
{
u32 i; // 用于遍历资源的索引
int ret; // 返回值
if (!pdev)
return -EINVAL; // 如果 pdev 为空,返回无效参数错误
if (!pdev->dev.parent)
pdev->dev.parent = &platform_bus; // 设置设备的父设备为 platform_bus
pdev->dev.bus = &platform_bus_type; // 设置设备的总线类型为 platform_bus_type
// 根据设备 ID 设置设备名称
switch (pdev->id) {
default:
dev_set_name(&pdev->dev, "%s.%d", pdev->name, pdev->id); // 设置设备名称为 "name.id"
break;
case PLATFORM_DEVID_NONE:
dev_set_name(&pdev->dev, "%s", pdev->name); // 设置设备名称为 "name"
break;
case PLATFORM_DEVID_AUTO:
/*
* 自动分配的设备 ID。我们将其标记为自动分配,以便记住它需要被释放,并附加一个后缀
* 以避免与显式 ID 的命名空间冲突。
*/
ret = ida_alloc(&platform_devid_ida, GFP_KERNEL); // 分配一个新的设备 ID
if (ret < 0)
goto err_out; // 如果分配失败,跳转到错误处理
pdev->id = ret; // 设置设备 ID
pdev->id_auto = true; // 标记为自动分配的 ID
dev_set_name(&pdev->dev, "%s.%d.auto", pdev->name, pdev->id); // 设置设备名称为 "name.id.auto"
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; // 如果是内存资源,父资源为 iomem_resource
else if (resource_type(r) == IORESOURCE_IO)
p = &ioport_resource; // 如果是 I/O 资源,父资源为 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)); // 打印调试信息
ret = device_add(&pdev->dev); // 将设备添加到设备层次结构中
if (ret == 0)
return ret; // 如果成功,返回 0
failed:
if (pdev->id_auto) {
ida_free(&platform_devid_ida, pdev->id); // 如果 ID 是自动分配的,释放 ID
pdev->id = PLATFORM_DEVID_AUTO; // 重置 ID
}
// 释放所有已分配的资源
while (i--) {
struct resource *r = &pdev->resource[i];
if (r->parent)
release_resource(r); // 释放资源
}
err_out:
return ret; // 返回错误码
}
EXPORT_SYMBOL_GPL(platform_device_add); // 导出符号,使其可以被 GPL 模块使用
数据结构原型:
/**
* struct bus_type - The bus type of the device
*
* @name: The name of the bus.
* @dev_name: Used for subsystems to enumerate devices like ("foo%u", dev->id).
* @bus_groups: Default attributes of the bus.
* @dev_groups: Default attributes of the devices on the bus.
* @drv_groups: Default attributes of the device drivers on the bus.
* @match: Called, perhaps multiple times, whenever a new device or driver
* is added for this bus. It should return a positive value if the
* given device can be handled by the given driver and zero
* otherwise. It may also return error code if determining that
* the driver supports the device is not possible. In case of
* -EPROBE_DEFER it will queue the device for deferred probing.
* @uevent: Called when a device is added, removed, or a few other things
* that generate uevents to add the environment variables.
* @probe: Called when a new device or driver add to this bus, and callback
* the specific driver's probe to initial the matched device.
* @sync_state: Called to sync device state to software state after all the
* state tracking consumers linked to this device (present at
* the time of late_initcall) have successfully bound to a
* driver. If the device has no consumers, this function will
* be called at late_initcall_sync level. If the device has
* consumers that are never bound to a driver, this function
* will never get called until they do.
* @remove: Called when a device removed from this bus.
* @shutdown: Called at shut-down time to quiesce the device.
*
* @online: Called to put the device back online (after offlining it).
* @offline: Called to put the device offline for hot-removal. May fail.
*
* @suspend: Called when a device on this bus wants to go to sleep mode.
* @resume: Called to bring a device on this bus out of sleep mode.
* @num_vf: Called to find out how many virtual functions a device on this
* bus supports.
* @dma_configure: Called to setup DMA configuration on a device on
* this bus.
* @dma_cleanup: Called to cleanup DMA configuration on a device on
* this bus.
* @pm: Power management operations of this bus, callback the specific
* device driver's pm-ops.
* @iommu_ops: IOMMU specific operations for this bus, used to attach IOMMU
* driver implementations to a bus and allow the driver to do
* bus-specific setup
* @need_parent_lock: When probing or removing a device on this bus, the
* device core should lock the device's parent.
*
* A bus is a channel between the processor and one or more devices. For the
* purposes of the device model, all devices are connected via a bus, even if
* it is an internal, virtual, "platform" bus. Buses can plug into each other.
* A USB controller is usually a PCI device, for example. The device model
* represents the actual connections between buses and the devices they control.
* A bus is represented by the bus_type structure. It contains the name, the
* default attributes, the bus' methods, PM operations, and the driver core's
* private data.
*/
struct bus_type {
const char *name;
const char *dev_name;
const struct attribute_group **bus_groups;
const struct attribute_group **dev_groups;
const struct attribute_group **drv_groups;
int (*match)(struct device *dev, struct device_driver *drv);
int (*uevent)(const struct device *dev, struct kobj_uevent_env *env);
int (*probe)(struct device *dev);
void (*sync_state)(struct device *dev);
void (*remove)(struct device *dev);
void (*shutdown)(struct device *dev);
int (*online)(struct device *dev);
int (*offline)(struct device *dev);
int (*suspend)(struct device *dev, pm_message_t state);
int (*resume)(struct device *dev);
int (*num_vf)(struct device *dev);
int (*dma_configure)(struct device *dev);
void (*dma_cleanup)(struct device *dev);
const struct dev_pm_ops *pm;
const struct iommu_ops *iommu_ops;
bool need_parent_lock;
};
后面设备和驱动匹配的时候就是调用了这些当中的一些函数。
调用过程关系
platform_device_register
platform_device_add
device_add
bus_probe_device
device_initial_probe
__device_attach
__device_attach_driver
driver_probe_device
__driver_probe_device
really_probe
在really_probe函数中调用找到的匹配上的驱动中的probe函数。这里看了驱动端的platform_driver_register函数的实现, 发现和设备端的实现差不多。直接上实现的原理
原理分析:
维护了两条链表,一个是设备的, 一个是驱动的, 当注册设备的时候, 调用总线的匹配函数, 这个匹配函数会根据规则匹配, 用当前注册的设备和驱动链表中的一 一做匹配, 匹配上之后, 调用驱动中的probe函数, 注册驱动也是一样的道理。