Linux kernel的中斷子系統之(二):IRQ Domain介紹

返回目錄:《ARM-Linux中斷系統》。

?

總結:一、二概述了軟硬件不同角度的IRQ Number和HW Interrupt ID,這就需要他們之間架個橋梁。

三介紹了架設這種橋梁的幾種方式:Linear、Radix Tree和no map。

四介紹了兩種基礎數據結構描述中斷域的irq_domain及針對中斷域的操作函數。

五針對中斷DeviceTree的個屬性進行了解釋。

六介紹了從DT到中斷映射數據庫的過程,也即HW interrupt ID到IRQ number之間的映射關系。

七介紹了實際使用中從中斷觸發時的HW interrupt ID如何映射到IRQ number,進而調用中斷例程的步驟。

原文地址:《Linux kernel的中斷子系統之(二):IRQ Domain介紹

一、概述

在linux kernel中,我們使用下面兩個ID來標識一個來自外設的中斷:

1、IRQ number。CPU需要為每一個外設中斷編號,我們稱之IRQ Number。這個IRQ number是一個虛擬的interrupt ID,和硬件無關,僅僅是被CPU用來標識一個外設中斷。

2、HW interrupt ID。對于interrupt controller而言,它收集了多個外設的interrupt request line并向上傳遞,因此,interrupt controller需要對外設中斷進行編碼。Interrupt controller用HW interrupt ID來標識外設的中斷。在interrupt controller級聯的情況下,僅僅用HW interrupt ID已經不能唯一標識一個外設中斷,還需要知道該HW interrupt ID所屬的interrupt controller(HW interrupt ID在不同的Interrupt controller上是會重復編碼的)。

這樣,CPU和interrupt controller在標識中斷上就有了一些不同的概念,但是,對于驅動工程師而言,我們和CPU視角是一樣的,我們只希望得到一個IRQ number,而不關系具體是那個interrupt controller上的那個HW interrupt ID。這樣一個好處是在中斷相關的硬件發生變化的時候,驅動軟件不需要修改。因此,linux kernel中的中斷子系統需要提供一個將HW interrupt ID映射到IRQ number上來的機制,這就是本文主要的內容。

Notes:兩種視角的中斷號:IRQ Number:從驅動軟件來看,CPU對每個中斷進行編號。HW interrupt ID:從中斷控制起來看,每個中斷控制器上的中斷都有一個編號。

這兩種不同視角就導致了,從硬件到軟件的一個轉換。

二、歷史

關于HW interrupt ID映射到IRQ number上 這事,在過去系統只有一個interrupt controller的時候還是很簡單的,中斷控制器上實際的HW interrupt line的編號可以直接變成IRQ number。例如我們大家都熟悉的SOC內嵌的interrupt controller,這種controller多半有中斷狀態寄存器,這個寄存器可能有64個bit(也可能更多),每個bit就是一個IRQ number,可以直接進行映射。這時候,GPIO的中斷在中斷控制器的狀態寄存器中只有一個bit,因此所有的GPIO中斷只有一個IRQ number,在該通用GPIO中斷的irq handler中進行deduplex,將各個具體的GPIO中斷映射到其相應的IRQ number上。如果你是一個足夠老的工程師,應該是經歷過這個階段的。

隨著linux kernel的發展,將interrupt controller抽象成irqchip這個概念越來越流行,甚至GPIO controller也可以被看出一個interrupt controller chip,這樣,系統中至少有兩個中斷控制器了,一個傳統意義的中斷控制器,一個是GPIO controller type的中斷控制器。隨著系統復雜度加大,外設中斷數據增加,實際上系統可以需要多個中斷控制器進行級聯,面對這樣的趨勢,linux kernel工程師如何應對?答案就是irq domain這個概念。

我們聽說過很多的domain,power domain,clock domain等等,所謂domain,就是領域,范圍的意思,也就是說,任何的定義出了這個范圍就沒有意義了。系統中所有的interrupt controller會形成樹狀結構,對于每個interrupt controller都可以連接若干個外設的中斷請求(我們稱之interrupt source),interrupt controller會對連接其上的interrupt source(根據其在Interrupt controller中物理特性)進行編號(也就是HW interrupt ID了)。但這個編號僅僅限制在本interrupt controller范圍內。

Notes:中斷控制器級聯,導致多irq domain。HW interrupt ID就只能在本irq domain有效,在驅動全局范圍內就需要進行不同映射。

三、接口

1、向系統注冊irq domain

具體如何進行映射是interrupt controller自己的事情,不過,有軟件架構思想的工程師更愿意對形形色色的interrupt controller進行抽象,對如何進行HW interrupt ID到IRQ number映射關系上進行進一步的抽象。因此,通用中斷處理模塊中有一個irq domain的子模塊,該模塊將這種映射關系分成了三類:

(1)線性映射。其實就是一個lookup table,HW interrupt ID作為index,通過查表可以獲取對應的IRQ number。對于Linear map而言,interrupt controller對其HW interrupt ID進行編碼的時候要滿足一定的條件:hw ID不能過大,而且ID排列最好是緊密的。對于線性映射,其接口API如下:

static inline struct irq_domain *irq_domain_add_linear(struct device_node *of_node,
???????????????????? unsigned int size,---------該interrupt domain支持多少IRQ
???????????????????? const struct irq_domain_ops *ops,---callback函數
???????????????????? void *host_data)-----driver私有數據
{
??? return __irq_domain_add(of_node, size, size, 0, ops, host_data);
}

(2)Radix Tree map。建立一個Radix Tree來維護HW interrupt ID到IRQ number映射關系。HW interrupt ID作為lookup key,在Radix Tree檢索到IRQ number。如果的確不能滿足線性映射的條件,可以考慮Radix Tree map。實際上,內核中使用Radix Tree map的只有powerPC和MIPS的硬件平臺。對于Radix Tree map,其接口API如下:

static inline struct irq_domain *irq_domain_add_tree(struct device_node *of_node,
???????????????????? const struct irq_domain_ops *ops,
???????????????????? void *host_data)
{
??? return __irq_domain_add(of_node, 0, ~0, 0, ops, host_data);
}

(3)no map。有些中斷控制器很強,可以通過寄存器配置HW interrupt ID而不是由物理連接決定的。例如PowerPC 系統使用的MPIC (Multi-Processor Interrupt Controller)。在這種情況下,不需要進行映射,我們直接把IRQ number寫入HW interrupt ID配置寄存器就OK了,這時候,生成的HW interrupt ID就是IRQ number,也就不需要進行mapping了。對于這種類型的映射,其接口API如下:

static inline struct irq_domain *irq_domain_add_nomap(struct device_node *of_node,
???????????????????? unsigned int max_irq,
???????????????????? const struct irq_domain_ops *ops,
???????????????????? void *host_data)
{
??? return __irq_domain_add(of_node, 0, max_irq, max_irq, ops, host_data);
}

這類接口的邏輯很簡單,根據自己的映射類型,初始化struct irq_domain中的各個成員,調用__irq_domain_add將該irq domain掛入irq_domain_list的全局列表。

2、為irq domain創建映射

上節的內容主要是向系統注冊一個irq domain,具體HW interrupt ID和IRQ number的映射關系都是空的,因此,具體各個irq domain如何管理映射所需要的database還是需要建立的。例如:對于線性映射的irq domain,我們需要建立線性映射的lookup table,對于Radix Tree map,我們要把那個反應IRQ number和HW interrupt ID的Radix tree建立起來。創建映射有四個接口函數:

(1)調用irq_create_mapping函數建立HW interrupt ID和IRQ number的映射關系。該接口函數以irq domain和HW interrupt ID為參數,返回IRQ number(這個IRQ number是動態分配的)。該函數的原型定義如下:

extern unsigned int irq_create_mapping(struct irq_domain *host,
?????????????????????? irq_hw_number_t hwirq);

驅動調用該函數的時候必須提供HW interrupt ID,也就是意味著driver知道自己使用的HW interrupt ID,而一般情況下,HW interrupt ID其實對具體的driver應該是不可見的,不過有些場景比較特殊,例如GPIO類型的中斷,它的HW interrupt ID和GPIO有著特定的關系,driver知道自己使用那個GPIO,也就是知道使用哪一個HW interrupt ID了。

(2)irq_create_strict_mappings。這個接口函數用來為一組HW interrupt ID建立映射。具體函數的原型定義如下:

extern int irq_create_strict_mappings(struct irq_domain *domain,
????????????????????? unsigned int irq_base,
????????????????????? irq_hw_number_t hwirq_base, int count);

(3)irq_create_of_mapping。看到函數名字中的of(open firmware),我想你也可以猜到了幾分,這個接口當然是利用device tree進行映射關系的建立。具體函數的原型定義如下:

extern unsigned int irq_create_of_mapping(struct of_phandle_args *irq_data);

通常,一個普通設備的device tree node已經描述了足夠的中斷信息,在這種情況下,該設備的驅動在初始化的時候可以調用irq_of_parse_and_map這個接口函數進行該device node中和中斷相關的內容(interrupts和interrupt-parent屬性)進行分析,并建立映射關系,具體代碼如下:

unsigned int irq_of_parse_and_map(struct device_node *dev, int index)
{
??? struct of_phandle_args oirq;

??? if (of_irq_parse_one(dev, index, &oirq))----分析device node中的interrupt相關屬性
??????? return 0;

??? return irq_create_of_mapping(&oirq);-----創建映射,并返回對應的IRQ number
}

對于一個使用Device tree的普通驅動程序(我們推薦這樣做),基本上初始化需要調用irq_of_parse_and_map獲取IRQ number,然后調用request_threaded_irq申請中斷handler。

