interrupt

http://www.cnblogs.com/pengdonglin137/p/6349209.html 基於tiny4412的Linux內核移植 — 實例學習中斷背後的知識(1)
http://www.cnblogs.com/pengdonglin137/p/6848851.html 基於設備樹的TQ2440的中斷(2)
http://blog.csdn.net/tiantao2012/article/details/52232490?locationNum=4 gic v3
http://blog.csdn.net/sunsissy/article/details/73882718 gic 的介紹

一,主中斷控制器(root層)
中斷的發生流程:
當中斷信號發生的時候,會跳轉到彙編代碼entry-armv.S__irq_svc處,會進入中斷異常,此時pc指針指向中斷向量表,即執行handle_arch_irq函數指針,

arch/arm/kernel/entry-armv.S
 41         .macro  irq_handler
 42 #ifdef CONFIG_MULTI_IRQ_HANDLER
 43         ldr     r1, =handle_arch_irq
 44         mov     r0, sp
 45         badr    lr, 9997f
 46         ldr     pc, [r1]
 47 #else
 48         arch_irq_handler_default
 49 #endif

這是所有中斷的總入口,每個中斷都要進入這個函數。這個函數會在相應的root 中斷處理器driver初始化中會被賦值,在gic中爲set_handle_irq(gic_handle_irq);
handle_arch_irq = gic_handle_irq,不同的主中斷處理器driver會有不同的入口函數實現,所以中斷的總入口被重新定向。
進入handle_arch_irq,會去讀寫相應的中斷狀態寄存器,查看到底是哪個bit即哪個中斷線有信號到來,就可以得到實際的hwirq號,通過這個實際的irq號,通過當前主中斷號的doamin層的映射關係查找出一個系統中唯一的系統中斷號virq(在內核啓動的時候,每層中斷處理器在自己doamin中所擁有的hwirq和virq已經建立了映射關係,新的dts方式的中斷不一定爲存在的每一個hwirq映射一個virq,只對在dts中引用這個中斷的時候去建立這個映射,不引用的不建立映射關係;不同的domain層可能會有相同的hwirq,但是映射出的virq是系統中唯一的,不會有相同的),然後通過generic_handle_irq(irq_find_mapping(virq);傳入這個唯一的virq,在generic_handle_irq中通過virq找到virq對應的irq_desc(irq_desc只和virq對應和hwirq沒有直接聯繫,唯一的聯繫是我們需要通過hw_irq去查找已經映射好的virq),然後將irq_desc執行desc->handle_irq(desc),desc->handle_irq是每個domain實現的用來解析desc的,
然後通過handle_irq(desc)去執行這個desc中的irq_data中的irq_action->handle,即通過request_irq()來註冊的中斷處理函數

desc->handle_irq:這個函數用來觸發和virq綁定的中斷處理事件,這個函數一定要用本級實現的函數去重新賦值,否則是空的
desc->handle_irq的初始話,在建立映射表的時候通過irq_domain_alloc_irqs_recursive去調用ret = domain->ops->alloc(domain, irq_base, nr_irqs, arg);然後call到該層中斷控制器的domain的ops的alloc函數。
gic driver中的實現:

-->gic_irq_domain_alloc
   -->gic_irq_domain_map
	-->irq_domain_set_info//在這個函數中將handle_fasteoi_irq賦值給desc->handle_irq
	desc->handle_irq = handle_fasteoi_irq
//所以上面的generic_handle_irq會call到各個中斷控制器實現的的desc->handle_irq,gic是handle_fasteoi_irq,這個函數的調用如下
handle_fasteoi_irq的調用
-->handle_fasteoi_irq
-->handle_irq_event
	-->handle_irq_event_percpu
		-->res = action->handler(irq, action->dev_id);//這裏可以看到執行了中斷處理事件,這個action->handler就是通過request_irq註冊的與該virq綁定的中斷處理事件

此時整個的一箇中斷觸發處理流程完畢。

hw_irq和virq的映射過程:
dts方式的中斷使用,其hw_irq和virq的映射過程是在解析dts的時候就已經完成的
先看下調用棧:
SPI中斷(相對應的是PPI和SGI中斷)的調用棧

[<c30174b4>] (unwind_backtrace) from [<c3013afc>] (show_stack+0x10/0x14)
[<c3013afc>] (show_stack) from [<c3225284>] (dump_stack+0x84/0x98)
[<c3225284>] (dump_stack) from [<c324be00>] (gic_irq_domain_alloc+0x28/0x7c)
[<c324be00>] (gic_irq_domain_alloc) from [<c3020d7c>] (tech_irq_domain_alloc+0x154/0x158)
[<c3020d7c>] (tech_irq_domain_alloc) from [<c306ca34>] (__irq_domain_alloc_irqs+0x130/0x324)
[<c306ca34>] (__irq_domain_alloc_irqs) from [<c306cd9c>] (irq_create_fwspec_mapping+0x174/0x1f8)
[<c306cd9c>] (irq_create_fwspec_mapping) from [<c306ce74>] (irq_create_of_mapping+0x54/0x5c)
[<c306ce74>] (irq_create_of_mapping) from [<c339d4f4>] (irq_of_parse_and_map+0x24/0x2c)
[<c339d4f4>] (irq_of_parse_and_map) from [<c339d514>] (of_irq_to_resource+0x18/0xbc)
[<c339d514>] (of_irq_to_resource) from [<c339d5f4>] (of_irq_to_resource_table+0x3c/0x54)
[<c339d5f4>] (of_irq_to_resource_table) from [<c339aad0>] (of_device_alloc+0x108/0x1a4)
[<c339aad0>] (of_device_alloc) from [<c339abb4>] (of_platform_device_create_pdata+0x48/0xc4)
[<c339abb4>] (of_platform_device_create_pdata) from [<c339ad38>] (of_platform_bus_create+0xfc/0x214)
[<c339ad38>] (of_platform_bus_create) from [<c339ad98>] (of_platform_bus_create+0x15c/0x214)
[<c339ad98>] (of_platform_bus_create) from [<c339afd0>] (of_platform_populate+0x5c/0xac)
[<c339afd0>] (of_platform_populate) from [<c36f9888>] (customize_machine+0x20/0x40)
[<c36f9888>] (customize_machine) from [<c300972c>] (do_one_initcall+0xc0/0x200)
[<c300972c>] (do_one_initcall) from [<c36f8d8c>] (kernel_init_freeable+0x150/0x1e0)
[<c36f8d8c>] (kernel_init_freeable) from [<c3528550>] (kernel_init+0xc/0xe0)
[<c3528550>] (kernel_init) from [<c3010138>] (ret_from_fork+0x14/0x3c)

在內核啓動期間解析設備樹,對於使用了中斷的節點而言,不論是使用主中斷還是級聯的子中斷,將device_node轉換爲platform_device的時候會處理節點中的interrupts屬性,將其轉換爲irq resource,同時在所屬的irq domain中建立起hwirq到virq的映射,下面列出主要的函數調用

of_platform_default_populate_init
    ---> of_platform_default_populate
---> of_platform_populate
            ---> of_platform_bus_create
                ---> of_platform_device_create_pdata
                    ---> of_device_alloc
                        ---> of_irq_to_resource_table
                            ---> of_irq_to_resource
                                ---> irq_of_parse_and_map
                                    ---> of_irq_parse_one//僅僅是將解析出的該節點的中斷相關信息進行檢測和二次存儲
                                    ---> irq_create_of_mapping/
                                        ---> irq_create_fwspec_mapping
                                            ---> irq_domain_translate // 解析參數,通過已經解析出的該節點的使用中斷的信息,去得到hw_irq,即dts中傳入的以gic16號中斷爲0號硬件中斷的硬件中斷號(這裏默認指gic中斷處理器,其他中斷處理器這個偏移根據具體的driver而定)這個函數會回調屬於當前doamin的ops結構體中的 translate成員函數
                                            ---> irq_create_mapping // 創建hwirq到virq的映射,返回一個系統中唯一的virq,同時將這種映射關係存入當前層的domainde linear_revmap[]中,並且建立該virq和irq_desc的綁定
			        ---irq_domain_alloc_irqs//或者走這個分支,利用bitmap算法分配一個唯一的virq號
                                                ---> irq_domain_alloc
				--->irq_domain_alloc_descs這個裏面會同時建立virq 和irq_desc 的綁定
但是要注意,hwirq僅僅在所處的irq_domain或者說irq_chip內纔有意義,不同的irq_domain可能會有相同的hwirq,比如gpx2_2的hwirq也是2,但是每一個hwirq對應的virq是系統唯一的,virq其實就是全局變量

各層中斷處理器的driver:
看到上面hw_irq和virq的映射過程可以看到,整個映射過程是需要各層的中斷處理器driver支持的。中斷處理器driver中重要的一點是實現屬於該中斷處理器的doamin,去處理hw_irq
和virq的轉化關係

外設對各層中斷的使用:
如果一個外設要使用一箇中斷,那麼就要在該外設的dts節點中知名使用的哪一級的中斷處理器,同時按照這一即的中斷處理器的格式要求去引用需要的中斷號。
此外設driver中 ,需要使用 下列操作去獲取virq,然後使用這個virq使用request_irq()去註冊和這個中斷號關聯的中斷處理函數.

irq1 = platform_get_irq(pdev, 0);//獲取該節點引用的第一個節點
irq2 = platform_get_irq(pdev, 1);//獲取該節點引用的第二個節點
platform_get_irq這裏有一個hwirq映射爲virq的關鍵操作:
-->platform_get_irq
	-->of_irq_get
		-->of_irq_parse_one////僅僅是將解析出的該節點的中斷相關信息進行檢測和二次存儲
		-->irq_create_of_mapping
			---> irq_create_fwspec_mapping
				--> irq_domain_translate // 解析參數,通過已經解析出的該節點的使用中斷的信息,去得到hw_irq
				-->irq_find_mapping//其實可以看到整個流程和之前建立hw_irq和virq的過程有點類似,唯一的區別在這裏

在進入 irq_create_fwspec_mapping函數通過 irq_domain_translate已經得到了hw_irq,此時會調用irq_find_mapping函數,這個函數會根據傳入的domain和hw_irq去尋找該domain層和這個
hw_irq所對應的virq,如果之前我們已經建立過了這種映射,即已經在dts的解析階段建立了映射,則會在domain中查找到一個virq(此時不是分配,是根據之前的映射結果去查找)
同樣的對於上面的"hw_irq和virq的映射過程"也會執行到這個函數,但是那個時候對於這個hw_irq因爲還沒有建立映射它會查找不到一個virq,所以會走else,執行irq_create_mapping去分配一個virq

unsigned int irq_create_fwspec_mapping(struct irq_fwspec *fwspec)
{
	struct irq_domain *domain;
	irq_hw_number_t hwirq;
	unsigned int type = IRQ_TYPE_NONE;
	int virq;

	if (fwspec->fwnode)
		//找到當前的中斷所依附的domain
		domain = irq_find_matching_fwnode(fwspec->fwnode, DOMAIN_BUS_ANY);
	else
		domain = irq_default_domain;

	if (!domain) {
		pr_warn("no irq domain found for %s !\n",
			of_node_full_name(to_of_node(fwspec->fwnode)));
		return 0;
	}
	//回調當前domain的ops的translate函數得到該中斷的硬件中斷號
	if (irq_domain_translate(domain, fwspec, &hwirq, &type))
		return 0;

	if (irq_domain_is_hierarchy(domain)) {
		/*
		 * If we've already configured this interrupt,
		 * don't do it again, or hell will break loose.
		 */
		 //節點的映射有在dts解析的時候按照各個節點的dts引用中斷的情況會進入該函數,
		 //會先執行irq_find_mapping,因爲沒有映射過,直接返回,進入irq_create_mapping去建立該中斷的映射關係
		 //第二次,當外設driver去解析自己節點的中斷的時候,就會進入在隔離去找對應的軟中斷號,進入
		 //irq_find_mapping函數,因爲之前已經映射好了,所以可以找到virq,直接返回



		//這裏當有節點去獲取軟件中斷號的時候,先在這裏找一下,如果之前已經有對該
		 //中斷號做了map,那麼先找一下,找到了就直接返回
		 //通過硬件中斷號找到軟件中斷號,如果是0代表沒找到
		virq = irq_find_mapping(domain, hwirq);
		if (virq)
			return virq;
		printk("%s %d\n",__FUNCTION__,__LINE__);
		virq = irq_domain_alloc_irqs(domain, 1, NUMA_NO_NODE, fwspec);
		if (virq <= 0)
			return 0;
	} else {
		/* Create mapping */
		printk("%s %d\n",__FUNCTION__,__LINE__);
		virq = irq_create_mapping(domain, hwirq);
		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);
	return virq;
}

二,特殊的級聯中段(子中斷控制器)
級聯中斷最經典的例子就是gpio中斷的例子了,因爲節省主中斷控制器的中斷線的原因(中斷線也是資源),會將所有的gpio接到一個主中斷線中,即所有的gpio中斷信號都匯聚到一個主中斷假設是以gic的16號中斷爲起始的0號中斷。但是soc這一層有gpio的中斷控制器,硬件層面,當gpio的某個pin有中斷髮生時,中斷信號都會觸發0號中斷。然後0號中斷去執行該中斷綁定的中斷處理函數,那如何知道是哪個pin呢,可以該層被觸發的中斷處理函數中去check gpio的中斷狀態寄存器,就可以知道是哪個pin有中斷來了

既然硬件支持級聯中斷,那麼每個pin就可以當作一箇中斷源,我們就可以爲每一個pin綁定一箇中斷處理函數,像普通中斷那樣使用。但是怎麼使用?
這裏就要將gpio的中斷處理器以級聯中斷的方式在系統中生成一個子中斷控制器,gic是主中斷控制器。
基本思路是給每一組gpio建立一箇中斷控制器抽象,同時建立屬於該中斷控制器的domain。每32個gpio爲一組,每一組都有從0開始的hw_irq,都會在該組的domain映射出一個系統唯一的virq,只要hw_irq和virq的關係建立起來了,我們就可以像使用普通中斷一樣去使用每個gpio中斷了。

看每組的gpio中斷控制器的實現:
首先,所有的gpio中斷都連在0號(假設是0號,這裏的0指的是以gic16號中斷爲起始)主中斷上,所以先作如下操作:

1.vm_gpio->irq = platform_get_irq(pdev, 0);//獲取0號主中斷的virq
2.ret = devm_request_irq(&pdev->dev, vm_gpio->irq, vm_gpio_irq_cascade, 
			       IRQF_TRIGGER_NONE | IRQF_SHARED, dev_name(&pdev->dev), vm_gpio);
 爲這個0號中斷綁定中斷處理函數,因爲幾組gpio使用的都是同一個0號主中斷,故使用共享中斷模式,這樣每組gpio在初始化的時候都可以註冊成功。vm_gpio_irq_cascade是0號中斷的中斷處理函數
3.vm_gpio->domain = irq_domain_add_linear(dn, vm_gpio->gc.ngpio, &irq_generic_chip_ops, vm_gpio);將改組的domain加入系統
4.ret = irq_alloc_domain_generic_chips(vm_gpio->domain, vm_gpio->gc.ngpio, 1, vm_gpio->gc.label, 
					     handle_edge_irq, IRQ_NOREQUEST, IRQ_NOPROBE, IRQ_GC_INIT_MASK_CACHE);//添加該層的執行中斷號綁定的事件的函數,即將handle_edge_irq函數賦值給generic_handle_irq中的desc->handle_irq(desc)。聯想中斷的發生流程中關於generic_handle_irq的解釋
5.各種初始化:
gc->reg_base = vm_gpio->base;
	gc->chip_types[0].type = IRQ_TYPE_EDGE_BOTH;
	gc->chip_types[0].chip.irq_ack = irq_gc_ack_set_bit;
	gc->chip_types[0].chip.irq_mask = irq_gc_mask_clr_bit;
	gc->chip_types[0].chip.irq_unmask = irq_gc_mask_set_bit;
	gc->chip_types[0].chip.irq_set_type = vm_gpio_irq_set_type;
	gc->chip_types[0].regs.ack = OFFSET_TO_REG_INT_STATUS;
	gc->chip_types[0].regs.mask = OFFSET_TO_REG_INT_EN;
6.重要的一點實現:vm_gpio->gc.to_irq = vm_gpio_to_irq;

中斷的發生過程:
來看看2中的vm_gpio_irq_cascade:

static irqreturn_t vm_gpio_irq_cascade(int irq, void *data)
{
	struct vm_gpio *vm_gpio = data;
	u32 r = vm_reg_read(vm_gpio, OFFSET_TO_REG_INT_STATUS);
	u32 m = vm_reg_read(vm_gpio, OFFSET_TO_REG_INT_EN);
	const unsigned long bits = r & m;
	int i = 0;

	for_each_set_bit(i, &bits, 32)
		generic_handle_irq(irq_find_mapping(vm_gpio->domain, i));

	return IRQ_HANDLED;
}

當任意一個gpio中斷髮生後,就會觸發0號主中斷,然後會執行該中斷的處理函數即這裏的vm_gpio_irq_cascade,在這個函數中,讀改組的gpio中斷狀態寄存器,看是哪個bit即哪個gpio有中斷到來,即得到了該組中斷的hw_irq,(看到了嗎,hw_irq只和該層中斷有關,即不同層的中斷會有相同的硬件中斷號,但是映射的virq是系統中唯一的)因爲在該組的domain中已經將這個hw_irq分配了相應的virq,即已經建立好了,所以通過irq_find_mapping(vm_gpio->domain, i)即可以查找返回一個屬於這個hw_irq的virq,然後再使用
generic_handle_irq(virq)去執行和這個virq相關的一箇中斷處理函數
hw_irq和virq的關係建立:
和上面主中斷控制器中hw_irq和virq的關係建立所描述的,只有當在dts中有其他節點引用gpio中斷節點和中斷號的時候,纔會建立該中斷號的映射關係。建立方法一致,會將相關信息保存在該組的domain中

gpio中斷當作普通中斷的的使用:
主控制器的節點表示爲:

gic: interrupt-controller@1bf01000 {
		compatible = "arm,cortex-a7-gic";
		interrupt-controller;
		#interrupt-cells = <3>;
		reg = <0x1bf01000 0x1000>,
		      <0x1bf02000 0x1000>,
		      <0x1bf04000 0x2000>,
		      <0x1bf06000 0x2000>;
	};

而普通外設引用主中斷的操作:

	timer {
		compatible = "arm,armv7-timer";
		interrupts = <GIC_PPI 13 (GIC_CPU_MASK_SIMPLE(4) | IRQ_TYPE_LEVEL_LOW)>,
			     <GIC_PPI 14 (GIC_CPU_MASK_SIMPLE(4) | IRQ_TYPE_LEVEL_LOW)>,
			     <GIC_PPI 11 (GIC_CPU_MASK_SIMPLE(4) | IRQ_TYPE_LEVEL_LOW)>,
			     <GIC_PPI 10 (GIC_CPU_MASK_SIMPLE(4) | IRQ_TYPE_LEVEL_LOW)>;
		interrupt-parent=<&igic>;
		clock-frequency = <27000000>;
		arm,cpu-registers-not-fw-configured;
gpio中的一個組作爲一個子中斷控制器的節點表示爲:
gpio_la: gpio@1808D000 {
			compatible = "tech,vm-gpio";
			reg = <0x1808D000 0x20>;
			offsets = <0x0 0x4 0x8 0xc 0x10 0x14 0x18>;
			interrupts = <0 0 4>;
			gpio-controller;
			#gpio-cells = <2>;
			ngpio = <32>;
			gpio-base = <0>;
			interrupt-parent=<&gic>;//他需要用到主中斷
			interrupt-controller;
			gpio-ranges = <&pinctrl 0 0 15>;
			#interrupt-cells = <1>;
		};
而普通外設使用goio中斷的節點爲:
xxx: xx@xxx {
			compatible = "tech,xxx";
			reg = <0x1808D000 0x20>;
			interrupts = <2 4>;//使用了改組的2號gpio中斷
			interrupt-parent=<&gpio_la>;
			#interrupt-cells = <1>;
		};

在使用gpio中斷的driver中,首先使用gpio_to _irq回調到該gpio組的vm_gpio_to_irq這個函數,得到一個virq

static int vm_gpio_to_irq(struct gpio_chip *chip, unsigned offset)
{
	struct vm_gpio *vm_gpio = to_vm_gpio(chip);
	return irq_create_mapping(vm_gpio->domain, offset);//其中的offset就是該組gpio的哪個bit,在這裏就是該組的哪個hw_irq,因爲之前已經映射好了,所以這裏直接使用irq_create_mapping就可以查找出在這個屬於這個組的中斷控制器的domain中的virq
}

然後再去使用這個系統的virq去註冊一箇中斷事件

問題:對於級聯的中斷,中斷的觸發總是先走主中斷的入口函數,然後找出主中斷層的hwirq,然後找到virq,然後通過generic_handle_irq去執行剛virq綁定的事件,那級聯的怎麼執行到呢?
思路是這樣的,對於級聯的中斷,先觸發的是級聯中斷所連的主中斷的事件,在這個事件中,去找到級聯層的中斷號,並使用級聯層domain找到這個中斷號對應的virq,再去使用
generic_handle_irq去call到該層的desc_handle函數執行這個virq綁定的事件

老的方式在start_kernel中先對中斷做了先期初始化,新的4.4的內核雖然使用了,但是好像沒有用來初始化靜態表,
好像新的內核就沒有使用靜態表,是動態的方式

asmlinkage void __init start_kernel(void)
{
……
   trap_init();
……
   early_irq_init();
   init_IRQ();
……
}
early_irq_init();中3.多的內核用與初始化靜態的中斷號描述符表格;不過ARM體系沒有實現arch_early_irq_init。
int __init early_irq_init(void)  
{  
    int count, i, node = first_online_node;  
    struct irq_desc *desc;  
  
    init_irq_default_affinity();  
  
    printk(KERN_INFO "NR_IRQS:%d\n", NR_IRQS);  
  
    desc = irq_desc;  
    count = ARRAY_SIZE(irq_desc);  //irq_descwei 
  
    for (i = 0; i < count; i++) {  
        desc[i].kstat_irqs = alloc_percpu(unsigned int);  
        alloc_masks(&desc[i], GFP_KERNEL, node);  
        raw_spin_lock_init(&desc[i].lock);  
        lockdep_set_class(&desc[i].lock, &irq_desc_lock_class);  
        desc_set_defaults(i, &desc[i], node, NULL);  
    }  
    return arch_early_irq_init();  
}  
在內核啓動期間解析設備樹,將device_node轉換爲platform_device的時候會處理節點中的interrupts屬性,將其轉換爲irq resource,同時在所屬的irq domain中建立起hwirq到virq的映射,下面列出主要的函數調用,期間就會調用上面中斷控制器的irq_domain的s3c24xx_irq_ops_of中的xlate和map:
of_platform_default_populate_init
    ---> of_platform_default_populate
---> of_platform_populate
            ---> of_platform_bus_create
                ---> of_platform_device_create_pdata
                    ---> of_device_alloc
                        ---> of_irq_to_resource_table
                            ---> of_irq_to_resource
                                ---> irq_of_parse_and_map
                                    ---> of_irq_parse_one
                                    ---> irq_create_of_mapping
                                        ---> irq_create_fwspec_mapping
                                            ---> irq_domain_translate // 解析參數
                                                ---> s3c24xx_irq_xlate_of
                                            ---> irq_create_mapping // 創建hwirq到virq的映射
                                                ---> irq_domain_associate
                                                    ---> s3c24xx_irq_map_of

但是要注意,hwirq僅僅在所處的irq_domain或者說irq_chip內纔有意義,不同的irq_domain可能會有相同的hwirq,比如gpx2_2的hwirq也是2,但是每一個hwirq對應的virq是系統唯一的,virq其實就是全局變量

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章