驱动设计的思想 面向对象 字符设备驱动程序抽象出一个 file_operations 结构体,在这个结构体中实现驱动相关的 open、write 等函数,把一个事件抽象为一个对象。在 Linux 驱动中,可以认为面向对象就是用结构体表示某一个对象。
分层 上下分层,可以将 LED 驱动程序分为两层: ①、上层实现硬件无关的操作,比如注册字符设备驱动:leddrv.c。 ②、下层实现硬件相关的操作,比如 board_A.c 实现单板 A 的 LED 操作。
分离 上下分层,下层左右分离。在上层统一使用 leddrv.c 实现 file_operations 注册驱动;下层定义两个文件,board_A_led.c 指明在 A 板上 LED 的引脚,chipY_gpio.c 实现了这款芯片的 GPIO 操作,所以使用不同的 board_B_led.c 时可以使用同一个 chipY_gpio.c ,减少我们的工作量。
flowchart TB
%% 上下分层:上层统一驱动注册,下层左右分离:板级/芯片级
subgraph L1["上层:统一驱动层"]
leddrv["leddrv.c\n(file_operations 注册/驱动入口)"]
end
subgraph L2["下层:适配层(左右分离)"]
direction LR
subgraph B["左:板级适配(Board)"]
boardA["board_A_led.c\n(声明 A 板 LED 引脚/资源)"]
boardB["board_B_led.c\n(声明 B 板 LED 引脚/资源)"]
end
subgraph C["右:芯片抽象(Chip)"]
chipY["chipY_gpio.c\n(GPIO 操作实现:read/write/dir...)"]
end
end
leddrv --> boardA
leddrv --> boardB
boardA --> chipY
boardB --> chipY
%% 强调复用关系
chipY -.复用.-> boardA
chipY -.复用.-> boardB
在 board_A_led.c 中,实现 led_resources 结构体。在 Linux 驱动中,这里的板级文件一般指驱动树,我们只需要修改驱动树中的 GPIO 组就可以了。
总线设备驱动模型 在传统写法中,我们用什么引脚、引脚的寄存器操作都写在一起,绑定紧密(分配/设置/注册 file_operations/使用 ioremap 映射寄存器/操作寄存器)。虽然简单,但是程序扩展性差。同时,Linux 需要支持很多的设备,如果按照上文所说的分离思想,要定义多个结构体,很明显这是不现实的,因此引入了总线设备驱动模型。
Linux 驱动里的“总线-设备-驱动模型”,本质上是一个相亲系统。内核不直接让驱动去“找硬件”,而是建立一个三方结构:总线(bus)负责牵线,设备(device)是被描述的硬件实体,驱动(driver)是实现功能的软件代码。当一个设备被注册到某条总线上时,总线会拿这个设备的“身份证”(比如 name、compatible、id 表)去匹配所有已经注册的驱动;如果某个驱动声明“我支持这种设备”,匹配成功,总线就调用驱动的 probe(),从此这个设备就“被驱动”了。设备拔掉时会调用 remove(),生命周期也归总线统一管理。一般情况下,在 ARM 这类 SOC 平台上,设备通常通过设备树修改。
具体上来说,平台设备和平台驱动是如何挂钩的呢?上文说到是通过总线(BUS)连接的。BUS 左侧是设备列表,右侧是驱动列表。当我们注册一个 platform_device时,总线会将其与驱动列表中已注册的 platform_driver 一一比较,若匹配成功,就调用那个 platform_driver 中的 probe 函数;一样的,当我们注册一个 platform_driver 时,总线会将其与设备列表中的设备一一驱动,若匹配成功,则调用其中的 probe 函数处理匹配的设备。
匹配规则的细节会随内核版本演进,但总体思路是:如果设备指定了 driver_override 就优先按它绑定;否则优先走设备树/ACPI 这类“硬件描述”匹配;再退回到 id_table 与名字匹配。匹配成功后调用 probe() 完成资源解析与设备初始化。
一般情况下,一个 driver 可以匹配一个或多个 device。如何匹配呢?第一步,若 device 中定义了 driver_override,bus 会将它和 driver 中的 driver.name 先比较;若没有定义driver_override,再将 device 中的 name 和 driver 的id_table 去比较;若还是匹配不上,则最后将 device 中的 name 和 driver 中的 driver.name 比较。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 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 ; const char *driver_override; struct mfd_cell *mfd_cell ; struct pdev_archdata archdata ; }; struct platform_driver { int (*probe)(struct platform_device *); int (*remove)(struct platform_device *); void (*remove_new)(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; bool driver_managed_dma; };
代码解析 基于韦东山的最小总线设备驱动模型框架代码(LED),我们来简要分析如何一步一步写出这份框架。代码放在文章末尾。
文件分别介绍 leddrv.c 作为上层框架做两件事: 1、register_chrdev()注册字符设备(使用 file_operation 结构体),提供 /dev/100ask_ledX 这种用户态入口; 2、class_create() 创建 class,用于后续 device_create() 自动生成设备节点。 这个文件不管硬件操作,只负责将用户写进的0/1转交给指针函数 p_led_opr->ctl(minor, status);。同时他还导出了三个函数给别的模块使用led_class_create_device(minor),led_class_destroy_device(minor),register_led_operations(opr)(注册“底层怎么点灯”的实现)
chip_demo_gpio.c 作为驱动层,干了以下两件事 1、platform_driver 去匹配板级设备并读取资源。
.driver.name = "100ask_led"
probe() 里通过 platform_get_resource(... IORESOURCE_IRQ ...) 把 board_A_led.c 提供的 .start 读出来,存进 g_ledpins[]
每读到一个资源就led_class_create_device(g_ledcnt) → 创建设备节点 /dev/100ask_led0, /dev/100ask_led1…,并且g_ledcnt++。
2、提供真正控制 LED 的函数实现
board_demo_led_init(which):根据 g_ledpins[which] 初始化。
board_demo_led_ctl(which, status):根据 g_ledpins[which] 设置高低电平(这里用 printk 模拟)。
并在 init 时调用:register_led_operations(&board_demo_led_opr); 让 leddrv.c 里的 p_led_opr 指向它。
board_A_led.c:板级资源声明 这里注册一个 platform_device,名字也叫 “100ask_led”,所以可以被 bus 匹配。通过 resources[] 把“本板子有哪些 LED、各自接在哪个 GPIO”描述出来。
串联 Step 0: 加载 leddrv.ko,注册字符设备(拿到 major),建立 led_class。加载 board_A_led.ko,platform_device_register() 注册一个名叫 “100ask_led” 的设备。加载 chip_demo_gpio.ko,platform_driver_register() 注册名叫 “100ask_led” 的驱动,内核发现已经存在同名 device,于是触发 probe()。
Step 1: 在 probe 函数中,从 platform_device 里把 resources 一个个读出来,填到 g_ledpins[],为每个 LED 创建一个 /dev/100ask_ledX。
Step 2: 用户态写入:
write(fd, &val, 1); // val=0/1
内核进入 leddrv.c: led_drv_write():通过 iminor(inode) 拿到 minor(决定是 LED0 还是 LED1),p_led_opr->ctl(minor, status)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 #include <linux/module.h> #include <linux/fs.h> #include <linux/errno.h> #include <linux/miscdevice.h> #include <linux/kernel.h> #include <linux/major.h> #include <linux/mutex.h> #include <linux/proc_fs.h> #include <linux/seq_file.h> #include <linux/stat.h> #include <linux/init.h> #include <linux/device.h> #include <linux/tty.h> #include <linux/kmod.h> #include <linux/gfp.h> #include "led_opr.h" static int major = 0 ;static struct class *led_class ;struct led_operations *p_led_opr ;#define MIN(a, b) (a < b ? a : b) void led_class_create_device (int minor) { device_create(led_class, NULL , MKDEV(major, minor), NULL , "100ask_led%d" , minor); } void led_class_destroy_device (int minor) { device_destroy(led_class, MKDEV(major, minor)); } void register_led_operations (struct led_operations *opr) { p_led_opr = opr; } EXPORT_SYMBOL(led_class_create_device); EXPORT_SYMBOL(led_class_destroy_device); EXPORT_SYMBOL(register_led_operations); static ssize_t led_drv_read (struct file *file, char __user *buf, size_t size, loff_t *offset) { printk("%s %s line %d\n" , __FILE__, __FUNCTION__, __LINE__); return 0 ; } static ssize_t led_drv_write (struct file *file, const char __user *buf, size_t size, loff_t *offset) { int err; char status; struct inode *inode = file_inode(file); int minor = iminor(inode); printk("%s %s line %d\n" , __FILE__, __FUNCTION__, __LINE__); err = copy_from_user(&status, buf, 1 ); p_led_opr->ctl(minor, status); return 1 ; } static int led_drv_open (struct inode *node, struct file *file) { int minor = iminor(node); printk("%s %s line %d\n" , __FILE__, __FUNCTION__, __LINE__); p_led_opr->init(minor); return 0 ; } static int led_drv_close (struct inode *node, struct file *file) { printk("%s %s line %d\n" , __FILE__, __FUNCTION__, __LINE__); return 0 ; } static struct file_operations led_drv = { .owner = THIS_MODULE, .open = led_drv_open, .read = led_drv_read, .write = led_drv_write, .release = led_drv_close, }; static int __init led_init (void ) { int err; printk("%s %s line %d\n" , __FILE__, __FUNCTION__, __LINE__); major = register_chrdev(0 , "100ask_led" , &led_drv); led_class = class_create(THIS_MODULE, "100ask_led_class" ); err = PTR_ERR(led_class); if (IS_ERR(led_class)) { printk("%s %s line %d\n" , __FILE__, __FUNCTION__, __LINE__); unregister_chrdev(major, "led" ); return -1 ; } return 0 ; } static void __exit led_exit (void ) { printk("%s %s line %d\n" , __FILE__, __FUNCTION__, __LINE__); class_destroy(led_class); unregister_chrdev(major, "100ask_led" ); } module_init(led_init); module_exit(led_exit); MODULE_LICENSE("GPL" );
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 #include <linux/module.h> #include <linux/fs.h> #include <linux/errno.h> #include <linux/miscdevice.h> #include <linux/kernel.h> #include <linux/major.h> #include <linux/mutex.h> #include <linux/proc_fs.h> #include <linux/seq_file.h> #include <linux/stat.h> #include <linux/init.h> #include <linux/device.h> #include <linux/tty.h> #include <linux/kmod.h> #include <linux/gfp.h> #include <linux/platform_device.h> #include "led_opr.h" #include "leddrv.h" #include "led_resource.h" static int g_ledpins[100 ];static int g_ledcnt = 0 ;static int board_demo_led_init (int which) { printk("init gpio: group %d, pin %d\n" , GROUP(g_ledpins[which]), PIN(g_ledpins[which])); switch (GROUP(g_ledpins[which])) { case 0 : { printk("init pin of group 0 ...\n" ); break ; } case 1 : { printk("init pin of group 1 ...\n" ); break ; } case 2 : { printk("init pin of group 2 ...\n" ); break ; } case 3 : { printk("init pin of group 3 ...\n" ); break ; } } return 0 ; } static int board_demo_led_ctl (int which, char status) { printk("set led %s: group %d, pin %d\n" , status ? "on" : "off" , GROUP(g_ledpins[which]), PIN(g_ledpins[which])); switch (GROUP(g_ledpins[which])) { case 0 : { printk("set pin of group 0 ...\n" ); break ; } case 1 : { printk("set pin of group 1 ...\n" ); break ; } case 2 : { printk("set pin of group 2 ...\n" ); break ; } case 3 : { printk("set pin of group 3 ...\n" ); break ; } } return 0 ; } static struct led_operations board_demo_led_opr = { .init = board_demo_led_init, .ctl = board_demo_led_ctl, }; struct led_operations *get_board_led_opr (void ) { return &board_demo_led_opr; } static int chip_demo_gpio_probe (struct platform_device *pdev) { struct resource *res ; int i = 0 ; while (1 ) { res = platform_get_resource(pdev, IORESOURCE_IRQ, i++); if (!res) break ; g_ledpins[g_ledcnt] = res->start; led_class_create_device(g_ledcnt); g_ledcnt++; } return 0 ; } static int chip_demo_gpio_remove (struct platform_device *pdev) { struct resource *res ; int i = 0 ; while (1 ) { res = platform_get_resource(pdev, IORESOURCE_IRQ, i); if (!res) break ; led_class_destroy_device(i); i++; g_ledcnt--; } return 0 ; } static struct platform_driver chip_demo_gpio_driver = { .probe = chip_demo_gpio_probe, .remove = chip_demo_gpio_remove, .driver = { .name = "100ask_led" , }, }; static int __init chip_demo_gpio_drv_init (void ) { int err; err = platform_driver_register(&chip_demo_gpio_driver); register_led_operations(&board_demo_led_opr); return 0 ; } static void __exit lchip_demo_gpio_drv_exit (void ) { platform_driver_unregister(&chip_demo_gpio_driver); } module_init(chip_demo_gpio_drv_init); module_exit(lchip_demo_gpio_drv_exit); MODULE_LICENSE("GPL" );
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 #include <linux/module.h> #include <linux/fs.h> #include <linux/errno.h> #include <linux/miscdevice.h> #include <linux/kernel.h> #include <linux/major.h> #include <linux/mutex.h> #include <linux/proc_fs.h> #include <linux/seq_file.h> #include <linux/stat.h> #include <linux/init.h> #include <linux/device.h> #include <linux/tty.h> #include <linux/kmod.h> #include <linux/gfp.h> #include <linux/platform_device.h> #include "led_resource.h" static void led_dev_release (struct device *dev) { } static struct resource resources [] = { { .start = GROUP_PIN(3 ,1 ), .flags = IORESOURCE_IRQ, .name = "100ask_led_pin" , }, { .start = GROUP_PIN(5 ,8 ), .flags = IORESOURCE_IRQ, .name = "100ask_led_pin" , }, }; static struct platform_device board_A_led_dev = { .name = "100ask_led" , .num_resources = ARRAY_SIZE(resources), .resource = resources, .dev = { .release = led_dev_release, }, }; static int __init led_dev_init (void ) { int err; err = platform_device_register(&board_A_led_dev); return 0 ; } static void __exit led_dev_exit (void ) { platform_device_unregister(&board_A_led_dev); } module_init(led_dev_init); module_exit(led_dev_exit); MODULE_LICENSE("GPL" );