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

本文深入探讨Linux系统中休眠与唤醒的处理流程,详细解析dpm_suspend_noirq函数如何遍历并关闭所有中断,以及如何通过设置IRQD_WAKEUP_STATE标志实现特定中断的唤醒功能。介绍了两种主要方法:在请求中断时使用IRQF_NO_SUSPEND标志,或在suspend和resume函数中调用irq_set_irq_wake来控制中断唤醒。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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

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

 

<think>我们正在处理一个关于RK3588芯片上实现GPIO中断触发休眠唤醒功能的问题。根据用户的问题,我们需要配置GPIO中断,并实现休眠唤醒功能。我们将参考提供的引用内容,特别是引用[1]和引用[3]中的相关配置。 步骤: 1. 设备树配置:配置GPIO作为中断源,并设置唤醒功能。 2. 驱动代码:实现中断处理函数,并在其中处理唤醒事件。 3. 休眠唤醒机制:使用等待队列和相应的休眠唤醒函数。 参考引用[1]:在设备树中配置蓝牙的唤醒中断,包括指定中断父节点、中断引脚和触发方式,并标记为唤醒源(wakeup-source)。 参考引用[3]:在rk3399上实现按键中断休眠唤醒的例子,包括中断处理函数中使用`wake_up_interruptible`唤醒等待队列,以及在read函数中使用`wait_event_interruptible`进行休眠等待。 因此,我们可以按照以下步骤实现: ### 1. 设备树配置 在设备树中,我们需要将某个GPIO配置为中断源,并设置为唤醒源。假设我们使用GPIO3_A0(即GPIO3的A0引脚,根据RK3588的GPIO命名规则)作为中断引脚。 设备树节点示例: ```dts &gpio3 { interrupt-parent = <&gic>; // 根据RK3588的中断控制器配置 }; my_gpio_key: my_gpio_key { compatible = "my-gpio-key"; interrupt-parent = <&gpio3>; interrupts = <RK_PA0 IRQ_TYPE_EDGE_BOTH>; // 使用GPIO3_A0,双边沿触发 gpios = <&gpio3 RK_PA0 GPIO_ACTIVE_HIGH>; wakeup-source; // 标记为唤醒源 }; ``` 注意:RK3588的中断控制器可能是GIC(Generic Interrupt Controller),所以中断父节点可能是gic。但是,对于GPIO中断,通常通过GPIO控制器连接至GIC。这里我们按照RK3588的实际情况,GPIO中断的父节点应该是GPIO控制器,而GPIO控制器再连接到GIC。不过,在设备树中,我们通常将中断父节点设置为GPIO控制器节点(如gpio3),然后指定具体的引脚和触发方式。 ### 2. 驱动代码 在驱动中,我们需要: - 申请中断 - 初始化等待队列 - 在中断处理函数中唤醒等待队列 - 在read函数中使进程休眠,直到中断发生 参考引用[3]中的代码结构,我们编写如下: ```c #include <linux/interrupt.h> #include <linux/gpio.h> #include <linux/wait.h> #include <linux/sched.h> static int gpio_irq; static DECLARE_WAIT_QUEUE_HEAD(gpio_key_wait); static int key_signal = 0; static irqreturn_t gpio_key_isr(int irq, void *dev_id) { struct gpio_desc *desc = dev_id; int val = gpiod_get_value(desc); // 将GPIO编号和电平值组合成一个信号值(可根据需要调整) key_signal = (desc_to_gpio(desc) << 8) | (val & 0xFF); printk(KERN_INFO "GPIO interrupt occurred, gpio=%d, value=%d\n", desc_to_gpio(desc), val); // 唤醒等待队列 wake_up_interruptible(&gpio_key_wait); return IRQ_HANDLED; } static ssize_t my_gpio_key_read(struct file *file, char __user *buf, size_t count, loff_t *ppos) { int ret; // 等待中断发生(条件为key_signal非0) wait_event_interruptible(gpio_key_wait, key_signal != 0); // 将信号值拷贝到用户空间 ret = copy_to_user(buf, &key_signal, sizeof(key_signal)); if (ret) return -EFAULT; // 重置信号值,以便下一次读取 key_signal = 0; return sizeof(key_signal); } // 在probe函数中申请中断 static int my_gpio_key_probe(struct platform_device *pdev) { struct gpio_desc *gpio; int irq, ret; // 获取设备树中定义的GPIO gpio = devm_gpiod_get(&pdev->dev, NULL, GPIOD_IN); if (IS_ERR(gpio)) { dev_err(&pdev->dev, "Failed to get GPIO\n"); return PTR_ERR(gpio); } // 获取GPIO对应的中断号 irq = gpiod_to_irq(gpio); if (irq < 0) { dev_err(&pdev->dev, "Failed to get IRQ for GPIO\n"); return irq; } // 申请中断,使用双边沿触发 ret = devm_request_irq(&pdev->dev, irq, gpio_key_isr, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, "my-gpio-key", gpio); if (ret) { dev_err(&pdev->dev, "Failed to request IRQ %d\n", irq); return ret; } gpio_irq = irq; // 其他初始化... return 0; } ``` ### 3. 休眠唤醒机制 在驱动中,我们使用了等待队列(`wait_event_interruptible`)使读取进程休眠,直到中断发生。在中断处理函数中,我们调用`wake_up_interruptible`来唤醒等待队列。 此外,设备树中的`wakeup-source`属性会使得系统在挂起时将该中断源作为唤醒源。这样,当系统进入休眠状态时,如果该GPIO上有中断事件(如按键按下),系统将被唤醒。 ### 注意事项 1. 在RK3588上,GPIO中断的配置需要确保中断控制器正确初始化。通常,平台相关的代码已经处理了这些,我们只需要在设备树中正确配置即可。 2. 在驱动中,我们使用了`devm_`系列的函数来管理资源,这样在设备卸载时资源会自动释放。 3. 双边沿触发(`IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING`)会在GPIO电平上升沿和下降沿都触发中断,适用于按键等场景。 ### 验证 - 加载驱动后,当GPIO状态变化时,应该会触发中断唤醒系统(如果系统处于休眠状态)。 - 通过读取设备文件,可以获取到中断发生时的GPIO状态。 ### 相关问题 1. 如何配置RK3588的GPIO中断唤醒源? 2. 在Linux驱动中,如何使用等待队列实现休眠/唤醒机制? 3. RK3588的GPIO中断处理流程是怎样的?</think>### RK3588 GPIO中断配置与休眠唤醒实现方法 在RK3588上实现GPIO中断触发休眠唤醒功能,需完成以下关键步骤: #### 1. **设备树配置** 在设备树(DTS)中声明GPIO中断属性并标记为唤醒源: ```dts &gpio3 { // 根据实际GPIO组选择 interrupt-parent = <&gpio>; // 中断控制器 interrupts = <RK_PA0 IRQ_TYPE_EDGE_BOTH>; // GPIO3_A0引脚,双边沿触发 wakeup-source; // 关键!声明为唤醒源 }; ``` **参数说明**: - `interrupt-parent`:指定GPIO所属的中断控制器 - `interrupts`:配置引脚(如`RK_PA0`)和触发方式(`IRQ_TYPE_EDGE_BOTH`为双边沿触发) - `wakeup-source`:必须添加此属性才能使GPIO唤醒系统[^1] #### 2. **驱动代码实现** ```c #include <linux/interrupt.h> #include <linux/wait.h> static DECLARE_WAIT_QUEUE_HEAD(gpio_wait_queue); static int wakeup_flag = 0; // 中断处理函数 static irqreturn_t gpio_irq_handler(int irq, void *dev_id) { struct gpio_desc *gpio = dev_id; int value = gpiod_get_value(gpio); // 记录唤醒状态(可选) wakeup_flag = (value << 8) | gpiod_to_desc(gpio)->gpiodev->base; wake_up_interruptible(&gpio_wait_queue); // 唤醒休眠进程[^3] return IRQ_HANDLED; } // 读函数(休眠等待) static ssize_t gpio_key_read(struct file *filp, char __user *buf, size_t count, loff_t *ppos) { wait_event_interruptible(gpio_wait_queue, wakeup_flag != 0); // 可中断休眠[^2] // 将唤醒状态拷贝到用户空间 copy_to_user(buf, &wakeup_flag, sizeof(wakeup_flag)); wakeup_flag = 0; // 重置标志 return sizeof(wakeup_flag); } // 初始化中断 static int gpio_irq_init(void) { struct gpio_desc *gpio; int irq_num, ret; // 获取GPIO描述符 gpio = gpiod_get(dev, "interrupt", GPIOD_IN); // 申请中断 irq_num = gpiod_to_irq(gpio); ret = request_irq(irq_num, gpio_irq_handler, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, "rk3588-gpio-wake", gpio); // 启用中断唤醒能力 enable_irq_wake(irq_num); // 关键步骤! } ``` #### 3. **关键API说明** | 函数/宏 | 作用 | |----------------------------|----------------------------------------------------------------------| | `wait_event_interruptible` | 使进程进入可中断休眠,直到被`wake_up_interruptible`唤醒[^2][^3] | | `enable_irq_wake` | 使能中断唤醒功能,允许GPIO中断将系统从休眠状态唤醒 | | `wake_up_interruptible` | 在中断上下文中唤醒休眠进程[^3] | | `IRQF_TRIGGER_EDGE_BOTH` | 双边沿触发(上升沿和下降沿均触发中断) | #### 4. **调试要点** 1. **休眠验证**: ```bash echo mem > /sys/power/state # 触发系统休眠 ``` 触发GPIO中断后,系统应立即唤醒 2. **唤醒源检查**: ```bash cat /sys/kernel/debug/wakeup_sources # 查看注册的唤醒源 ``` 3. **中断状态确认**: ```bash cat /proc/interrupts | grep gpio # 观察中断计数 ``` #### 5. **常见问题解决** - **无法唤醒**: 1. 确认设备树中已添加`wakeup-source`属性 2. 检查驱动中是否调用`enable_irq_wake()` 3. 测量GPIO电平是否达到触发阈值 - **误唤醒**: 1. 增加GPIO硬件消抖电路(RC滤波) 2. 在驱动中添加软件去抖逻辑: ```c // 在中断处理函数中添加延时检测 udelay(1000); // 1ms延时 if (gpiod_get_value(gpio) == target_value) wake_up_interruptible(&gpio_wait_queue); ``` - **中断丢失**: 1. 确保未在中断处理中进行耗时操作 2. 检查`/proc/interrupts`确认中断未被屏蔽 #### 注意事项 1. **电源域配置**:确保GPIO所在电源域在休眠期间保持供电(通过`&power`节点配置) 2. **电平匹配**:唤醒时的GPIO电平需与中断触发方式一致(如配置为上升沿触发,唤醒时必须为高电平) 3. **共享中断**:若使用共享中断(`IRQF_SHARED`),需在`request_irq`中声明 通过以上步骤,即可在RK3588上实现可靠的GPIO中断休眠唤醒功能。实际开发中建议参考RK官方SDK中的`gpio-keys`驱动实现(路径:`drivers/input/keyboard/gpio_keys.c`)。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值