我怎样才能延迟 Linux 中断处理程序(我知道睡眠通常是不可能的)

How can I delay in a Linux interrupt handler (I know sleeping usually not possible)

我正在开发一个嵌入式 Linux ARM 系统,该系统需要通过按特定顺序关闭某些电源(通过 GPIO 控制)来对电源故障信号作出反应。这个过程需要尽快启动,所以我安装了一个中断处理程序来检测这个电源故障。

问题是我们需要在关闭每个电源之间引入一点延迟。我知道中断处理程序通常不允许延迟,但如果此处理程序从不 returns(电源故障!),那完全没问题。

我正在尝试使用 this post 中描述的方法引入延迟,但我无法在我的一生中实际导致可测量的延迟(在示波器上观察到)。

我做错了什么,我该如何做对?

下面是相关代码

/* This function sets en_gpio low, then waits until pg_gpio goes low. */
static inline void powerdown(int en_gpio, int pg_gpio)
{
    /* Bring the enable line low. */
    gpio_set_value(en_gpio, 0);
    /* Loop until power good goes low. */
    while (gpio_get_value(pg_gpio) != 0);
}

/* This is my attempt at a delay function. */
#define DELAY_COUNT 1000000000
static void delay(void)
{
    volatile u_int32_t random;
    volatile u_int32_t accum;
    volatile u_int32_t i;

    get_random_bytes((void*)&random, 4);
    accum = 0;
    for (i = 0; i < DELAY_COUNT; i++)
        accum = accum * random;
}

/* This is the interrupt handler. */
static irqreturn_t power_fail_interrupt(int irq, void *dev_id)
{
    powerdown(VCC0V75_EN, VCC0V75_PG);
    delay();
    powerdown(DVDD15_EN, DVDD15_PG);
    delay();
    powerdown(DVDD18_EN, DVDD18_PG);
    delay();
    powerdown(CVDD1_EN, CVDD1_PG);
    delay();
    powerdown(CVDD_EN, CVDD_PG);
    /* It doesn't matter if we get past this point. Power is failing. */
    /* I'm amazed this printk() sometimes gets the message out before power drops! */
    printk(KERN_ALERT "egon_power_fail driver: Power failure detected!\n");
    return IRQ_HANDLED;
}

在硬 IRQ 处理程序中使用 delay 函数通常不是一个好主意,因为在硬 IRQ 处理程序中 中断被禁用 并且系统将挂起,直到您的硬 IRQ 函数完成.另一方面,您不能在硬 IRQ 处理程序中使用 sleep 函数,因为硬 IRQ 是 atomic context.

考虑到所有这些,您可能需要使用 threaded IRQ。这样硬 IRQ 处理程序只会唤醒 下半部分 IRQ 处理程序(在内核线程中执行)。在此线程处理程序中,您可以使用常规 sleep 函数。

要实现线程 IRQ 而不是常规 IRQ,只需将 request_irq() 函数替换为 request_threaded_irq() 函数即可。例如。如果你有这样的 IRQ 请求:

ret = request_irq(irq, your_irq_handler, IRQF_SHARED,
                  dev_name(&dev->dev), chip);

你可以用这样的东西代替它:

ret = request_threaded_irq(irq, NULL, your_irq_handler,
                           IRQF_ONESHOT | IRQF_SHARED,
                           dev_name(&dev->dev), chip);

这里的NULL表示将使用标准的hard IRQ handler(它只是wakes threaded IRQ handler),而your_irq_handler()函数将在内核线程中执行(你可以调用sleep 函数)。此外,在请求线程 IRQ 时应使用 IRQF_ONESHOT 标志。

还应该提到 managed 版本的 request_threaded_irq() 函数,称为 devm_request_threaded_irq()。使用它(而不是常规 request_threaded_irq())允许您在驱动程序退出函数(以及错误路径)中省略 free_irq() 函数。我建议你使用 devm_* 函数(如果你的内核版本已经有了)。但是,如果您决定使用 devm_*.

,请不要忘记删除驱动程序中的所有 free_irq() 调用

TL;DR

request_irq() 替换为 request_threaded_irq()(如上所示),您将能够在 IRQ 处理程序中使用 sleep

我会将其重新设计为两部分:

  1. 一个中断处理程序
  2. 等待中断处理程序,然后执行定时逻辑的应用程序。

如您所见,在 IRQ 处理程序中休眠 不好。任何重要的忙碌等待也是如此,因为它会扼杀系统其余部分的响应能力。

交互的具体机制可以是几种方式中的任何一种。

如果使用 Linux 设备驱动程序,它可以在中断发生时接受 read() 操作和 return 某些东西(比如等待多长时间,甚至单个字节为零) .因此,应用程序将打开设备,执行阻塞 read(),当它 return 成功(没有错误)时,在用户模式下以(可能)正常优先级执行任何需要的逻辑。

事实证明我的问题的根本原因是引脚配置错误(中断信号打开的那个),我的中断没有发生...我正在查看 rails 即将到来自己不受控制地倒下。我猜我在处理系统的另一部分时把它搞砸了...

我最终使用以下函数来实现我在硬中断中的延迟。它并不性感,但它确实有效而且很简单,而且我相信移位操作可以避免溢出,正如@specializt 在评论中指出的那样。

这段代码非常针对单台设备,我今天做的测试表明它非常稳定。

/* This is my attempt at a delay function. */
/* A count of 8 is approximately 100 microseconds */
static void delay(int delay_count)
{
    volatile u_int32_t random;
    volatile u_int64_t accum;
    volatile u_int32_t i;

    accum = 0;
    for (i = 0; i < delay_count; i++) 
    {
    get_random_bytes((void*)&random, 4);
        accum = accum * random;
    accum = accum >> 32;
    }
}