(4)irq_create_direct_mapping。這是給no map那種類型的interrupt controller使用的,這里不再贅述。

四、數據結構描述

1、irq domain的callback接口

struct irq_domain_ops抽象了一個irq domain的callback函數,定義如下:

struct irq_domain_ops {
??? int (*match)(struct irq_domain *d, struct device_node *node);
??? 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,
???????????? const u32 *intspec, unsigned int intsize,
???????????? unsigned long *out_hwirq, unsigned int *out_type);
};

我們先看xlate函數,語義是翻譯(translate)的意思,那么到底翻譯什么呢?在DTS文件中,各個使用中斷的device node會通過一些屬性(例如interrupts和interrupt-parent屬性)來提供中斷信息給kernel以便kernel可以正確的進行driver的初始化動作。這里,interrupts屬性所表示的interrupt specifier只能由具體的interrupt controller(也就是irq domain)來解析。而xlate函數就是將指定的設備(node參數)上若干個(intsize參數)中斷屬性(intspec參數)翻譯成HW interrupt ID(out_hwirq參數)和trigger類型(out_type)。

match是判斷一個指定的interrupt controller(node參數)是否和一個irq domain匹配(d參數),如果匹配的話,返回1。實際上,內核中很少定義這個callback函數,實際上struct irq_domain中有一個of_node指向了對應的interrupt controller的device node,因此,如果不提供該函數,那么default的匹配函數其實就是判斷irq domain的of_node成員是否等于傳入的node參數。

map和unmap是操作相反的函數,我們描述其中之一就OK了。調用map函數的時機是在創建(或者更新)HW interrupt ID(hw參數)和IRQ number(virq參數)關系的時候。其實,從發生一個中斷到調用該中斷的handler僅僅調用一個request_threaded_irq是不夠的,還需要針對該irq number設定:

(1)設定該IRQ number對應的中斷描述符(struct irq_desc)的irq chip

(2)設定該IRQ number對應的中斷描述符的highlevel irq-events handler

(3)設定該IRQ number對應的中斷描述符的 irq chip data

這些設定不適合由具體的硬件驅動來設定,因此在Interrupt controller,也就是irq domain的callback函數中設定。

2、irq domain

在內核中,irq domain的概念由struct irq_domain表示:

struct irq_domain {
??? struct list_head link;
??? const char *name;
??? const struct irq_domain_ops *ops; ----callback函數
??? void *host_data;

??? /* Optional data */
??? struct device_node *of_node; ----該interrupt domain對應的interrupt controller的device node
??? struct irq_domain_chip_generic *gc; ---generic irq chip的概念,本文暫不描述

??? /* reverse map data. The linear map gets appended to the irq_domain */
??? irq_hw_number_t hwirq_max; ----該domain中最大的那個HW interrupt ID
??? unsigned int revmap_direct_max_irq; ----
??? unsigned int revmap_size; ---線性映射的size,for Radix Tree map和no map,該值等于0
??? struct radix_tree_root revmap_tree; ----Radix Tree map使用到的radix tree root node
??? unsigned int linear_revmap[]; -----線性映射使用的lookup table
};

linux內核中,所有的irq domain被掛入一個全局鏈表,鏈表頭定義如下:

static LIST_HEAD(irq_domain_list);

struct irq_domain中的link成員就是掛入這個隊列的節點。通過irq_domain_list這個指針,可以獲取整個系統中HW interrupt ID和IRQ number的mapping DB。host_data定義了底層interrupt controller使用的私有數據,和具體的interrupt controller相關(對于GIC,該指針指向一個struct gic_chip_data數據結構)。

對于線性映射:

(1)linear_revmap保存了一個線性的lookup table,index是HW interrupt ID,table中保存了IRQ number值

(2)revmap_size等于線性的lookup table的size。

(3)hwirq_max保存了最大的HW interrupt ID

(4)revmap_direct_max_irq沒有用,設定為0。revmap_tree沒有用。

對于Radix Tree map:

(1)linear_revmap沒有用,revmap_size等于0。

(2)hwirq_max沒有用,設定為一個最大值。

(3)revmap_direct_max_irq沒有用,設定為0。

(4)revmap_tree指向Radix tree的root node。

五、中斷相關的Device Tree知識回顧

想要進行映射,首先要了解interrupt controller的拓撲結構。系統中的interrupt controller的拓撲結構以及其interrupt request line的分配情況(分配給哪一個具體的外設)都在Device Tree Source文件中通過下面的屬性給出了描述。這些內容在Device Tree的三份文檔中給出了一些描述,這里簡單總結一下:

對于那些產生中斷的外設,我們需要定義interrupt-parent和interrupts屬性:

(1)interrupt-parent。表明該外設的interrupt request line物理的連接到了哪一個中斷控制器上

