硬件相关配置:
*S2 eint0 GPF0 *S3 eint2 GPF2 *S4 eint11 GPG3 *S5 eint19 GPG11
先看测试程序中的main函数(thirdtest.c)
1 int main(int argc, char **argv) 2 { 3 int fd; 4 unsigned char key_vals[3]; 5 6 /*,open返回整型变量,若值等于-1,说明打开文件出现错误, 7 *如果为大于0的值,那么这个值代表的就是文件描述符fd*/ 8 fd = open("/dev/buttons", O_RDWR); /*控制符:O_RDWR 读、写打开*/ 9 if(fd < 0) 10 { 11 printf("can not open!\n"); 12 } 13 14 while(1) 15 { 16 read(fd, &key_val, 1); 17 printf("key_val = 0x%x\n", key_val); 18 } 19 return 0; 20 }
(所有的操作都是以open函数来开始,它用来获取fd,然后后期的其他操作全部控制fd来完成对硬件设备的实际操作)
应用程序打开设备,调用系统调用open时,操作系统会将文件系统对应设备文件的inode中的file_operations安装进用户进程的task_struct中的file_struct,最终会调用到调用具体文件的file_operations底层操作函数 drv_open , 在这个函数里面干什么呢?
注册中断:
(1)向irq_des[irq]结构中的action链表中链入了用户注册的中断处理函数
(2)设置中断的触发方式为边沿触发
(3)使能中断
1 static int drv_open(struct inode *inode, struct file *file) 2 { 3 /* 配置GPF0,2,GPG3,11为中断引脚 */ 4 request_irq(IRQ_EINT0, buttons_irq, IRQT_BOTHEDGE, "S2", 1); 5 request_irq(IRQ_EINT2, buttons_irq, IRQT_BOTHEDGE, "S3", 1); 6 request_irq(IRQ_EINT11, buttons_irq, IRQT_BOTHEDGE, "S4", 1); 7 request_irq(IRQ_EINT19, buttons_irq, IRQT_BOTHEDGE, "S5", 1); 8 return 0; 9 }
现在来看 request_irq(IRQ_EINT0, buttons_irq, IRQT_BOTHEDGE, "S2", 1);
1 int request_irq(unsigned int irq, irq_handler_t handler, 2 unsigned long irqflags, const char *devname, void *dev_id) 3 4 irq:中断号 5 handler:处理函数 6 irqflags:上升沿触发,下降沿触发,边沿触发等。指定了快速中断或中断共享等中断处理属性. 7 *devname:中断名字。通常是设备驱动程序的名称。改值用在 /proc/interrupt 系统 (虚拟) 8 文件上,或内核发生中断错误时使用。 9 dev_id 可作为共享中断时的中断区别参数,也可以用来指定中断服务函数需要参考的数据地址。也用于卸载action
1.确定中断号,可以查看这个函数的调用 s3c24xx_init_irq
很明显的看到能够使用的宏在 include/asm-arm/arch/irqs.h
中定义
1 #define IRQ_EINT0 S3C2410_IRQ(0) /*对应宏为 16 */ 2 #define IRQ_EINT2 S3C2410_IRQ(2) 3 #define IRQ_EINT11 S3C2410_IRQ(39) 4 #define IRQ_EINT19 S3C2410_IRQ(47)
2.中断标志irqflags
,同样在 s3c24xx_init_irq
找到相关的 set_irq_chip
,找到对应的chip,深入分析下
set_type
函数,最后在include/asm-arm/irq.h 发现双边沿触发的宏。
1 ...... 2 #define __IRQT_FALEDGE IRQ_TYPE_EDGE_FALLING 3 #define __IRQT_RISEDGE IRQ_TYPE_EDGE_RISING 4 ..... 5 #define IRQT_BOTHEDGE (__IRQT_RISEDGE|__IRQT_FALEDGE)
1 #define IRQ_TYPE_EDGE_RISING 0x00000001 /* Edge rising type */ 2 #define IRQ_TYPE_EDGE_FALLING 0x00000002 /* Edge falling type */
3.char *devname
中断名随便取名
分别取名为“s2, s3, s3, s4”
4.dev_id
可用作释放中断函数中删除action
的标识,这里可以先写作1
5. handler中断处理函数
1 /*功能打印中断号*/ 2 static irqreturn_t buttons_irq(int irq, void *dev_id) 3 { 4 printk("irq%d\r\n",irq); 5 return IRQ_HANDLED; 6 }
关闭中断:这里需要增加释放删除action
链表的函数
1 int drv_close(struct inode *inode, struct file *file) 2 { 3 free_irq(IRQ_EINT0, 1); 4 free_irq(IRQ_EINT2, 1); 5 free_irq(IRQ_EINT11,1); 6 free_irq(IRQ_EINT19,1); 7 return 0; 8 }
回到main函数,open配置中断之后,得到正确的文件描述符fd, 进入while循环,执行read函数,最终会执行到底层操作函数drv_read
1 static ssize_t drv_read(struct file *file, char __user *buf, size_t size, loff_t *ppos) 2 {
/*此处并不实现其他功能*/ 3 return 0; 4 }
最后,测试按键触发中断,打印设备号。
完整程序:
1 #include <sys/types.h> 2 #include <sys/stat.h> 3 #include <fcntl.h> 4 #include <stdio.h> 5 6 /* thirddrvtest on 7 */ 8 int main(int argc, char **argv) 9 { 10 int fd; 11 unsigned char key_vals[3]; 12 13 /*,open返回的是一个整型变量,如果这个值等于-1,说明打开文件出现错误, 14 *如果为大于0的值,那么这个值代表的就是文件描述符fd*/ 15 fd = open("/dev/buttons", O_RDWR); /*O_RDWR 读、写打开*/ 16 if(fd < 0) 17 { 18 printf("can not open!\n"); 19 } 20 21 while(1) 22 { 23 read(fd, &key_val, 1); 24 printf("key_val = 0x%x\n", key_val); 25 } 26 return 0; 27 }
1 #include <linux/module.h> 2 #include <linux/kernel.h> 3 #include <linux/fs.h> 4 #include <linux/init.h> 5 #include <linux/delay.h> 6 #include <linux/irq.h> 7 #include <asm/uaccess.h> 8 #include <asm/irq.h> 9 #include <asm/io.h> 10 #include <asm/arch/regs-gpio.h> 11 #include <asm/hardware.h> 12 //#include <linux/interrupt.h> 13 14 15 16 volatile unsigned long *gpfcon; 17 volatile unsigned long *gpfdat; 18 volatile unsigned long *gpgcon; 19 volatile unsigned long *gpgdat; 20 21 22 static struct class *drv_class; 23 static struct class_device *drv_class_dev; 24 25 static irqreturn_t buttons_irq(int irq, void *dev_id) 26 { 27 printk("irq%d\r\n",irq); 28 return IRQ_HANDLED; 29 } 30 31 static int drv_open(struct inode *inode, struct file *file) 32 { 33 /* 配置GPF0,2为输入引脚 */ 34 /* 配置GPG3,11为输入引脚 */ 35 request_irq(IRQ_EINT0, buttons_irq, IRQT_BOTHEDGE, "S2", 1); 36 request_irq(IRQ_EINT2, buttons_irq, IRQT_BOTHEDGE, "S3", 1); 37 request_irq(IRQ_EINT11, buttons_irq, IRQT_BOTHEDGE, "S4", 1); 38 request_irq(IRQ_EINT19, buttons_irq, IRQT_BOTHEDGE, "S5", 1); 39 return 0; 40 } 41 42 int drv_close(struct inode *inode, struct file *file) 43 { 44 free_irq(IRQ_EINT0, 1); 45 free_irq(IRQ_EINT2, 1); 46 free_irq(IRQ_EINT11,1); 47 free_irq(IRQ_EINT19,1); 48 return 0; 49 } 50 51 52 static ssize_t drv_write(struct file *file, const char __user *buf, size_t count, loff_t * ppos) 53 { 54 //int minor = MINOR(file->f_dentry->d_inode->i_rdev); 55 //printk("drv_write=%d\n",minor); 56 return 0; 57 } 58 59 static ssize_t drv_read(struct file *file, char __user *buf, size_t size, loff_t *ppos) 60 { 61 return 0; 62 } 63 64 65 static struct file_operations drv_fops = { 66 .owner = THIS_MODULE, /* 这是一个宏,推向编译模块时自动创建的__this_module变量 */ 67 .open = drv_open, 68 .write = drv_write, 69 .read = drv_read, 70 .release = drv_close, 71 }; 72 73 static int major; 74 static int drv_init(void) 75 { 76 int minor=0; 77 major=register_chrdev(0, "drv", &drv_fops); // 注册, 告诉内核 78 drv_class = class_create(THIS_MODULE, "drv"); 79 drv_class_dev = class_device_create(drv_class, NULL, MKDEV(major, 0), NULL, "xyz%d", minor); 80 81 gpfcon = (volatile unsigned long *)ioremap(0x56000050, 16); 82 gpfdat = gpfcon + 1; 83 gpgcon = (volatile unsigned long *)ioremap(0x56000060, 16); 84 gpgdat = gpgcon + 1; 85 return 0; 86 } 87 88 static void drv_exit(void) 89 { 90 unregister_chrdev(major, "drv"); // 卸载 91 class_device_unregister(drv_class_dev); 92 class_destroy(drv_class); 93 iounmap(gpfcon); 94 iounmap(gpgcon); 95 } 96 97 module_init(drv_init); 98 module_exit(drv_exit); 99 MODULE_LICENSE("GPL");
改进1:如何区分是不同的按键,按下还是松开,并读出按键值?
要读出引脚的电平,在裸机学习中,可知,可以通过读取对应寄存器,进行移位操作来获取引脚状态
1 /* 读GPF0,2 */ 2 regval = *gpfdat; 3 key_vals[0] = (regval & (1<<0)) ? 1 : 0; 4 key_vals[1] = (regval & (1<<2)) ? 1 : 0; 5 6 7 /* 读GPG3,11 */ 8 regval = *gpgdat; 9 key_vals[2] = (regval & (1<<3)) ? 1 : 0; 10 key_vals[3] = (regval & (1<<11)) ? 1 : 0;
幸运的是在Linux内部有系统函数中已经有一个这样功能的函数了,直接拿来用就可以了。
输入系统定义的引脚 pin 对应的宏 S3C24XX_GPIO_BASE(pin), 即可读出引脚电平
1 unsigned int s3c2410_gpio_getpin(unsigned int pin) 2 { 3 void __iomem *base = S3C24XX_GPIO_BASE(pin); 4 unsigned long offs = S3C2410_GPIO_OFFSET(pin); 5 6 return __raw_readl(base + 0x04) & (1<< offs); 7 }
为了区分不同的按键,我们给每个按键赋一个键值,同时构造一个结构体数组,来存放这些按键对应的键值
1.先构造一个结构体 pin_desc
1 struct pin_desc{ 2 unsigned int pin; 3 unsigned int key_val; 4 };
2.再构造构造一个结构体数组pin_desc[4]
1 /*键值: 按下时,0x01, 0x02, 0x03, 0x04*/ 2 /*键值: 松开时,0x81, 0x82, 0x83, 0x84*/ 3 struct pin_desc pins_desc[4] = { 4 /* pin ,key_val */ 5 {S3C2410_GPF0, 0x01}, 6 {S3C2410_GPF2, 0x02}, 7 {S3C2410_GPG3, 0x03}, 8 {S3C2410_GPG11,0x04}, 9 };
OK, 那怎么使用这个数组呢?(将引脚的键值,通过注册函数的dev_id 注册进去)
1 static int third_drv_open(struct inode *inode, struct file *file) 2 { 3 //GPF0 GPF2 GPG3 GPG11,使用虚拟地址 4 /*设置GPF0,2 GPG3,11为中断引脚*/ 5 request_irq(IRQ_EINT0, buttons_irq, IRQT_BOTHEDGE, "s2", &pins_desc[0]); 6 request_irq(IRQ_EINT2, buttons_irq, IRQT_BOTHEDGE, "s3", &pins_desc[1]); 7 request_irq(IRQ_EINT11, buttons_irq, IRQT_BOTHEDGE, "s4", &pins_desc[2]); 8 request_irq(IRQ_EINT19, buttons_irq, IRQT_BOTHEDGE, "s5", &pins_desc[3]); 9 return 0; 10 }
1 int third_drv_close(struct inode *inode, struct file *file) 2 { 3 free_irq(IRQ_EINT0, &pins_desc[0]); 4 free_irq(IRQ_EINT2, &pins_desc[1]); 5 free_irq(IRQ_EINT11, &pins_desc[2]); 6 free_irq(IRQ_EINT19, &pins_desc[3]); 7 return 0; 8 }
引脚的键值已经配置好了,那怎么读出单个引脚状态值所对应的键值呢?
通过读取 s3c2410_gpio_getpin
1 /*确定按键值*/ 2 static irqreturn_t buttons_irq(int irq, void *dev_id) 3 { 4 /*在中断处理函数中如何使用pins_desc[4] ?*/ 5 struct pin_desc *pin = (struct pin_desc *)dev_id; 6 unsigned int pinval; 7 8 /*系统函数,可读出单个引脚状态值,高低电平*/ 9 pinval = s3c2410_gpio_getpin(pin_desc->pin); /* 1 / 0 */ 10 11 /*确定按键值*/ 12 if (pinval) 13 { 14 /* 为1--松开 */ 15 key_val = 0x80 | pin_desc->key_val; 16 17 } 18 else 19 { 20 /* 0--按下 */ 21 key_val = pin_desc->key_val; 25 26 return IRQ_HANDLED; 27 }
注册号中断引脚之后,发生中断,引脚及键值的地址--&pins_desc[0],就被传入中断处理函数
如何使用pins_desc[i] 来将引脚传递给 s3c2410_gpio_getpin(unsigned int pin)
构造结构体指针,利用由request初始化的 dev_id 指针传递参数,第5-9行,通过指向pin_desc的指针,把pin传递给它。
改进2:
我们使用中断的目的,就是为了在中断发生时,才去读操作,避免像查询一样一直read,从而占据大量的CPU。
休眠读取:
程序设计目的:App
去读取按键值,如果有按键中断触发(键值有改变)则打印,否则休眠.
如上框图所示:
在main函数中,进入while(1)死循环之后,执行read操作,
若按键值更新,则读取键值
若未更新,则进入休眠并等待更新,更新后,唤醒进程。
如何设置休眠机制?
驱动程序中需要设计休眠,中断发生来唤醒首先定义一个等待队列,下述是一个宏
1 //生成一个等待队列头wait_queue_head_t,名字为name 2 DECLARE_WAIT_QUEUE_HEAD(name) 3 4 // 定义一个名为`button_waitq`的队列 5 static DECLARE_WAIT_QUEUE_HEAD(button_waitq);
休眠函数如下,condition=0
才休眠,定义在include/linux/wait.h
1 #define wait_event_interruptible(wq, condition) \ 2 ({ \ 3 int __ret = 0; \ 4 if (!(condition)) \ 5 __wait_event_interruptible(wq, condition, __ret); \ 6 __ret; \ 7 })
唤醒也是一个宏,参数是等待队列,放置在中断函数处理中,定义在include/linux/wait.h
1 wake_up_interruptible(&button_waitq); /* 唤醒休眠的进程 */
在read函数中设置
1 ssize_t third_drv_read(struct file *file, char __user *buf, size_t size, loff_t *ppos) 2 { 3 /*size 在此即为sizeof(key_val), 例如:0x01,一个字节大小*/ 4 if(size != 1) 5 return -EINVAL; 6 7 /*如果没有按键动作,ev_press = 0, 将进程挂在队列里面等待,休眠, 不会执行下面的函数*/ 8 wait_event_interruptible(button_waitq, ev_press); 9 //谁来唤醒进程?-> 中断发生的时候,就唤醒进程 10 11 /*/*如果有按键动作,ev_press = 1,会执行到此函数,并返回键值,将变量清零*/*/ 12 copy_to_user(buf, &key_val, 1); 13 ev_press = 0; /*清零,为下一次操作赋初值*/ 14 return 1; 15 }
在third_drv_open中设置
1 /*确定按键值*/ 2 static irqreturn_t buttons_irq(int irq, void *dev_id) 3 { 4 /*在中断处理函数中如何使用pins_desc[4] */ 5 struct pin_desc *pin = (struct pin_desc *)dev_id; 6 unsigned int pinval; 7 8 /*系统函数,可读出单个引脚状态值,高低电平*/ 9 pinval = s3c2410_gpio_getpin(pin_desc->pin); 10 11 /*确定按键值*/ 12 if (pinval) 13 { 14 /* 为1--松开 */ 15 key_val = 0x80 | pin_desc->key_val; 16 17 } 18 else 19 { 20 /* 0--按下 */ 21 key_val = pin_desc->key_val; 22 } 23 ev_press = 1; /*表示中断发生了*/ 24 wake_up_interruptible(&button_waitq); /*去队列button_waitq里面唤醒休眠的进程*/ 25 26 return IRQ_HANDLED; 27 }
改进后的完整代码如下:
1 // app.c 2 #include <sys/types.h> 3 #include <sys/stat.h> 4 #include <fcntl.h> 5 #include <stdio.h> 6 #include <unistd.h> 7 8 int main(int argc, char **argv) 9 { 10 int fd; 11 unsigned char key_val; 12 fd = open("/dev/xyz0", O_RDWR); 13 if (fd < 0) 14 { 15 printf("can't open!\n"); 16 } 17 while (1) 18 { 19 read(fd, &key_val, 1); 20 printf("key_val = 0x%x\n", key_val); 21 } 22 return 0; 23 }
1 #include <linux/module.h> 2 #include <linux/kernel.h> 3 #include <linux/fs.h> 4 #include <linux/init.h> 5 #include <linux/delay.h> 6 #include <linux/irq.h> 7 #include <asm/uaccess.h> 8 #include <asm/irq.h> 9 #include <asm/io.h> 10 #include <asm/arch/regs-gpio.h> 11 #include <asm/hardware.h> 12 //#include <linux/interrupt.h> 13 14 volatile unsigned long *gpfcon; 15 volatile unsigned long *gpfdat; 16 volatile unsigned long *gpgcon; 17 volatile unsigned long *gpgdat; 18 19 static struct class *drv_class; 20 static struct class_device *drv_class_dev; 21 22 // 定义一个名为`button_waitq`的队列 23 static DECLARE_WAIT_QUEUE_HEAD(button_waitq); 24 // flag=1 means irq happened and need to update 25 int flag=0; 26 27 struct pin_desc{ 28 unsigned int pin; 29 unsigned int key_val; 30 }; 31 32 /* 键值: 按下时, 0x01, 0x02, 0x03, 0x04 */ 33 /* 键值: 松开时, 0x81, 0x82, 0x83, 0x84 */ 34 static unsigned char key_val; 35 36 struct pin_desc pins_desc[4] = { 37 {S3C2410_GPF0, 0x01}, 38 {S3C2410_GPF2, 0x02}, 39 {S3C2410_GPG3, 0x03}, 40 {S3C2410_GPG11, 0x04}, 41 }; 42 43 static irqreturn_t buttons_irq(int irq, void *dev_id) 44 { 45 printk("irq%d\r\n",irq); 46 47 struct pin_desc * pindesc = (struct pin_desc *)dev_id; 48 unsigned int pinval; 49 50 pinval = s3c2410_gpio_getpin(pindesc->pin); 51 52 if (pinval) 53 { 54 /* 松开 */ 55 key_val = 0x80 | pindesc->key_val; 56 } 57 else 58 { 59 /* 按下 */ 60 key_val = pindesc->key_val; 61 } 62 63 wake_up_interruptible(&button_waitq); /* 唤醒休眠的进程 */ 64 flag=1; 65 66 return IRQ_RETVAL(IRQ_HANDLED); 67 } 68 69 static int drv_open(struct inode *inode, struct file *file) 70 { 71 /* 配置GPF0,2为输入引脚 */ 72 /* 配置GPG3,11为输入引脚 */ 73 request_irq(IRQ_EINT0, buttons_irq, IRQT_BOTHEDGE, "S2", &pins_desc[0]); 74 request_irq(IRQ_EINT2, buttons_irq, IRQT_BOTHEDGE, "S3", &pins_desc[1]); 75 request_irq(IRQ_EINT11, buttons_irq, IRQT_BOTHEDGE, "S4", &pins_desc[2]); 76 request_irq(IRQ_EINT19, buttons_irq, IRQT_BOTHEDGE, "S5", &pins_desc[3]); 77 return 0; 78 } 79 80 int drv_close(struct inode *inode, struct file *file) 81 { 82 free_irq(IRQ_EINT0, &pins_desc[0]); 83 free_irq(IRQ_EINT2, &pins_desc[1]); 84 free_irq(IRQ_EINT11,&pins_desc[2]); 85 free_irq(IRQ_EINT19,&pins_desc[3]); 86 return 0; 87 } 88 89 90 static ssize_t drv_write(struct file *file, const char __user *buf, size_t count, loff_t * ppos) 91 { 92 //int minor = MINOR(file->f_dentry->d_inode->i_rdev); 93 //printk("drv_write=%d\n",minor); 94 return 0; 95 } 96 97 static ssize_t drv_read(struct file *file, char __user *buf, size_t size, loff_t *ppos) 98 { 99 if (size != 1) 100 return -EINVAL; 101 102 /* 如果没有按键动作, 休眠 */ 103 wait_event_interruptible(button_waitq, flag); 104 105 /* 如果有按键动作, 返回键值 */ 106 copy_to_user(buf, &key_val, 1); 107 flag = 0; 108 109 return 1; 110 } 111 112 113 static struct file_operations drv_fops = { 114 .owner = THIS_MODULE, /* 这是一个宏,推向编译模块时自动创建的__this_module变量 */ 115 .open = drv_open, 116 .write = drv_write, 117 .read = drv_read, 118 .release = drv_close, 119 }; 120 121 static int major; 122 static int drv_init(void) 123 { 124 int minor=0; 125 major=register_chrdev(0, "drv", &drv_fops); // 注册, 告诉内核 126 drv_class = class_create(THIS_MODULE, "drv"); 127 drv_class_dev = class_device_create(drv_class, NULL, MKDEV(major, 0), NULL, "xyz%d", minor); 128 129 gpfcon = (volatile unsigned long *)ioremap(0x56000050, 16); 130 gpfdat = gpfcon + 1; 131 gpgcon = (volatile unsigned long *)ioremap(0x56000060, 16); 132 gpgdat = gpgcon + 1; 133 return 0; 134 } 135 136 static void drv_exit(void) 137 { 138 unregister_chrdev(major, "drv"); // 卸载 139 class_device_unregister(drv_class_dev); 140 class_destroy(drv_class); 141 iounmap(gpfcon); 142 iounmap(gpgcon); 143 } 144 145 module_init(drv_init); 146 module_exit(drv_exit); 147 MODULE_AUTHOR("xxx"); 148 MODULE_VERSION("0.1.0"); 149 MODULE_DESCRIPTION("S3C2410/S3C2440 LED Driver"); 150 MODULE_LICENSE("GPL");
测试
测试运行./text /dev/xyz0 &
后台运行
# ./test /dev/xyz0 & # irq55 key_val = 0x3 irq55 key_val = 0x83 irq18 key_val = 0x2 irq18 key_val = 0x82 irq16 key_val = 0x1 irq16 key_val = 0x81 irq63 key_val = 0x4 irq63 key_val = 0x84
使用top
查看占用
Load average: 0.00 0.01 0.00 PID PPID USER STAT VSZ %MEM %CPU COMMAND 782 770 0 R 3096 5% 0% top 770 1 0 S 3096 5% 0% -sh 781 770 0 S 1312 2% 0% ./test /dev/xyz0
使用ps
查看任务为S
状态 休眠状态
# ps PID Uid VSZ Stat Command 1 0 3092 S init 781 0 1312 S ./test /dev/xyz0 783 0 3096 R ps