驱动设计思想_面向对象_分层_分离
在这里以一个demo为例子讲解。
首先讲一下理论思想
再贴出demo代码
最后根据demo代码分析结构,然后给出流程图
面向对象_分层_分离
1.面向对象
字符设备驱动程序抽象出一个 file_operations 结构体;
我们写的程序针对硬件部分抽象出 led_operations 结构体。
2.分层
上下分层,比如我们前面写的 LED 驱动程序就分为 2 层:
① 上层实现硬件无关的操作,比如注册字符设备驱动:leddrv.c
② 下层实现硬件相关的操作,比如 board_A.c 实现单板 A 的 LED 操作
3.分离
在 board_A.c 中,实现了一个 led_operations,为 LED 引脚实现了初始
化函数、控制函数:
static struct led_operations board_demo_led_opr = { .num = 1, .init = board_demo_led_init, .ctl = board_demo_led_ctl, };
如果硬件上更换一个引脚来控制 LED 怎么办?你要去修改上面结构体中的
init、ctl 函数。
实际情况是,每一款芯片它的 GPIO 操作都是类似的。比如:GPIO1_3、
GPIO5_4 这 2 个引脚接到 LED:
① GPIO1_3 属于第 1 组,即 GPIO1。
a) 有方向寄存器 DIR、数据寄存器 DR 等,基础地址是
addr_base_addr_gpio1。
b) 设置为 output 引脚:修改 GPIO1 的 DIR 寄存器的 bit3。
c) 设置输出电平:修改 GPIO1 的 DR 寄存器的 bit3。
② GPIO5_4 属于第 5 组,即 GPIO5。
a) 有方向寄存器 DIR、数据寄存器 DR 等,基础地址是
addr_base_addr_gpio5。
b) 设置为 output 引脚:修改 GPIO5 的 DIR 寄存器的 bit4。
c) 设置输出电平:修改 GPIO5 的 DR 寄存器的 bit4。
既然引脚操作那么有规律,并且这是跟主芯片相关的,那可以针对该芯片写
出比较通用的硬件操作代码。
比如 board_A.c 使用芯片 chipY,那就可以写出:chipY_gpio.c,它实现
芯片 Y 的 GPIO 操作,适用于芯片 Y 的所有 GPIO 引脚。
使用时,我们只需要在 board_A_led.c 中指定使用哪一个引脚即可。程序
结构如下:
以面向对象的思想,在 board_A_led.c 中实现 led_resouce 结构体,它定
义“资源”──要用哪一个引脚。
在 chipY_gpio.c 中仍是实现 led_operations 结构体,它要写得更完善,
支持所有 GPIO。
demo代码
ledtest.c
#include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #include <stdio.h> #include <string.h> /* * ./ledtest /dev/100ask_led0 on * ./ledtest /dev/100ask_led0 off */ int main(int argc, char **argv) { int fd; char status; /* 1. 判断参数 */ if (argc != 3) { printf("Usage: %s <dev> <on | off>\n", argv[0]); return -1; } /* 2. 打开文件 */ fd = open(argv[1], O_RDWR); if (fd == -1) { printf("can not open file %s\n", argv[1]); return -1; } /* 3. 写文件 */ if (0 == strcmp(argv[2], "on")) { status = 1; write(fd, &status, 1); } else { status = 0; write(fd, &status, 1); } close(fd); return 0; }
leddrv.c
#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" #define LED_NUM 2 /* 1. 确定主设备号 */ static int major = 0; static struct class *led_class; struct led_operations *p_led_opr; #define MIN(a, b) (a < b ? a : b) /* 3. 实现对应的open/read/write等函数,填入file_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; } /* write(fd, &val, 1); */ 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); /* 根据次设备号和status控制LED */ 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__); /* 根据次设备号初始化LED */ 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; } /* 2. 定义自己的file_operations结构体 */ 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, }; /* 4. 把file_operations结构体告诉内核:注册驱动程序 */ /* 5. 谁来注册驱动程序啊?得有一个入口函数:安装驱动程序时,就会去调用这个入口函数 */ static int __init led_init(void) { int err; int i; printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__); major = register_chrdev(0, "100ask_led", &led_drv); /* /dev/led */ 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; } for (i = 0; i < LED_NUM; i++) device_create(led_class, NULL, MKDEV(major, i), NULL, "100ask_led%d", i); /* /dev/100ask_led0,1,... */ p_led_opr = get_board_led_opr(); return 0; } /* 6. 有入口函数就应该有出口函数:卸载驱动程序时,就会去调用这个出口函数 */ static void __exit led_exit(void) { int i; printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__); for (i = 0; i < LED_NUM; i++) device_destroy(led_class, MKDEV(major, i)); /* /dev/100ask_led0,1,... */ device_destroy(led_class, MKDEV(major, 0)); class_destroy(led_class); unregister_chrdev(major, "100ask_led"); } /* 7. 其他完善:提供设备信息,自动创建设备节点 */ module_init(led_init); module_exit(led_exit); MODULE_LICENSE("GPL");
led_opr.h
#ifndef _LED_OPR_H #define _LED_OPR_H struct led_operations { int (*init) (int which); /* 初始化LED, which-哪个LED */ int (*ctl) (int which, char status); /* 控制LED, which-哪个LED, status:1-亮,0-灭 */ }; struct led_operations *get_board_led_opr(void); #endif
chip_demo_gpio.c
#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" #include "led_resource.h" static struct led_resource *led_rsc; static int board_demo_led_init (int which) /* 初始化LED, which-哪个LED */ { //printk("%s %s line %d, led %d\n", __FILE__, __FUNCTION__, __LINE__, which); if (!led_rsc) { led_rsc = get_led_resouce(); } printk("init gpio: group %d, pin %d\n", GROUP(led_rsc->pin), PIN(led_rsc->pin)); switch(GROUP(led_rsc->pin)) { 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) /* 控制LED, which-哪个LED, status:1-亮,0-灭 */ { //printk("%s %s line %d, led %d, %s\n", __FILE__, __FUNCTION__, __LINE__, which, status ? "on" : "off"); printk("set led %s: group %d, pin %d\n", status ? "on" : "off", GROUP(led_rsc->pin), PIN(led_rsc->pin)); switch(GROUP(led_rsc->pin)) { 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; }
led_resource.h
#ifndef _LED_RESOURCE_H #define _LED_RESOURCE_H /* GPIO3_0 */ /* bit[31:16] = group */ /* bit[15:0] = which pin */ #define GROUP(x) (x>>16) #define PIN(x) (x&0xFFFF) #define GROUP_PIN(g,p) ((g<<16) | (p)) struct led_resource { int pin; }; struct led_resource *get_led_resouce(void); #endif
board_A_led.c
#include "led_resource.h" static struct led_resource board_A_led = { .pin = GROUP_PIN(3,1), }; struct led_resource *get_led_resouce(void) { return &board_A_led; }
结构分析
在这里我们从应用态到核心态进行分析
1.首先ledtest.c调用系统函数open,read,write,close等,通过中断陷入内核态之后调用sys_open,sys_read,sys_write,sys_close等函数,再调用drv_open,drv_close,drv_read,drv_write等驱动函数。而drv_open,drv_close,drv_read,drv_write,等驱动函数编写在字符设备file_operations结构体中,例如在led_drv.c函数里面定义了名为led_drv。并且在drv_open和drv_write函数里面会调用p_led_opr(类型为led_operations)来指向ledGPIO操作。而在chip_demo_gpio.c里面又会调用led_resource.h文件里面定义的硬件结构体。从而实现分层分离的操作。这样对于驱动程序的拓展性更强。