龙岩网站建设要多久,php开发的大型网站有哪些,建设银行官网站下载,室内设计效果图的软件Linux内核使用总线来处理设备#xff0c;总线连接了CPU与这些设备。有些总线足够智能#xff0c;并内嵌了可发现性逻辑以枚举连接到总线上的设备。在引导阶段的初期#xff0c;Linux内核会请求这些总线提供它们所枚举的设备以及这些设备正常工作所需的资源#xff08;如中断…Linux内核使用总线来处理设备总线连接了CPU与这些设备。有些总线足够智能并内嵌了可发现性逻辑以枚举连接到总线上的设备。在引导阶段的初期Linux内核会请求这些总线提供它们所枚举的设备以及这些设备正常工作所需的资源如中断线和内存区域。PCI、USB和SATA总线都属于这种可发现性总线的范畴。但实际情况往往并不能如我们所愿。有许多设备CPU仍然无法检测到。其中大部分不可发现的设备位于芯片内部尽管有些设备位于慢速或低级总线上这些总线并不支持设备发现。因此内核必须提供机制来接收有关硬件的信息用户必须告诉内核可以从哪里找到这些设备。在Linux内核中这些不可发现的设备称为平台设备(platform device)。由于它们并没有位于已知的I2C、SPI总线或任何不可发现的总线上Linux内核实现了平台总线也称为伪平台总线的概念以维护“设备始终通过总线连接到CPU”的范式。Linux内核平台核心抽象struct platform_devicename在上述数据结构中name表示平台设备的名称。必须谨慎选择分配给平台设备的名称。平台设备与平台驱动程序的匹配是在伪平台总线匹配函数platform_match()中进行的在这个函数中在某些情况下没有设备树或ACPI支持且没有id表匹配匹配会回退到名称匹配即比较驱动程序的名称和平台设备的名称。idplatform_device的id字段是一个整数核心使命是当系统中存在多个同类型的平台设备比如 2 个片上 UART、3 个 GPIO 控制器时通过id给每个设备分配唯一的 “编号”让内核能区分它们若只有一个该类型设备则用特殊值-1表示 “无需编号”。取值 - 1PLATFORM_DEVID_NONE单设备无编号适用场景系统中只有一个该类型的平台设备比如单板上只有 1 个 I2C 控制器、1 个 SPI 控制器内核操作直接将底层设备struct device的名称设置为platform_device.name不加任何后缀例子structplatform_deviceuart_dev{.nameuart,.idPLATFORM_DEVID_NONE,// 等价于-1// 其他属性...};内核生成的设备名称为uart无后缀。2. 取值 - 2PLATFORM_DEVID_AUTO多设备自动分配 id适用场景系统中有多个同类型的平台设备比如单板上有 3 个 UART、2 个 PWM 控制器且不想手动指定编号内核操作遍历已注册的同名称平台设备找到最小的未使用整数 id从 0 开始将该 id 分配给新设备设备名称拼接为name.id例子// 注册第一个UART设备structplatform_deviceuart_dev1{.nameuart,.idPLATFORM_DEVID_AUTO};// 内核分配id0 → 设备名uart.0// 注册第二个UART设备structplatform_deviceuart_dev2{.nameuart,.idPLATFORM_DEVID_AUTO};// 内核分配id1 → 设备名uart.1其他值手动指定 id自定义编号适用场景需要明确指定编号区分同类型设备比如按硬件位置指定UART0 对应串口 1、UART1 对应串口 2内核操作直接用指定的 id 拼接设备名称无需自动分配注意手动指定的 id 不能重复否则内核会报错设备注册失败例子// 手动指定id2 → 设备名uart.2structplatform_deviceuart_dev{.nameuart,.id2};resource和num_resourcesresource是分配给平台设备的资源数组num_resources是该数组中元素的数量。id_entry对于通过id表进行平台设备和驱动程序匹配的情况pdev-id_entry将指向structplatform_device_id类型的匹配id表条目该条目将使平台驱动程序与此平台设备匹配。无论平台设备如何注册它们都需要由适当的驱动程序即平台驱动程序驱动。此类 驱动程序必须实现一组回调函数当设备在平台总线上出现/消失时平台核心会使用这些回调函数。platform_driver平台驱动程序在Linux内核中表示为platform_driver结构体实例该结构体定义如下probe· probe这是匹配发生后设备声明驱动程序时调用的探测函数。内核负责为platform_device提供参数。当设备驱动程序在内核中注册时总线驱动程序将调用探测函数。remove· remove当设备不再需要驱动程序时将调用此移除函数来删除驱动程序。移除函数声明如下driver设备模型的基础驱动程序结构必须提供名称名称必须谨慎选择、所有者以及其他一些字段如设备树匹配表。对于平台驱动程序在对驱动程序和设备进行匹配之前platform_device.name和platform_driver.driver.name字段必须相同。【兜底条件】id_table平台驱动程序向总线代码提供的一种将实际设备绑定到驱动程序的方式。还有一种方式是通过设备树进行绑定处理平台设备分配和注册平台设备由于平台设备无法自行向系统注册因此必须手动填充并注册平台设备到系统中同时也需要注册它们的资源和私有数据。在早期的平台核心阶段平台设备在板级文件如i.MX6的arch/arm/mach-imx/mach-imx6q.c中声明并使用platform_device_add()或platform_device_register()注册到内核中具体使用哪一个取决于平台设备的分配方式。静态方法你需要在代码中枚举平台设备并在每个平台上调用platform_device_register()函数来进行注册。这个函数定义如下static struct platform_device my_pdev { .name my_drv_name, .id 0, .resource jz4740_udc_resources, .num_resources ARRAY_SIZE(jz4740_udc_resources), }; int foo() { [...] return platform_device_register(my_pdev); }通过静态方法平台设备被静态初始化并传递给platform_device_register()函数。静态方法还允许批量添加平台设备相应的函数可以使用platform_add_devices()它接收一个指向平台设备的指针数组和该数组中元素的数量作为参数。该函数定义如下动态方法调用platform_device_alloc()函数以动态分配和初始化平台设备struct platform_device* platform_device_alloc(const char *name, int id);其中name是所要添加的设备的基本名称id是平台设备的实例id。如果执行成功此函数将返回一个有效的平台设备对象执行失败则返回NULL。使用动态方法分配的平台设备专门使用platform_device_add()函数向系统注册int platform_device_add(struct platform_device *pdev);这个函数唯一的参数是使用platform_device_alloc()函数分配的平台设备。如果执行成功该函数返回0如果执行失败则返回错误码。add 是更底层的操作仅负责 “把设备加入内核设备模型”不触发总线匹配对字段的校验更基础但核心字段仍不能为空register 是 add 的 “超集”内部会调用 add但多了 “注册到 platform 总线、触发驱动匹配” 的逻辑校验更严格实际使用中你在调用 add 前一定会先填充字段否则 add 也会失败而直接调用 register 时跳过了填充步骤才会觉得 “add 可以、register 不行”。如果使用platform_device_add()注册平台设备失败则应该使用platform_device_put()函数释放platform_device结构体占用的内存。该函数定义如下status platform_device_add(evm_led_dev); if (status 0) { platform_device_put(evm_led_dev); [...] }也就是说无论平台设备以何种方式分配和注册都必须使用platform_device_unregister()函数取消注册该函数定义如下需要注意的是platform_device_unregister()会在内部调用platform_device_put()。使用设备树可以把设备树里的simple-bus节点比作嵌入式系统里的 “简易硬件货架”这个 “货架”simple-bus 节点本身没有任何 “智能功能”—— 不需要专门的驱动来控制它唯一作用就是 “摆放子设备子节点”货架上的 “商品”子设备没法被 “自动扫码识别”无动态探测只能靠预先贴好的 “位置标签”设备树里的地址信息如reg属性、地址找到系统启动时内核会自动把这个货架上的所有 “第一层商品”一级子节点都转换成可被 platform 驱动管理的 “平台设备”platform_device。用法 1在 SoC 节点 / 片上内存映射总线下声明片上设备SoC芯片内部的核心外设如 UART、SPI 控制器、GPIO、定时器、DMA没有独立的物理总线比如 I2C 的硬件线路而是通过内存映射方式访问 —— 即这些外设的控制寄存器直接对应到 CPU 的物理内存地址写某个内存地址 操作外设寄存器。这类外设无法归到 I2C/SPI 等物理总线因此放在兼容simple-bus的 SoC 节点下统一管理。在上面的例子中只有bar和foz节点被注册为平台设备baz节点不会被注册其直接父节点在compatible字符串中没有simple-bus。因此只要有任何平台驱动程序的compatible匹配表中包含company、product和/或labcsmart、something这些平台设备就将被探测。用法 2声明调节器设备常见的一种用法是在SoC节点或片上内存映射总线下声明芯片上的设备。另一种用法是声明调节器设备如下所示使用平台资源可热插拔设备可枚举并广告所需的资源与之相反内核并不知道系统中的平台设备是什么、它们具备什么功能或者它们需要什么资源才能正常工作。由于缺乏自动协商过程为内核提供给定平台设备所需资源的任何信息都是受欢迎的。这些资源可以是IRQ线、DMA通道、内存区域、I/O端口等。资源在代码中表示为resource结构体实例该结构体定义在include/linux/ioport.h文件中如下所示在上述数据结构中start/end指向资源的开始/结束。它们指示I/O或内存区域的开始和终止位置。因为IRQ线、总线和DMA通道没有范围所以通常为start和end赋予相同的值。flags是描述资源类型的掩码如IORESOURCE_BUS。flags可能的值如下。· IORESOURCE_IO表示PCI/ISA I/O端口。· IORESOURCE_MEM表示内存区域。· IORESOURCE_REG表示寄存器偏移量。· IORESOURCE_IRQ表示IRQ线。· IORESOURCE_DMA表示DMA通道。· IORESOURCE_BUS表示总线。最后用name字段标识或描述资源因此我们可以通过name字段来提取资源。将这些资源分配给平台设备的方式有两种第一种是在同一编译单元中声明和注册平台设备时进行分配第二种是从设备树中进行分配。旧方法旧方法主要在不支持设备树的内核中使用主要用于多功能设备其中的主芯片封装芯片与子设备共享资源。资源的提供方式与平台设备相同。资源分配static struct resource foo_resources[] { [0] { .start 0x10000000, .end 0x10001000, .flags IORESOURCE_MEM, .name mem1, }, [1] { .start JZ4740_UDC_BASE_ADDR2, .end JZ4740_UDC_BASE_ADDR2 0x10000 - 1, .flags IORESOURCE_MEM, .name mem2, }, [2] { .start 90, .end 90, .flags IORESOURCE_IRQ, .name mc-irq, }, };上面的代码展示了3个资源两个内存类型的资源和一个IRQ类型的资源它们的类型可以用IORESOURCE_IRQ和IORESOURCE_MEM进行标识。第1个是4KB的内存区域第2个也是一个内存区域其范围由一个宏定义最后一个是IRQ 90。对于静态分配的平台设备资源应该按以下方式分配static struct platform_device foo_pdev { .name foo-device, .resource foo_resources, .num_resources ARRAY_SIZE(foo_resources), [...] };对于动态分配的平台设备资源的分配是在一个函数中完成的分配方式如下struct platform_device *foo_pdev; [...] foo_pdev platform_device_alloc(foo-device, ...); if (!foo_pdev) return -ENOMEM; foo_pdev-resource foo_resources; foo_pdev-num_resources ARRAY_SIZE(foo_resources);资源获取参数n表示需要哪种类型的资源若为0则表示第一个MMIO区域。例如驱动程序可以通过以下代码找到它的第二个MMIO区域平台数据platform_data如果struct resource不足以编码信息作为扩展可以使用platform_device.device.platform_data。数据可以包含在更大的结构体中并赋值给这个额外字段platform_data。以下代码描述了额外的平台数据这些数据对应一组指向平台设备类型私有函数的指针对于静态分配的平台设备执行以下命令如果发现平台设备是动态分配的则使用platform_device_add_data()函数分配数据该函数定义如下上述函数如果执行成功则返回0。参数data是用作平台数据的数据size是数据的大小。在平台驱动程序中平台设备的pdev-dev.platform_data字段将指向平台数据。虽然我们可以引用该字段但建议使用内核提供的dev_get_platdata()函数该函数定义如下为了获取包含函数结构的集合驱动程序可以执行以下操作新方法随着设备树的出现事情变得更加简单了。为了保持兼容性设备树中指定的内存区域、中断和DMA资源被转换为resource结构体实例以便平台核心可以通过platform_get_resource()、platform_get_resource_by_name()或platform_get_irq()返回合适的资源无论这个资源是以传统方式获取的还是从设备树中获取的都没有影响。设备树的局限驱动往往需要「定制化的平台数据」—— 这是驱动开发者自己定义的结构体比如struct led_platform_data设备树是纯静态的硬件描述文件不知道这个结构体的字段、大小、含义因此无法直接按这个结构体格式提供数据举例设备树能告诉驱动 “LED 的 GPIO 引脚是 10”但没法直接告诉驱动 “LED 默认闪烁频率是 500ms、最大亮度是 100”这些是驱动专属的逻辑参数对应自定义结构体。为了传递这样的额外数据驱动可以使用 of_device_id 中的.data 字段以使平台设备和驱动程序匹配。然后.data 字段可以指向平台数据”核心解决方案of_device_id是设备树和驱动的「匹配规则表」其中的.data字段是一个无类型指针void*可以指向驱动自定义的平台数据结构体工作逻辑驱动定义of_device_id数组每个条目包含compatible匹配设备树节点的标识和.data指向该设备对应的定制化平台数据内核通过compatible匹配设备树节点平台设备和驱动匹配成功后驱动可以拿到.data字段指向的平台数据补充设备树无法传递的定制化参数。如果驱动程序期望以传统方式接收平台数据则应该检查platform_device-dev.platform_data指针。如果该指针有非空值则意味着设备是以传统方式实例化的同时与平台数据一起使用而没有使用设备树。在这种情况下平台数据应像往常一样使用。然而如果设备是从设备树代码实例化的则platform_data指针将为NULL表示必须直接从设备树获取信息。在这种情况下驱动程序将在平台设备的dev.of_node字段中找到一个device_node指针然后便可以使用各种设备树访问例程[特别是of_get_property()]从设备树中提取所需的数据。平台驱动程序抽象和架构探测和释放设备平台驱动程序的入口点是探测函数该函数在与平台设备匹配后会被调用。探测函数原型如下无论平台设备以传统方式实例化还是从设备树中创建都可以使用传统的平台核心API[如platform_get_resource()、platform_get_resource_by_name()、platform_get_irq()等类似的API]来提取其资源。在探测函数中必须请求驱动程序所需的任何资源如GPIO、时钟、IIO通道等和数据。如果需要进行映射则可以在探测函数中执行此操作。移除设备当从系统中移除设备或注销平台驱动程序时必须撤销探测函数已完成的所有操作。移除函数就是用来实现这一目的的移除函数原型如下移除函数只有在所有资源被释放和清理完毕后才应返回0否则应返回适当的错误码以通知用户。移除函数的参数包括之前传递给探测函数的同一平台设备。配置platform_device_id平台设备id匹配方式驱动程序本身是无用的要使其对设备有用就必须告诉内核驱动程序可以管理哪些设备。为此必须提供一个id表并将它分配给platform_driver.id_table字段。这将允许平台设备匹配。但是为了将此功能扩展到模块自动加载就必须将同样的id表提供给MODULE_DEVICE_TABLE以便生成模块别名。在上述数据结构中name是设备的名称driver_data是驱动程序的状态值可以设置为指向每个设备数据结构的指针。下面是一个来自drivers/mmc/host/mxs-mmc.c的例子当把mxs_ssp_ids赋值给platform_driver.id_table字段时平台设备将能够根据它们的名称与任意platform_device_id.name表项进行匹配platform_device. id_entry将指向此表中触发匹配的表项。#include linux/platform_device.h #include linux/module.h // 1. 定义驱动侧的匹配清单mxs_ssp_ids static const struct platform_device_id mxs_ssp_ids[] { // 表项1匹配namemxs-ssp.0的设备附加数据100 { .name mxs-ssp.0, .driver_data 100 }, // 表项2匹配namemxs-ssp.1的设备附加数据200 { .name mxs-ssp.1, .driver_data 200 }, { } // 哨兵项结束数组 }; // 告诉内核这是平台设备ID表必须 MODULE_DEVICE_TABLE(platform, mxs_ssp_ids); // 2. 定义probe函数使用id_entry获取匹配项 static int mxs_ssp_probe(struct platform_device *pdev) { // 匹配成功后pdev-id_entry指向mxs_ssp_ids中匹配的表项 if (!pdev-id_entry) { pr_err(无匹配的id_entry\n); return -ENODEV; } // 打印匹配结果设备名称、匹配的表项名称、附加数据 pr_info(设备name%s\n, pdev-name); pr_info(匹配的id_table表项name%s\n, pdev-id_entry-name); pr_info(匹配表项的附加数据%lu\n, pdev-id_entry-driver_data); // 后续硬件初始化逻辑... return 0; } // 3. 定义platform驱动结构体赋值id_table static struct platform_driver mxs_ssp_driver { .probe mxs_ssp_probe, .id_table mxs_ssp_ids, // 核心把匹配清单赋值给id_table .driver { .name mxs-ssp-driver, }, }; // 4. 注册驱动 module_platform_driver(mxs_ssp_driver); MODULE_LICENSE(GPL);配置of_device_id设备树匹配方式为了允许通过设备树中声明的兼容字符串匹配平台设备平台驱动程序必须使用structof_device_id的元素列表设置platform_driver.driver.of_match_table。然后为了允许从设备树匹配自动加载模块必须将设备树匹配表提供给MODULE_DEVICE_ TABLE。以下是一个示例如果在驱动程序中设置了of_device_id则匹配是根据任意of_device_id.compatible元素与设备节点中compatible属性的值是否匹配来判断的。要获得导致匹配的of_device_id表项驱动程序应调用of_match_device()将设备树匹配表和底层设备结构platform_ device.dev作为参数传递。为什么需要of_match_device()对比之前的platform_device_id匹配内核会自动把platform_device.id_entry指向匹配的platform_device_id表项但of_device_id匹配没有这个 “自动赋值” 的字段驱动必须主动调用of_match_device()才能拿到 “到底是哪一个of_device_id表项匹配了当前设备”。#include linux/platform_device.h #include linux/of.h #include linux/module.h // 1. 定义驱动专属的平台数据通过of_device_id.data传递 struct ssp_platform_data { int clock_freq; // SSP时钟频率 bool dma_enable; // 是否启用DMA }; // 2. 定义不同版本SSP设备的平台数据 static struct ssp_platform_data ssp_v1_data { .clock_freq 10000000, // 10MHz .dma_enable false, }; static struct ssp_platform_data ssp_v2_data { .clock_freq 20000000, // 20MHz .dma_enable true, }; // 3. 定义of_device_id匹配表关联compatible和.data static const struct of_device_id mxs_ssp_of_match[] { { .compatible fsl,mxs-ssp-v1, .data ssp_v1_data, // 绑定v1版本的平台数据 }, { .compatible fsl,mxs-ssp-v2, .data ssp_v2_data, // 绑定v2版本的平台数据 }, { } // 哨兵项 }; MODULE_DEVICE_TABLE(of, mxs_ssp_of_match); // 4. probe函数中调用of_match_device()获取匹配表项 static int mxs_ssp_probe(struct platform_device *pdev) { // 核心调用of_match_device()获取匹配的of_device_id表项 const struct of_device_id *match of_match_device(mxs_ssp_of_match, pdev-dev); if (!match) { pr_err(未找到匹配的of_device_id表项\n); return -ENODEV; } // 从匹配表项中获取附加数据.data字段 struct ssp_platform_data *pdata (struct ssp_platform_data *)match-data; if (!pdata) { pr_err(无平台数据\n); return -EINVAL; } // 打印匹配结果和附加数据 pr_info(匹配成功兼容标识%s\n, match-compatible); pr_info(SSP时钟频率%d HzDMA启用%d\n, pdata-clock_freq, pdata-dma_enable); // 后续硬件初始化逻辑根据平台数据配置SSP... return 0; } // 5. 定义platform驱动结构体 static struct platform_driver mxs_ssp_driver { .probe mxs_ssp_probe, .driver { .name mxs-ssp-driver, .of_match_table mxs_ssp_of_match, // 关联设备树匹配表 }, }; // 6. 注册驱动 module_platform_driver(mxs_ssp_driver); MODULE_LICENSE(GPL);在定义了这些匹配表之后可以将它们赋值给平台驱动程序数据结构代码如下驱动程序初始化和注册向内核注册平台驱动程序很简单只需要在模块的初始化函数中调用platform_driver_register()或platform_driver_probe()函数即可。必须调用platform_driver_unregister()函数来注销平台驱动程序。platform_driver_register和platform_driver_probe函数的区别· platform_driver_register()函数用于将驱动程序注册并放入内核维护的驱动程序列表中这意味着可以按需调用其探测函数前提是与平台设备的匹配发生了新的变化。使用platform_driver_register()函数注册的任何平台驱动程序都必须使用platform_driver_unregister()函数来注销。· 若使用platform_driver_probe()函数内核将立即运行匹配循环以检查是否有平台设备可以与该平台驱动程序匹配并为每个匹配的设备调用其探测函数。如果此时没有找到设备则简单地忽略该平台驱动程序。这种方法可以防止延迟探测因为不会在系统中注册驱动程序。探测函数被放置在__init部分当内核启动完成时这部分将被释放前提是驱动程序被编译为静态模块从而避免了延迟探测减少了驱动程序占用的内存。如果完全确定设备在系统中请使用这种方法。核心区别在驱动生命周期register让驱动常驻内核支持后续新设备probe让驱动 “一次性工作”不常驻、省内存如果已知设备不支持热插拔则可以将探测函数放置在__init部分。在模块加载完成后立即注册平台驱动程序是正确的选择。注销平台驱动程序亦如此必须在模块的卸载路径中完成。注销平台驱动程序的适当位置是module_exit()函数。#include linux/module.h #include linux/kernel.h #include linux/init.h #include linux/platform_device.h static int my_pdrv_probe(struct platform_device *pdev) { pr_info(Device probed\n); return 0; } static void my_pdrv_remove(struct platform_device *pdev) { pr_info(Device remove\n); } static struct platform_driver mypdrv { .probe my_pdrv_probe, .remove my_pdrv_remove, [...] }; static int __init foo_init(void) { [...] return platform_driver_register(mypdrv); } module_init(foo_init); static void __exit foo_cleanup(void) { [...] platform_driver_unregister(mypdrv); } module_exit(foo_cleanup);module_init()和module_exit()可以使用module_platform_driver()宏代替从零编写平台驱动现在让我们想象一个平台设备它是内存映射设备其映射的内存范围控制在从地址0x02008000开始大小为0x4000。然后假设该平台设备可以在任务完成时向CPU发送中断并且中断线号为31。在设备树中实例化平台设备要将一个节点注册为平台设备该节点的直接父节点就必须在其兼容性字符串列表中包含“simple-bus”这也是下面所要实现的内容demo { compatible simple-bus; demo_pdev: demo_pdev0 { compatible labcsmart,demo-pdev; reg 0x02008000 0x4000; interrupts 0 31 IRQ_TYPE_LEVEL_HIGH; }; };设备树节点命名中xxx的部分叫单元地址Unit Address它的核心作用是「区分同一父节点下同名的子节点」而非直接对应reg里的物理地址 —— 这是新手最容易混淆的点。单元地址的核心规则单元地址的值由父节点的#address-cells地址单元格数和#size-cells大小单元格数决定是父节点视角下的 “逻辑地址”而非硬件的物理地址你的示例中父节点demo只标注了compatible “simple-bus”但未显式定义#address-cells和#size-cells—— 内核对simple-bus的默认处理是#address-cells 1、#size-cells 1但单元地址的取值仅用于区分节点不要求和reg的物理地址一致0只是一个 “逻辑标识”表示这是父节点demo下第一个逻辑地址 0demo_pdev类型的节点。如果父节点下有第二个demo_pdev节点就可以写demo_pdev1和reg的物理地址无关。传统方式实例化平台设备#include linux/module.h #include linux/init.h #include linux/platform_device.h #define DEV_BASE 0x02008000 #define PDEV_IRQ 31 static struct resource pdev_resource[] { [0] { .start DEV_BASE, .end DEV_BASE 0x4000, .flags IORESOURCE_MEM, }, [1] { .start PDEV_IRQ, .end PDEV_IRQ, .flags IORESOURCE_IRQ, }, }; struct platform_device demo_pdev { .name demo_pdev, .id 0, .num_resources ARRAY_SIZE(pdev_resource), .resource pdev_resource, /* .dev { .platform_data (void *)big_struct_1, } /* };注册平台设备static int demo_pdev_init() { return platform_device_register(demo_pdev); } module_init(demo_pdev_init); static void demo_pdev_exit() { platform_device_unregister(demo_pdev); } module_exit(demo_pdev_exit);编写设备树匹配表和id表#include linux/module.h #include linux/init.h #include linux/interrupt.h #include linux/of_platform.h #include linux/platform_device.h static const struct of_device_id labcsmart_dt_ids[] { { .compatible labcsmart,demo-pdev, /*.data (void *)big_struct_1, */ } { .compatible labcsmart,other-pdev, /*.data (void *)big_struct_2, */ } {/*sentinel*/} }; MODULE_DEVICE_TABLE(of, labcsmart_dt_ids);在上述代码中设备树匹配表枚举了支持的设备并根据它们的compatible属性进行了区分。这里我们再次注释掉了.data赋值语句这只是为了展示我们如何根据与平台设备匹配的条目传递特定于平台的数据结构。这个特定于平台的数据结构可以是big_struct_1或big_struct_2。当需要传递平台数据但仍想使用设备树时建议使用这种方式。static const struct platform_device_id labcsmart_ids[] { { .name demo-pdev, /*.driver_data big_struct_1,*/ }, { .name other-pdev, /*.driver_data big_struct_2,*/ }, {/*sentinel*/} }; MODULE_DEVICE_TABLE(platform, labcsmart_ids);实现探测函数static u32 *reg_base; static struct resource *res_irq; static int demo_pdev_probe(struct platform_device *pdev) { struct resource *regs; regs platform_get_resource(pdev, IORESOURCE_MEM, 0); if (!regs) { dev_err(pdev-dev, coiuld not get IO memory\n); return -ENXIO; } reg_base devm_ioremap(pdev-dev, regs-start, resource_size(regs)); if (!reg_base) { dev_err(pdev-dev, could not remap memory\n); return 0; } res_irq platform_get_resource(pdev, IORESOURCE_IRQ, 0); devm_request_irq(pdev-dev, res_irq-start, top_half, IRQF_TRIGGER_FALLING, demo-pdev, NULL); [...] return 0; }在上面的探测函数中可以添加许多内容或以不同的方式执行操作。其中一种情况是如果驱动程序只与设备树兼容并且相关的平台设备也从设备树进行实例化则执行以下操作struct device_node *np pdev-dev.of_node; const struct of_device_id *of_id of_match_device(labcsmart_dt_ids, pdev-dev); struct big_struct *pdata_struct (big_struct*)of_id-data;在上述代码中我们获取了与平台设备相关联的设备树节点的引用然后可以使用任何与设备树相关的API[如of_get_property()]从中提取数据。接下来在设备树匹配表中为支持的设备提供特定于平台的数据结构使用of_match_device()指向对应的条目并提取特定平台的数据。如果匹配是通过id表发生的pdev-id_entry将指向导致匹配发生的条目pdev-id_entry-driver_data将指向适当的大型数据结构。需要特别提醒你的是探测函数专门使用devm_前缀函数处理资源。这些函数会在适当的时机释放资源。这意味着不需要移除函数。如果不用devm移除函数一般这么写在上述代码中IRQ资源被释放内存映射被销毁。初始化和注册平台驱动static struct platform_driver demo_driver { .probe demo_pdrv_probe, /* .remove demo_pdrv_remove, */ .driver { .owner THIS_MODULE, .name demo_pdev, } }; module_platform_driver(demo_driver);还有一个重要的问题需要考虑平台设备、平台驱动程序和设备树中设备节点的名称。它们都相同名为demo_pdev。这是一种提供匹配机会的方式即通过平台设备和平台驱动程序名称进行匹配因为当设备树、id表和ACPI匹配全部失败时名称匹配将被用作后备选项。