Linux 中斷子系統之中斷映射

中斷是當前計算機系統的基礎功能,也是系統響應外設事件的必備橋樑。不同的架構對中斷控制器有不同的設計理念,本文針對 ARM 公司提供的通用中斷控制器(GIC,Generic Interrupt Controller)介紹在 linux 系統中的硬件中斷號與軟件中斷號的映射過程。

首先,我們先來理解一下硬件中斷號和軟件中斷號。

 IC 分配的中斷號範圍

爲什麼要進行中斷映射呢?簡單來講,軟件可以不需要關注該中斷在硬件上是哪個中斷來源。簡單的 SOC 內部對中斷的管理也比較簡單,通常會有一個全局的中斷狀態寄存器來記錄外設中斷,這樣直接將硬件中斷號線性映射到軟件中斷號即可。但是隨着芯片技術的發展,SOC 越來越複雜,通常內部會有多箇中斷控制器(比如 GIC interrupt controller, GPIO interrupt controller), 每一箇中斷控制器對應多箇中斷號,而硬件中斷號在不同的中斷控制器上是會重複編碼, 這時僅僅用硬中斷號已經不能唯一標識一個外設中斷。尤其在多箇中斷控制器級聯的情況下,會變得更加複雜。這樣對軟件編程來講極不友好,作爲軟件工程師,我們更願意集中精力關注軟件層面的內容。

接下來,我們看看 Linux 系統中斷映射過程涉及到的數據結構,數據結構詳細信息請參看 Linux 內核源碼。

 □ struct irq_desc,中斷描述符,每個 irq 都會在系統中分配一個該數據結構,用於描述該 irq 的相關信息,包含了 irqaction 。

□struct irq_domain/struct irq_chip, 是對中斷控制器的軟件抽象。

 irq_domain 側重於對級聯關係的描述,以及該中斷控制器對某個中斷響應的描述在 struct irq_domain_ops 中進行組織。irq_domain 中的 linear_revmap[] 和 revmap_tree 用於該 domain 中 hwirq 的 map。

irq_chip 側重對該中斷控制器響應中斷過程的描述。

□struct irq_data, 是中斷映射的關鍵數據結構,映射過程這要是填充該數據結構中的 irq 與 hwirq。irq 爲軟件系統分配的虛擬中斷號,hwirq 爲該 domain 分配的硬件中斷號。

Linux 系統中斷映射過程涉及到的數據結構間的關係如下:

映射過程大致分爲如下幾個過程:

     1. 系統維護全局 allocated_irqs

     2. 中斷控制器的初始化

     3. 軟件中斷號的獲取與映射

     4. 硬件中斷號的獲取與映射

一、系統維護全局 allocated_irqs

在開了 CONFIG_SPARSE_IRQ 的系統中,系統中維護的全局 allocated_irqs 有 NR_IRQS + 8196 位,每一位代表一個軟件中斷號。在 virq 的申請過程中,既可以單箇中斷號申請,也可以幾個中斷號一起申請。在申請過程中,從 allocated_irqs 的低位到高位查詢第一個連續 N 個空閒的位作爲軟件中斷號。

系統中的 allocated_irqs 定義如下:

二、中斷控制器的初始化

 中斷控制器通過 IRQCHIP_DECLARE 宏註冊到__irqchip_of_table,系統開機初始化階段 start_kernel() => init_IRQ()=> irqchip_init 調度到註冊的 gic_of_init() 對 GIC 進行初始化。

Gic_of_init 中註冊 domain 相關的處理函數,其中 gic_irq_domain_translate() 將 DTS 中解析的中斷信息翻譯得到 HWirq。通過 irq_domain_create_tree() 申請一個 domain 內存,賦值後加入到全局的 irq_domain_list 中。同時,設置中斷處理入口函數。

三、軟件中斷號的獲取與映射

 kernel_init() => kernel_init_freeable() => do_pre_smp_initcalls()=> do_one_initcall() => arm64_device_init() => of_platform_populate()=> of_platform_bus_create() => of_platform_device_create_pdata() =>of_device_alloc()

   Of_device_alloc() 中,想通過 of_irq_count() 從 dts node 中計算出該 node 中的 irq 數量。然後對應每一個 irq 調用 of_irq_to_resource() => of_irq_get() 獲取軟件中斷號。of_irq_get() 調用 of_irq_parse_one() 從 dts 中收集該中斷的相關信息如觸發方式,中斷號,中斷類型等信息並記錄在 oirq 結構體中。然後調用 irq_create_of_mapping() 進行中斷映射並返回軟件中斷號。

 irq_create_of_mapping() => irq_create_fwspec_mapping() 通過 irq_domain_translate() 調用 gic_of_init() 過程中註冊的 gic_irq_domain_translate() 進行翻譯得到 hwirq。然後調用 irq_find_mapping() 嘗試通過 domain->revmap_tree 獲取 irq_data, 從而後去 irq。如果獲取到 irq 說明該中斷已完成映射,無需再進行下去。如果沒有獲取到 irq,說明該中斷沒有進行過映射,繼續往下。irq_create_mapping() 中先通過 bitmap_find_next_zero_area() 從 allocated_irqs 中找到第一個空閒位 start 作爲軟件中斷號。然後申請一箇中斷描述符 irq_desc, 完成相關賦值後,將軟件中斷號作爲鍵值把 irq_desc 加入到 irq_desc_tree 基樹上。此後,

就可以通過 irq 從 irq_desc_tree 基樹上快速獲取對應的 irq_desc,從而獲取其他相關信息。

四、硬件中斷號的獲取與映射

irq_create_fwspec_mapping() 先調用 irq_domain_translate() 獲取到硬件中斷號,由於預留了 SGI,PPI 的中斷號,這裏需要加上對應的中斷號偏移。然後在 irq_domain_associate() 中通過給該 irq_data->hwirq 賦值爲獲取到的硬件中斷號, 同時在 irq_domain_set_mapping() 中進行映射建立起 hwirq 與 irq_data 之間的聯繫。

irq_domain_set_mapping() 中的映射包含兩個部分,一是在該控制器支持一定的線性映射前提下,當 hwirq revmap_size 時進行線性映射。否則以 hwirq 作爲鍵值將 irq_data 放入該 domain 的 revmap_tree 基樹中,搭建起通過 hwirq 快速查詢 virq 的途徑。

五、總結

通過以上操作,可以以 virq=> 從 irq_desc_tree 中獲取該 irq 的中斷描述符結構體 desc=> 再從 desc 中獲取 irq_data=> 進而獲取 hwirq;也可以通過 hwirq=> 從該 domain 的 revmap_tree 中獲取對應的 irq_data=> 從而獲取 data->irq。

本文大體講訴了 linux kernel 中斷映射的解析與映射過程,並沒有對具體的代碼展開詳細的講解。讀者可以根據梳理的框架進行代碼詳情的研究。

參考文檔:

  1. Kernel-5.10 源碼

   https://www.kernel.org/pub/

  1. 《奔跑吧 linux 內核》-- 張天飛
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/WBFX-yrpLrxGfT9d52yr6Q