(2)interrupts。這個屬性描述了具體該外設產生的interrupt的細節信息(也就是傳說中的interrupt specifier)。例如:HW interrupt ID(由該外設的device node中的interrupt-parent指向的interrupt controller解析)、interrupt觸發類型等。

對于Interrupt controller,我們需要定義interrupt-controller和#interrupt-cells的屬性:

(1)interrupt-controller。表明該device node就是一個中斷控制器

(2)#interrupt-cells。該中斷控制器用多少個cell(一個cell就是一個32-bit的單元)描述一個外設的interrupt request line。?具體每個cell表示什么樣的含義由interrupt controller自己定義。

(3)interrupts和interrupt-parent。對于那些不是root 的interrupt controller,其本身也是作為一個產生中斷的外設連接到其他的interrupt controller上,因此也需要定義interrupts和interrupt-parent的屬性。

六、Mapping DB的建立

1、概述

系統中HW interrupt ID和IRQ number的mapping DB是在整個系統初始化的過程中建立起來的,過程如下:

(1)DTS文件描述了系統中的interrupt controller以及外設IRQ的拓撲結構,在linux kernel啟動的時候,由bootloader傳遞給kernel(實際傳遞的是DTB)。

(2)在Device Tree初始化的時候,形成了系統內所有的device node的樹狀結構,當然其中包括所有和中斷拓撲相關的數據結構(所有的interrupt controller的node和使用中斷的外設node)

(3)在machine driver初始化的時候會調用of_irq_init函數,在該函數中會掃描所有interrupt controller的節點,并調用適合的interrupt controller driver進行初始化。毫無疑問,初始化需要注意順序,首先初始化root,然后first level,second level,最好是leaf node。在初始化的過程中,一般會調用上節中的接口函數向系統增加irq domain。有些interrupt controller會在其driver初始化的過程中創建映射

(4)在各個driver初始化的過程中,創建映射

Notes:從最開始的DTB文件,到初始化DeviceTree的時候關于中斷拓撲數據結構,然后在of_irq_init中調用合適的驅動進行初始化,最后在驅動初始化中創建映射關系。

2、 interrupt controller初始化的過程中,注冊irq domain

我們以GIC的代碼為例。具體代碼在gic_of_init->gic_init_bases中,如下:

void __init gic_init_bases(unsigned int gic_nr, int irq_start,
?????????????? void __iomem *dist_base, void __iomem *cpu_base,
?????????????? u32 percpu_offset, struct device_node *node)
{
??? irq_hw_number_t hwirq_base;
??? struct gic_chip_data *gic;
??? int gic_irqs, irq_base, i;

……
對于root GIC
??????? hwirq_base = 16;
??????? gic_irqs = 系統支持的所有的中斷數目-16。之所以減去16主要是因為root GIC的0~15號HW interrupt 是for IPI的,因此要去掉。也正因為如此hwirq_base從16開始

??? irq_base = irq_alloc_descs(irq_start, 16, gic_irqs, numa_node_id());申請gic_irqs個IRQ資源,從16號開始搜索IRQ number。由于是root GIC,申請的IRQ基本上會從16號開始

??? gic->domain = irq_domain_add_legacy(node, gic_irqs, irq_base,
??????????????????? hwirq_base, &gic_irq_domain_ops, gic);---向系統注冊irq domain并創建映射

……
}

很遺憾,在GIC的代碼中沒有調用標準的注冊irq domain的接口函數。要了解其背后的原因,我們需要回到過去。在舊的linux kernel中,ARM體系結構的代碼不甚理想。在arch/arm目錄充斥了很多board specific的代碼,其中定義了很多具體設備相關的靜態表格,這些表格規定了各個device使用的資源,當然,其中包括IRQ資源。在這種情況下,各個外設的IRQ是固定的(如果作為驅動程序員的你足夠老的話,應該記得很長篇幅的針對IRQ number的宏定義),也就是說,HW interrupt ID和IRQ number的關系是固定的。一旦關系固定,我們就可以在interupt controller的代碼中創建這些映射關系。具體代碼如下:

struct irq_domain *irq_domain_add_legacy(struct device_node *of_node,
???????????????????? unsigned int size,
???????????????????? unsigned int first_irq,
???????????????????? irq_hw_number_t first_hwirq,
???????????????????? const struct irq_domain_ops *ops,
???????????????????? void *host_data)
{
??? struct irq_domain *domain;

??? domain = __irq_domain_add(of_node, first_hwirq + size,----注冊irq domain
????????????????? first_hwirq + size, 0, ops, host_data);
??? if (!domain)
??????? return NULL;

??? irq_domain_associate_many(domain, first_irq, first_hwirq, size); ---創建映射

??? return domain;
}

這時候,對于這個版本的GIC driver而言,初始化之后,HW interrupt ID和IRQ number的映射關系已經建立,保存在線性lookup table中,size等于GIC支持的中斷數目,具體如下:

