10 中断下半部
笔记说明¶
这一节,这部分涉及的知识点有tasklet和工作队列,并且补充一个中断线程。这一部分的内容在树莓派上实现的方式,和韦东山讲的在IMX6ULL上实现的方式就没什么不同了。我也只是把韦东山的我觉得重要的部分提取了出来,方便自己查询。
主要参考的文章
韦东山《01_嵌入式Linux应用开发完全手册V5.1_IMX6ULL_Pro开发板》
本节源码路径02_Firmware/13_key_drv_irq_tasklet、02_Firmware/14_key_drv_irq_workqueue、02_Firmware/15_key_drv_irq_threaded
tasklet¶
中断中触发tasklet,然后再tasklet的服务函数中执行下半部
static irqreturn_t gpio_key_isr(int irq, void *dev_id)
{
key_gpio_t *gpio_key = dev_id;
tasklet_schedule(&gpio_key->tasklet);
mod_timer(&gpio_key->key_timer, jiffies + HZ/10);
return IRQ_RETVAL(IRQ_HANDLED);
}
static void key_tasklet_func(unsigned long data)
{
/* data ==> gpio */
key_gpio_t *gpio_key = (key_gpio_t *)data;
int val;
int key;
val = gpiod_get_value(gpio_key->key_gpiod);
printk("key_tasklet_func key %d %d\n", gpio_key->key_num, val);
}
static void key_timer_expire(struct timer_list *t)
{
/* data ==> gpio */
key_gpio_t *gpio_key = from_timer(gpio_key, t, key_timer);
int val;
val = gpiod_get_value(gpio_key->key_gpiod);
printk("key %d %d\n", gpio_key->key_num, val);
g_key = (gpio_key->key_num << 8) | val;
wake_up_interruptible(&gpio_key_wait);
kill_fasync(&button_fasync, SIGIO, POLL_IN);
}
static int key_probe(struct platform_device *dev)
{
// 中断初始化
// ********** //
for(int i=0 ; i < 3; i++){
timer_setup(&key_dev.key_gpio[i].key_timer, key_timer_expire, 0);
key_dev.key_gpio[i].key_timer.expires = ~0;
add_timer(&key_dev.key_gpio[i].key_timer);
tasklet_init(&key_dev.key_gpio[i].tasklet, key_tasklet_func, &key_dev.key_gpio[i]);
}
// 注册misc字符设备驱动
// ********** //
return 0;
}
static int key_remove(struct platform_device *dev)
{
// 注销gpio
// ********** //
for (int i = 0; i < 3; i++)
{
free_irq(key_dev.key_gpio[i].key_irq, &key_dev.key_gpio[i]);
del_timer(&key_dev.key_gpio[i].key_timer);
tasklet_kill(&key_dev.key_gpio[i].tasklet);
}
// 注销misc设备
// ********** //
return 0;
}
工作队列¶
工作队列就是通过一个函数触发一个任务,感觉和异步通知有点像。
这部分程序在定时器后面
先在结构体中定义struct work_struct work;。
struct key_dev{
struct class *class;
struct device *device;
struct device_node *nd;
struct gpio_desc *gpiod;
struct timer_list timer;
struct tasklet_struct tasklet;
struct work_struct work;
int gpio;
int irq;
int major;
int minor;
};
然后在初始化程序中初始化work。需要先定义好处理函数。
//work处理函数
static void key_work_func(struct work_struct *work)
{
struct key_dev *gpio_key = container_of(work, struct key_dev, work);
int val;
val = gpiod_get_value(gpio_key->gpiod);
printk("key_work_func: the process is %s pid %d\n",current->comm, current->pid);
printk("key_work_func key %d %d\n", gpio_key->gpio, val);
}
static int gpio_key_probe(struct platform_device *pdev)
{
/*注册按键中断及定时器*/
INIT_WORK(&key.work, key_work_func);
/*注册设备*/
return 0;
}
在中断服务函数中使用schedule_work触发这个队列任务
static irqreturn_t gpio_key_isr(int irq, void *dev_id)
{
struct key_dev *gpio_key = dev_id;
tasklet_schedule(&gpio_key->tasklet);
mod_timer(&gpio_key->timer, jiffies + HZ/5);
schedule_work(&gpio_key->work);
return IRQ_HANDLED;
}
中断线程¶
就是初始化的时候使用request_threaded_irq函数来进行中断注册。