龍芯中斷初探(一)
“
最近幾個月調了很多中斷的bug,啃了很久的源碼。整理了一些東西,大佬們笑納。 離開了架構談中斷都是不深刻的,大佬們肯定玩膩了X86了,今天就以龍芯內核(龍芯官網即可獲得:git://cgit.loongnix.org/kernel/linux-3.10.git)爲例簡單介紹一下哈。中斷在內核中的生命週期主要分爲三個部分:初始化,註冊和中斷處理,剩餘的所有事情都是硬件完成的。這部分打算分享四節內容:中斷初始化、註冊中斷處理以及中斷排錯。這次先分享前兩部分內容。 在一切的前面,先看看中斷相關的數據結構。
”
0 1
中斷相關數據結構
話不多說,上圖
linux kernel中,對於每一個外設的IRQ都用struct irq_desc來描述,我們稱之中斷描述符(struct irq_desc)。linux kernel中會有一個數據結構保存了關於所有IRQ的中斷描述符信息,我們稱之中斷描述符DB(上圖中的數據結構)。當發生中斷後,首先獲取觸發中斷的HW interupt ID,然後通過irq domain翻譯成IRQ nuber,然後通過IRQ number就可以獲取對應的中斷描述符。調用中斷描述符中的highlevel irq-events handler來進行中斷處理就OK了。而highlevel irq-events handler主要進行下面兩個操作:
(1)調用中斷描述符的底層irq chip driver進行mask,ack等callback函數,進行interrupt flow control。
(2)調用該中斷描述符上的action list中的specific handler(我們用這個術語來區分具體中斷handler和high level的handler)。
接下來看中斷初始化,很多架構中斷初始化代碼都在arch目錄下,龍芯採用mips架構,因此關注點在arch/mips/目錄中,一般架構都是定義好某個中斷號被哪個設備佔用,初始化工作是:申請中斷處理需要的desc結構,每個掃描設備給每個desc填充好handle_irq 和chip結構,給中斷號綁定處理函數。中斷註冊都是在init階段通過兩個接口完成的,爲什麼是兩個接口呢,因爲龍芯一部分中斷是CPU控制,另一部分由橋片控制。由一個宏定義分開,宏定義定義在arch下,名字一般是XXX_IRQ_BASE,XXX是架構名,例如:LS7A_PCH_IRQ_BASE,。中斷號小於該宏是有CPU直接控制的中斷,比如說時鐘中斷,串口中斷等等。大於該宏則是橋片上的,例如:顯卡、網卡、硬盤等等。這也跟龍芯自己的硬件設計有關。
0 2
中斷初始化
這個很容易理解,中斷初始化肯定在內核啓動中,因爲內核非常需要時鐘等中斷。那就先從start_kernel開始看吧,這個函數執行的功能十分冗雜,現在我們只關注irq相關的部分。差不多翻兩頁以後,看到下面這個代碼塊:
1 if (initcall_debug) 2 initcall_debug_enable(); 3 4 context_tracking_init(); 5 /* init some links before init_ISA_irqs() */ 6 early_irq_init(); 7 init_IRQ(); 8 tick_init(); 9 rcu_init_nohz(); 10 init_timers(); 11 hrtimers_init(); 12 softirq_init(); 13 timekeeping_init(); 14 time_init(); 15 printk_safe_init(); 16 perf_event_init(); 17 profile_init(); 18 call_function_init(); 19 WARN(!irqs_disabled(), "Interrupts were enabled early\n"); 20 21 early_boot_irqs_disabled = false; 22 local_irq_enable();
這是除了start_kenrel一進來就disable_irq之後第一個出現irq字樣的代碼塊了,各種init irq,nice,盤他!先去看看early_init_irq。定義在kernel下。那就是通用的咯,那就應該是初始化前的準備工作,沒關係,先進去看看。
1int __init early_irq_init(void) 2{ 3 int i, initcnt, node = first_online_node; 4 struct irq_desc *desc; 5 init_irq_default_affinity(); 6 /* Let arch update nr_irqs and return the nr of preallocated irqs */ 7 initcnt = arch_probe_nr_irqs(); 8 printk(KERN_INFO "NR_IRQS: %d, nr_irqs: %d, preallocated irqs: %d\n", 9 NR_IRQS, nr_irqs, initcnt); 10 11 if (WARN_ON(nr_irqs > IRQ_BITMAP_BITS)) 12 nr_irqs = IRQ_BITMAP_BITS; 13 14 if (WARN_ON(initcnt > IRQ_BITMAP_BITS)) 15 initcnt = IRQ_BITMAP_BITS; 16 17 if (initcnt > nr_irqs) 18 nr_irqs = initcnt; 19 20 for (i = 0; i < initcnt; i++) 21 { 22 desc = alloc_desc(i, node, 0, NULL, NULL); 23 set_bit(i, allocated_irqs); 24 irq_insert_desc(i, desc); 25 } 26 return arch_early_irq_init(); 27}
好,設置irq affinity標誌,計算中斷數量,分配中斷描述符,中斷描述符插入DB,然後去arch_early_irq_init,再跳進去看看,恩,kernel下的一個空函數。ok,這時候中斷描述符DB已經創建完成了,只是每個desc還是空的。
接下來看init_IRQ——這個看名字就很重要的函數。ctrl+T,arch下的!!開心,一下就過去了。選擇arch/mips/下的定義:
1void __init init_IRQ(void) 2{ 3 int i; 4 unsigned int order = get_order(IRQ_STACK_SIZE); 5 for (i = 0; i < NR_IRQS; i++) 6 irq_set_noprobe(i); 7 if (cpu_has_veic) 8 clear_c0_status(ST0_IM); 9 arch_init_irq(); 10 for_each_possible_cpu(i) { 11 void *s = (void *)__get_free_pages(GFP_KERNEL, order); 12 irq_stack[i] = s; 13 pr_debug("CPU%d IRQ stack at 0x%p - 0x%p\n", i, 14 irq_stack[i], irq_stack[i] + IRQ_STACK_SIZE); 15 } 16}
挨個設置noprobe,判斷cpu有沒有擴展中斷控制模式(external interrupt controller mode, eic),調arch_init_irq,多CPU挨個申請頁面作爲中斷棧。毫無疑問,註冊流在arch_init_irq裏,就這樣一個一個的跳轉,最後到這裏:
1void __init mach_init_irq(void) 2{ 3 int i; 4 u64 intenset_addr; 5 u64 introuter_lpc_addr; 6 clear_c0_status(ST0_IM | ST0_BEV); 7 mips_cpu_irq_init(); 8 if (loongson_pch) 9 loongson_pch->init_irq(); 10 /* setup CASCADE irq */ 11 setup_irq(LOONGSON_BRIDGE_IRQ, &cascade_irqaction); 12 irq_set_chip_and_handler(LOONGSON_UART_IRQ, 13 &loongson_irq_chip, handle_level_irq); 14 set_c0_status(STATUSF_IP2 | STATUSF_IP6); 15}
這裏可以看到,init是分兩個過程的,先是mips架構通用的mips_cpu_irq_init,再是調用橋片自己的init_irq。這就是我一開始說的,註冊是分兩步完成的。mips_cpu_irq_init函數負責初始cpu直接控制的中斷,挨個設置chip和handle_irq.
1static int mips_cpu_intc_map(struct irq_domain *d, unsigned int irq, 2 irq_hw_number_t hw) 3{ 4 static struct irq_chip *chip; 5 6 if (hw < 2 && cpu_has_mipsmt) { 7 /* Software interrupts are used for MT/CMT IPI */ 8 chip = &mips_mt_cpu_irq_controller; 9 } else { 10 chip = &mips_cpu_irq_controller; 11 } 12 13 if (cpu_has_vint) 14 set_vi_handler(hw, plat_irq_dispatch); 15 16 irq_set_chip_and_handler(irq, chip, handle_percpu_irq); 17 18 return 0; 19 }
初始化流程還是比較簡單,可以看到,在這裏給每一個cpu產生的中斷設置了chip和handle_irq。CPU檢測到中斷觸發直接調用handle_percpu_irq最後到真正的中斷處理函數。接下來看走msi中斷的註冊,這部分稍微複雜一點:
1void __init ls7a_init_irq(void) 2{ 3 writeq(0x0ULL, LS7A_INT_EDGE_REG); 4 writeq(0x0ULL, LS7A_INT_STATUS_REG); 5 /* Mask all interrupts except LPC (bit 19) */ 6 writeq(0xfffffffffff7ffffULL, LS7A_INT_MASK_REG); 7 writeq(0xffffffffffffffffULL, LS7A_INT_CLEAR_REG); 8 9 /* Enable the LPC interrupt */ 10 writel(0x80000000, LS7A_LPC_INT_CTL); 11 /* Clear all 18-bit interrupt bits */ 12 writel(0x3ffff, LS7A_LPC_INT_CLR); 13 14 if (pci_msi_enabled()) 15 loongson_pch->irq_dispatch = ls7a_msi_irq_dispatch; 16 .... 17 18 init_7a_irq(LS7A_IOAPIC_LPC_OFFSET , LS7A_IOAPIC_LPC_IRQ ); 19 init_7a_irq(LS7A_IOAPIC_UART0_OFFSET , LS7A_IOAPIC_UART0_IRQ ); 20 init_7a_irq(LS7A_IOAPIC_I2C0_OFFSET , LS7A_IOAPIC_I2C0_IRQ ); 21 22 ......... 23 } 24}
設置中斷掩碼、中斷清除等寄存器,設置中斷分發函數,調用init_7a_irq爲每個外設中斷設置handle_irq和chip結構。代碼如下:
1static void init_7a_irq(int dev, int irq) 2{ 3 *irq_mask &= ~(1ULL << dev); 4 *(volatile unsigned char *)(LS7A_IOAPIC_ROUTE_ENTRY + dev) = USE_7A_INT0; 5 smp_mb(); 6 irq_set_chip_and_handler(irq, &pch_irq_chip, handle_level_irq); 7 if(ls3a_msi_enabled) { 8 *irq_msi_en |= 1ULL << dev; 9 *(volatile unsigned char *)(irq_msi_vec+dev) = irq; 10 smp_mb(); 11 } 12}
這裏就是所有中斷初始化流程了。看到這裏很多人會覺得茫然了,按照上面的流程分析,第一個被初始化到的中斷是MIPS_CPU_IRQ_BASE 也就是56,那前面的中斷號幹嘛去了?前面的中斷號是故意留下來,具體原因先賣個關子 :wink:
未完待續。。。