一、上半部和下半部(顶半部和底半部)
上半部:我们在使用request_irq申请中断时注册的中断服务函数属于中断处理的上半部。适合耗时不长的程序。
下半部:适合处理过程耗时的代码。
①、 如果要处理的内容不希望被其他中断打断,可以放到上半部。
②、如果要处理的任务对时间敏感,可以放到上半部。
③、如果要处理的任务与硬件有关,可以放到上半部
1、软中断
Linux 内核使用结构体 softirq_action 表示软中断, softirq_action
结构体定义在文件 include/linux/interrupt.h 中
2、tasklet
tasklet 是利用软中断来实现的另外一种下半部机制,在软中断和tasklet 之间,建议大家使用 tasklet。Linux 内核使用结构体
struct tasklet_struct { struct tasklet_struct *next; /* 下一个 tasklet */ unsigned long state; /* tasklet 状态 */ atomic_t count; /* 计数器,记录对 tasklet 的引用数 */ void (*func)(unsigned long); /* tasklet 执行的函数 */ unsigned long data; /* 函数 func 的参数 */ };
使用示例
/* 定义 taselet */ struct tasklet_struct testtasklet; /* tasklet 处理函数 */ void testtasklet_func(unsigned long data) { /* tasklet 具体处理内容 */ } /* 中断处理函数 */ irqreturn_t test_handler(int irq, void *dev_id) { ...... /* 调度 tasklet */ tasklet_schedule(&testtasklet); ...... } /* 驱动入口函数 */ static int __init xxxx_init(void) { ...... /* 初始化 tasklet */ tasklet_init(&testtasklet, testtasklet_func, data); /* 注册中断处理函数 */ request_irq(xxx_irq, test_handler, 0, "xxx", &xxx_dev); ...... }
3、工作队列
工作队列是另外一种下半部执行方式,工作队列在进程上下文执行,工作队列将要推后的工作交给一个内核线程去执行,因为工作队列工作在进程上下文,因此工作队列允许睡眠或重新调度。因此如果你要推后的工作可以睡眠那么就可以选择工作队列,否则的话就只能选择软中断或 tasklet。
使用示例
/* 定义工作(work) */ struct work_struct testwork; /* work 处理函数 */ void testwork_func_t(struct work_struct *work); { /* work 具体处理内容 */ } /* 中断处理函数 */ irqreturn_t test_handler(int irq, void *dev_id) { ...... /* 调度 work */ schedule_work(&testwork); ...... } /* 驱动入口函数 */ static int __init xxxx_init(void) { ...... /* 初始化 work */ INIT_WORK(&testwork, testwork_func_t); /* 注册中断处理函数 */ request_irq(xxx_irq, test_handler, 0, "xxx", &xxx_dev); ...... }
4、中断示例
(1)设备树修改
(2)主程序
(2.1)open函数
static int imx6uirq_open(struct inode *inode, struct file *filp) { filp->private_data = imx6uirq; return 0; }
在OPEN函数里添加中断结构体为私有数据
(2.2)read函数
static int imx6uirq_read(struct file *filp, char __user *buf, size_t cnt loff_t *offt) { int ret = 0; unsigned char keyvalue = 0; unsigned char releasekey = 0; struct imx6uirq_dev *dev = (struct imx6uirq_dev *)filp->private_data; //加锁读值 keyvalue = atomic_read(&dev->keyvalue); releasekey = atomic_read(&dev->releasekey); if(releasekey){ if(keyvalue & 0x80){ keyvalue &= ~0x80; ret = copy_to_user(buf, &keyvalue, sizeof(keyvalue));//传入用户层 }else{ goto data_error; } atomic_set(&dev->releasekey, 0); }else{ goto data_error; } return 0; data_error: return -EINVAL; }
加原子锁读取按键值并复制到用户空间
(2.3)IO初始化
从设备树中提取GPIO然后设置成中断模式,根据中断处理函数和中断号申请中断。中断服务函数里负责处理定时器,再进入定时器函数里读取IO值,延时的目的为了消除按键抖动带来的误差。
static int keyio_init(void) { unsigned char i = 0; char name[10]; int ret = 0; imx6uirq.nd = of_find_node_by_path("key"); if(imx6uirq.nd == NULL){ printk("key node can not find!|r\n"); return -EINVAL; } //提取GPIO for(i = 0; i< KEY_NUM; i++){ imx6uirq.irqkeydesc[i].gpio = of_get_named_gpio(imx6uirq.nd, "key-gpio", i); if(imx6uirq.irqkeydesc[i]<0){ printk("can not get key%d\r\n", i); } } //初始化IO ,设置中断模式 for(i = 0; i < KEY_NUM; i++){ memset(imx6uirq.irqkeydesc[i].name,0, sizeof(name)); sprintf(imx6uirq.irqkeydesc[i].name, "KEY%d", i); gpio_request(imx6uirq.irqkeydesc[i].gpio, name);//获取gpio gpio_direction_input(imx6uirq.irqkeydesc[i].gpio);//设置为输出 imx6uirq.irqkeydesc[i].irqnum = irq_of_parse_and_map(imx6uirq.nd, i);//获取中断号并设置 //申请中断 imx6uirq.irqkeydesc[0].handler = key0_handler; imx6uirq.irqkeydesc[0].value = KEY0VALUE; for(i=0; i<KEY_NUM; i++){ ret = request_irq(imx6uirq.irqkeydesc[i].irqnum, imx6uirq.irqkeydesc[i].handler, IRQ_TRIGGER_EDGE_FALLING|IRQ_TRIGGER_EDGE_RISING, imx6uirq.irqkeydesc[i].name, &imx6uirq); if(ret < 0){ printk("irq %d request failed!\r\n", imx6uirq.irqkeydesc[i].irqnum); return -EFAULT; } } //创建定时器 init_timer(&imx6uirq.timer); imx6uirq.timer.function = timer_function; return 0; } } void timer_function(unsigned long arg) { unsigned char value; unsigned char num; struct irq_keydesc *keydesc; struct imx6uirq_dev *dev = (struct imx6uirq_dev *)arg; num = dev->cur_keynum; keydesc = &dev->irqkeydesc[num]; // value = gpio_get_value(keydesc->gpio); //读取IO值 if(value == 0){ atomic_set(&dev->keyvalue, keydesc->value); //按下按键 }else{ atomic_set(&dev->keyvalue, 0x80); //按键松开 atomic_set(&dev->releasekey, 1); //标记松开 } }
总结中断的使用:驱动入口函数里正常注册字符设备后,进行按键IO的初始化,进入IO初始化函数里,获取设备树IO并设置中断模式、中断的处理函数然后申请中断、创建定时器读取按键值。