index 0~15對應的IRQ無效

16號IRQ? <------------------>16號HW interrupt ID

17號IRQ? <------------------>17號HW interrupt ID

……

如果想充分發揮Device Tree的威力,3.14版本中的GIC 代碼需要修改。

3、在各個硬件外設的驅動初始化過程中,創建HW interrupt ID和IRQ number的映射關系

我們上面的描述過程中,已經提及:設備的驅動在初始化的時候可以調用irq_of_parse_and_map這個接口函數進行該device node中和中斷相關的內容(interrupts和interrupt-parent屬性)進行分析,并建立映射關系,具體代碼如下:

unsigned int irq_of_parse_and_map(struct device_node *dev, int index)
{
??? struct of_phandle_args oirq;

??? if (of_irq_parse_one(dev, index, &oirq))----分析device node中的interrupt相關屬性
??????? return 0;

??? return irq_create_of_mapping(&oirq);-----創建映射
}

我們再來看看irq_create_of_mapping函數如何創建映射:

unsigned int irq_create_of_mapping(struct of_phandle_args *irq_data)
{
??? struct irq_domain *domain;
??? irq_hw_number_t hwirq;
??? unsigned int type = IRQ_TYPE_NONE;
??? unsigned int virq;

??? domain = irq_data->np ? irq_find_host(irq_data->np) : irq_default_domain;--A
??? if (!domain) {
??????? return 0;
??? }

??? if (domain->ops->xlate == NULL)--------------B
??????? hwirq = irq_data->args[0];
??? else {
??????? if (domain->ops->xlate(domain, irq_data->np, irq_data->args,----C
??????????????????? irq_data->args_count, &hwirq, &type))
??????????? return 0;
??? }

??? /* Create mapping */
??? virq = irq_create_mapping(domain, hwirq);--------D
??? if (!virq)
??????? return virq;

??? /* Set type if specified and different than the current one */
??? if (type != IRQ_TYPE_NONE &&
??????? type != irq_get_trigger_type(virq))
??????? irq_set_irq_type(virq, type);---------E
??? return virq;
}

A:這里的代碼主要是找到irq domain。這是根據傳遞進來的參數irq_data的np成員來尋找的,具體定義如下:

struct of_phandle_args {
??? struct device_node *np;---指向了外設對應的interrupt controller的device node
??? int args_count;-------該外設定義的interrupt相關屬性的個數
??? uint32_t args[MAX_PHANDLE_ARGS];----具體的interrupt相當屬性的定義
};

B:如果沒有定義xlate函數,那么取interrupts屬性的第一個cell作為HW interrupt ID。

C:解鈴還需系鈴人,interrupts屬性最好由interrupt controller(也就是irq domain)解釋。如果xlate函數能夠完成屬性解析,那么將輸出參數hwirq和type,分別表示HW interrupt ID和interupt type(觸發方式等)。

D:解析完了,最終還是要調用irq_create_mapping函數來創建HW interrupt ID和IRQ number的映射關系。

E:如果有需要,調用irq_set_irq_type函數設定trigger type

irq_create_mapping函數建立HW interrupt ID和IRQ number的映射關系。該接口函數以irq domain和HW interrupt ID為參數,返回IRQ number。具體的代碼如下:

unsigned int irq_create_mapping(struct irq_domain *domain,
??????????????? irq_hw_number_t hwirq)
{
??? unsigned int hint;
??? int virq;

如果映射已經存在,那么不需要映射,直接返回
??? virq = irq_find_mapping(domain, hwirq);
??? if (virq) {
??????? return virq;
??? }

??? hint = hwirq % nr_irqs;-------分配一個IRQ 描述符以及對應的irq number
??? if (hint == 0)
??????? hint++;
??? virq = irq_alloc_desc_from(hint, of_node_to_nid(domain->of_node));
??? if (virq <= 0)
??????? virq = irq_alloc_desc_from(1, of_node_to_nid(domain->of_node));
??? if (virq <= 0) {
??????? pr_debug("-> virq allocation failed\n");
??????? return 0;
??? }

??? if (irq_domain_associate(domain, virq, hwirq)) {---建立mapping
??????? irq_free_desc(virq);
??????? return 0;
??? }

??? return virq;
}

對于分配中斷描述符這段代碼,后續的文章會詳細描述。這里簡單略過,反正,指向完這段代碼,我們就可以或者一個IRQ number以及其對應的中斷描述符了。程序注釋中沒有使用IRQ number而是使用了virtual interrupt number這個術語。virtual interrupt number還是重點理解“virtual”這個詞,所謂virtual,其實就是說和具體的硬件連接沒有關系了,僅僅是一個number而已。具體建立映射的函數是irq_domain_associate函數,代碼如下:

