在Linux设备树语法详解和Linux Platform驱动模型(一) _设备信息中咱们讨论了设备信息的写法,本文主要讨论平台总线中另一部分-驱动方法,将试图回答下面几个问题:html
写驱动也有一段时间了,能够发现,其实驱动本质上只作了两件事:向上提供接口,向下控制硬件,固然,这里的向上并非直接提供接口到应用层,而是提供接口给内核再由内核间接的将咱们的接口提供给应用层。而写驱动也是有一些套路可寻的,拿到一个硬件,咱们大致能够按照下面的流程写一个驱动:node
内核用platform_driver结构来表示一个驱动方法对象linux
//include/linux/device.h 173 struct platform_driver { 174 int (*probe)(struct platform_device *); 175 int (*remove)(struct platform_device *); 176 void (*shutdown)(struct platform_device *); 177 int (*suspend)(struct platform_device *, pm_message_t state); 178 int (*resume)(struct platform_device *); 179 struct device_driver driver; 180 const struct platform_device_id *id_table; 181 bool prevent_deferred_probe; 182 };
在这个结构中,咱们主要关心如下几个成员数组
struct platform_driver
--174-->探测函数,若是驱动匹配到了目标设备,总线会自动回调probe函数,必须实现,下面详细讨论。
--175-->释放函数,若是匹配到的设备从总线移除了,总线会自动回调remove函数,必须实现
--179-->platform_driver的父类,咱们接下来讨论
--180-->用于C语言写的设备信息,下面详细讨论。架构
platform_driver里面有些内容须要在父类driver中实现,函数
//include/linux/device.h 228 struct device_driver { 229 const char *name; 230 struct bus_type *bus; 231 232 struct module *owner; 233 const char *mod_name; /* used for built-in modules */ 234 235 bool suppress_bind_attrs; /* disables bind/unbind via sysfs */ 236 237 const struct of_device_id *of_match_table; 238 const struct acpi_device_id *acpi_match_table; 239 240 int (*probe) (struct device *dev); 241 int (*remove) (struct device *dev); 242 void (*shutdown) (struct device *dev); 243 int (*suspend) (struct device *dev, pm_message_t state); 244 int (*resume) (struct device *dev); 245 const struct attribute_group **groups; 246 247 const struct dev_pm_ops *pm; 248 249 struct driver_private *p; 250 };
下面是咱们关心的几个成员ui
struct device_driver
--229-->驱动名,若是这个驱动只匹配一个C语言的设备,那么能够经过name相同来匹配
--230-->总线类型,这个成员由内核填充
--232-->owner,一般就写THIS_MODULE
--237-->of_device_id顾名思义就是用来匹配用设备树写的设备信息,下面详细讨论
--249-->私有数据编码
设备信息有三种表达方式,而一个驱动是能够匹配多个设备的,平台总线中的驱动要具备三种匹配信息的能力,基于这种需求,platform_driver中使用不一样的成员来进行相应的匹配。设计
对于使用设备树编码的设备信息,咱们使用其父类device_driver中的of_match_table就是用来匹配code
//include/linux/mod_devicetable.h 220 /* 221 * Struct used for matching a device 222 */ 223 struct of_device_id 224 { 225 char name[32]; 226 char type[32]; 227 char compatible[128]; 228 const void *data; 229 };
struct of_device_id
--225-->name[32]设备名
--226-->type[32]设备类型
--227-->重点!compatible[128]用于与设备树compatible属性值匹配的字符串
--228-->data驱动私有数据
对于一个驱动匹配多个设备的状况,咱们使用struct of_device_id tbl[]来表示。
struct of_device_id of_tbl[] = { {.compatible = "xj4412,demo0",}, {.compatible = "xj4412,demo1",}, {}, };
对于使用C语言编码的设备信息,咱们用platform_driver对象中的id_table就是用来匹配。咱们使用struct platform_device_id ids[]来实现一个驱动匹配多个C语言编码的设备信息。
//include/linux/mod_deviceid.h 485 struct platform_device_id { 486 char name[PLATFORM_NAME_SIZE]; 487 kernel_ulong_t driver_data; 488 };
struct platform_device_id
--486-->name就是设备名
下面这个例子就是用一个驱动来匹配两个分别叫"demo0"和"demo1"的设备,注意,数组最后的{}是必定要的,这个是内核判断数组已经结束的标志。
static struct platform_device_id tbl[] = { {"demo0"}, {"demo1"}, {}, };
若是platform_driver和C语言编码的platform_device是一一匹配的,咱们还可使用device_driver中的name来进行匹配
填充完platform_driver结构以后,咱们应该将其中用到的设备表注册到内核,虽然不注册也能够工做,可是注册能够将咱们表加入到相关文件中,便于内核管理设备。
MODULE_DEVICE_TABLE(类型, ID表); 设备树ID表 类型:of C写的platform_device的ID表 类型:platform C写的i2c设备的ID表 类型:i2c C写的USB设备的ID表 类型:usb
细心的读者可能会发现,这么多方式都写在一个对象中,那若是我同时注册了三种匹配结构内核该用哪一种呢?此时就须要咱们搬出平台总线的匹配方式:
//drivers/base/platform.c 748 static int platform_match(struct device *dev, struct device_driver *drv) 749 { 750 struct platform_device *pdev = to_platform_device(dev); 751 struct platform_driver *pdrv = to_platform_driver(drv); 752 753 /* Attempt an OF style match first */ 754 if (of_driver_match_device(dev, drv)) 755 return 1; 756 757 /* Then try ACPI style match */ 758 if (acpi_driver_match_device(dev, drv)) 759 return 1; 760 761 /* Then try to match against the id table */ 762 if (pdrv->id_table) 763 return platform_match_id(pdrv->id_table, pdev) != NULL; 764 765 /* fall-back to driver name match */ 766 return (strcmp(pdev->name, drv->name) == 0); 767 }
从中不难看出,这几中形式的匹配是有优先级的:of_match_table>id_table>name,了解到这点,咱们甚至能够构造出同时适应两种设备信息的平台驱动:
static struct platform_driver drv = { .probe = demo_probe, .remove = demo_remove, .driver = { .name = "demo", #ifdef CONFIG_OF .of_match_table = of_tbl, #endif }, .id_table = tbl, };
此外,若是你追一下of_driver_match_device(),就会发现平台总线的最终的匹配是compatible,name,type三个成员,其中一个为NULL或""时表示任意,因此咱们使用平台总线时老是使用compatile匹配设备树,而不是节点路径或节点名。
probe即探测函数,若是驱动匹配到了目标设备,总线会自动回调probe函数,下面详细讨论。并把匹配到的设备信息封装策划嗯platform_device对象传入,里面主要完成下面三个工做
显然,remove主要完成与probe相反的操做,这两个接口都是咱们必须实现的。
在probe的工做中,最多见的就是提取设备信息,虽然总线会将设备信息封装成一个platform_device对象并传入probe函数,咱们能够很容易的获得关于这个设备的全部信息,可是更好的方法就是直接使用内核API中相关的函数
/** * platform_get_resource - 获取资源 * @dev: 平台总线设备 * @type:资源类型,include/linux/ioport.h中有定义 * @num: 资源索引,即第几个此类型的资源,从0开始 */ struct resource *platform_get_resource(struct platform_device *dev,unsigned int type, unsigned int num)
注意,经过内核API(eg,上下这两个API)获取的resource若是是中断,那么只能是软中断号,而不是芯片手册/C语言设备信息/设备树设备信息中的硬中断号,可是此时获取的resource的flag是能够正确的反映该中断的触发方式的,只须要flag & IRQF_TRIGGER_MASK
便可获取该中断的触发方式。
/** * platform_get_irq - 获取一个设备的中断号 * @dev: 平台总线设备 * @num: 中断号索引,即想要获取的第几个中断号,从0开始 */ int platform_get_irq(struct platform_device *dev, unsigned int num)
/** * dev_get_platdata - 获取私有数据 */ static inline void *dev_get_platdata(const struct device *dev){ return dev->platform_data; }
内核提供了两个API来注册/注销platform_driver对象到内核
/** * platform_driver_register - 注册 */ int platform_driver_register(struct platform_driver *drv);
/** * platform_driver_unregister - 注销 */ int platform_driver_unregister(struct platform_driver *drv);
在动态编译的状况下,咱们每每在模块初始化函数中注册一个驱动方法对象,而在模块卸载函数中注销一个驱动方法对象,因此咱们可使用内核中以下的宏来提升代码复用
module_platform_driver(driver_name);
这个实例同时使用了设备信息模块和设备树两种设备信息来源,不过最终使用的是设备树,须要注意的是,当咱们用设备树的设备信息时,有一个成员platform_device.device.of_node来表示设备的节点,这样就容许咱们使用丰富的设备树操做API来操做。
//#include "private.h" /* /{ demo{ compatible = "4412,demo0"; reg = <0x5000000 0x2 0x5000008 0x2>; interrupt-parent = <&gic>; interrupts = <0 25 0>, <0 26 0>; intpriv = <0x12345678>; strpriv = "hello world"; }; }; */ struct privatedata { int val; char str[36]; }; static void getprivdata(struct device_node *np) { struct property *prop; prop = of_find_property(np, "intpriv", NULL); if(prop) printk("private val: %x\n", *((int *)(prop->value))); prop = of_find_property(np, "strpriv", NULL); if(prop) printk("private str: %s\n", (char *)(prop->value) ); } static int demo_probe(struct platform_device *pdev) { int irq; struct resource *addr; struct privatedata *priv; printk(KERN_INFO "%s : %s : %d - entry.\n", __FILE__, __func__, __LINE__); priv = dev_get_platdata(&pdev->dev); if(priv){ printk(KERN_INFO "%x : %s \n", priv->val, priv->str); }else{ getprivdata(pdev->dev.of_node); } addr = platform_get_resource(pdev, IORESOURCE_MEM, 0); if(addr){ printk(KERN_INFO "0: %x : %d \n", addr->start, resource_size(addr)); } addr = platform_get_resource(pdev, IORESOURCE_MEM, 1); if(addr){ printk(KERN_INFO "1: %x : %d \n", addr->start, resource_size(addr)); } addr = platform_get_resource(pdev, IORESOURCE_MEM, 2); if(!addr){ printk(KERN_INFO "No 2 resource\n"); } irq = platform_get_irq(pdev, 0); if(0 > irq){ return irq; }else{ printk(KERN_INFO "irq 0: %d \n", irq); } irq = platform_get_irq(pdev, 1); if(0 > irq){ return irq; }else{ printk(KERN_INFO "irq 0: %d \n", irq); } irq = platform_get_irq(pdev, 2); if(0 > irq){ printk(KERN_INFO "No 2 irq\n"); } return 0; } static int demo_remove(struct platform_device *pdev) { return 0; } static struct platform_device_id tbl[] = { {"demo0"}, {"demo1"}, {}, }; MODULE_DEVICE_TABLE(platform, tbl); #ifdef CONFIG_OF struct of_device_id of_tbl[] = { {.compatible = "4412,demo0",}, {.compatible = "4412,demo1",}, {}, }; #endif //1. alloc obj static struct platform_driver drv = { .probe = demo_probe, .remove = demo_remove, .driver = { .name = "demo", #ifdef CONFIG_OF .of_match_table = of_tbl, #endif }, .id_table = tbl, }; static int __init drv_init(void) { //get command and pid printk(KERN_INFO "(%s:pid=%d), %s : %s : %d - entry.\n",current->comm, current->pid, __FILE__, __func__, __LINE__); return platform_driver_register(&drv); } static void __exit drv_exit(void) { //get command and pid printk(KERN_INFO "(%s:pid=%d), %s : %s : %d - leave.\n",current->comm, current->pid, __FILE__, __func__, __LINE__); platform_driver_unregister(&drv); } module_init(drv_init); module_exit(drv_exit); MODULE_LICENSE("GPL");