我正在研究一个嵌入式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
。
我会将其重新构建为两部分:
- 中断处理程序
- 等待中断处理程序,然后执行定时逻辑的应用程序。
交互作用的具体机制可能是以下几种方式中的任何一种。
如果使用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;
}
}