中斷是裝置需要維修時產生的非同步事件。舉例來說,當序列埠提供資料或乙太網路封包抵達時,就會產生中斷。中斷可讓驅動程式庫在事件發生時立即得知,但不必花時間輪詢 (主動等待) 事件。
使用中斷的驅動程式庫一般架構,是在驅動程式庫啟動 / 繫結作業期間建立背景中斷處理執行緒 (IHT)。這個執行緒會等待中斷發生,並在發生中斷時執行某種服務動作。
以序列埠驅動程式庫為例。發生下列任一事件時,裝置可能會收到中斷信號:
- 已收到一或多個字元,
- 現在可傳輸一或多個字元,
- 控制線 (例如
DTR) 的狀態已變更。
中斷會喚醒 IHT。 IHT 會判斷事件原因,通常是讀取一些狀態暫存器。接著,系統會執行適當的服務函式來處理事件。完成後,IHT 會返回休眠狀態,等待下一個中斷。
舉例來說,如果收到字元,IHT 會喚醒、讀取指出「資料可用」的狀態暫存器,然後呼叫函式,將序列埠 FIFO 中的所有可用字元排入驅動程式庫的緩衝區。
不需要核心層級的程式碼
您可能熟悉使用中斷服務常式 (ISR) 的其他作業系統。這些是核心層級的處理常式,會在具備權限的模式中執行,並與中斷控制器硬體介面互動。
在 Fuchsia 中,核心會處理中斷處理的特殊權限部分,並提供驅動程式庫使用的執行緒層級函式。
不同的是,IHT 是在執行緒層級執行,而 ISR 則是在核心層級執行,環境非常受限 (有時也很脆弱)。主要優點是如果 IHT 發生當機,只會導致驅動程式庫停止運作,但如果 ISR 發生故障,則可能導致整個作業系統停止運作。
附加至中斷
目前只有 PCI 匯流排會提供中斷。 支援兩種中斷類型:傳統和訊息信號中斷 (MSI)。
因此,如要在 PCI 上使用中斷,請採取下列做法:
- 判斷裝置支援的類型 (舊版或 MSI),
- 將中斷模式設為相符,
- 取得裝置的中斷向量控制碼 (通常只有一個,但可能有多個),
- 啟動 IHT 背景執行緒,
- 安排 IHT 執行緒等待中斷 (步驟 3 中的控制代碼)。
步驟 1 和 2 由 `Pci::ConfigureInterruptMode 輔助函式處理:
// Configure interrupt mode.
uint32_t irq_cnt = 1;
fuchsia_hardware_pci::InterruptMode mode;
zx_status_t status = pci.ConfigureInterruptMode(irq_cnt, &mode);
if (status != ZX_OK) {
// handle error
}
Pci::ConfigureInterruptMode() 會採用兩個引數:
#include <lib/device-protocol/pci.h>
zx_status_t Pci::ConfigureInterruptMode(uint32_t requested_irq_count,
fpci::InterruptMode* out_mode);
第一個引數 requested_irq_count 是您想要的插斷數。
第二個引數是裝置支援的中斷模式的 out 參數。
設定中斷支援後,請呼叫 Pci::MapInterrupt(),為所選中斷建立控制代碼。請注意, Pci::MapInterrupt() 具有下列原型:
#include <lib/device-protocol/pci.h>
zx_status_t Pci::MapInterrupt(uint32_t which_irq, zx::interrupt* out_interrupt);
第一個引數 which_irq 表示您想要的裝置相對中斷編號,第二個引數則是所建立中斷物件的指標。
現在您已取得中斷控制代碼。
請注意,絕大多數裝置只有一個中斷,因此只要為
which_irq傳遞0即可,這是正常現象。如果裝置有多個中斷,一般做法是在for迴圈中執行 pci::MapInterrupt() 函式,並將控制代碼繫結至每個中斷。
等待中斷
在 IHT 中,您會呼叫 zx::interrupt::wait() 等待中斷。 適用下列原型:
zx_status_t zx::interrupt::wait(zx::time* out_timestamp);
第一個參數可以是 nullptr (一般情況),也可以是指向時間戳記的指標,指出中斷觸發時間 (以奈秒為單位,相對於使用 zx_clock_get_monotonic() 擷取的單調時鐘來源)。
因此,典型的 IHT 會具有下列形狀:
int MyDevice::IrqThread() {
for (;;) {
zx_status_t status = dev->irq_.wait(nullptr);
// do stuff
}
}
裝置類別有一個成員 (此處為 irq_),是您從 Pci::MapInterrupt() 取得的物件。
邊緣與層級中斷模式
中斷硬體可採用「邊緣」或「層級」兩種模式運作。
在邊緣模式下,中斷會在上升緣 (硬體訊號從非作用中變為作用中時) 觸發,且只會觸發一次。也就是說,信號必須先返回非使用中狀態,才能再次獲得辨識。
在層級模式中,硬體訊號處於有效狀態時,中斷會處於有效狀態。
通常,中斷專用時會使用邊緣模式,中斷由多部裝置共用時則會使用層級模式 (因為您希望中斷保持有效,直到所有裝置都取消判斷要求行)。
Zircon 核心會視情況自動遮蔽及取消遮蔽中斷。如果是層級觸發的硬體中斷,zx::interrupt::wait() 會先遮罩中斷,然後再傳回,並在下次呼叫時取消遮罩。如果是邊緣觸發的中斷,中斷會保持未遮蔽狀態。
IHT 不應執行任何長時間執行的工作。如果驅動程式要執行長時間的工作,請使用背景工作執行緒。
關閉使用中斷的驅動程式庫
如要乾淨地關閉使用中斷的驅動程式庫,可以透過 zx::interrupt::destroy() 中止 zx::interrupt::wait() 呼叫。
這個概念是,當前景執行緒判斷應關閉驅動程式庫時,只要毀損中斷控制代碼,就會導致 IHT 關閉:
void MyDevice::Stop() {
// destroy the handle, this will cause zx_interrupt_wait() to pop
irq_.destroy();
irq_thread_.join();
...
}
int MyDevice::IrqThread() {
...
for(;;) {
zx_status_t status = irq_.wait(nullptr);
if (status == ZX_ERR_CANCELED) {
// we are being shut down, do any cleanups required
...
break;
}
...
}
}
主執行緒在要求關閉時,會終止中斷控制代碼。這會導致 IHT 的 zx::interrupt::wait() 呼叫以錯誤代碼喚醒。IHT 會查看錯誤代碼 (在本例中為 ZX_ERR_CANCELED),並決定是否要終止。同時,主執行緒會等待加入 IHT,並呼叫 thread::join()。IHT 結束後,thread::join() 會傳回,主執行緒即可完成處理作業。
進階讀者可以查看其他可用的中斷相關函式: