字符设备驱动(1)驱动代码完整源码:charButtons.c
内核版本:Linux3.0.8
开发板:基于三星S5PV210处理器的Tiny210开发板
驱动名称:charButtons.c
驱动描述:按键触发中断,中断处理程序执行相应的简单LED点亮操作
方案1注册字符设备使用新的接口实现(需要好几个函数来实现。貌似更复杂)
方案2注册字符设备使用老的接口实现(貌似老接口更简单)
/***************************************************************************** 简 述:简单字符型驱动程序,手动静态分配设备号,手动创建设备节点 ******************************************************************************/ #include <linux/module.h> #include <linux/moduleparam.h> #include <linux/cdev.h> #include <linux/fs.h> #include <linux/wait.h> #include <linux/poll.h> #include <linux/sched.h> #include <linux/irq.h> #include <asm/irq.h> #include <linux/interrupt.h> #include <mach/map.h> #include <mach/gpio.h> #include <mach/regs-gpio.h> #include <plat/gpio-cfg.h> #include <linux/slab.h> #define DEVICE_NAME "buttons" struct button_desc { int gpio; int number; char *name; }; struct led_desc { int gpio; int number; char *name; }; static struct button_desc buttons[] = { { S5PV210_GPH2(0), 0, "KEY0" }, { S5PV210_GPH2(1), 1, "KEY1" }, { S5PV210_GPH2(2), 2, "KEY2" }, { S5PV210_GPH2(3), 3, "KEY3" }, { S5PV210_GPH3(0), 4, "KEY4" }, { S5PV210_GPH3(1), 5, "KEY5" }, { S5PV210_GPH3(2), 6, "KEY6" }, { S5PV210_GPH3(3), 7, "KEY7" }, }; static struct led_desc leds[] = { {S5PV210_GPJ2(0),1,"LED1"}, {S5PV210_GPJ2(1),2,"LED2"}, {S5PV210_GPJ2(2),3,"LED3"}, {S5PV210_GPJ2(3),4,"LED4"}, }; #define OK (0) #define ERROR (-1) struct gpio_chip *chip; struct cdev *gDev; struct file_operations *gFile; dev_t devNum; unsigned int subDevNum = 1;//要申请的次设备号个数 int reg_major = 234; int reg_minor = 0; static irqreturn_t button_interrupt(int irq, void *dev_id) { struct button_desc *bdata = (struct button_desc *)dev_id; int down; unsigned tmp; tmp = gpio_get_value(bdata->gpio); /* active low */ down = !tmp; printk("KEY %d: %08x\n", bdata->number, down); if(bdata->number < 4) { gpio_set_value(leds[bdata->number].gpio,0); printk("LED %d: On \n",leds[bdata->number].number); } else { gpio_set_value(leds[(bdata->number) - 4].gpio,1); printk("LED %d: OFF \n",leds[(bdata->number)-4].number); } return IRQ_HANDLED; } int butsOpen(struct inode *p, struct file *f) { int irq; int i; int err = 0; printk(KERN_EMERG"butsOpen\r\n"); for (i = 0; i < ARRAY_SIZE(buttons); i++) { if (!buttons[i].gpio) continue; irq = gpio_to_irq(buttons[i].gpio); // irq = IRQ_EINT(16)+i =160+i "S5PV210_GPH2(i)" //irq = IRQ_EINT(24)+i =168+i "S5PV210_GPH3(i)" err = request_irq(irq, button_interrupt, IRQ_TYPE_EDGE_BOTH, buttons[i].name, (void *)&buttons[i]); if (err) break; } for(i = 0; i<ARRAY_SIZE(leds);i++) { if(!leds[i].gpio) continue; gpio_direction_output(leds[i].gpio,1); } if (err) { i--; for (; i >= 0; i--) { if (!buttons[i].gpio) continue; irq = gpio_to_irq(buttons[i].gpio); disable_irq(irq); free_irq(irq, (void *)&buttons[i]); } return -EBUSY; } return 0; } int charDrvInit(void) { devNum = MKDEV(reg_major, reg_minor); printk(KERN_EMERG"devNum is %d\r\n", devNum); if(OK == register_chrdev_region(devNum, subDevNum, DEVICE_NAME)) { printk(KERN_EMERG"register_chrdev_region ok\r\n"); } else { printk(KERN_EMERG"register_chrdev_region error\r\n"); return ERROR; } /*if(OK == alloc_chrdev_region(&devNum, subDevNum, subDevNum,"test")) { printk(KERN_EMERG"register_chrdev_region ok\r\n"); } else { printk(KERN_EMERG"register_chrdev_region error\r\n"); return ERROR; }*/ gDev = kzalloc(sizeof(struct cdev), GFP_KERNEL); gFile = kzalloc(sizeof(struct file_operations), GFP_KERNEL); gFile->open = butsOpen; //注册设备函数到file_operations结构体gFile //gDev->owner = THIS_MODULE; gFile->owner = THIS_MODULE; cdev_init(gDev, gFile); //在cdev结构体中添加指针指向file_operations结构体gFile cdev_add(gDev, devNum, 3); //建立设备号与cdev结构体联系 printk(KERN_EMERG"button driver initial done...\r\n"); return 0; } void __exit charDrvExit(void) { int i,irq; cdev_del(gDev); unregister_chrdev_region(devNum, subDevNum); for (i = 0; i < ARRAY_SIZE(buttons); i++) { if (!buttons[i].gpio) continue; irq = gpio_to_irq(buttons[i].gpio); disable_irq(irq); free_irq(irq, (void *)&buttons[i]); } return; } module_init(charDrvInit);//执行insmod时会执行此行代码并调用charDrvInit,驱动开始 module_exit(charDrvExit);//执行rmmod时,结束 MODULE_LICENSE("GPL");
/***************************************************************************** 简 述:简单字符型驱动程序,手动静态分配设备号,手动创建设备节点 ******************************************************************************/ #include <linux/module.h> #include <linux/fs.h> #include <mach/gpio.h> #include <linux/irq.h> #include <linux/kdev_t.h> #include <linux/interrupt.h> #include <linux/init.h> #define DEVICE_NAME "leds" struct button_desc { int gpio; int number; char *name; }; struct led_desc { int gpio; int number; char *name; }; static struct button_desc buttons[] = { { S5PV210_GPH2(0), 0, "KEY0" }, { S5PV210_GPH2(1), 1, "KEY1" }, { S5PV210_GPH2(2), 2, "KEY2" }, { S5PV210_GPH2(3), 3, "KEY3" }, { S5PV210_GPH3(0), 4, "KEY4" }, { S5PV210_GPH3(1), 5, "KEY5" }, { S5PV210_GPH3(2), 6, "KEY6" }, { S5PV210_GPH3(3), 7, "KEY7" }, }; static struct led_desc leds[] = { {S5PV210_GPJ2(0),1,"LED1"}, {S5PV210_GPJ2(1),2,"LED2"}, {S5PV210_GPJ2(2),3,"LED3"}, {S5PV210_GPJ2(3),4,"LED4"}, }; #define OK (0) #define ERROR (-1) dev_t devNum; unsigned int subDevNum = 1;//要申请的次设备号个数 int reg_major = 234; int reg_minor = 0; static irqreturn_t button_interrupt(int irq, void *dev_id) { struct button_desc *bdata = (struct button_desc *)dev_id; int down; unsigned tmp; tmp = gpio_get_value(bdata->gpio); /* active low */ down = !tmp; printk("KEY %d: %08x\n", bdata->number, down); if(bdata->number < 4) { gpio_set_value(leds[bdata->number].gpio,0); printk("LED %d: On \n",leds[bdata->number].number); } else { gpio_set_value(leds[(bdata->number) - 4].gpio,1); printk("LED %d: OFF \n",leds[(bdata->number)-4].number); } return IRQ_HANDLED; } int butsOpen(struct inode *p, struct file *f) { int irq; int i; int err = 0; printk(KERN_EMERG"butsOpen\r\n"); for (i = 0; i < ARRAY_SIZE(buttons); i++) { if (!buttons[i].gpio) continue; irq = gpio_to_irq(buttons[i].gpio); // irq = IRQ_EINT(16)+i =160+i "S5PV210_GPH2(i)" //irq = IRQ_EINT(24)+i =168+i "S5PV210_GPH3(i)" err = request_irq(irq, button_interrupt, IRQ_TYPE_EDGE_FALLING, buttons[i].name, (void *)&buttons[i]); if (err) break; } for(i = 0; i<ARRAY_SIZE(leds);i++) { if(!leds[i].gpio) continue; gpio_direction_output(leds[i].gpio,1); } if (err) { i--; for (; i >= 0; i--) { if (!buttons[i].gpio) continue; irq = gpio_to_irq(buttons[i].gpio); disable_irq(irq); free_irq(irq, (void *)&buttons[i]); } return -EBUSY; } return 0; } static const struct file_operations gFile = { .owner = THIS_MODULE, .open = butsOpen, }; int charDrvInit(void) { devNum = MKDEV(reg_major, reg_minor); printk(KERN_EMERG"devNum is %d\r\n", devNum); if(OK == register_chrdev(reg_major, DEVICE_NAME, &gFile)) { printk(KERN_EMERG "register_chrdev_region ok\r\n"); } else { printk(KERN_EMERG"register_chrdev_region error\r\n"); return ERROR; } printk(KERN_EMERG "button driver initial done...\r\n"); return 0; } void __exit charDrvExit(void) { int i,irq; unregister_chrdev(reg_major, DEVICE_NAME); for (i = 0; i < ARRAY_SIZE(buttons); i++) { if (!buttons[i].gpio) continue; irq = gpio_to_irq(buttons[i].gpio); disable_irq(irq); free_irq(irq, (void *)&buttons[i]); } return; } module_init(charDrvInit);//执行insmod时会执行此行代码并调用charDrvInit,驱动开始 module_exit(charDrvExit);//执行rmmod时,结束 MODULE_LICENSE("GPL"); MODULE_AUTHOR("LiuB");
函数修饰符
__init,本质上是一个宏定义,在内核源代码中定义:#define __init __section(.init.text) __cold notrace
作用就是,将被它修饰的函数放入.init.text段中去。所以所有的内核模块的__init修饰的函数被放在一起。内核启动时统一加载,加载完后统一释放以节省内存。
__exit,同上:#define __exit __section(.exit.text) __exitused __cold notrace
printk()函数:内核封装出来的打印函数。格式:
printk( 打印级别 “打印信息”) //printk的打印级别是用来控制printk打印的信息是否在终端显示
#define KERN_EMERG "<0>" /* system is unusable */ #define KERN_ALERT "<1>" /* action must be taken immediately */ #define KERN_CRIT "<2>" /* critical conditions */ #define KERN_ERR "<3>" /* error conditions */ #define KERN_WARNING "<4>" /* warning conditions */ #define KERN_NOTICE "<5>" /* normal but significant condition */ #define KERN_INFO "<6>" /* informational */ #define KERN_DEBUG "<7>" /* debug-level messages */ /* Use the default kernel loglevel */ #define KERN_DEFAULT "<d>"
如果定义了模块,即这一驱动被动态编译为模块时
驱动代码中的module_init、module_exit的内核定义(/include/linux/init.h)
/* Each module must use one module_init(). */ #define module_init(initfn) \ static inline initcall_t __inittest(void) { return initfn; } \ int init_module(void) __attribute__((alias(#initfn))); typedef int (*initcall_t)(void); //static inline initcall_t __inittest(void) { return initfn; } //inittest这个函数返回值为一个initcall_t类型的initfn //而initcall_t是一个函数指针,返回值为整型,输入参数为空
/* This is only required if you want to be unloadable. */ #define module_exit(exitfn) \ static inline exitcall_t __exittest(void) { return exitfn; } \ void cleanup_module(void) __attribute__((alias(#exitfn))); typedef void (*exitcall_t)(void);
如果未定义模块,这一驱动被静态编译进内核
驱动代码中的module_init、module_exit的内核定义(/include/linux/init.h)
/** * module_init() - driver initialization entry point * @x: function to be run at kernel boot time or module insertion * * module_init() will either be called during do_initcalls() (if * builtin) or at module insertion time (if a module). There can only * be one per module. */ #define module_init(x) __initcall(x); #define __initcall(fn) device_initcall(fn) #define device_initcall(fn) __define_initcall("6",fn,6) /* initcalls are now grouped by functionality into separate * subsections. Ordering inside the subsections is determined * by link order. * For backwards compatibility, initcall() puts the call in * the device init subsection. * * The `id' arg to __define_initcall() is needed so that multiple initcalls * can point at the same handler without causing duplicate-symbol build errors. */ #define __define_initcall(level,fn,id) \ static initcall_t __initcall_##fn##id __used \ __attribute__((__section__(".initcall" level ".init"))) = fn
通过这些段代码,我们能够看出最终的结果是将我们使用module_init修饰的函数指针链接到一个叫.initcall的段里,也就是说最终所有使用module_init修饰的函数指针都被链接在这个段里,内核在启动的时候顺序调用所有链接在这个段里的函数,实现设备的初始化