《驱动学习 - 按键驱动程序》
1.查看原理图和芯片手册
2.驱动程序分析
2.1 init函数和exit函数,向内核注册file_operations结构体。并且创建设备信息
static struct class *thirddrv_class; static struct device *thirddrv_class_dev; static struct file_operations sencod_drv_fops = { .owner = THIS_MODULE, /* 这是一个宏,推向编译模块时自动创建的__this_module变量 */ .open = third_drv_open, .read = third_drv_read, .release = third_drv_close, }; int major; static int third_drv_init(void) { major = register_chrdev(0, "third_drv", &sencod_drv_fops); thirddrv_class = class_create(THIS_MODULE, "third_drv"); thirddrv_class_dev = device_create(thirddrv_class, NULL, MKDEV(major, 0), NULL, "buttons"); /* /dev/buttons */ return 0; } static void third_drv_exit(void) { unregister_chrdev(major, "third_drv"); device_unregister(thirddrv_class_dev); class_destroy(thirddrv_class); return 0; } module_init(third_drv_init); module_exit(third_drv_exit); MODULE_LICENSE("GPL");
2.2 编写open函数
struct pin_desc{ unsigned int pin; unsigned int key_val; }; /* 键值: 按下时, 0x01, 0x02, 0x03, 0x04 */ /* 键值: 松开时, 0x81, 0x82, 0x83, 0x84 */ static unsigned char key_val; /* * K1,K2,K3,K4对应GPG0,GPG3,GPG5,GPG6 */ struct pin_desc pins_desc[4] = { {S3C2410_GPG(0), 0x01}, {S3C2410_GPG(3), 0x02}, {S3C2410_GPG(5), 0x03}, {S3C2410_GPG(6), 0x04}, }; static int third_drv_open(struct inode *inode, struct file *file) { /* GPG0,GPG3,GPG5,GPG6为中断引脚: EINT8,EINT11,EINT13,EINT14 */ request_irq(IRQ_EINT8, buttons_irq, IRQ_TYPE_EDGE_BOTH, "K1", &pins_desc[0]); request_irq(IRQ_EINT11, buttons_irq, IRQ_TYPE_EDGE_BOTH, "K2", &pins_desc[1]); request_irq(IRQ_EINT13, buttons_irq, IRQ_TYPE_EDGE_BOTH, "K3", &pins_desc[2]); request_irq(IRQ_EINT14, buttons_irq, IRQ_TYPE_EDGE_BOTH, "K4", &pins_desc[3]); return 0; }
request_irq函数用于向内核申请中断。
第一个参数:要注册中断服务函数的中断号
第二个参数:中断服务函数
第三个参数:触发中断的参数,比如边沿触发。这里是双边沿触发,定义在include/linux/irq.h
第四个参数:中断程序的名字,使用cat /proc/interrupt 可以查看中断程序名字
第五个参数:传入中断处理程序的参数,注册共享中断时不能为NULL,因为卸载时需要这个做参数,避免卸载其它中断服务函数
2.3 close函数
int third_drv_close(struct inode *inode, struct file *file) { free_irq(IRQ_EINT8, &pins_desc[0]); free_irq(IRQ_EINT11, &pins_desc[1]); free_irq(IRQ_EINT13, &pins_desc[2]); free_irq(IRQ_EINT14, &pins_desc[3]); return 0; }
free_irq函数:释放分配给已定中断的内存
第一个参数:要卸载的中断号
第二个参数:这个是要卸载的中断action下的哪个服务函数
2.4 read函数
static DECLARE_WAIT_QUEUE_HEAD(button_waitq); /* 中断事件标志, 中断服务程序将它置1,third_drv_read将它清0 */ static volatile int ev_press = 0; ssize_t third_drv_read(struct file *file, char __user *buf, size_t size, loff_t *ppos) { if (size != 1) return -EINVAL; /* 如果没有按键动作, 休眠 */ wait_event_interruptible(button_waitq, ev_press); /* 如果有按键动作, 返回键值 */ copy_to_user(buf, &key_val, 1); ev_press = 0; return 1; }
DECLARE_WAIT_QUEUE_HEAD是一个宏定义,来定义一个
struct __wait_queue_head {
spinlock_t lock;
struct list_head task_list;
};
等待队列头的结构体。后面用于进程的休眠和唤醒。
wait_event_interruptible(button_waitq, ev_press)函数:当第二个参数为0时,就会使应用程序进入休眠。
2.5 中断服务函数
static irqreturn_t buttons_irq(int irq, void *dev_id) { struct pin_desc * pindesc = (struct pin_desc *)dev_id; unsigned int pinval; pinval = s3c2410_gpio_getpin(pindesc->pin); if (pinval) { /* 松开 */ key_val = 0x80 | pindesc->key_val; } else { /* 按下 */ key_val = pindesc->key_val; } ev_press = 1; /* 表示中断发生了 */ wake_up_interruptible(&button_waitq); /* 唤醒休眠的进程 */ return IRQ_RETVAL(IRQ_HANDLED); }
如果是由IRQ_EXIT8触发中断,第一个参数irq就是IRQ_EXIT8,第二个参数就是pins_desc[0]的地址
当触发中断时,就会进入中断服务函数。然后将ev_press设置为1,然后唤醒应用程序。这样就又会进入read函数,然后将数据发送给应用程序,再将ev_press设置为0,这样应用程序就又休眠。