Linux內核筆記之中斷映射

前言
本文基於linux 4.19, 體系結構是aarch64.

1. 硬中斷和虛擬中斷號

Linux 內核筆記之高層中斷處理一文中,介紹了ARM gic中斷控制器對於硬中斷的處理過程。gic的中斷處理程序是從ack一個硬件中斷開始的, 在gic的中斷處理過程中,會根據中斷的映射去尋找對應的虛擬中斷號, 再去進行後續的中斷處理。
gic_handle_irq->handle_domain_irq

int __handle_domain_irq(struct irq_domain *domain, unsigned int hwirq,
			bool lookup, struct pt_regs *regs)
{
...
#ifdef CONFIG_IRQ_DOMAIN
	if (lookup)
		irq = irq_find_mapping(domain, hwirq);   ---- 獲得虛擬中斷號
#endif
...
}

那麼問題來了,爲什麼要有一個虛擬中斷號的概念?
當前的SOC,通常內部會有多箇中斷控制器(比如gic interrupt controller, gpio interrupt controller), 每一箇中斷控制器對應多箇中斷號, 而硬件中斷號在不同的中斷控制器上是會重複編碼的, 這時僅僅用硬中斷號已經不能唯一標識一個外設中斷。 對於軟件工程師而言,我們不需要care是中斷哪個中斷控制器的第幾個中斷號, 因此linux kernel提供了一個虛擬中斷號的概念。

2. irq_domain

接下來討論硬件中斷號是如何映射到虛擬中斷號的
linux kernel提供irq_domain的管理框架, 將hwirq映射到虛擬中斷號上。
每一箇中斷控制器都需要註冊一個irq_domain。

2.1 irq_domain數據結構:

struct irq_domain {
        struct list_head link;
        const char *name;
        const struct irq_domain_ops *ops;
        void *host_data;
        unsigned int flags;
        unsigned int mapcount;

        /* Optional data */
        struct fwnode_handle *fwnode;
        enum irq_domain_bus_token bus_token;
        struct irq_domain_chip_generic *gc;
#ifdef  CONFIG_IRQ_DOMAIN_HIERARCHY
        struct irq_domain *parent;
#endif
#ifdef CONFIG_GENERIC_IRQ_DEBUGFS
        struct dentry           *debugfs_file;
#endif

        /* reverse map data. The linear map gets appended to the irq_domain */
        irq_hw_number_t hwirq_max;
        unsigned int revmap_direct_max_irq;
        unsigned int revmap_size;
        struct radix_tree_root revmap_tree;
        struct mutex revmap_tree_mutex;
        unsigned int linear_revmap[];
};

link: 用於將irq domain連接到全局鏈表irq_domain_list中;
name: irq domain的名稱;
ops: irq domain映射操作使用方法的集合;
mapcount: 映射好的中斷的數量;
fwnode: 對應中斷控制器的device node;
parent: 指向父級irqdomain的指針,用於支持級聯irq_domain;
hwirq_max: 該irq domain支持的中斷最大數量;
revmap_tree: Radix Tree 映射的根節點;
linear_revmap: hwirq->virq 反向映射的線性表;

從該結構體中我們可以看出irq_domain支持多種類型的映射。

2.2 irq_domain映射類型
(1) 線性映射
線性映射保留一張固定的表,通過hwirq number來索引.當hwirq被映射後, 會相應地分配
一個irq_desc, IRQ number就被存在表中.
當hwirqs是固定的而且小於256, 用線性映射更好.它的優勢是尋找時間固定,並且irq_descs
只在in-use IRQs分配.缺點是表格和hwirq 最大numbers一樣大.

irq_domain_add_linear

(2) 樹映射
此種方法使用radix tree來維護映射, 通過key來查找
此方法適合hwirq number非常大的時候, 因爲它不需要分配和hwirq一樣大的table.
缺點是查表效率依賴與table裏的entries數量.

irq_domain_add_tree

(3) 不映射
當有些硬件可以對hwirq number編程時,IRQ number被編進硬件寄存器裏,那麼就不需要映射了.
這種情況下通過irq_create_direct_mapping()實現.

irq_domain_add_nomap()

3. 中斷映射的完整過程

以arm64 dtb啓動爲例分析完整的中斷映射過程。