int irq_domain_associate(struct irq_domain *domain, unsigned int virq,
???????????? irq_hw_number_t hwirq)
{
??? struct irq_data *irq_data = irq_get_irq_data(virq);
??? int ret;

??? mutex_lock(&irq_domain_mutex);
??? irq_data->hwirq = hwirq;
??? irq_data->domain = domain;
??? if (domain->ops->map) {
??????? ret = domain->ops->map(domain, virq, hwirq);---調用irq domain的map callback函數
??? }

??? if (hwirq < domain->revmap_size) {
??????? domain->linear_revmap[hwirq] = virq;----填寫線性映射lookup table的數據
??? } else {
??????? mutex_lock(&revmap_trees_mutex);
??????? radix_tree_insert(&domain->revmap_tree, hwirq, irq_data);--向radix tree插入一個node
??????? mutex_unlock(&revmap_trees_mutex);
??? }
??? mutex_unlock(&irq_domain_mutex);

??? irq_clear_status_flags(virq, IRQ_NOREQUEST); ---該IRQ已經可以申請了,因此clear相關flag

??? return 0;
}

七、將HW interrupt ID轉成IRQ number

創建了龐大的HW interrupt ID到IRQ number的mapping DB,最終還是要使用。具體的使用場景是在CPU相關的處理函數中,程序會讀取硬件interrupt ID,并轉成IRQ number,調用對應的irq event handler。在本章中,我們以一個級聯的GIC系統為例,描述轉換過程

1、GIC driver初始化

上面已經描述了root GIC的的初始化,我們再來看看second GIC的初始化。具體代碼在gic_of_init->gic_init_bases中,如下:

void __init gic_init_bases(unsigned int gic_nr, int irq_start,
?????????????? void __iomem *dist_base, void __iomem *cpu_base,
?????????????? u32 percpu_offset, struct device_node *node)
{
??? irq_hw_number_t hwirq_base;
??? struct gic_chip_data *gic;
??? int gic_irqs, irq_base, i;

……
對于second GIC
??????? hwirq_base = 32;
??????? gic_irqs = 系統支持的所有的中斷數目-32。之所以減去32主要是因為對于second GIC,其0~15號HW interrupt 是for IPI的,因此要去掉。而16~31號HW interrupt 是for PPI的,也要去掉。也正因為如此hwirq_base從32開始

??? irq_base = irq_alloc_descs(irq_start, 16, gic_irqs, numa_node_id());申請gic_irqs個IRQ資源,從16號開始搜索IRQ number。由于是second GIC,申請的IRQ基本上會從root GIC申請的最后一個IRQ號+1開始

??? gic->domain = irq_domain_add_legacy(node, gic_irqs, irq_base,
??????????????????? hwirq_base, &gic_irq_domain_ops, gic);---向系統注冊irq domain并創建映射

……
}

second GIC初始化之后,該irq domain的HW interrupt ID和IRQ number的映射關系已經建立,保存在線性lookup table中,size等于GIC支持的中斷數目,具體如下:

index 0~32對應的IRQ無效

root GIC申請的最后一個IRQ號+1? <------------------>32號HW interrupt ID

root GIC申請的最后一個IRQ號+2? <------------------>33號HW interrupt ID

……

OK,我們回到gic的初始化函數,對于second GIC,還有其他部分的初始化內容:

int __init gic_of_init(struct device_node *node, struct device_node *parent)
{

……

??? if (parent) {
??????? irq = irq_of_parse_and_map(node, 0);--解析second GIC的interrupts屬性,并進行mapping,返回IRQ number
??????? gic_cascade_irq(gic_cnt, irq);---設置handler
??? }
……
}

上面的初始化函數去掉和級聯無關的代碼。對于root GIC,其傳入的parent是NULL,因此不會執行級聯部分的代碼。對于second GIC,它是作為其parent(root GIC)的一個普通的irq source,因此,也需要注冊該IRQ的handler。由此可見,非root的GIC的初始化分成了兩個部分:一部分是作為一個interrupt controller,執行和root GIC一樣的初始化代碼。另外一方面,GIC又作為一個普通的interrupt generating device,需要象一個普通的設備驅動一樣,注冊其中斷handler。

irq_of_parse_and_map函數相信大家已經熟悉了,這里不再描述。gic_cascade_irq函數如下:

void __init gic_cascade_irq(unsigned int gic_nr, unsigned int irq)
{
??? if (irq_set_handler_data(irq, &gic_data[gic_nr]) != 0)---設置handler data
??????? BUG();
??? irq_set_chained_handler(irq, gic_handle_cascade_irq);---設置handler
}

2、具體如何在中斷處理過程中,將HW interrupt ID轉成IRQ number

在系統的啟動過程中,經過了各個interrupt controller以及各個外設驅動的努力,整個interrupt系統的database(將HW interrupt ID轉成IRQ number的數據庫,這里的數據庫不是指SQL lite或者oracle這樣通用數據庫軟件)已經建立。一旦發生硬件中斷,經過CPU architecture相關的中斷代碼之后,會調用irq handler,該函數的一般過程如下:

