linux系统休眠时的中断操作

概述

无论是gpio控制器,还是其他外设控制器,捕获到中断,会发给gic;gic根据mask决定是否分发给cpu;cpu如果调用local_irq_disable,就不会来响应中断信号。

linux进入休眠,中断相关操作如下:

1.会打开中断控制器的irq_set_wake函数(有些架构有这个实现;有些架构,打开中断就有这个功能),来一一打开,那些标记了唤醒功能的中断的唤醒功能;

2.然后关闭其他没标记唤醒功能的中断

3.打开cpu的中断响应

suspend_enter
    dpm_suspend_noirq
        device_wakeup_arm_wake_irqs
            dev_pm_arm_wake_irq
                enable_irq_wake
                    irq_set_irq_wake
                        set_irq_wake_real
                            chip->irq_set_wake

        suspend_device_irqs
            suspend_device_irq
                __disable_irq

    arch_suspend_disable_irqs //local_irq_disable()
    arch_suspend_enable_irqs //local_irq_enable()

对应代码如下

static int suspend_enter(suspend_state_t state, bool *wakeup)
{
	int error;

	error = platform_suspend_prepare(state);
	if (error)
		goto Platform_finish;

	error = dpm_suspend_late(PMSG_SUSPEND);
	if (error) {
		pr_err("late suspend of devices failed\n");
		goto Platform_finish;
	}
	error = platform_suspend_prepare_late(state);
	if (error)
		goto Devices_early_resume;

	error = dpm_suspend_noirq(PMSG_SUSPEND);
	if (error) {
		pr_err("noirq suspend of devices failed\n");
		goto Platform_early_resume;
	}
	error = platform_suspend_prepare_noirq(state);
	if (error)
		goto Platform_wake;

	if (suspend_test(TEST_PLATFORM))
		goto Platform_wake;

	if (state == PM_SUSPEND_TO_IDLE) {
		s2idle_loop();
		goto Platform_wake;
	}

	error = suspend_disable_secondary_cpus();
	if (error || suspend_test(TEST_CPUS))
		goto Enable_cpus;

	arch_suspend_disable_irqs();
	BUG_ON(!irqs_disabled());

	system_state = SYSTEM_SUSPEND;

	error = syscore_suspend();
	if (!error) {
		*wakeup = pm_wakeup_pending();
		if (!(suspend_test(TEST_CORE) || *wakeup)) {
			trace_suspend_resume(TPS("machine_suspend"),
				state, true);
			error = suspend_ops->enter(state);
			trace_suspend_resume(TPS("machine_suspend"),
				state, false);
		} else if (*wakeup) {
			error = -EBUSY;
		}
		syscore_resume();
	}

	system_state = SYSTEM_RUNNING;

	arch_suspend_enable_irqs();
	BUG_ON(irqs_disabled());

 Enable_cpus:
	suspend_enable_secondary_cpus();

 Platform_wake:
	platform_resume_noirq(state);
	dpm_resume_noirq(PMSG_RESUME);

 Platform_early_resume:
	platform_resume_early(state);

 Devices_early_resume:
	dpm_resume_early(PMSG_RESUME);

 Platform_finish:
	platform_resume_finish(state);
	return error;
}

使能中断的唤醒功能

dpm_suspend_noirq处理中断唤醒相关操作

int dpm_suspend_noirq(pm_message_t state)
{
	int ret;

	cpuidle_pause();

	device_wakeup_arm_wake_irqs();
	suspend_device_irqs();

	ret = dpm_noirq_suspend_devices(state);
	if (ret)
		dpm_resume_noirq(resume_event(state));

	return ret;
}

device_init_wakeup标记唤醒源,device_wakeup_arm_wake_irqs来打开标记唤醒源的中断,并使能中断唤醒功能

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)
		dev_pm_arm_wake_irq(ws->wakeirq);
	srcu_read_unlock(&wakeup_srcu, srcuidx);
}

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

	if (device_may_wakeup(wirq->dev)) {
		if (wirq->status & WAKE_IRQ_DEDICATED_ALLOCATED &&
		    !pm_runtime_status_suspended(wirq->dev))
			enable_irq(wirq->irq);

		enable_irq_wake(wirq->irq);
	}
}

