Linux休眠后开启中断的方法及中断唤醒过程

Linux进入休眠后,会进入dpm_suspend_noirq函数。该函数会遍历所有的中断,进行关闭。

int dpm_suspend_noirq(pm_message_t state)
{
	device_wakeup_arm_wake_irqs();
	suspend_device_irqs();
}

static inline bool irqd_is_wakeup_set(struct irq_data *d)
{
	return __irqd_to_state(d) & IRQD_WAKEUP_STATE;
}

void suspend_device_irqs(void)
{
	struct irq_desc *desc;
	for_each_irq_desc(irq, desc) {
		raw_spin_lock_irqsave(&desc->lock, flags);
		sync = suspend_device_irq(desc);
		raw_spin_unlock_irqrestore(&desc->lock, flags);
	}
}

static bool suspend_device_irq(struct irq_desc *desc)
{
	if (!desc->action || irq_desc_is_chained(desc) ||
	    desc->no_suspend_depth)
		return false;

	if (irqd_is_wakeup_set(&desc->irq_data)) {
		irqd_set(&desc->irq_data, IRQD_WAKEUP_ARMED);
		/*
		 * We return true here to force the caller to issue
		 * synchronize_irq(). We need to make sure that the
		 * IRQD_WAKEUP_ARMED is visible before we return from
		 * suspend_device_irqs().
		 */
		return true;
	}

	desc->istate |= IRQS_SUSPENDED;
	__disable_irq(desc);

	/*
	 * Hardware which has no wakeup source configuration facility
	 * requires that the non wakeup interrupts are masked at the
	 * chip level. The chip implementation indicates that with
	 * IRQCHIP_MASK_ON_SUSPEND.
	 */
	if (irq_desc_get_chip(desc)->flags & IRQCHIP_MASK_ON_SUSPEND)
		mask_irq(desc);
	return true;
}

从上面的代码可以看出,如果no_suspend_depth计数不为0或者有设置IRQD_WAKEUP_STATE标志,就不会关闭该中断。

方法1:可以在申请中断时,加入参数IRQF_NO_SUSPEND,no_suspend_depth计数不为0,其原理如下

static inline int __must_check request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,
	    const char *name, void *dev)
{
	return request_threaded_irq(irq, handler, NULL, flags, name, dev);
}

int request_threaded_irq(unsigned int irq, irq_handler_t handler,irq_handler_t thread_fn, unsigned long irqflags,const char *devname, void *dev_id)
{
	desc = irq_to_desc(irq);
	if (!desc)
		return -EINVAL;
	action = kzalloc(sizeof(struct irqaction), GFP_KERNEL);
	if (!action)
		return -ENOMEM;

	action->handler = handler;
	action->thread_fn = thread_fn;
	action->flags = irqflags;
	action->name = devname;
	action->dev_id = dev_id;

	retval = __setup_irq(irq, desc, action);
}

static int __setup_irq(unsigned int irq, struct irq_desc *desc, struct irqaction *new)
{
	irq_pm_install_action(desc, new);
}