(1)首先找到root interrupt controller對應的irq domain。

(2)根據HW 寄存器信息和irq domain信息獲取HW interrupt ID

(3)調用irq_find_mapping找到HW interrupt ID對應的irq number

(4)調用handle_IRQ(對于ARM平臺)來處理該irq number

對于級聯的情況,過程類似上面的描述,但是需要注意的是在步驟4中不是直接調用該IRQ的hander來處理該irq number因為,這個irq需要各個interrupt controller level上的解析。舉一個簡單的二階級聯情況:假設系統中有兩個interrupt controller,A和B,A是root interrupt controller,B連接到A的13號HW interrupt ID上。在B interrupt controller初始化的時候,除了初始化它作為interrupt controller的那部分內容,還有初始化它作為root interrupt controller A上的一個普通外設這部分的內容。最重要的是調用irq_set_chained_handler設定handler。這樣,在上面的步驟4的時候,就會調用13號HW interrupt ID對應的handler(也就是B的handler),在該handler中,會重復上面的(1)~(4)。

原創文章,轉發請注明出處。蝸窩科技。http://www.wowotech.net/linux_kenrel/irq-domain.html

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/454946.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/454946.shtml
英文地址,請注明出處:http://en.pswp.cn/news/454946.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

mysql返回yyyy mm dd_怎么把取出mysql數據庫中的yyyy-MM-dd日期轉成yyyy年MM月dd日格式...

您好&#xff0c;通過兩個個步驟可以完成轉換&#xff1a;第一步&#xff1a;日期處理可以在模板數據集中通過sql語句轉換&#xff0c;轉換方式方式如下&#xff1a;SELECT DATE_FORMAT(NOW(),%Y) YEAR輸出結果&#xff1a;2018SELECT DATE_F…

關于JS的時間控制

關于JS的時間控制實現動態效果及實例操作 <script>BOM //Bowers Object Model 瀏覽器對象模型setTimeout() // 延遲執行一次setInterval() // 間隔執行var a 300;window.setTimeout(abc(a),3000); // 自定義函數賦值function abc(i){alert(i);}//setInterv…

感動一生的幾句話

為什么80%的碼農都做不了架構師&#xff1f;>>> 很多東西就掌握在我們手中&#xff1a; 比如快樂&#xff0c;你不快樂&#xff0c;誰會同情你的悲傷&#xff1b; 比如堅強&#xff0c;你不堅強&#xff0c;誰會憐憫你的懦弱&#xff1b; 比如努力&#xff0c;你不…

mysql5.6 memcached_MySQL 5.6 安裝配置InnoDB memcached Plugin

準備工作, 安裝libmemached包&#xff0c;提供一些memcat/cp/dump命令&#xff0c;方便測試。# yum install libmemcached.x86_64 -y1. Setup required tables.mysql> source MYSQL_HOME/share/innodb_memcached_config.sqlQuery OK, 1 row affected (0.00 sec)Database cha…

Java 監聽器,國際化

1. 監聽器 1.1 概述 監聽器&#xff1a; 主要是用來監聽特定對象的創建或銷毀、屬性的變化的&#xff01; 是一個實現特定接口的普通java類&#xff01; 對象&#xff1a; 自己創建自己用 (不用監聽) 別人創建自己用 &#xff08;需要監聽&#xff09; Servlet中哪些對象需要監…

patator mysql 字典_利用patator進行子域名爆破

前言:原來朋友寫的一個子域名爆破工具挺好用,這前幾天API接口關了.痛苦萬分.自己也寫了一個類似的但是不咋穩定.特地google找了下 找到一款patator.效果和速度還是不錯的。knock的速度真心受不了啊patator是由Python寫的 不用安裝下載即可.下載地址&#xff1a;http://code.goo…

div 超出高度滾動條,超出寬度點點點

超出高度滾動條style"width:230px; height: 180px; overflow: auto;"超出寬度點點點style"width: 220px; overflow: hidden; white-space:nowrap; text-overflow:ellipsis;"轉載于:https://www.cnblogs.com/thinkingthigh/p/7603703.html

mp4(H264容器)的詳細文件格式分析

十六進制碼流分析&#xff1a; ftyp Box 00 00 00 1C: size ,28,表示此BOX有28個字節&#xff0c;表示長度的四個字節也計算在內。以下同 66 74 79 70: type,表示BOX TYPE,此處為ftyp 6D 70 34 32: 可能是兼容的格式信息&#xff0c;/mp42 00 00 00…

hdu 5925 搜索

題意&#xff1a;一個圖&#xff0c;n個障礙&#xff0c;求聯通塊 思路&#xff1a; 圖很大&#xff0c;障礙物很少。把聯通的障礙物塊摳出來&#xff0c;然后暴力。 代碼&#xff1a; #include<bits/stdc.h> using namespace std; #define MEM(a,b) memset(a,b,sizeof(a…