static inline int enable_irq_wake(unsigned int irq)
{
	return irq_set_irq_wake(irq, 1);
}

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 (!desc)
		return -EINVAL;

	/* Don't use NMIs as wake up interrupts please */
	if (desc->istate & IRQS_NMI) {
		ret = -EINVAL;
		goto out_unlock;
	}

	/* wakeup-capable irqs can be shared between drivers that
	 * don't need to have the same sleep mode behaviors.
	 */
	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);
		}
	}

out_unlock:
	irq_put_desc_busunlock(desc, flags);
	return ret;
}

chip可能是gpio控制器,也可能是gic控制器,也可能是其他包含中断控制的外设控制器

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;
}

没有单独wake控制

没有wake控制,中断使能后,就自带中断唤醒功能gicv3,对应文件drivers/irqchip/irq-gic-v3.c;只有irq_unmask来使能中断,没有irq_set_wake成员

static struct irq_chip gic_chip = {
        .name                   = "GICv3",
        .irq_mask               = gic_mask_irq,
        .irq_unmask             = gic_unmask_irq,
        .irq_eoi                = gic_eoi_irq,
        .irq_set_type           = gic_set_type,
        .irq_set_affinity       = gic_set_affinity,
        .irq_get_irqchip_state  = gic_irq_get_irqchip_state,
        .irq_set_irqchip_state  = gic_irq_set_irqchip_state,
        .irq_nmi_setup          = gic_irq_nmi_setup,
        .irq_nmi_teardown       = gic_irq_nmi_teardown,
        .ipi_send_mask          = gic_ipi_send_mask,
        .flags                  = IRQCHIP_SET_TYPE_MASKED |
                                  IRQCHIP_SKIP_SET_WAKE |
                                  IRQCHIP_MASK_ON_SUSPEND,
};

没有wakeup控制的gpio控制器,有irq_set_wake成员,但实际是空操作

static void pxa_ack_muxed_gpio(struct irq_data *d)
{
        struct pxa_gpio_chip *pchip = irq_data_get_irq_chip_data(d);
        unsigned int gpio = irqd_to_hwirq(d);
        void __iomem *base = gpio_bank_base(&pchip->chip, gpio);

        writel_relaxed(GPIO_bit(gpio), base + GEDR_OFFSET);
}

static void pxa_mask_muxed_gpio(struct irq_data *d)
{
        struct pxa_gpio_chip *pchip = irq_data_get_irq_chip_data(d);
        unsigned int gpio = irqd_to_hwirq(d);
        struct pxa_gpio_bank *b = gpio_to_pxabank(&pchip->chip, gpio);
        void __iomem *base = gpio_bank_base(&pchip->chip, gpio);
        uint32_t grer, gfer;

        b->irq_mask &= ~GPIO_bit(gpio);

        grer = readl_relaxed(base + GRER_OFFSET) & ~GPIO_bit(gpio);
        gfer = readl_relaxed(base + GFER_OFFSET) & ~GPIO_bit(gpio);
        writel_relaxed(grer, base + GRER_OFFSET);
        writel_relaxed(gfer, base + GFER_OFFSET);
}

static int pxa_gpio_set_wake(struct irq_data *d, unsigned int on)
{
        struct pxa_gpio_chip *pchip = irq_data_get_irq_chip_data(d);
        unsigned int gpio = irqd_to_hwirq(d);

        if (pchip->set_wake)
                return pchip->set_wake(gpio, on);
        else
                return 0;
}

static void pxa_unmask_muxed_gpio(struct irq_data *d)
{
        struct pxa_gpio_chip *pchip = irq_data_get_irq_chip_data(d);
        unsigned int gpio = irqd_to_hwirq(d);
        struct pxa_gpio_bank *c = gpio_to_pxabank(&pchip->chip, gpio);

        c->irq_mask |= GPIO_bit(gpio);
        update_edge_detect(c);
}

