1. 程式人生 > >Linux + arm 中斷系統理解

Linux + arm 中斷系統理解

arm中斷框架: 在這裡插入圖片描述 我把中斷系統的硬體組成分為三個部分,CPU、中斷控制器、外設中斷源。imx6q的Interrupt Controller為GIC,支援多CPU Core。 在這裡插入圖片描述 【1】首先來看CPU 目標架構相關的的中斷處理 中斷向量表 arch/arm/kernel/entry-armv.S中 .section .vectors, “ax”, %progbits __vectors_start: W(b) vector_rst W(b) vector_und W(ldr) pc, __vectors_start + 0x1000 W(b) vector_pabt W(b) vector_dabt W(b) vector_addrexcptn W(b) vector_irq //中斷髮生時跳轉的地址 W(b) vector_fiq .data

關於vector_irq的定義分析到跳轉流程如下 .macro vector_stub, name, mode, correction=0 .align 5 vector_\name: .if \correction sub lr, lr, #\correction .endif vector_stub irq, IRQ_MODE, 4 .long __irq_usr @ 0 (USR_26 / USR_32) .long __irq_invalid @ 1 (FIQ_26 / FIQ_32) .long __irq_invalid @ 2 (IRQ_26 / IRQ_32) .long __irq_svc @ 3 (SVC_26 / SVC_32) .long __irq_invalid @ 4 .long __irq_invalid @ 5 .long __irq_invalid @ 6 .long __irq_invalid @ 7 .long __irq_invalid @ 8 .long __irq_invalid @ 9 .long __irq_invalid @ a .long __irq_invalid @ b .long __irq_invalid @ c .long __irq_invalid @ d .long __irq_invalid @ e .long __irq_invalid @ f 從上面的定義可以看到,arm下只有usr狀態的中斷和svc狀態的中斷,繼續分析 __irq_usr 和 __irq_svc的定義。

__irq_usr: usr_entry ----->儲存現場 kuser_cmpxchg_check irq_handler ---->處理中斷 get_thread_info tsk mov why, #0 b ret_to_user_from_irq —>恢復現場 UNWIND(.fnend ) ENDPROC(__irq_usr) __irq_svc: svc_entry —>儲存現場 irq_handler ---->處理中斷 #ifdef CONFIG_PREEMPT get_thread_info tsk ldr r8, [tsk, #TI_PREEMPT] @ get preempt count ldr r0, [tsk, #TI_FLAGS] @ get flags teq r8, #0 @ if preempt count != 0 movne r0, #0 @ force flags to 0 tst r0, #_TIF_NEED_RESCHED blne svc_preempt #endif svc_exit r5, irq = 1 @ return from exception 恢復現場 UNWIND(.fnend ) ENDPROC(__irq_svc)

繼續跟蹤irq_handler 的處理 .macro irq_handler #ifdef CONFIG_MULTI_IRQ_HANDLER ldr r1, =handle_arch_irq mov r0, sp adr lr, BSYM(9997f) ldr pc, [r1] #else arch_irq_handler_default #endif 最終跳轉到handle_arch_irq,在arch/arm/kernel/irq.c中使用set_handle_irq設定handle_arch_irq的函式指標 void __init set_handle_irq(void (*handle_irq)(struct pt_regs *)) { if (handle_arch_irq) return; handle_arch_irq = handle_irq; }