void irq_pm_install_action(struct irq_desc *desc, struct irqaction *action)
{
	desc->nr_actions++;

	if (action->flags & IRQF_FORCE_RESUME)
		desc->force_resume_depth++;

	WARN_ON_ONCE(desc->force_resume_depth &&
		     desc->force_resume_depth != desc->nr_actions);

	if (action->flags & IRQF_NO_SUSPEND)
		desc->no_suspend_depth++;
	else if (action->flags & IRQF_COND_SUSPEND)
		desc->cond_suspend_depth++;

	WARN_ON_ONCE(desc->no_suspend_depth &&
		     (desc->no_suspend_depth +
	

方法2:在进入suspend函数中加入irq_set_irq_wake(irq,1),该方法会设置IRQD_WAKEUP_STATE标志,resume函数中加入irq_set_irq_wake(irq,0)其原理如下

int irq_set_irq_wake(unsigned int irq, unsigned int on)
{
    unsigned long flags;
    struct irq_desc *desc = irq_get_desc_buslock(irq, &flags, IRQ_GET_DESC_CHECK_GLOBAL);
    int ret = 0;

    if (on) {
        if (desc->wake_depth++ == 0) {
            ret = set_irq_wake_real(irq, on);
            if (ret)
                desc->wake_depth = 0;
            else
                irqd_set(&desc->irq_data, IRQD_WAKEUP_STATE);
        }
    } else {
        if (desc->wake_depth == 0) {
            WARN(1, "Unbalanced IRQ %d wake disable\n", irq);
        } else if (--desc->wake_depth == 0) {
            ret = set_irq_wake_real(irq, on);
            if (ret)
                desc->wake_depth = 1;
            else
                irqd_clear(&desc->irq_data, IRQD_WAKEUP_STATE);
        }
    }

    return ret;
}


static int set_irq_wake_real(unsigned int irq, unsigned int on)
{
    struct irq_desc *desc = irq_to_desc(irq);
    int ret = -ENXIO;

    if (irq_desc_get_chip(desc)->flags &  IRQCHIP_SKIP_SET_WAKE)
        return 0;

    if (desc->irq_data.chip->irq_set_wake)
        ret = desc->irq_data.chip->irq_set_wake(&desc->irq_data, on);

    return ret;
}

另外,休眠和唤醒流程中有如下函数

int dpm_suspend_noirq(pm_message_t state)
{
    device_wakeup_arm_wake_irqs();
}

void dpm_resume_noirq(pm_message_t state)
{
    device_wakeup_disarm_wake_irqs();
}
void dev_pm_arm_wake_irq(struct wake_irq *wirq)
{
    if (!wirq)
        return;

    if (device_may_wakeup(wirq->dev))
        enable_irq_wake(wirq->irq);
}

void device_wakeup_arm_wake_irqs(void)
{
    struct wakeup_source *ws;
    int srcuidx;

    srcuidx = srcu_read_lock(&wakeup_srcu);
    list_for_each_entry_rcu(ws, &wakeup_sources, entry) {
        if (ws->wakeirq)
            dev_pm_arm_wake_irq(ws->wakeirq);
    }
    srcu_read_unlock(&wakeup_srcu, srcuidx);
}

void dev_pm_disarm_wake_irq(struct wake_irq *wirq)
{
    if (!wirq)
        return;

    if (device_may_wakeup(wirq->dev))
        disable_irq_wake(wirq->irq);
}
void device_wakeup_disarm_wake_irqs(void)
{
    struct wakeup_source *ws;
    int srcuidx;

    srcuidx = srcu_read_lock(&wakeup_srcu);
    list_for_each_entry_rcu(ws, &wakeup_sources, entry) {
        if (ws->wakeirq)
            dev_pm_disarm_wake_irq(ws->wakeirq);
    }
    srcu_read_unlock(&wakeup_srcu, srcuidx);
}

int device_wakeup_attach_irq(struct device *dev,
                 struct wake_irq *wakeirq)
{
    struct wakeup_source *ws;

    ws = dev->power.wakeup;
    if (!ws) {
        dev_err(dev, "forgot to call call device_init_wakeup?\n");
        return -EINVAL;
    }

    if (ws->wakeirq)
        return -EEXIST;

    ws->wakeirq = wakeirq;
    return 0;
}

static int dev_pm_attach_wake_irq(struct device *dev, int irq,struct wake_irq *wirq)
{
    err = device_wakeup_attach_irq(dev, wirq);
}

int dev_pm_set_wake_irq(struct device *dev, int irq)
{
    struct wake_irq *wirq;
    int err;

    if (irq < 0)
        return -EINVAL;

    wirq = kzalloc(sizeof(*wirq), GFP_KERNEL);
    if (!wirq)
        return -ENOMEM;

    wirq->dev = dev;
    wirq->irq = irq;

    err = dev_pm_attach_wake_irq(dev, irq, wirq);
    if (err)
        kfree(wirq);

    return err;
}

static inline int device_init_wakeup(struct device *dev, bool val)
{
    device_set_wakeup_capable(dev, val);
    device_set_wakeup_enable(dev, val);
    return 0;
}

在初始化中加入如下函数,系统默认会自动调用irq_set_irq_wake开启和关闭中断唤醒。

device_init_wakeup(struct device *dev, bool val)
dev_pm_set_wake_irq(struct device *dev, int irq)

总结

1.申请中断时加入IRQF_NO_SUSPEND标志。

中断唤醒cpu后,如果中断处理里有调用某些资源(如i2c),内核会报i2c处于suspend状态,这是因为i2c还没唤醒,需要进行一定的延时,建议使用irq_set_irq_wake函数,因为按照休眠唤醒逻辑,i2c已经唤醒了。其深度休眠,唤醒流程跟非深度休眠流程一致。

2.休眠时开中断唤醒,唤醒时关闭中断唤醒。

irq_set_irq_wake(irq,1)
irq_set_irq_wake(irq,0)

对应某些场景,我们需要中断总是能唤醒cpu(而不是由用户设置),我们可以在probe函数里直接调用irq_set_irq_wake(irq,1)即可(不需要关闭)。

3.初始化调用如下函数,其原理是自动调用irq_set_irq_wake函数。

device_init_wakeup(struct device *dev, bool val)
dev_pm_set_wake_irq(struct device *dev, int irq)

具体的唤醒流程参考http://www.wowotech.net/irq_subsystem/irq_handle_procedure.html

唤醒后,通过irq_set_irq_wake(irq,1)设置的中断,处理流程如下

bool irq_pm_check_wakeup(struct irq_desc *desc)
{
	if (irqd_is_wakeup_armed(&desc->irq_data)) {
		irqd_clear(&desc->irq_data, IRQD_WAKEUP_ARMED);
		desc->istate |= IRQS_SUSPENDED | IRQS_PENDING;
		desc->depth++;
		irq_disable(desc);-----------------------------先关闭irq
		pm_system_irq_wakeup(irq_desc_get_irq(desc));
		return true;
	}
	return false;
}

static bool irq_may_run(struct irq_desc *desc)
{
	unsigned int mask = IRQD_IRQ_INPROGRESS | IRQD_WAKEUP_ARMED;

	/*
	 * If the interrupt is not in progress and is not an armed
	 * wakeup interrupt, proceed.
	 */
	if (!irqd_has_set(&desc->irq_data, mask))
		return true;

	/*
	 * If the interrupt is an armed wakeup source, mark it pending
	 * and suspended, disable it and notify the pm core about the
	 * event.
	 */
	if (irq_pm_check_wakeup(desc))
		return false;

	/*
	 * Handle a potential concurrent poll on a different core.
	 */
	return irq_check_poll(desc);
}

static inline void mask_ack_irq(struct irq_desc *desc)
{
	if (desc->irq_data.chip->irq_mask_ack)
		desc->irq_data.chip->irq_mask_ack(&desc->irq_data);
	else {
		desc->irq_data.chip->irq_mask(&desc->irq_data);
		if (desc->irq_data.chip->irq_ack)
			desc->irq_data.chip->irq_ack(&desc->irq_data);
	}
	irq_state_set_masked(desc);
}

void __enable_irq(struct irq_desc *desc)
{
	switch (desc->depth) {
	case 0:
 err_out:
		WARN(1, KERN_WARNING "Unbalanced enable for IRQ %d\n",
		     irq_desc_get_irq(desc));
		break;
	case 1: {
		if (desc->istate & IRQS_SUSPENDED)
			goto err_out;
		/* Prevent probing on this irq: */
		irq_settings_set_noprobe(desc);
		irq_enable(desc);-----------------------------再开启irq
		check_irq_resend(desc);
		/* fall-through */
	}
	default:
		desc->depth--;
	}
}

void irq_enable(struct irq_desc *desc)
{
	irq_state_clr_disabled(desc);
	if (desc->irq_data.chip->irq_enable)
		desc->irq_data.chip->irq_enable(&desc->irq_data);
	else
		desc->irq_data.chip->irq_unmask(&desc->irq_data);
	irq_state_clr_masked(desc);
}

void check_irq_resend(struct irq_desc *desc)
{
	/*
	 * We do not resend level type interrupts. Level type
	 * interrupts are resent by hardware when they are still
	 * active. Clear the pending bit so suspend/resume does not
	 * get confused.
	 */
	if (irq_settings_is_level(desc)) {
		desc->istate &= ~IRQS_PENDING;
		return;
	}
	if (desc->istate & IRQS_REPLAY)
		return;
	if (desc->istate & IRQS_PENDING) {
		desc->istate &= ~IRQS_PENDING;
		desc->istate |= IRQS_REPLAY;

		if (!desc->irq_data.chip->irq_retrigger ||
		    !desc->irq_data.chip->irq_retrigger(&desc->irq_data)) {
#ifdef CONFIG_HARDIRQS_SW_RESEND
			unsigned int irq = irq_desc_get_irq(desc);

			/*
			 * If the interrupt is running in the thread
			 * context of the parent irq we need to be
			 * careful, because we cannot trigger it
			 * directly.
			 */
			if (irq_settings_is_nested_thread(desc)) {
				/*
				 * If the parent_irq is valid, we
				 * retrigger the parent, otherwise we
				 * do nothing.
				 */
				if (!desc->parent_irq)
					return;
				irq = desc->parent_irq;
			}
			/* Set it pending and activate the softirq: */
			set_bit(irq, irqs_resend);
			tasklet_schedule(&resend_tasklet);
#endif
		}
	}
}

void handle_level_irq(struct irq_desc *desc)
{
	raw_spin_lock(&desc->lock);
	mask_ack_irq(desc);

	if (!irq_may_run(desc))
		goto out_unlock;

	desc->istate &= ~(IRQS_REPLAY | IRQS_WAITING);
	kstat_incr_irqs_this_cpu(desc);

	/*
	 * If its disabled or no action available
	 * keep it masked and get out of here
	 */
	if (unlikely(!desc->action || irqd_irq_disabled(&desc->irq_data))) {
		desc->istate |= IRQS_PENDING;
		goto out_unlock;
	}

	handle_irq_event(desc);

	cond_unmask_irq(desc);

out_unlock:
	raw_spin_unlock(&desc->lock);
}

void handle_edge_irq(struct irq_desc *desc)
{
	raw_spin_lock(&desc->lock);

	desc->istate &= ~(IRQS_REPLAY | IRQS_WAITING);

	if (!irq_may_run(desc)) {
		desc->istate |= IRQS_PENDING;
		mask_ack_irq(desc);
		goto out_unlock;
	}

	/*
	 * If its disabled or no action available then mask it and get
	 * out of here.
	 */
	if (irqd_irq_disabled(&desc->irq_data) || !desc->action) {
		desc->istate |= IRQS_PENDING;
		mask_ack_irq(desc);
		goto out_unlock;
	}

	kstat_incr_irqs_this_cpu(desc);

	/* Start handling the irq */
	desc->irq_data.chip->irq_ack(&desc->irq_data);

	do {
		if (unlikely(!desc->action)) {
			mask_irq(desc);
			goto out_unlock;
		}

		/*
		 * When another irq arrived while we were handling
		 * one, we could have masked the irq.
		 * Renable it, if it was not disabled in meantime.
		 */
		if (unlikely(desc->istate & IRQS_PENDING)) {
			if (!irqd_irq_disabled(&desc->irq_data) &&
			    irqd_irq_masked(&desc->irq_data))
				unmask_irq(desc);
		}

		handle_irq_event(desc);

	} while ((desc->istate & IRQS_PENDING) &&
		 !irqd_irq_disabled(&desc->irq_data));

out_unlock:
	raw_spin_unlock(&desc->lock);
}

check_irq_resend直译过来就是检查irq并重发(软件模拟)
对于电平中断来说,handle_level_irq关掉中断后,再次开启irq,中断信号还在,会重新调用handle_level_irq
对应边沿中断来说,只会触发一次,handle_edge_irq已经关掉了一次,再次开启irq时,由于没信号过来了,需要软件模拟中断过来,即重新调用一遍desc->handle_irq()/handle_edge_irq

static DECLARE_TASKLET(resend_tasklet, resend_irqs, 0);
static void resend_irqs(unsigned long arg)
{
	struct irq_desc *desc;
	int irq;

	while (!bitmap_empty(irqs_resend, nr_irqs)) {
		irq = find_first_bit(irqs_resend, nr_irqs);
		clear_bit(irq, irqs_resend);
		desc = irq_to_desc(irq);
		local_irq_disable();
		desc->handle_irq(desc);
		local_irq_enable();
	}
}

 电平中断和边沿中断的处理流程有差异,见下图

 

  • 1
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值