分析數據庫CitusDB:提供彈性計算能力

本文講的是分析數據庫CitusDB&#xff1a;提供彈性計算能力,企業數據庫市場很龐大&#xff0c;在這個領域既有Oracle這樣行家&#xff0c;也有IBM(DB2)和微軟(SQL Server)這樣的跨界巨頭。它們都與中小企業常用到的開源數據庫MySQL一樣&#xff0c;都屬于傳統關系型數據庫。似乎…

mysql不能創建innodb類型表_MYSQL have_innodb DISABLED無法創建innodb類型的表

今天在一臺MYSQL服務器上發現&#xff0c;明明用了engineinnodb創建的表&#xff0c;結果創建出來卻成了myisam的表。再看show variables like %innodb%;have_innodb 成了DISABLED。經過一番試驗&#xff0c;發現是我關閉數據庫后&#xff0c;直接刪除ibdata1文件造成的。刪除該…

[bzoj1059]矩陣游戲

雖然是一道水難題&#xff0c;但是我這種蒟蒻還是要講一講的。 Description 小Q是一個非常聰明的孩子&#xff0c;除了國際象棋&#xff0c;他還很喜歡玩一個電腦益智游戲——矩陣游戲。矩陣游戲在一個N*N黑白方陣進行&#xff08;如同國際象棋一般&#xff0c;只是顏色是隨意的…

H264 RTP頭分析

h264 RTP頭解析流程 結合NALDecoder.c分析 協議分析 &#xff1a;每一個RTP數據報都由頭部&#xff08;Header&#xff09;和負載&#xff08;Payload&#xff09;兩個部分組成&#xff0c;其中頭部前 12 個字節的含義是固定的&#xff0c;而負載則可以是音頻或者視頻數據。 一…

golang mysql 插入_Mysql學習(一)添加一個新的用戶并用golang操作Mysql

Mysql添加一個新的用戶并賦予權限添加一個自己的用戶到mysql首先我們需要先用root用戶登錄mysql&#xff0c;但是剛安裝完沒有密碼&#xff0c;我們先跳過密碼ailumiyanaailumiyana:~/Git_Project/Go_Test$ sudo mysqld_safe --skip-grant-tables2019-01-07T01:35:51.559420Z m…

云計算構建基石之Hyper-V:虛擬機管理

本文講的是云計算構建基石之Hyper-V:虛擬機管理,作為云計算的重要基石&#xff0c;虛擬化技術的好壞起著關鍵作用。Hyper-V作為微軟重要的虛擬化解決技術&#xff0c;在微軟云計算構建解決方案中&#xff0c;更是關鍵至關鍵&#xff0c;基礎之基礎。在本系列文章中&#xff0c;…

Delphi語言最好的JSON代碼庫 mORMot學習筆記1

mORMot沒有控件安裝&#xff0c;直接添加到lib路徑,工程中直接添加syncommons&#xff0c;syndb等到uses里 --------------------------------------------------------- 在進行網絡編程中需要JSON對象的構建與解析&#xff0c;這個Delphi XE自帶&#xff1a;{$IF CompilerVers…

3GP文件格式分析

1. 概述現在很多智能手機都支持多媒體功能&#xff0c;特別是音頻和視頻播放功能&#xff0c;而3GP文件格式是手機端普遍支持的視頻文件格式。目前很多手機都支持h263視頻編碼格式的視頻文件播放&#xff0c;還有些手機支持h264。音頻文件格式普遍支持amr&#xff0c;有些手…

mysql group concat_MySQL 的 GROUP_CONCAT 函數詳解

GROUP_CONCAT(expr) 函數會從 expr 中連接所有非 NULL 的字符串。如果沒有非 NULL 的字符串&#xff0c;那么它就會返回 NULL。語法如下&#xff1a;GROUP_CONCAT 語法規則它在遞歸查詢中用的比較多&#xff0c;但要使用好它并不容易。所以讓我們一起來看看吧&#xff1a;假設有…

ORACLE數據庫 常用命令和Sql常用語句

ORACLE 賬號相關 如何獲取表及權限 1.COPY表空間backup scottexp登錄管理員賬號system2.創建用戶 create user han identified(認證) by mima default tablespace users&#xff08;默認的表空間&#xff09; quota&#xff08;配額&#xff09;10M on users;創建賬號分配權限g…

光榮之路測試開發面試linux考題之四:性能命令

Hi,大家好我是tom,I am back.今天要給大家講講linux系統一些性能相關命令。 1.fdisk 磁盤管理 是一個強大的危險命令&#xff0c;所有涉及磁盤的操作都由該命令完成&#xff0c;包括&#xff1a;新增磁盤、增刪改磁盤分區等。 1.fdisk -l 查看磁盤分區情況 Disk /dev/sda: 27.8…