Linux platform平台驱动
1. 总览在platform设备驱动中分为设备、驱动和总线三部分开发者需要完成的是设备部分以及驱动部分总线部分是内核本身就提供的是不需要开发者编写的当然如果说开发者想要创造一条全新的虚拟/物理总线比如 FPGA 内部自定义总线、I3C、RPMSG 定制总线等就必须自己写 bus driver提供匹配规则、probe/remove 逻辑、电源管理回调等。在这种场景下总线部分也需要开发者编写。设备只放“户口本”资源、中断、GPIO。驱动只写“业务能力”申请资源、实现功能。总线做“媒婆”匹配、调用 probe。匹配成功则调用驱动中的probe函数一张图掌握 Linux platform 平台设备驱动框架【建议收藏】2. platform device代码编写2.1 platform_device实例的定义需要对struct platform_device xxx_device实例的结构体成员进行填充一般需要进行填充的有// struct resource为设备的寄存器信息structresourcexxx_resource[]{[0]{.startSTART_PHYSICAL_ADDR0,// 起始地址以及终止地址均是实际的物理地址.endEND_PHYSICAL_ADDR0,// 地址以字节为单位.flagsIORESOURCE_MEM,},[1]{.startSTART_PHYSICAL_ADDR1,.endEND_PHYSICAL_ADDR1,.flagsIORESOURCE_MEM,},};structplatform_devicexxx_device{.namexxx,// 用于sysfs文件的创建以及与驱动的匹配.id-1,// 如果只有一个设备赋值为-1若有多个设备应设置不同的值此时该值也将用于sysfs文件的创建.resourcexxx_resource,.num_resourcesizeof(xxx_resource)/sizeof(structresource),.dev{.releasexxx_release,// 一般情况下不需要自定义}};2.2 platform_device的注册以下是paltform_deivce的注册方式完成注册后将会在/sys/devices/platform路径下相应的设备文件夹如果只有一个该设备可以赋值xxx_device.id -1那么生成的设备文件夹的名称即为xxxxxx为struct platform_device xxx_device实例的结构体成员name即xxx_device-name如果不止一个该设备可以赋值xxx_device.id num那么生成的设备文件夹的名称即为xxx.num。staticint__initxxx_device_init(void){returnplatform_device_register(xxx_device);}module_init(xxx_device_init);如下生成的设备文件夹/sys/devices/platform/myledzhy.100myledzhy即为pdev-name100即为pdev-id如果pdev-id -1则生成的文件夹名为/sys/devices/platform/myledzhy生成/sys/devices/platform/myledzhy.100设备文件夹的同时也会在/sys/bus/platform/devices路径下生成对应的设备文件夹链接文件。2.3 platform_device的注销platform_device_unregister()时会做两件事device_del()将设备从总线上摘除sysfs节点消失put_device()将引用计数减一。如果引用计数变成0内核会异步执行kobject_release()-devcie_release()-pdev-dev.release()也就是说在platform_devcie_unregister()之后如果这是最后一个引用计数则也会执行pdev-dev.release()。在定义pdev实例时如果没有为pdev-dev.release赋值自定义的release函数那么在platform_device注册时将会为其赋值内核中缺省的platform_device_release()函数该函数负责platform_device注销时相关系统资源的回收。只有在“你显式分配了额外内存/资源且devm机制无法自动回收”时才需要写一个自定义的release回调并在里面最后手动再调一次 platform_device_release()兜底一般情况下不需要自定义release函数。staticvoid__exitxxx_device_exit(void){platform_device_unregister(xxx_device);}module_exit(xxx_device_exit);3. platform driver代码编写3.1 platform_driver实例的定义structplatform_driverxxx_driver{.probexxx_driver_probe,// 驱动、设备完成匹配时执行的回调函数.removexxx_driver_remove,// platform_driver注销时执行的回调函数.driver{.namexxx,// 驱动的名字用于sysfs文件的创建以及驱动、设备的匹配.of_match_tablexxx_match_table,// 可用于驱动、设备的匹配可以查看第5章节}};3.2 platform_driver的注册以下是paltform_driver的注册方式。staticint__initxxx_driver_init(void){returnplatform_driver_register(xxx_driver);}module_init(xxx_driver_init);完成注册后将会在/sys/bus/platform/drivers路径下创建生成相应的驱动文件夹生成的驱动文件夹的名称xxx即为pdrv-driver-name。platform_driver完成注册之后将会遍历platform_device链表如果有可以相匹配的设备即执行pdrv-probe()函数在该函数中一般为驱动的初始化逻辑。probe()函数的入参为struct platform_device *即完成驱动-设备匹配后在回调执行pdrv-probe()函数时驱动匹配到的设备的指针也会随回调返回通过这种方式驱动侧可以获取到设备侧定义的设备信息。驱动与设备的分层也主要是通过这种机制当设备的硬件发生改变时只需要更改设备的代码比如说硬件型号发生了变化需要在设备层修改寄存器信息驱动层可以通过probe()函数获取到设备的信息驱动层的代码就不需要修改3.3 platform_driver的注销platform_driver在注销时将会回调执行pdrv-remove()函数staticint__ exitxxx_driver_exit(void){returnplatform_driver_unregister(xxx_driver);}module_exit(xxx_driver_exit);3.4 platform常用的函数// [include/linux/platform_device.h](https://elixir.bootlin.com/linux/v4.14/source/include/linux/platform_device.h)// 在完成驱动-设备的匹配之后驱动可以通过platform_get_resource函数获得设备的resource资源structresource*platform_get_resource(structplatform_device*,unsignedint,unsignedint);// 在完成驱动-设备的匹配之后驱动可以通过platform_get_irq函数获得设备的中断号intplatform_get_irq(structplatform_device*,unsignedint);// 在完成驱动-设备的匹配之后驱动可以通过platform_irq_count函数获得设备的中断量intplatform_irq_count(structplatform_device*);// 在完成驱动-设备的匹配之后驱动获取设备侧的某个resource该resource的类型(如IORESOURCE_MEM)以及name需与入参匹配structresource*platform_get_resource_byname(structplatform_device*,unsignedint,constchar*);intplatform_get_irq_byname(structplatform_device*,constchar*);intplatform_add_devices(structplatform_device**,int);intplatform_device_add_resources(structplatform_device*pdev,conststructresource*res,unsignedintnum);intplatform_device_add_data(structplatform_device*pdev,constvoid*data,size_tsize);intplatform_device_add_properties(structplatform_device*pdev,conststructproperty_entry*properties);intplatform_device_add(structplatform_device*pdev);voidplatform_device_del(structplatform_device*pdev);voidplatform_device_put(structplatform_device*pdev);staticinlinevoid*[platform_get_drvdata](https://elixir.bootlin.com/linux/v4.14/C/ident/platform_get_drvdata)(const struct [platform_device](https://elixir.bootlin.com/linux/v4.14/C/ident/platform_device) *pdev);staticinlinevoid[platform_set_drvdata](https://elixir.bootlin.com/linux/v4.14/C/ident/platform_set_drvdata)(struct [platform_device](https://elixir.bootlin.com/linux/v4.14/C/ident/platform_device) *pdev, void *data);4. platform设备-驱动匹配机制总线类型的基类struct bus_type中的成员int (*match)(struct device *dev, struct device_driver *drv)用于设备-驱动的匹配。* 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. struct bus_type { const char *name; const char *dev_name; struct device *dev_root; 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)(struct device *dev, struct kobj_uevent_env *env); int (*probe)(struct device *dev); int (*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); const struct dev_pm_ops *pm; const struct iommu_ops *iommu_ops; struct subsys_private *p; struct lock_class_key lock_key; };在platform_bus_type实例中platform_bus_type.match platform_matchstructbus_typeplatform_bus_type{.nameplatform,.dev_groupsplatform_dev_groups,.matchplatform_match,.ueventplatform_uevent,.pmplatform_dev_pm_ops,};EXPORT_SYMBOL_GPL(platform_bus_type);继续查看platform_match函数static int platform_match(struct device *dev, struct device_driver *drv) { struct platform_device *pdev to_platform_device(dev); struct platform_driver *pdrv to_platform_driver(drv); /* When driver_override is set, only bind to the matching driver */ if (pdev-driver_override) return !strcmp(pdev-driver_override, drv-name); /* Attempt an OF style match first */ if (of_driver_match_device(dev, drv)) return 1; /* Then try ACPI style match */ if (acpi_driver_match_device(dev, drv)) return 1; /* Then try to match against the id table */ if (pdrv-id_table) return platform_match_id(pdrv-id_table, pdev) ! NULL; /* fall-back to driver name match */ return (strcmp(pdev-name, drv-name) 0); }if (pdev-driver_override)return !strcmp(pdev-driver_override, drv-name);其通过pdrv-driver-name字段与pdev-driver_override字段进行匹配if (of_driver_match_device(dev, drv))return 1;其通过pdrv-driver-of_match_table与pdev-dev-of_node进行匹配pdrv-driver-of_match_table-compatible与pdev-dev-of_node节点的compatible属性值pdrv-driver-of_match_table-type与pdev-dev-of_node-typepdrv-driver-of_match_table-name与pdev-dev-of_node-name以上进行比对后给出匹配度最高的if (acpi_driver_match_device(dev, drv))return 1;if (pdrv-id_table)return platform_match_id(pdrv-id_table, pdev) ! NULL;pdrv-id_table-name数组元素依次与pdev-name进行匹配strcmp(pdev-name, drv-name) 0pdrv-driver-name与pdev-name进行匹配一般采用第二种和第五种第二种主要适用于设备树的情况与设备树中节点的compatible属性进行匹配第五种主要用于无设备树的情况struct[of_device_id](https://elixir.bootlin.com/linux/v4.14/C/ident/of_device_id) {charname[32];chartype[32];char[compatible](https://elixir.bootlin.com/linux/v4.14/C/ident/compatible)[128];constvoid*data;};struct[platform_device_id](https://elixir.bootlin.com/linux/v4.14/C/ident/platform_device_id) {charname[[PLATFORM_NAME_SIZE](https://elixir.bootlin.com/linux/v4.14/C/ident/PLATFORM_NAME_SIZE)];[kernel_ulong_t](https://elixir.bootlin.com/linux/v4.14/C/ident/kernel_ulong_t) [driver_data](https://elixir.bootlin.com/linux/v4.14/C/ident/driver_data);};typedefunsignedlong[kernel_ulong_t](https://elixir.bootlin.com/linux/v4.14/C/ident/kernel_ulong_t);5. 由设备树生成设备/sys/devices/soc0是内核在解析设备树时为SoC这一顶层平台设备自动注册的soc0是一个platform_device/sys/devices/soc0/目录下挂载的子目录并不是platform_device而是挂在这条总线下的普通设备节点它们多数仍然是 platform_device但由 “soc 总线” 这个中间容器统一管理。设备树中以根节点为父节点的节点会挂在soc0之下。这些设备就不需要再编写platform_device的代码了在platfoem_driver_register时就是去匹配设备树中这些节点的compatible属性匹配 成功后就会通过pdrv-probe函数返回并获取动pdev指针然后就可以通过pdev-dev-of_node获取到设备树的节点作用类似于of_find_node_by_path()函数用于获取设备的设备树节点。获取到节点后就可以读取设备树中的配置信息进行后续的操作了。rootALTK-ZYNQ:~# ls /sys/devices/soc0/amba beeper family fpga-full led revision subsystem amba_pl f8891000.pmu fixedregulator key power soc_id uevent rootALTK-ZYNQ:~# ls /sys/devices/soc0/leddriver driver_override modalias myled of_node power subsystem uevent rootALTK-ZYNQ:~# ls /sys/devices/soc0/led/of_nodecompatible default-state led-gpio name status rootALTK-ZYNQ:~# hexdump -C /sys/devices/soc0/led/of_node/led-gpio00000000 00 00 00 07 00 00 00 07 00 00 00 00|............|0000000c rootALTK-ZYNQ:~# hexdump -C /sys/devices/soc0/led/of_node/compatible00000000 7a68792c 6c65647300|zhy,leds.|00000009若要使用设备树的方式进行设备-驱动匹配在驱动中要对pdrv-driver-of_match_table进行赋值其中的compatible字段需要与设备中的节点compatible属性一致。staticconststructof_device_idzynq_gpio_of_match[]{{.compatiblexlnx,zynq-gpio-1.0,.datazynq_gpio_def},{.compatiblexlnx,zynqmp-gpio-1.0,.datazynqmp_gpio_def},{/* end of table */}};MODULE_DEVICE_TABLE(of,zynq_gpio_of_match);staticstructplatform_driverzynq_gpio_driver{.driver{.nameDRIVER_NAME,.pmzynq_gpio_dev_pm_ops,.of_match_tablezynq_gpio_of_match,},.probezynq_gpio_probe,.removezynq_gpio_remove,};如果对你有帮助欢迎点赞收藏有相关问题也可以评论讨论后续将继续更新该系列。