跳转至

10 中断下半部

笔记说明

这一节,这部分涉及的知识点有tasklet和工作队列,并且补充一个中断线程。这一部分的内容在树莓派上实现的方式,和韦东山讲的在IMX6ULL上实现的方式就没什么不同了。我也只是把韦东山的我觉得重要的部分提取了出来,方便自己查询。

主要参考的文章

韦东山《01_嵌入式Linux应用开发完全手册V5.1_IMX6ULL_Pro开发板》

本节源码路径02_Firmware/13_key_drv_irq_tasklet02_Firmware/14_key_drv_irq_workqueue02_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函数来进行中断注册。

for (int i = 0; i < 3; i++)

{
    ret = request_threaded_irq(key_dev.key_gpio[i].key_irq, gpio_key_isr, gpio_key_thread_func,  IRQF_TRIGGER_FALLING, "shb_gpio_key", &key_dev.key_gpio[i]);

}