Linux驱动---Platform 平台驱动设备的介绍以及platform驱动框架的搭建

📅 发布时间:2026/8/14 16:41:17
Linux驱动---Platform 平台驱动设备的介绍以及platform驱动框架的搭建 目录一. Platform 平台驱动设备1.1 驱动的分离与分层1.2 platform 平台驱动简介1.2.1 匹配过程1.2.2 优势1.2.3 驱动运行过程1、insmod 驱动2、platform 总线启动匹配逻辑3、匹配成功执行 probe 函数4、匹配失败1.2.4 驱动与设备匹配方式1、OF 类型的匹配2、ACPI 匹配方式3、id_table 匹配方式4、name 匹配方式二. platform 驱动2.1 platform_driver 结构体2.2 device_driver 结构体2.3 platform 驱动框架2.3.1 platform_driver_register 函数2.3.2 platform _driver_unregister 函数2.3.3 platform 框架三. 示例代码前面所编写的设备驱动都是对 IO 进行最简单的读写操作。像 I2C、SPI、LCD 等这些复杂外设的驱动就不能这么去写Linux 系统要考虑到驱动的可重用性 因此提出了驱动的分离与分层这样的思路在这个思路下诞生了platform 设备驱动也叫做平台设备驱动一. Platform 平台驱动设备1.1 驱动的分离与分层假如现在有三个平台 A、B 和 C这三个平台(这里的平台说的是 SOC)上都有 MPU6050 这 个 I2C 接口的六轴传感器按照写裸机 I2C 驱动的时候的思路每个平台都有一个 MPU6050 的驱动因此编写出来的最简单的驱动框架如图所示从图中可以看出每种平台下都有一个主机驱动和设备驱动主机驱动肯定是必须要的毕竟不同的平台其 I2C 控制器不同。但是右侧的设备驱动就没必要每个平台都写一个 因为不管对于那个 SOC 来说MPU6050 都是一样通过 I2C 接口读写数据就行了只需要一个 MPU6050 的驱动程序即可。如果再来几个 I2C 设备比如 AT24C02、FT5206(电容触摸屏) 等如果按照图 中的写法那么设备端的驱动将会重复的编写好几次。显然在 Linux 驱动程序中这种写法是不推荐的最好的做法就是每个平台的 I2C 控制器都提供一个统一的接口(也叫做主机驱动)每个设备也只提供一个驱动程序(设备驱动)每个设备通过统一的 I2C 接口驱动来访问这样就可以大大简化驱动文件驱动框架就可以简化为图所示实际的 I2C 驱动设备肯定有很多种不止 MPU6050 这一个那么实际的驱动架构如图所示以上就是驱动的分隔也就是将主机驱动和设备驱动分隔开来比如 I2C、SPI 等等都会采 用驱动分隔的方式来简化驱动的开发。在实际的驱动开发中一般 I2C主机控制器驱动已经由半导体厂家编写好而设备驱动一般也由设备器件的厂家编写好了我们只需要提供设备信息即可比如 I2C 设备的话提供设备连接到了哪个 I2C 接口上I2C 的速度是多少等等。相当于将设备信息从设备驱动中剥离开来驱动使用标准方法去获取到设备信息(比如从设备树中获取到设备信息)然后根据获取到的设备信息来初始化设备。这样就相当于驱动只负责驱动 设备只负责设备想办法将两者进行匹配即可。这个就是 Linux 中的总线(bus)、驱动(driver)和设备(device)模型也就是驱动分离。总线就是进行驱动与设备的匹配如下所示当向系统注册一个驱动的时候总线就会在右侧的设备中查找看看有没有与之匹配的设备如果有的话就将两者联系起来。同样的当向系统中注册一个设备的时候总线就会在左侧的驱动中查找看有没有与之匹配的设备有的话也联系起来。驱动分离思想代码的可重用性跨平台1.2 platform 平台驱动简介前面讲了设备驱动的分离并且引出了总线(bus)、驱动(driver)和设备(device)模型比如 I2C、SPI、USB 等总线。但是在 SOC 中有些外设是没有总线这个概念的但是又要使用总线、驱动和设备模型该怎么办呢为了解决此问题Linux 提出了 platform 这个虚拟总线相应的就有platform_driver和platform_deviceplatform 虚拟总线负责对设备与驱动进行管理及匹配两条链表一条挂设备一条挂驱动1.2.1 匹配过程匹配过程1、内核解析设备树所有节点遍历根下子节点2、识别平台设备节点普通带 compatible 属性无特殊总线(I2C/SPI)的节点内核调用of_device_alloc-platform_device_add 自动生成一个 platform_device 结构体填充 dev.of_node 指向设备树节点拷贝 compatible 字符串到设备结构解析 reg gpio interrupt 等资源存入设备资源列表把这个 platform_device 挂载到 platform 总线上3、总线自动触发匹配遍历总线维护两条链表总线驱动链表所有注册成功的 platform_driver总线设备链表所有 platform_device每当新增一个 device总线遍历所有已经注册的 driver 逐一比对匹配规则每当新增一个 driver总线遍历所有已经存在的 device 逐一对比内核遍历函数如下list_for_each_entry 作用遍历内核链表early_platform_match这个函数是内核启动后的第一个C程序start_kernel调用的static struct platform_device * __init early_platform_match(struct early_platform_driver *epdrv, int id) { struct platform_device *pd; list_for_each_entry(pd, early_platform_device_list, dev.devres_head) if (platform_match(pd-dev, epdrv-pdrv-driver)) if (pd-id id) return pd; return NULL; }Linux 系统内核使用 bus_type 结构体表示总线而 platform 是 bus_type 的一个具体实例platform 总线定义如下struct bus_type platform_bus_type { .name platform, .dev_groups platform_dev_groups, .match platform_match, .uevent platform_uevent, .pm platform_dev_pm_ops, };其中 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); }1.2.2 优势设备存放硬件平台的寄存器信息及地址信息后来衍生出了设备树概念总线负责对设备与驱动进行管理及匹配驱动通过总线与设备按照规则匹配成功后拿到设备信息并进行对设备的操作。相比如I2c、 SPI这些有实际物理总线的硬件芯片上面有一些外设之间没有通过总线连接比如ADC、网卡如果我们想将这些硬件驱动也通过这种模型设计完成内核设计者们为我们设计了platform总线架构也称为虚拟平台总线结构中设备类型为 platform_device 类型通过编写设备树总线类型为 platform_bus_type 类型系统提供驱动类型为 platform_driver 类型自己编写驱动设备端包含了物理平台寄存器信息及中断信息这部分信息目前位于设备树中内核自动将其转换为platform_device类型总线负责将设备与驱动进行匹配匹配成功执行驱动中的probe函数指针指向的接口来加载驱动。相比如之前通过驱动通过设备树手动获得节点数据这种总线形式能够自动匹配并获得节点数据。两者的区别1.2.3 驱动运行过程1、insmod 驱动platform_driver_register 完成初始化 driver 内嵌的 struct device_driver将驱动结构体插入 platform 总线的驱动全局链表2、platform 总线启动匹配逻辑总线拿到新 driver 后遍历总线上所有已经存在的 platform_device 5层规则依次比对driver_override 强制指定设备树 OF compatibleACPI ID 匹配platform_driverid_table 匹配设备名与驱动名字符串全等兜底3、匹配成功执行 probe 函数probe 是驱动真正初始化硬件的地方解析设备树 gpio、 irq、 reg 资源申请 GPIO、注册中断、初始化定时器、注册 input/misc 设备分配私有设备结构体存入 dev-platform_data/dev-driver_data probe 返回 0 代表初始化正常返回负数错误码总线会解绑标记匹配失败4、匹配失败所有设备全部遍历完毕无匹配驱动静静挂在总线链表等待后续如果加载新设备极少动态创建设备树节点会再次触发匹配1.2.4 驱动与设备匹配方式1、OF 类型的匹配OF 类型的匹配也就是设备树采用的匹配方式。device_driver 结构体(表示设备驱动)中有个名为of_match_table的成员变量此成员变量保存着驱动的compatible匹配表设备树中的每个设备节点的 compatible 属性会和 of_match_table 表中的所有成员比较查看是否有相同的条目如果有的话就表示设备和此驱动匹配设备和驱动匹配成功以后probe 函数就会执行。2、ACPI 匹配方式x86架构使用ARM不适用3、id_table 匹配方式id_table 匹配每个platform_driver 结构体有一个 id_table 成员变量保存了很多 id 信息。这些 id 信息存放着这个 platformd 驱动所支持的驱动类型。4、name 匹配方式直接比较驱动和设备的name 字段看看是不是相等如果相等的话就匹配成功二. platform 驱动2.1 platform_driver 结构体platform_driver 结构体表示 platform 驱动内容如下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; };1、probe 函数当驱动与设备匹配成功以后 probe 函数就会执行2、driver 成员为 device_driver 结构体变量表示设备驱动3、id_table 表 platform 总线匹配驱动和设备的时候采用的第三种方法id_table 是个表(也就是数组)2.2 device_driver 结构体device_driver 结构体内容如下struct device_driver { const char *name; struct bus_type *bus; struct module *owner; const char *mod_name; /* used for built-in modules */ bool suppress_bind_attrs; /* disables bind/unbind via sysfs */ const struct of_device_id *of_match_table; const struct acpi_device_id *acpi_match_table; int (*probe) (struct device *dev); int (*remove) (struct device *dev); void (*shutdown) (struct device *dev); int (*suspend) (struct device *dev, pm_message_t state); int (*resume) (struct device *dev); const struct attribute_group **groups; const struct dev_pm_ops *pm; struct driver_private *p; };of_match_table 就是采用设备树的时候驱动使用的匹配表同样是数组每个匹配项都为 of_device_id 结构体类型内容如下其中最重要的是compatible因为对于设备树而言就是通过设备节点的 compatible 属性值和 of_match_table 中每个项目的 compatible 成员变量进行比较如果有相等的就表示设备和此驱动匹配成功struct of_device_id { char name[32]; char type[32]; char compatible[128]; const void *data; };2.3 platform 驱动框架在编写 platform 驱动的时候首先定义一个platform_driver结构体变量然后初始化结构体中的各个成员变量重点是实现匹配方法以及 probe 函数。当驱动和设备匹配成功以后 probe 函数就会执行具体的驱动程序在 probe 函数里面编写比如字符设备驱动等等2.3.1 platform_driver_register 函数作用当定义并初始化好 platform_driver 结构体变量以后需要在驱动入口函数里面调用 platform_driver_register 函数向 Linux 内核注册一个 platform 驱动参数要注册的 platform 驱动返回值成功0失败负数2.3.2 platform _driver_unregister 函数作用从内核中卸载 platform 驱动参数要卸载的 platform 驱动返回值无2.3.3 platform 框架platform_driver 结构体定义的一般方法static const struct of_device_id of_key_device_id[] { {.compatible pt,pute-key}, {} }; static const struct platform_device_id platform_key_device_id[] { {.name key_platform_driver}, {} }; static struct platform_driver key_platform_driver { .probe myprobe, .remove myremove, .driver { .name key_platform_driver, .owner THIS_MODULE, .of_match_table of_key_device_id, }, .id_table platform_key_device_id, };代码框架/* 设备结构体 */ struct xxx_dev{ struct cdev cdev; /* 设备结构体其他具体内容 */ }; struct xxx_dev xxxdev; /* 定义个设备结构体变量 */ static int xxx_open(struct inode *inode, struct file *filp) { /* 函数具体内容 */ return 0; } static ssize_t xxx_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt) { /* 函数具体内容 */ return 0; } /* * 字符设备驱动操作集 */ static struct file_operations xxx_fops { .owner THIS_MODULE, .open xxx_open, .write xxx_write, }; /* * platform 驱动的 probe 函数 * 驱动与设备匹配成功以后此函数就会执行 */ static int xxx_probe(struct platform_device *dev) { ...... misc_register(xxx_dev)/* 注册杂项设备 */ /* 函数具体内容 */ return 0; } static int xxx_remove(struct platform_device *dev) { ...... /释放杂项设备 /释放gpio /释放设备树节点 --- of_node_put /等等 /* 函数具体内容 */ return 0; } /* 匹配列表 */ static const struct of_device_id xxx_of_match[] { { .compatible xxx-gpio }, { /* Sentinel */ } }; /* * platform 平台驱动结构体 */ static struct platform_driver xxx_driver { .driver { .name xxx, .of_match_table xxx_of_match, }, .probe xxx_probe, .remove xxx_remove, }; /* 驱动模块加载 */ static int __init xxxdriver_init(void) { return platform_driver_register(xxx_driver); } /* 驱动模块卸载 */ static void __exit xxxdriver_exit(void) { platform_driver_unregister(xxx_driver); } module_init(xxxdriver_init); module_exit(xxxdriver_exit); MODULE_LICENSE(GPL);三. 示例代码使用按键功能#define DEV_NAME mykey0 static int myprobe(struct platform_device *pplatform_device); static int myremove(struct platform_device *pplatform_device); static ssize_t myread(struct file *f, char __user *userdata, size_t size, loff_t *offset); static ssize_t mywrite(struct file *f, const char __user *userdata, size_t size, loff_t *offset); static int myopen(struct inode *pinode, struct file *f); static int myrelease(struct inode *pinode, struct file *f); static irqreturn_t irq_handler_key(int iqr_num, void *arg); static void timer_handler_key(unsigned long arg); static void workque_handler(struct work_struct *work_que_key); //自定义结构体 typedef struct key_dev{ int major; //主设备号 int minor; //次设备号 struct device_node *pnode_key; //按键节点 struct device_node *pnode_led; //led节点 int gpio_num_key; //按键 gpio 编号 int gpio_num_led; //led gpio 编号 int irq_num_key; //按键中断号 struct timer_list timer_key; //按键消抖定时器 struct work_struct work_que_key; //工作队列 wait_queue_head_t wait_head_key; //等待队列 int wait_condition; //唤醒条件 }key_dev_t; key_dev_t key_dev; typedef enum key_state{ KEY_OFF, KEY_ON }KEY_STATE; //按键状态 KEY_STATE key_status; //使用设备树匹配 static const struct of_device_id of_key_device_id[] { {.compatible pt,pute-key}, {} }; //使用id_table匹配 static const struct platform_device_id platform_key_device_id[] { {.name key_platform_driver}, {} }; //platform 驱动结构体 static struct platform_driver key_platform_driver { .probe myprobe, .remove myremove, .driver { .name key_platform_driver, .owner THIS_MODULE, .of_match_table of_key_device_id, }, .id_table platform_key_device_id, }; //操作函数集合 static struct file_operations fops { .open myopen, .release myrelease, .read myread, .write mywrite, }; //杂项设备结构体 static struct miscdevice misc_key { .minor MISC_DYNAMIC_MINOR, .name mykey0, .fops fops, }; //中断处理函数 static irqreturn_t irq_handler_key(int irq_num, void *arg) { schedule_work(key_dev.work_que_key); return IRQ_HANDLED; } //工作队列处理函数 static void workque_handler(struct work_struct *work_que_key) { //启动定时器 mod_timer(key_dev.timer_key, jiffies msecs_to_jiffies(10)); return ; } //定时器处理函数 static void timer_handler_key(unsigned long arg) { int ret 0; ret gpio_get_value(key_dev.gpio_num_key); if(ret 0) { gpio_set_value(key_dev.gpio_num_led, 0); key_status KEY_ON; } else { gpio_set_value(key_dev.gpio_num_led, 1); key_status KEY_OFF; } //唤醒等待条件 key_dev.wait_condition 1; wake_up(key_dev.wait_head_key); return ; } static ssize_t myread(struct file *f, char __user *userdata, size_t size, loff_t *offset) { int ret 0; //等待唤醒阻塞IO key_dev.wait_condition 0; wait_event_interruptible(key_dev.wait_head_key, key_dev.wait_condition); ret copy_to_user(userdata, key_status, sizeof(key_status)); if(ret) { pr_err(copy_to_user error, errno : %d\n, ret); return ret; } return size; } static ssize_t mywrite(struct file *f, const char __user *userdata, size_t size, loff_t *offset) { return size; } static int myopen(struct inode *pinode, struct file *f) { //私有数据 f-private_data key_dev; pr_info(myopen finish!\n); return 0; } static int myrelease(struct inode *pinode, struct file *f) { pr_info(myrelease finish!\n); return 0; } static int myprobe(struct platform_device *pplatform_device) { int ret 0; //注册杂项设备 ret misc_register(misc_key); if(ret) { pr_err(misc_register error, errno:%d\n, ret); return ret; } key_dev.major 10; key_dev.minor misc_key.minor; //获取设备节点 key_dev.pnode_key of_find_node_by_path(/putekey); if(key_dev.pnode_key NULL) { ret -ENODEV; pr_err(of_find_node_by_path error, errno:%d\n, ret); goto del_misc; } //获取gpio编号 key_dev.gpio_num_key of_get_named_gpio(key_dev.pnode_key, gpio-key, 0); if(key_dev.gpio_num_key 0) { ret key_dev.gpio_num_key; pr_err(of_get_named_gpio error, errno:%d\n, ret); goto put_node; } //申请gpio资源 ret gpio_request(key_dev.gpio_num_key, mykey); if(ret) { pr_err(gpio_request error, errno:%d\n, ret); goto put_node; } //设置gpio输入输出 ret gpio_direction_input(key_dev.gpio_num_key); if(ret) { pr_err(gpio_direction_input error, errno:%d\n, ret); goto del_gpio; } //获取中断号 key_dev.irq_num_key gpio_to_irq(key_dev.gpio_num_key); if(key_dev.irq_num_key 0) { ret -EFAULT; pr_err(gpio_to_irq error, errno:%d\n, ret); goto del_gpio; } //注册中断 ret request_irq(key_dev.irq_num_key, irq_handler_key, IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING, mykey_irq, NULL); if(ret) { pr_err(request_irq error, errno:%d\n, ret); goto del_gpio; } //定义 初始化工作队列 INIT_WORK(key_dev.work_que_key, workque_handler); //初始化定时器 key_dev.timer_key.function timer_handler_key; init_timer(key_dev.timer_key); //阻塞IO部分等待队列 init_waitqueue_head(key_dev.wait_head_key); //led 部分 //获取设备节点 key_dev.pnode_led of_find_node_by_path(/puteled); if(key_dev.pnode_led NULL) { ret -ENODEV; pr_err(of_find_node_by_path error, errno:%d\n, ret); goto free_irq_timer; } //获取gpio编号 key_dev.gpio_num_led of_get_named_gpio(key_dev.pnode_led, gpio-led, 0); if(key_dev.gpio_num_led 0) { ret key_dev.gpio_num_led; pr_err(of_get_named_gpio error, errno:%d\n, ret); goto put_node_led; } //申请gpio资源 ret gpio_request(key_dev.gpio_num_led, myled); if(ret) { pr_err(gpio_request error, errno:%d\n, ret); goto put_node_led; } //设置gpio输入输出 ret gpio_direction_output(key_dev.gpio_num_led, 1); if(ret) { pr_err(gpio_direction_input error, errno:%d\n, ret); goto del_gpio_led; } pr_info(my_probe finish!\n); return 0; del_gpio_led: gpio_free(key_dev.gpio_num_led); put_node_led: of_node_put(key_dev.pnode_led); free_irq_timer: del_timer(key_dev.timer_key); free_irq(key_dev.irq_num_key, NULL); del_gpio: gpio_free(key_dev.gpio_num_key); put_node: of_node_put(key_dev.pnode_key); del_misc: misc_register(misc_key); return ret; } static int myremove(struct platform_device *pplatform_device) { gpio_free(key_dev.gpio_num_led); of_node_put(key_dev.pnode_led); del_timer(key_dev.timer_key); free_irq(key_dev.irq_num_key, NULL); gpio_free(key_dev.gpio_num_key); of_node_put(key_dev.pnode_key); misc_deregister(misc_key); pr_info(my_remove finish!\n); return 0; } static int __init mydrv_init(void) { int ret 0; ret platform_driver_register(key_platform_driver); if(ret) { pr_err(platform_driver_register error, errno : %d\n, ret); return ret; } pr_info(mydrv_init finish!\n); return 0; } static void __exit mydrv_exit(void) { platform_driver_unregister(key_platform_driver); pr_info(mydrv_exit finish!\n); return; } module_init(mydrv_init); module_exit(mydrv_exit); MODULE_LICENSE(GPL); MODULE_DESCRIPTION(this drv is key_platform test\n); MODULE_AUTHOR(dl);