本系列文章是记录在学习韦东山老师的嵌入式开发教程中的课程笔记,方便一起学习的朋友们参考。如果还没有购买韦老师的教学视频,或者不知道去哪里购买的,这里给大家一个小程序链接![]() ![]() 一 why在初级字符设备驱动程序编写时,我们的应用程序是通过open函数来实现调用底层驱动的,比如:fd = open("/dev/buttons", O_RDWR); read(fd, &key_val, 1); 字符驱动设备框架为:写file_operations结构体的成员函数: .open()、.read()、.write() 在入口函数里通过register_chrdev()创建驱动名,生成主设备号,赋入file_operations结构体 在出口函数里通过unregister_chrdev() 卸载驱动 思考一下,这样做有什么不好的地方呢? 若有多个不同的驱动程序时,应用程序就要打开多个不同的驱动设备,由于是自己写肯定会很清楚,如果给别人来使用时是不是很麻烦? 因此,我们使用现成的驱动,将我们上面写的驱动程序融合到内核中,这个现成的驱动就是input子系统。 二 whatlinux内核为了能够处理各种不同类型的输入设备,比如触摸屏、鼠标、键盘等等,为驱动层程序提供统一的接口函数,为上层应用提供试图统一的抽象层,这就是输入子系统input system。a. input子系统组成及其框架图 输入子系统由输入子系统核心层( Input Core ),驱动层和事件处理层(Event Handler)三部份组成 ![]() b. 核心层 根据上面的图可知,显然核心层是连接左右两边的device和driver的桥梁,源码为 /drivers/input/input.c。我们阅读一下它的入口函数input_init() staticconststructfile_operationsinput_fops = { .owner = THIS_MODULE, .open = input_open_file, .llseek = noop_llseek, }; ...... err = register_chrdev(INPUT_MAJOR, "input", &input_fops); (1)那么应该怎么去读呢,input_open_file是如何实现呢? input_open_file handler = input_table[iminor(inode) >> 5]; new_fops = fops_get(handler->fops); // 假设 => evdev_fops(evdev.c中) file->f_op = new_fops; err = new_fops->open(inode, file); app应用程序读的时候,应用程序调用read函数,一直会调用到内核底层的file->f_op->read read > ... > file->f_op->read 在上面的code中,input_table 数组由谁构造? input_register_handler
注册 input_handler input_register_handler // 放入数组 input_table[handler->minor >> 5] = handler;
// 放入链表 list_add_tail(&handler->node, &input_handler_list);
// 对于每一个input_dev, 调用 input_attach_handler list_for_each_entry(dev, &input_dev_list, node) input_attach_handler(dev, handler); // 根据input_handler的 id_table 判断能否支持这个 input_device 注册输入设备 input_register_device // 放入链表中 list_add_tail(&dev->node, &input_dev_list);
// 对于每一个 input_handler 都调用 input_attach_handler 函数 list_for_each_entry(handler, &input_handler_list, node) input_attach_handler(dev, handler); // 根据input_handler的 id_table 判断能否支持这个 input_device
input_attach_handler id = input_match_device(handler, dev);
error = handler->connect(handler, dev, id); input_attach_handler id = input_match_device(handler, dev);
error = handler->connect(handler, dev, id); 注册 input_dev 和 input_handler 时, 会两两比较左边的 input_dev 和右边的 input_handler,根据 input_handler 的 id_table判断这个 input_handler能够支持这个 input_dev。如果能支持,则调用 input_handler的connect函数建立"连接"。(2)怎么建立连接? 分配一个 input_handle结构体 input_handle.dev = input_dev; //指向框架图左边的dev结构体 input_handle.handler = input_handler; //指向框架图右边的handler结构体 注册 input_handler->h_list = &input_handle input_dev->h_list = &input_handle
evdev_connect evdev = kzalloc(sizeof(struct evdev), GFP_KERNEL); 分配一个 input_handle
evdev->handle.dev = input_get_device(dev); //指向框架图左边的dev结构体 evdev->handle.name = dev_name(&evdev->dev); evdev->handle.handler = handler; //指向框架图右边的handler结构体 evdev->handle.private = evdev; (3)怎么写符合输入子系统框架的驱动程序 1. 分配一个 input_dev 结构体2. 设置3. 注册4. 硬件相关的代码,比如中断服务程序里上报事件 三 how根据上面的分析,显然我们的驱动程序需要实现两个模块,dev驱动以及drv驱动,我们以点亮一个led为例,分别实现led_dev以及led_drva. 实现led_dev.c (1)初始化和退出驱动,这个调用platform_device_register和platform_device_unregister即可 staticintled_dev_init(void) { platform_device_register(&led_dev); return0; }
staticvoidled_dev_exit(void) { platform_device_unregister(&led_dev); } (2)初始化platform_device结构体 staticstructplatform_deviceled_dev = { .name = "myled", .id = -1, .num_resources = ARRAY_SIZE(led_resource), .resource = led_resource, .dev = { .release = led_release, }, }; (3)初始化device资源,第0个表示led使用的gpio寄存器,第1个表示gpio引脚。这里的resource资源需要根据芯片手册,以及硬件原理图来确定 staticstruct resource led_resource[] = { [0] = { .start = 0x56000050, .end = 0x56000050 + 8 - 1, .flags = IORESOURCE_MEM, }, [1] = { .start = 5, .end = 5, .flags = IORESOURCE_IRQ, }, }; 完整代码如下: /* 分配设置注册一个platform_device */ #include<linux/module.h> #include<linux/version.h> #include<linux/init.h>
#include<linux/kernel.h> #include<linux/types.h> #include<linux/interrupt.h> #include<linux/list.h> #include<linux/timer.h> #include<linux/init.h> #include<linux/serial_core.h> #include<linux/platform_device.h> #include<linux/io.h>
staticvoidled_release(struct device *dev) { return0; }
staticstructresourceled_resource[] = { [0] = { .start = 0x56000050, .end = 0x56000050 + 8 - 1, .flags = IORESOURCE_MEM, }, [1] = { .start = 5, .end = 5, .flags = IORESOURCE_IRQ, }, };
staticstructplatform_deviceled_dev = { .name = "myled", .id = -1, .num_resources = ARRAY_SIZE(led_resource), .resource = led_resource, .dev = { .release = led_release, }, };
staticintled_dev_init(void) { platform_device_register(&led_dev); return0; }
staticvoidled_dev_exit(void) { platform_device_unregister(&led_dev); }
module_init(led_dev_init); module_exit(led_dev_exit); MODULE_LICENSE("GPL"); b. 实现led_drv.c (1) 初始化和退出驱动,这个调用platform_driver_register和platform_driver_unregister即可 staticintled_drv_init(void) { platform_driver_register(&led_drv); return0; }
staticvoidled_drv_exit(void) { platform_driver_unregister(&led_drv); } (2) 初始化platform_driver staticstruct platform_driver led_drv = { .probe = led_probe, .remove = led_remove, .driver = { .name = "myled", .owner = THIS_MODULE, } }; (3)led_probe以及led_remove staticint led_probe(struct platform_device *pdev) { struct resource *res;
/* 根据platform的资源进行ioremap */ res = platform_get_resource(pdev, IORESOURCE_MEM, 0); //printk("res=0x%p res->start=0x%p\n", res, res->start); if (res == NULL){ printk("get resource err!\n"); return-1; } gpio_con = ioremap(res->start, res->end - res->start + 1); gpio_dat = gpio_con + 1;
res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); if (res == NULL){ printk("get resource err!\n"); return-2; } //printk("res1=0x%p res1->start=0x%p\n", res, res->start); pin = res->start;
/* 注册字符设备驱动 */ printk("led_probe, found led\n"); major = register_chrdev(0, "myled", &led_fops);
cls = class_create(THIS_MODULE, "myled"); device_create(cls, NULL, MKDEV(major, 0), NULL, "led");
return0; }
staticint led_remove(struct platform_device *pdev) { /* 卸载字符设备驱动 */ /* 根据platform的资源进行iounmap */ printk("led_remove, remove led\n"); device_destroy(cls, MKDEV(major, 0)); class_destroy(cls); unregister_chrdev(major, "myled"); iounmap(gpio_con); return0; } (4)初始化file_operations staticstructfile_operationsled_fops = { .owner = THIS_MODULE, /* 这是一个宏,推向编译模块时自动创建的__this_module变量 */ .open = led_open, .write = led_write, }; (5)led_open和led_write staticintled_open(struct inode *inode, struct file *file) { /* 配置为输出引脚 */ *gpio_con &= ~(0x3<<(pin*2)); *gpio_con |= (0x1<<(pin*2)); return0; }
static ssize_t led_write(struct file *file, constchar __user *buf, size_t count, loff_t * ppos) { int val;
//printk("first_drv_write\n");
copy_from_user(&val, buf, count); // copy_to_user();
if (val == 1) { // 点灯 *gpio_dat &= ~(1<<pin); } else { // 灭灯 *gpio_dat |= (1<<pin); }
return0; } 完整代码 /* 分配设置注册一个platform_driver */ #include<linux/module.h> #include<linux/version.h>
#include<linux/init.h> #include<linux/fs.h> #include<linux/interrupt.h> #include<linux/irq.h> #include<linux/sched.h> #include<linux/pm.h> #include<linux/sysctl.h> #include<linux/proc_fs.h> #include<linux/delay.h> #include<linux/platform_device.h> #include<linux/input.h> #include<linux/kernel.h> #include<linux/device.h> #include<asm/uaccess.h> #include<asm/io.h>
staticint major;
staticstructclass *cls; staticvolatileunsignedlong *gpio_con; staticvolatileunsignedlong *gpio_dat; staticint pin;
staticintled_open(struct inode *inode, struct file *file) { /* 配置为输出引脚 */ *gpio_con &= ~(0x3<<(pin*2)); *gpio_con |= (0x1<<(pin*2)); return0; }
static ssize_t led_write(struct file *file, constchar __user *buf, size_t count, loff_t * ppos) { int val;
//printk("first_drv_write\n");
copy_from_user(&val, buf, count); // copy_to_user();
if (val == 1) { // 点灯 *gpio_dat &= ~(1<<pin); } else { // 灭灯 *gpio_dat |= (1<<pin); }
return0; }
staticstructfile_operationsled_fops = { .owner = THIS_MODULE, /* 这是一个宏,推向编译模块时自动创建的__this_module变量 */ .open = led_open, .write = led_write, };
staticintled_probe(struct platform_device *pdev) { structresource *res;
/* 根据platform的资源进行ioremap */ res = platform_get_resource(pdev, IORESOURCE_MEM, 0); //printk("res=0x%p res->start=0x%p\n", res, res->start); if (res == NULL){ printk("get resource err!\n"); return-1; } gpio_con = ioremap(res->start, res->end - res->start + 1); gpio_dat = gpio_con + 1;
res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); if (res == NULL){ printk("get resource err!\n"); return-2; } //printk("res1=0x%p res1->start=0x%p\n", res, res->start); pin = res->start;
/* 注册字符设备驱动 */ printk("led_probe, found led\n"); major = register_chrdev(0, "myled", &led_fops);
cls = class_create(THIS_MODULE, "myled"); device_create(cls, NULL, MKDEV(major, 0), NULL, "led");
return0; }
staticintled_remove(struct platform_device *pdev) { /* 卸载字符设备驱动 */ /* 根据platform的资源进行iounmap */ printk("led_remove, remove led\n"); device_destroy(cls, MKDEV(major, 0)); class_destroy(cls); unregister_chrdev(major, "myled"); iounmap(gpio_con); return0; }
staticstructplatform_driverled_drv = { .probe = led_probe, .remove = led_remove, .driver = { .name = "myled", .owner = THIS_MODULE, } };
staticintled_drv_init(void) { platform_driver_register(&led_drv); return0; }
staticvoidled_drv_exit(void) { platform_driver_unregister(&led_drv); }
module_init(led_drv_init); module_exit(led_drv_exit); MODULE_LICENSE("GPL"); c. 测试程序,验证上面的驱动框架,完整代码如下 #include<sys/types.h> #include<sys/stat.h> #include<fcntl.h> #include<stdio.h>
/* led_test on * led_test off */ intmain(int argc, char **argv) { int fd; int val = 1;
fd = open("/dev/led", O_RDWR); if (fd < 0) { printf("can't open!\n"); return0; } if (argc != 2) { printf("Usage :\n"); printf("%s <on|off>\n", argv[0]); return0; }
if (strcmp(argv[1], "on") == 0) { val = 1; } else { val = 0; }
write(fd, &val, 4); return0; } 四 验证测试a. 编译并上传至nfs make arm-linux-gcc -o led_test led_test.c cp*.ko /work/nfs_root/fs_mini_mdev_new b. 加载驱动 insmodled_dev.ko insmodled_drv.ko c. 运行测试,此时gpio4对应的led灯会被点亮或者熄灭 chmod +x led_test ./led_test on ./led_test off ---------------------------------------------------------------------------------------------------------------------- 我们尊重原创,也注重分享,文章来源于微信公众号:张笑生的地盘,建议关注公众号查看原文。如若侵权请联系qter@qter.org。 ---------------------------------------------------------------------------------------------------------------------- |