通常我们在编写中断唤醒系统的驱动时,常使用以下的处理流程:
static int xxx_suspend()
{
...
spin_lock_irqsave(<r->irq_lock, irqflags);//加spin lock保护
enable_irq(irq);
spin_unlock_irqrestore(<r->irq_lock, irqflags);
atomic_inc(&irq_count);
...
}
static int xxx_resume()
{
...
if(atomic_read(&irq_count)){
spin_lock_irqsave(<r->irq_lock, irqflags);
disable_irq_nosync(irq);
spin_unlock_irqrestore(<r->irq_lock, irqflags);
atomic_dec(&irq_count);
}
...
}
static irqreturn_t xxx_irq_handler()
{
spin_lock_irqsave(<r->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(<r->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(<r->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(<r->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计数
}