static struct irq_chip pxa_muxed_gpio_chip = {
        .name           = "GPIO",
        .irq_ack        = pxa_ack_muxed_gpio,
        .irq_mask       = pxa_mask_muxed_gpio,
        .irq_unmask     = pxa_unmask_muxed_gpio,
        .irq_set_type   = pxa_gpio_irq_type,
        .irq_set_wake   = pxa_gpio_set_wake,
};

有单独的wake控制

使能gpio,和设置中断唤醒功能,是单独的两个接口;irq_unmask来开启中断;irq_set_wake来设置唤醒功能

static void puv3_mask_irq(struct irq_data *d)
{
        writel(readl(INTC_ICMR) & ~(1 << d->irq), INTC_ICMR);
}

static void puv3_unmask_irq(struct irq_data *d)
{
        writel(readl(INTC_ICMR) | (1 << d->irq), INTC_ICMR);
}

/*
 * Apart form GPIOs, only the RTC alarm can be a wakeup event.
 */
static int puv3_set_wake(struct irq_data *d, unsigned int on)
{
        if (d->irq == IRQ_RTCAlarm) {
                if (on)
                        writel(readl(PM_PWER) | PM_PWER_RTC, PM_PWER);
                else
                        writel(readl(PM_PWER) & ~PM_PWER_RTC, PM_PWER);
                return 0;
        }
        return -EINVAL;
}

static struct irq_chip puv3_normal_chip = {
        .name           = "PKUnity-v3",
        .irq_ack        = puv3_mask_irq,
        .irq_mask       = puv3_mask_irq,
        .irq_unmask     = puv3_unmask_irq,
        .irq_set_wake   = puv3_set_wake,
};

关闭其他不是唤醒源的中断

遍历irq描述符,中断跟嵌套线程有关,就不去关闭;中断关闭了之后,会通过synchronize_irq等待中断处理操作执行完毕

void suspend_device_irqs(void)
{
	struct irq_desc *desc;
	int irq;

	for_each_irq_desc(irq, desc) {
		unsigned long flags;
		bool sync;

		if (irq_settings_is_nested_thread(desc))
			continue;
		raw_spin_lock_irqsave(&desc->lock, flags);
		sync = suspend_device_irq(desc);
		raw_spin_unlock_irqrestore(&desc->lock, flags);

		if (sync)
			synchronize_irq(irq);
	}
}

如果这个irq设置了唤醒功能,成为唤醒源;就不在gic屏蔽这个中断

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;
}

使能中断

使能中断,最终调用chip的irq_unmask来实现

enable_irq
    __enable_irq
        irq_startup
            irq_enable
                unmask_irq
                    chip->irq_unmask

源码如下

/**
 *	enable_irq - enable handling of an irq
 *	@irq: Interrupt to enable
 *
 *	Undoes the effect of one call to disable_irq().  If this
 *	matches the last disable, processing of interrupts on this
 *	IRQ line is re-enabled.
 *
 *	This function may be called from IRQ context only when
 *	desc->irq_data.chip->bus_lock and desc->chip->bus_sync_unlock are NULL !
 */
void enable_irq(unsigned int irq)
{
	unsigned long flags;
	struct irq_desc *desc = irq_get_desc_buslock(irq, &flags, IRQ_GET_DESC_CHECK_GLOBAL);

	if (!desc)
		return;
	if (WARN(!desc->irq_data.chip,
		 KERN_ERR "enable_irq before setup/request_irq: irq %u\n", irq))
		goto out;

	__enable_irq(desc);
out:
	irq_put_desc_busunlock(desc, flags);
}

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);
		/*
		 * Call irq_startup() not irq_enable() here because the
		 * interrupt might be marked NOAUTOEN. So irq_startup()
		 * needs to be invoked when it gets enabled the first
		 * time. If it was already started up, then irq_startup()
		 * will invoke irq_enable() under the hood.
		 */
		irq_startup(desc, IRQ_RESEND, IRQ_START_FORCE);
		break;
	}
	default:
		desc->depth--;
	}
}

