我如何在Linux中断处理程序中延迟(我知道睡眠通常是不可能的)



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

问题是我们需要在关闭每个电源之间引入一点延迟。我知道在中断处理程序中通常不允许延迟,但如果该处理程序永远不会返回(电源故障!),则完全可以。

我试图用这篇文章中描述的方法来引入延迟,但我不能用我的生命来实际引起可测量的延迟(在示波器上观察到)。

我做错了什么,我怎样才能做对?

以下是相关代码。

/* 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是原子上下文

考虑到所有这些因素,您可能希望使用线程IRQ。这样,硬IRQ处理程序只唤醒下半部分 IRQ处理程序(在内核线程中执行)。在这个线程处理程序中,你可以使用常规的sleep函数。

要实现线程IRQ而不是常规IRQ,只需将request_threaded_irq()函数替换为request_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意味着将使用标准的硬IRQ处理程序(这只是唤醒线程IRQ处理程序),your_irq_handler()函数将在内核线程中执行(您可以调用sleep函数)。当请求线程IRQ时,也应该使用IRQF_ONESHOT标志。

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

TL;博士

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

我会将其重新构建为两部分:

    中断处理程序
  1. 等待中断处理程序,然后执行定时逻辑的应用程序。
正如您所经历的那样,在IRQ处理程序中睡觉是不好的。任何明显的繁忙等待也是如此,因为它会杀死系统其余部分的响应性。

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

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

事实证明,我的问题的根本原因是一个错误配置的引脚(中断信号的一个),我的中断不是事件发生…我看着铁轨不受控制地往下掉。我猜我在系统的另一部分工作时把它搞砸了…

我最终使用以下函数来实现硬中断中的延迟。这并不性感,但它确实有效,而且很简单,我相信shift操作避免了@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;
    }
}

相关内容

最新更新