隨着現代CPU的複雜度加大,外設中斷數量增加,實際上系統可能同時需要多箇中斷控制器進行級聯,面對這樣的趨勢,Linux引入了irq domain這個概念。 |
對於Linux系統中所有的interrupt controller都會形成樹狀結構,對於每個interrupt controller都可以連接若干個外設的中斷請求,interrupt controller會對連接其上的interrupt source進行編號(也就是HW interrupt ID,後面我們叫hirq),但這個編號僅僅限制在本interrupt controller範圍內。既然存在hirq,那麼在系統軟件中, 就存在一個全局統管的編號,這個編號可以定位控制器和控制器中的中斷號,我們把這個編號叫做virq(我們在Linux系統中調用API request_irq中需要的irq號 實際上是virq號)。對於中斷級聯,我們以TI的TiTDA系列爲例,如下圖:
如上圖,該CPU是直接連接在中斷控制器GICv3上面的,但是GIC不是直接和設備相連,而是與INTR控制器連接,而INTR控制器又與INTA控制器連接,最後INTA控制器才直接和設備相連。在這裏其實就簡單的形成了一個簡單的樹形結構。
domain裏面的hirq排列可以是樹形的也可以是線性的,本文主要說樹形的
咋們先看看domain的結構(一箇中斷控制器就可以看成是一個domain):
struct irq_domain { struct list_head link; const char *name; // domain的名字 const struct irq_domain_ops *ops; // 當前domain的處理函數 void *host_data; // 存放私有數據 unsigned int flags; unsigned int mapcount; /* Optional data */ struct fwnode_handle *fwnode; // 和設備樹有關係 enum irq_domain_bus_token bus_token; // bus標記,用於匹配domain struct irq_domain_chip_generic *gc; #ifdef CONFIG_IRQ_DOMAIN_HIERARCHY struct irq_domain *parent; // 樹形結構的父節點domain #endif irq_hw_number_t hwirq_max; // hwirq樹形(radix tree)支持的最大節點數量 unsigned int revmap_direct_max_irq; // hwirq和virq 1:1映射支持的最大數量 unsigned int revmap_size; // 線性映射支持的最大數量 struct radix_tree_root revmap_tree; // radix tree的樹根 struct mutex revmap_tree_mutex; unsigned int linear_revmap[]; // 線性映射的數組 }
如上圖可知,domain的結構裏面基本都是關於hirq相關信息的,因此,我們可以推斷domain就是用於管理hirq的(每個domain都有自己的一套hirq),而domain中的ops字段就是用於操作這些hirq的, 下面是domain的操作函數ops的結構:
struct irq_domain_ops { int (*match)(struct irq_domain *d, struct device_node *node, enum irq_domain_bus_token bus_token); // 用於匹配domain,優先級低於selec int (*select)(struct irq_domain *d, struct irq_fwspec *fwspec, enum irq_domain_bus_token bus_token); // 用於匹配domain,優先級高於match int (*map)(struct irq_domain *d, unsigned int virq, irq_hw_number_t hw); // 線性映射 void (*unmap)(struct irq_domain *d, unsigned int virq); int (*xlate)(struct irq_domain *d, struct device_node *node, // 通過參數,線性hirq獲取 const u32 *intspec, unsigned int intsize, unsigned long *out_hwirq, unsigned int *out_type); #ifdef CONFIG_IRQ_DOMAIN_HIERARCHY /* extended V2 interfaces to support hierarchy irq_domains */ int (*alloc)(struct irq_domain *d, unsigned int virq,//樹形節點申請,映射(只有樹形纔有這個) unsigned int nr_irqs, void *arg); void (*free)(struct irq_domain *d, unsigned int virq, unsigned int nr_irqs); int (*activate)(struct irq_domain *d, struct irq_data *irqd, bool reserve); // 中斷激活 void (*deactivate)(struct irq_domain *d, struct irq_data *irq_data); int (*translate)(struct irq_domain *d, struct irq_fwspec *fwspec, //通過參數對樹形hirq獲取 unsigned long *out_hwirq, unsigned int *out_type); #endif }
如上圖,對於domain的hirq,我們可以看到結構裏面有查詢分配hirq,以及hirq與virq的映射的函數。現在,我們已經瞭解了domain的數據結構,下面是一箇中斷控制器註冊一個domain的流程:
irq_domain_add_hierarchy irq_domain_create_tree __irq_domain_add list_add(&domain->link, &irq_domain_list)
可見最終所有的domain都添加到irq_domain_list鏈表當中的。但是我們知道,我們調用request_irq用的是virq號,而並不是hirq,因爲hirq號不是唯一的,每個domain都可能會存在一個相同的hirq,因此Linux才增加的virq編號,這個編號是獨立的且唯一。現在domain已經註冊好了,但是hirq和virq直接的關係還沒有建立起來,因此我們還不能用virq來註冊中斷,如下是把hirq和virq做一個映射函數調用:
irq_create_fwspec_mapping(傳入domain和參數) irq_find_matching_fwspec domain->ops->match // 查看當前domain和參數指定的domain是否匹配 irq_domain_translate domain->ops->translate // 通過參數申請一個hirq號 irq_find_mapping // 通過hirq去查詢,這個hirq是否已經映射,如果已經映射,就返回virq irq_domain_alloc_irqs // hirq沒有映射,通過該函數去申請一個virq並映射 __irq_domain_alloc_irqs irq_domain_alloc_descs // 申請一個irq_desc描述 irq_domain_alloc_irq_data // 爲virq設置irq_data樹,該virq會爲它所在的domain的父domain,一直到root domain添加irq_data irq_domain_alloc_irqs_hierarchy // 向domain申請hirq,並和virq做映射 domain->ops->alloc irq_domain_set_hwirq_and_chip // 將hirq與virq的irq_data關聯 irq_domain_insert_irq//將virq信息插入到每一級irq_data裏面(一個domain對應一個irq_data)
如上圖,當驅動調用irq_create_fwspec_mapping函數的時候,該函數會通過參數在指定的domain裏面的申請hirq,然後申請一個virq,並將virq和hirq關聯起來。下圖是,irq_data的結構圖:
如上圖所示,在domain裏面利用linear_revmap(線性)或者revmap_tree(radix tree)來保存irq_data結構,在接收到中斷的時候,我們只知道hirq號和對應的domain,因此可以以hirq爲key值從revmap_tree或者linear_revmap當中獲取到irq_data數據結構;當我們調用request_irq的時候,只填入了virq,因此也可以通過virq做爲key在irq_desc_tree或者irq_desc[]數組中獲取到irq_desc描述,從而獲取到這個描述的irq_data。irq_data包含irq_chip(中斷控制屏蔽掩碼,設置觸發方式等函數)、virq(這個值和該irq_data的父節點parent_data(irq_data)的virq的值相同),hwirq(每個irq_data的hwirq是不同的,對應不同的domain內部編號)、domain(指向當前irq_data所在的domain)、chip_data(指向當前irq_data所在domain的私有數據)、parent_data(指向irq_data的父節點,這個父節點存放父domain相關信息)。
linear_revmap和revmap_tree是domain結構裏面的變量,每個domain都有,用於保存hirq與irq_data的對應關係;irq_desc_tree和irq_desc[]是全局變量,用於保存irq_desc結構的鏈表或者樹。
通常在ARMv8體系結構中,中斷處理的流程,如下圖:
如上圖,我們這個處理流程是el1模式的,在el1模式的時候,彙編級別的共用中斷函數爲el1_irq。圖中我們可以看見幾個箭頭指示的點,1、handle_arch_irq;2、desc->handle_irq;3、irqaction->handler。我們知道,通常ARM體系結構的CPU都和GIC中斷控制器相連,詳細《ARM中斷控制器-GICv2》,因此,handle_arch_irq這個全局函數指針,是有通用的中斷控制器GIC驅動設置的(GIC驅動調用set_handle_irq函數設置),這樣CPU上的所有異常中斷都經過GIC的回調函數處理。GIC的這個回調函數gic_handle_irq(就是handle_arch_irq),負責mask中斷和eoi中斷(eoi就是ack控制器)。在這個回調函數裏面,我們可以通過寄存器值獲取到當前控制的那個中斷號(該domain的hirq)觸發了中斷,然後通過hirq可以獲取到對應的virq,從而獲取到irq_desc描述,最後調用irq_desc描述的handle_irq來處理該中斷。由圖中,我們還看到handle_irq這個函數其實也是註冊的,但是爲了方便統一,這個回調通常都是gic驅動爲每個中斷註冊一個統一的回調函數(handle_fasteoi_irq);handle_fasteoi_irq這個函數支持中斷共享,然後依次調用掛在irq_desc下面的irqaction結構中的handle函數(玩家每調用依次request_irq就會在對應的virq上增加一個irqaction)。
irq_desc是一個virq對應的描述結構體,每個virq對應一個該結構。在irq_desc結構體裏面有irq_data和irqaction變量,一個用於存放與當前virq映射的hirq信息,另一個用於存放該virq的中斷回調處理函數。nr_irqs全局變量保存當前支持最大的中斷數(可以通過irq_expand_nr_irqs函數擴展)