通常我们在编写中断唤醒系统的驱动时,常使用以下的处理流程:

static int xxx_suspend()
{
...
    spin_lock_irqsave(&ltr->irq_lock, irqflags);//加spin lock保护
    enable_irq(irq);
    spin_unlock_irqrestore(&ltr->irq_lock, irqflags);

    atomic_inc(&irq_count);
...
}

static int xxx_resume()
{
...
    if(atomic_read(&irq_count)){
        spin_lock_irqsave(&ltr->irq_lock, irqflags);
        disable_irq_nosync(irq);
        spin_unlock_irqrestore(&ltr->irq_lock, irqflags);

        atomic_dec(&irq_count);
    }
...
}

static irqreturn_t xxx_irq_handler()
{
    spin_lock_irqsave(&ltr->irq_lock, irqflags);
    disable_irq_nosync(irq);    ---->关闭中断
    //do something
    //调度一个delay work,将耗时操作放到中断下半部执行,如果此work涉及到与外设通讯的话,通常还需要延时几毫秒后再
    //来执行work,因为这个时候,总线可能还未唤醒
    schedule_delayed_work(&xxx_work,msecs_to_jiffies(5));
    spin_unlock_irqrestore(&ltr->irq_lock, irqflags);

    atomic_dec(&irq_count);
}

static xxx_delay_work()
{
    //communicate with peripheral device
    input_report_key(my_dev, KEY_WAKEUP, 1);//模拟KEY_WAKEUP事件
	input_sync(my_dev);
	input_report_key(my_dev, KEY_WAKEUP, 0);
	input_sync(my_dev);
}

static int xxx_setup_irq()
{
    irq = gpio_to_irq(pin_num);
    request_irq(irq,xxx_irq_handler,IRQ_TYPE_LEVEL_LOW | IRQF_NO_SUSPEND,"PS_EINT",NULL);

    //enable irq wake when setup
    enable_irq_wake(irq);
}

中断处理程序中,首先调用disable_irq_nosync()关闭中断,然后调度一个delay work,将耗时操作放到中断下半部执行,如果此work涉及到与外设通讯的话,通常还需要延时几毫秒后再来执行work,因为这个时候,总线可能还未唤醒。

此种做法存在一个风险,想象一种场景,应用请求休眠,系统进入休眠流程,此时如果设备触发了中断,中断处理程序中首先关闭中断,然后调度内核线程去处理work,但假如这个时候此work还未被调度到,系统就进入休眠了,那么这个设备就被永久关闭中断了,再也不能唤醒系统。pm_stay_awake()和pm_relax()的设计就是用来解决这个问题。

pm_stay_awake()和pm_relax()维护着combined_event_count的原子计数,pm_wakeup_pending()主要是判断combined_event_count变量在suspend的过程中是否改变,如果改变suspend就abort。因此在中断handler开始处增加combined_event_count计数,工作队列函数结尾位置减小combined_event_count计数即可

static irqreturn_t xxx_irq_handler()
{
    pm_stay_awake(&xxx_dev);//增加combined_event_count计数

    spin_lock_irqsave(&ltr->irq_lock, irqflags);
    disable_irq_nosync(irq);    ---->关闭中断
    //do something
    //调度一个delay work,将耗时操作放到中断下半部执行,如果此work涉及到与外设通讯的话,通常还需要延时几毫秒后再
    //来执行work,因为这个时候,总线可能还未唤醒
    schedule_delayed_work(&xxx_work,msecs_to_jiffies(5));
    spin_unlock_irqrestore(&ltr->irq_lock, irqflags);

    atomic_dec(&irq_count);
}

static xxx_delay_work()
{
    //communicate with peripheral device
    input_report_key(my_dev, KEY_WAKEUP, 1);//模拟KEY_WAKEUP事件
	input_sync(my_dev);
	input_report_key(my_dev, KEY_WAKEUP, 0);
	input_sync(my_dev);

    pm_relax(&xxx_dev);//减小combined_event_count计数
}
07-15 15:22