int irq_startup(struct irq_desc *desc, bool resend, bool force)
{
	struct irq_data *d = irq_desc_get_irq_data(desc);
	struct cpumask *aff = irq_data_get_affinity_mask(d);
	int ret = 0;

	desc->depth = 0;

	if (irqd_is_started(d)) {
		irq_enable(desc);
	} else {
		switch (__irq_startup_managed(desc, aff, force)) {
		case IRQ_STARTUP_NORMAL:
			if (d->chip->flags & IRQCHIP_AFFINITY_PRE_STARTUP)
				irq_setup_affinity(desc);
			ret = __irq_startup(desc);
			if (!(d->chip->flags & IRQCHIP_AFFINITY_PRE_STARTUP))
				irq_setup_affinity(desc);
			break;
		case IRQ_STARTUP_MANAGED:
			irq_do_set_affinity(d, aff, false);
			ret = __irq_startup(desc);
			break;
		case IRQ_STARTUP_ABORT:
			irqd_set_managed_shutdown(d);
			return 0;
		}
	}
	if (resend)
		check_irq_resend(desc);

	return ret;
}

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

void unmask_irq(struct irq_desc *desc)
{
	if (!irqd_irq_masked(&desc->irq_data))
		return;

	if (desc->irq_data.chip->irq_unmask) {
		desc->irq_data.chip->irq_unmask(&desc->irq_data);
		irq_state_clr_masked(desc);
	}
}

失能中断

失能中断,最终调用chip的irq_mask

disable_irq
    __disable_irq_nosync
        __irq_disable
            mask_irq
                chip->irq_mask

源码如下

void disable_irq(unsigned int irq)
{
	if (!__disable_irq_nosync(irq))
		synchronize_irq(irq);
}

static int __disable_irq_nosync(unsigned int irq)
{
	unsigned long flags;
	struct irq_desc *desc = irq_get_desc_buslock(irq, &flags, IRQ_GET_DESC_CHECK_GLOBAL);

	if (!desc)
		return -EINVAL;
	__disable_irq(desc);
	irq_put_desc_busunlock(desc, flags);
	return 0;
}

static void __irq_disable(struct irq_desc *desc, bool mask)
{
	if (irqd_irq_disabled(&desc->irq_data)) {
		if (mask)
			mask_irq(desc);
	} else {
		irq_state_set_disabled(desc);
		if (desc->irq_data.chip->irq_disable) {
			desc->irq_data.chip->irq_disable(&desc->irq_data);
			irq_state_set_masked(desc);
		} else if (mask) {
			mask_irq(desc);
		}
	}
}

void mask_irq(struct irq_desc *desc)
{
	if (irqd_irq_masked(&desc->irq_data))
		return;

	if (desc->irq_data.chip->irq_mask) {
		desc->irq_data.chip->irq_mask(&desc->irq_data);
		irq_state_set_masked(desc);
	}
}

使能cpu中断响应

arch_suspend_enable_irqs调用local_irq_enable()来使能本地cpu中断响应;对于arm架构最终会通过arch_local_irq_enable执行相关指令

static inline void arch_local_irq_enable(void)
{
	unsigned long temp;
	asm volatile(
		"	mrs	%0, cpsr	@ arch_local_irq_enable\n"
		"	bic	%0, %0, #128\n"
		"	msr	cpsr_c, %0"
		: "=r" (temp)
		:
		: "memory", "cc");
}

  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Linux驱动开发中,中断是一种重要的机制。在Linux中,中断有两种类型:可抢占的内核代码运行在进程上下文中,而中断上下文则是不可被抢占的,会一直运行直到结束。对于硬件产生的中断,比如按键中断或网卡中断,被称为硬件中断,每个硬件中断都有对应的处理函数。在中断处理函数中,由于中断必须短暂且不能休眠,因此不能使用信号量或互斥锁,而是使用自旋锁来实现对共享资源的保护。此外,在Linux驱动开发中,可以使用计函数来获取当前的系统间。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *3* [linux驱动开发-中断](https://blog.csdn.net/weixin_29898767/article/details/124320089)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT0_1"}}] [.reference_item style="max-width: 50%"] - *2* [关于Linux驱动开发中断处理](https://blog.csdn.net/weixin_44468026/article/details/119763134)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT0_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值