[英]How can I delay in a Linux interrupt handler (I know sleeping usually not possible)
我正在開發一個嵌入式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_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
意味着將使用標准的硬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()
調用。
將request_irq()
替換為request_threaded_irq()
(如上所示),您將能夠在IRQ處理程序中使用sleep
。
我將其重新構建為兩部分:
正如您所經歷的那樣,在IRQ處理程序中睡眠是不好的 。 因此,任何重要的忙碌等待都會影響系統其他部分的響應能力。
交互的具體機制可以是幾種手段中的任何一種。
如果使用Linux設備驅動程序,它可以接受read()操作並在發生中斷時返回一些內容(例如等待時間,甚至是零字節)。 因此,應用程序將打開設備,執行阻塞讀取()以及何時成功返回(無錯誤)在用戶模式下執行所有邏輯(可能)正常優先級。
事實證明我的問題的根本原因是錯誤配置的引腳(中斷信號打開的那個),我的中斷不是事件發生的......我正在看着自己不受控制的鐵軌。 當我正在研究系統的另一部分時,我猜我搞砸了這個...
我最終使用以下函數來實現硬中斷的延遲。 它並不性感,但確實有效,而且很簡單,我相信轉換操作可以避免溢出,如@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;
}
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.