(1) interrupt controller初始化的過程中,註冊irq domain
, 以gic 註冊irq_domain爲例

+-> gic_of_init
	+-> gic_init_bases
		+-> irq_domain_add_linear
			+-> _irq_domain_add
struct irq_domain *__irq_domain_add(struct fwnode_handle *fwnode, int size,
				    irq_hw_number_t hwirq_max, int direct_max,
				    const struct irq_domain_ops *ops,
				    void *host_data)
{
	struct device_node *of_node = to_of_node(fwnode);
	struct irqchip_fwid *fwid;
	struct irq_domain *domain;

	static atomic_t unknown_domains;

	domain = kzalloc_node(sizeof(*domain) + (sizeof(unsigned int) * size),
			      GFP_KERNEL, of_node_to_nid(of_node));       
	if (WARN_ON(!domain))
		return NULL;
	...
	of_node_get(of_node);
	/* Fill structure */
	INIT_RADIX_TREE(&domain->revmap_tree, GFP_KERNEL);
	mutex_init(&domain->revmap_tree_mutex);
	domain->ops = ops;
	domain->host_data = host_data;
	domain->hwirq_max = hwirq_max;
	domain->revmap_size = size;
	domain->revmap_direct_max_irq = direct_max;
	irq_domain_check_hierarchy(domain);

	mutex_lock(&irq_domain_mutex);
	debugfs_add_domain_dir(domain);
	list_add(&domain->link, &irq_domain_list);     
	mutex_unlock(&irq_domain_mutex);

	pr_debug("Added domain %s\n", domain->name);
	return domain;
}

irq_domain_add()用於初始化一個irq_domain數據結構。
irq_domain分配的內存大小爲sizeof(*domain) + (sizeof(unsigned int) * size), (sizeof(unsigned int) * size)大小的空間是用於linear_revmap[]成員。最後,irq_domain添加到全局的鏈表irq_domain_list中。

(2) 外設的驅動初始化過程中,創建硬中斷和虛擬中斷號的映射關係
設備的驅動在初始化的時候可以調用irq_of_parse_and_map這個接口函數進行該device node中和中斷相關的內容的解析,並建立映射關係

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))
		return 0;

	return irq_create_of_mapping(&oirq);
}

of_irq_parse_one()函數用於解析DTS文件中設備定義的屬性,如"reg", “interrupt”, 最後把DTS中的"interrupts"存放在*out_irq->args[1].

+-> irq_create_of_mapping
	+->irq_create_fwspec_mapping
		+-> irq_find_matching_fwspec // 找到device node對應的irq_domain, 每一個irq_domain都定義了一系列的映射相關的方法
		+-> irq_domain_translate //解析中斷信息,如硬件中斷號, 中斷出發類型
			+-> domain->ops->translate (gic_irq_domain_translate)
		+-> irq_create_mapping // 映射硬件中斷號到虛擬中斷號
			+-> irq_domain_alloc_descs // 分配一個虛擬中斷號 從allocated_irq位圖中取第一個空閒的bit位作爲虛擬中斷號
				+-> irq_domain_alloc_irqs_hierarchy
					+-> domain->ops->alloc (gic_irq_domain_alloc)
						+-> gic_irq_domain_map // gic創建硬中斷和虛擬中斷號的映射,並且根據中斷類型設置struct irq_desc->handle_irq處理函數
			+-> irq_domain_associate
					+-> domain->ops->map 
		
+->gic_irq_domain_map 
	+-> irq_domain_set_info
		+-> irq_domain_set_hwirq_and_chip
int irq_domain_set_hwirq_and_chip(struct irq_domain *domain, unsigned int virq,
				  irq_hw_number_t hwirq, struct irq_chip *chip,
				  void *chip_data)
{
	struct irq_data *irq_data = irq_domain_get_irq_data(domain, virq);

	if (!irq_data)
		return -ENOENT;

	irq_data->hwirq = hwirq;
	irq_data->chip = chip ? chip : &no_irq_chip;
	irq_data->chip_data = chip_data;

	return 0;
}

通過虛擬中斷號獲取irq_data結構體,並將hwirq設置到irq_data->hwirq中, 完成了硬中斷到虛擬中斷號的映射。

在這裏插入圖片描述

4. 參考資料

  1. 《奔跑吧linux內核》
  2. IRQ-domain.txt
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章