繼續跟蹤,在imx6q目標架構下,driver/irqchip/irq-gic.c中呼叫set_handle_irq。設定中斷處理函式入口。 static void __exception_irq_entry gic_handle_irq(struct pt_regs *regs) { u32 irqstat, irqnr; struct gic_chip_data *gic = &gic_data[0]; void __iomem *cpu_base = gic_data_cpu_base(gic); do { irqstat = readl_relaxed(cpu_base + GIC_CPU_INTACK); irqnr = irqstat & GICC_IAR_INT_ID_MASK; if (likely(irqnr > 15 && irqnr < 1021)) { handle_domain_irq(gic->domain, irqnr, regs); continue; } if (irqnr < 16) { writel_relaxed(irqstat, cpu_base + GIC_CPU_EOI); #ifdef CONFIG_SMP handle_IPI(irqnr, regs); #endif continue; } break; } while (1); } cpu架構相關的中斷程式碼暫時跟蹤到這裡,繼續跟蹤基本上是和imx6q片上暫存器設定相關的程式碼,需要熟悉imx6q的架構和暫存器設定等內容。 【2】再看ARM 的GIC中斷控制器相關分析 GIC的全稱為Generic Interrupt Controller,是ARM公司提供的通用的中斷控制器。目前共有V1 V2 V3 V4 四個版本。imx6q使用的為V2版本。Linux核心中關於GIC中斷控制器的程式碼目錄為 drivers/irqchip/irq-gic.c 和 drivers/irqchip/irq-common.c 先補充一下GIC中斷控制器的幾個知識點: GIC中斷控制器中的中斷輸入訊號,分為兩種,分別是PPI和SPI, PPI即Private Peripheral Interrupt,該型別的中斷訊號為CPU私有的中斷訊號,每個CPU都有其特定的PPI訊號輸入。對於IMX6Q使用的Cortex-A9結構,她的PPI中斷訊號包括5根: a>.nLEGACYIRQ, interrupt ID=31,nLEGACYIRQ可以直接連到對應CPU的nIRQCPU訊號線上,這種情況下,該CPU不參與其他屬於該CPU的PPI以及SPI中斷的響應,只是特別的為這一根中斷線服務。 b>.nLEGACYFRQ, interrupt ID=28,功能同nLEGACYIRQ c>.Private timer, interrupt ID=29 d>.Watch Dog Timer, interrupt ID=30 e>.Global Time, interrupt ID=27 SPI,即Shared Peripheral Interrupt,所有CPU之間共享的中斷,通過暫存器GICD_TYPER可以設定SPI的個數。GIC支援多少個SPI中斷,其輸入訊號就有多少個SPI的request signal。

從程式碼driver/irqchip/irq-gic.c中的gic_handle_irq可以看出,ID0-ID15,為CPU的PPI中斷ID,ID16-ID1020為SPI中斷ID。

Linux GIC原始碼分析 driver的載入入口: IRQCHIP_DECLARE(cortex_a9_gic, “arm,cortex-a9-gic”, gic_of_init); 展開IRQCHIP_DECLARE 巨集 #define IRQCHIP_DECLARE(name, compat, fn) OF_DECLARE_2(irqchip, name, compat, fn) #define OF_DECLARE_2(table, name, compat, fn) _OF_DECLARE(table, name, compat, fn, of_init_fn_2) #define _OF_DECLARE(table, name, compat, fn, fn_type) static const struct of_device_id _of_table##name __used section(##table##_of_table) = { .compatible = compat, .data = (fn == (fn_type)NULL) ? fn : fn }

最終初始化了一個struct of_decice_id的靜態常量,並存儲在_irqchip_of_table_section中。在核心啟動過程中根據裝置樹種的內容匹配當前晶片的of_decice_id,進行驅動的載入過程。

static int __init gic_of_init(struct device_node *node, struct device_node *parent) { void __iomem *cpu_base; void __iomem *dist_base; u32 percpu_offset; int irq; if (WARN_ON(!node)) return -ENODEV; dist_base = of_iomap(node, 0); //對映GIC 分發器的暫存器地址 WARN(!dist_base, “unable to map gic dist registers\n”); cpu_base = of_iomap(node, 1); //對映GIC CPU INTERFACE的暫存器地址 WARN(!cpu_base, “unable to map gic cpu registers\n”); if (of_property_read_u32(node, “cpu-offset”, &percpu_offset)) percpu_offset = 0; //處理cpu-offset屬性 //gic_init_bases是GIC的主要處理過程 gic_init_bases(gic_cnt, -1, dist_base, cpu_base, percpu_offset, node); if (!gic_cnt) gic_init_physaddr(node); //處理interrupt級聯關係 if (parent) { irq = irq_of_parse_and_map(node, 0);//解析second GIC的interrupt屬性並進行mapping,返回的是irq number gic_cascade_irq(gic_cnt, irq); } if (IS_ENABLED(CONFIG_ARM_GIC_V2M)) gicv2m_of_init(node, gic_data[gic_cnt].domain); gic_cnt++; return 0; }

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; BUG_ON(gic_nr >= MAX_GIC_NR); gic = &gic_data[gic_nr]; / * Initialize the CPU interface map to all CPUs. * It will be refined as each CPU probes its ID. / for (i = 0; i < NR_GIC_CPU_IF; i++) gic_cpu_map[i] = 0xff; / * Find out how many interrupts are supported. * The GIC only supports up to 1020 interrupt sources. / gic_irqs = readl_relaxed(gic_data_dist_base(gic) + GIC_DIST_CTR) & 0x1f; gic_irqs = (gic_irqs + 1) * 32; if (gic_irqs > 1020) gic_irqs = 1020; gic->gic_irqs = gic_irqs; /gic_irqs儲存了GIC支援的最大中斷數量,從GICD_TYPER暫存器中的低五位讀出,如果讀出值為N, 那麼最大支援的中斷數目是32(N+1)。GIC規定的最大中斷資料不超過1020/ if (node) { / DT case / gic->domain = irq_domain_add_linear(node, gic_irqs, &gic_irq_domain_hierarchy_ops, gic); } else { / Non-DT case / / * For primary GICs, skip over SGIs. * For secondary GICs, skip over PPIs, too. / if (gic_nr == 0 && (irq_start & 31) > 0) { hwirq_base = 16; if (irq_start != -1) irq_start = (irq_start & ~31) + 16; } else { hwirq_base = 32; } /gic_nr表示GIC_NUMBER, 0表示root GIC,hwirq表示硬體中斷id,並不是GIC上的每個中斷id都需要對映到Linux 中斷系統中的id。例如軟體中斷SGI,用於cpu之間的通訊,沒有必要進行 中斷ID的對映。hwirq_base 表示GIC上需要進行id map的base,16表示忽略掉16個SGI。對於系統 中的其他GIC,其PPI也沒有必要進行mapping,因此hwirq_base=32/ gic_irqs -= hwirq_base; / calculate # of irqs to allocate */ /*從gic_irqs 中減去那些不需要mapping的interrupt ID / irq_base = irq_alloc_descs(irq_start, 16, gic_irqs, numa_node_id()); if (IS_ERR_VALUE(irq_base)) { WARN(1, “Cannot allocate irq_descs @ IRQ%d, assuming pre-allocated\n”, irq_start); irq_base = irq_start; } /分配中斷描述符 如果irq_start大於0,那麼說明是指定IRQ number的分配,這種情況,irq_start=-1 不用指定IRQ NUMBER。讓其自動搜尋,引數2是起始搜尋的IRQ NUMBER. gic_irqs則指明瞭要分配的 irq_number 的數目。/ gic->domain = irq_domain_add_legacy(node, gic_irqs, irq_base, hwirq_base, &gic_irq_domain_ops, gic); /向系統中註冊一個irq_domain的資料結構,關於irq_domain 後面專門分析/ } if (WARN_ON(!gic->domain)) return; if (gic_nr == 0) { #ifdef CONFIG_SMP set_smp_cross_call(gic_raise_softirq); /設定一個多個cpu直接通訊的callback函式。當一個cpu core上的軟體控制行為需要傳遞到其他的cpu上時, 就會呼叫這個函式set_smp_cross_call,對於GIC,這裡實際上觸發IPI中斷/ register_cpu_notifier(&gic_cpu_notifier); /註冊cpu的通知事件,當cpu 狀態發生變化時,GIC driver需要接收到這些事件,並對GIC 的 cpu interface進行設定/ #endif set_handle_irq(gic_handle_irq); /設定所有中斷的處理函式入口/ } gic_dist_init(gic); gic_cpu_init(gic); gic_pm_init(gic); / GIC的電源初始化,主要分配兩個per cpu的記憶體。這些記憶體在系統進行sleep狀態時儲存PPI的暫存器狀態資訊,在resume的時候,寫回暫存器。對於root GIC, 需要註冊一個和電源管理的時間通知事件回掉函式。 */ }

static void __init gic_dist_init(struct gic_chip_data *gic) { unsigned int i; u32 cpumask; unsigned int gic_irqs = gic->gic_irqs; //GIC支援的IRQ數量 void __iomem base = gic_data_dist_base(gic); //獲取GIC Distributor分發器基地址 writel_relaxed(GICD_DISABLE, base + GIC_DIST_CTRL); / GIC_DIST_CTRL暫存器用來控制全域性中斷向CPU Interface分發,寫入0表示不向CPU Interface傳送中斷請求訊號 即為關閉全部中斷請求。 / / * Set all global interrupts to this CPU only. */ cpumask = gic_get_cpumask(gic); cpumask |= cpumask << 8; cpumask |= cpumask << 16; for (i = 32; i < gic_irqs; i += 4) /設定每個SPI型別的中斷都是隻送達該CPU/ writel_relaxed(cpumask, base + GIC_DIST_TARGET + i * 4 / 4); /配置GIC Distributor的其他暫存器/ gic_dist_config(base, gic_irqs, NULL); writel_relaxed(GICD_ENABLE, base + GIC_DIST_CTRL); }

static void gic_cpu_init(struct gic_chip_data *gic) { void __iomem *dist_base = gic_data_dist_base(gic); //獲取 GIC Distrbutor的基地址 void __iomem base = gic_data_cpu_base(gic); //獲取 GIC CPU Interface的基地址 unsigned int cpu_mask, cpu = smp_processor_id(); //獲取CPU的邏輯ID int i; / * Get what the GIC says our CPU mask is. / BUG_ON(cpu >= NR_GIC_CPU_IF); cpu_mask = gic_get_cpumask(gic); //獲取cpu_mask gic_cpu_map[cpu] = cpu_mask; / * Clear our mask from the other map entries in case they’re * still undefined. */ for (i = 0; i < NR_GIC_CPU_IF; i++) if (i != cpu) gic_cpu_map[i] &= ~cpu_mask; /設定SGI PPI的初始值/ gic_cpu_config(dist_base, NULL);

//讓所有的interrupt interface都可以送達CPU
writel_relaxed(GICC_INT_PRI_THRESHOLD, base + GIC_CPU_PRIMASK);
//設定cpu interface的control register,enable group-0的中斷,disable group-1的中斷,group 0的interrupt source
//觸發irq中斷,而非fiq中斷
gic_cpu_if_up();

}