在 Linux kernel 中,如果要 diable 某個 irq,會使用disable_irq(irq) 這個 API ,而這個 API 有一個有趣的實作:它有可能不會"真的"馬上在硬體上關閉該 irq ,而是到了下一次真的有該 irq 觸發時,才在 flow handler 中進行 disable。相關代碼如下:
252 void irq_disable(struct irq_desc *desc) 253 { 254 irq_state_set_disabled(desc); /* set SW status as disabled */ 255 if (desc->irq_data.chip->irq_disable) { 256 desc->irq_data.chip->irq_disable(&desc->irq_data); /* if registered irq_disable() callback, use it */ 257 irq_state_set_masked(desc); 258 } else if (irq_settings_disable_unlazy(desc)) { 259 mask_irq(desc); /* if not disable lazy, mask it */ 260 } 261 }
525 void handle_fasteoi_irq(struct irq_desc *desc) 526 { 527 struct irq_chip *chip = desc->irq_data.chip; 528 529 raw_spin_lock(&desc->lock); 530 531 if (!irq_may_run(desc)) 532 goto out; 533 534 desc->istate &= ~(IRQS_REPLAY | IRQS_WAITING); 535 536 /* 537 * If its disabled or no action available 538 * then mask it and get out of here: 539 */ 540 if (unlikely(!desc->action || irqd_irq_disabled(&desc->irq_data))) { 541 desc->istate |= IRQS_PENDING; 542 mask_irq(desc); 543 goto out; 544 } 545 546 kstat_incr_irqs_this_cpu(desc); 547 if (desc->istate & IRQS_ONESHOT) 548 mask_irq(desc); 549 550 preflow_handler(desc); 551 handle_irq_event(desc); 552 553 cond_unmask_eoi_irq(desc, chip); 554 555 raw_spin_unlock(&desc->lock); 556 return; 557 out: 558 if (!(chip->flags & IRQCHIP_EOI_IF_HANDLED)) 559 chip->irq_eoi(&desc->irq_data); 560 raw_spin_unlock(&desc->lock); 561 } 562 EXPORT_SYMBOL_GPL(handle_fasteoi_irq);
一般情況下不會有問題,因為 ISR 被沒有被喚起,但是對於 suspend/resume flow就會有了影響。在 suspend flow,kernel 除了已經設定為 wakeup source 的 irq 外,都會透過 disable_irq() 將所有 irq 關閉:
71 static bool suspend_device_irq(struct irq_desc *desc) 72 { 73 if (!desc->action || irq_desc_is_chained(desc) || 74 desc->no_suspend_depth) 75 return false; 76 77 if (irqd_is_wakeup_set(&desc->irq_data)) { 78 irqd_set(&desc->irq_data, IRQD_WAKEUP_ARMED); 79 /* 80 * We return true here to force the caller to issue 81 * synchronize_irq(). We need to make sure that the 82 * IRQD_WAKEUP_ARMED is visible before we return from 83 * suspend_device_irqs(). 84 */ 85 return true; 86 } 87 88 desc->istate |= IRQS_SUSPENDED; 89 __disable_irq(desc); 90 91 /* 92 * Hardware which has no wakeup source configuration facility 93 * requires that the non wakeup interrupts are masked at the 94 * chip level. The chip implementation indicates that with 95 * IRQCHIP_MASK_ON_SUSPEND. 96 */ 97 if (irq_desc_get_chip(desc)->flags & IRQCHIP_MASK_ON_SUSPEND) 98 mask_irq(desc); 99 return true; 100 }
然而此時該 irq 沒有並真的 disabled,進而造成系統不斷被喚醒。解決方法倒也不難,就是在 intc driver 補 disable_irq() callback,或者per irq設定UNLAZY即可。這個設計應該是為了節省write intc register的時間,因為通常register access會比memory access多上一個數量級,所以只在真的還有下一次irq發生時再進行 disable 即可。不過我是很好奇到底可以省多少啊...@@
留言
張貼留言