作者
姓名:彭東林
E-mail:pengdonglin137@163.com
QQ:405728433
平台
板子:TQ2440
內核:Linux-4.9
u-boot: 2015.04
工具鏈: arm-none-linux-gnueabi-gcc 4.8.3
概述
在博文讓TQ2440也用上設備樹(1)將支持devicetree的Linux4.9移植到了tq2440上面,而在基於tiny4412的Linux內核移植 --- 實例學習中斷背后的知識(1)中介紹了最新的Linux下中斷的知識,下面我們再結合TQ2440來分析一下。
正文
一、基礎知識
關於這部分請參考S3C2440的芯片手冊或者博文TQ2440中斷系統
下面簡單介紹:

從圖中可以看到,中斷主要分為兩級,我們可以理解為是兩個中斷控制器的嵌套或者級聯。S3C2440總共支持60個中斷源,包含了主中斷源和子中斷源。
寄存器功能介紹:
1、SRCPND 地址: 0x4A000000 功能:每一位代表一個主中斷,置1表示有對應的主中斷請求,對應位寫入1可以清除中斷
2、INTMOD 地址: 0x4A000004 功能:設置對應的主中斷為IRQ還是FIQ, 置1表示FIQ
3、INTMSK 地址: 0x4A000008 功能:置1表示對應的主中斷被屏蔽(不會影響SRCPND)
4、INTPND 地址: 0x4A000010 功能:表示對應的主中斷被request,只可能有一位被置位,寫入1可以清除中斷
5、INTOFFSET 地址:0x4A000014 功能:存放的是發生中斷請求的主中斷號
6、SUBSRCPND 地址:0x4A000018 功能:每一位代表一個子中斷,置一表示對應子中斷請求,對應位寫入1清除子中斷請求
7、INTSUBMSK 地址:0x4A00001C 功能:置1表示對應的子中斷被屏蔽
32個主中斷:

15個子中斷:


外部中斷:
EINT0~7對應的GPIO是GPF0~7
EINT8~23對應的GPIO是GPG0~15
二、設備樹
1、中斷控制器
intc:interrupt-controller@4a000000 { compatible = "samsung,s3c2410-irq"; reg = <0x4a000000 0x100>; interrupt-controller; #interrupt-cells = <4>; };
serial@50000000 { compatible = "samsung,s3c2440-uart"; reg = <0x50000000 0x4000>; interrupts = <1 28 0 4>, <1 28 1 4>; status = "okay"; clock-names = "uart"; clocks = <&clock PCLK_UART0>; pinctrl-names = "default"; pinctrl-0 = <0x3>; }; i2c:i2c@54000000 { compatible = "samsung,s3c2410-i2c"; reg = <0x54000000 0x100>; interrupts = <0 0 27 3>; #address-cells = <1>; #size-cells = <0>; };

1 /* Translate our of irq notation 2 * format: <ctrl_num ctrl_irq parent_irq type> 3 */ 4 static int s3c24xx_irq_xlate_of(struct irq_domain *d, struct device_node *n, 5 const u32 *intspec, unsigned int intsize, 6 irq_hw_number_t *out_hwirq, unsigned int *out_type) 7 { 8 struct s3c_irq_intc *intc; 9 struct s3c_irq_intc *parent_intc; 10 struct s3c_irq_data *irq_data; 11 struct s3c_irq_data *parent_irq_data; 12 int irqno; 13 14 if (WARN_ON(intsize < 4)) //如果參數個數不能小於4 15 return -EINVAL; 16 // 從這里知道,第一個參數不能大於2,只能是0和1, 0表示主中斷,2表示子中斷 17 if (intspec[0] > 2 || !s3c_intc[intspec[0]]) { 18 pr_err("controller number %d invalid\n", intspec[0]); 19 return -EINVAL; 20 } 21 // s3c_intc[0]表示主中斷控制器,s3c_intc[1]表示子中斷控制器 22 intc = s3c_intc[intspec[0]]; 23 24 // 第三個參數表示的是硬件中斷號,從這里知道,主中斷的硬件中斷是0~31,子中斷的硬件中斷是32及其以上 25 *out_hwirq = intspec[0] * 32 + intspec[2]; 26 // 第四個參數表示的是中斷類型,可以查看IRQ_TYPE_SENSE_MASK定義,就知道含義: 27 // 1表示上升沿觸發,2表示下降沿觸發,3表示雙邊沿觸發,4表示高電平觸發,8表示低電平觸發,12表示高低電平觸發 28 *out_type = intspec[3] & IRQ_TYPE_SENSE_MASK; 29 // 如果是主中斷,則intc->parent為NULL, 否則非空 30 parent_intc = intc->parent; 31 if (parent_intc) { // 子中斷 32 irq_data = &intc->irqs[intspec[2]]; 33 // 對於子中斷,第二個參數才有意義,表示該子中斷所隸屬的主中斷的硬件中斷號 34 irq_data->parent_irq = intspec[1]; 35 parent_irq_data = &parent_intc->irqs[irq_data->parent_irq]; 36 parent_irq_data->sub_intc = intc; 37 // sub_bits中記錄該主中斷下的子中斷被被使用的情況 38 parent_irq_data->sub_bits |= (1UL << intspec[2]); 39 // 將主中斷號映射成虛擬中斷號 40 /* parent_intc is always s3c_intc[0], so no offset */ 41 irqno = irq_create_mapping(parent_intc->domain, intspec[1]); 42 if (irqno < 0) { 43 pr_err("irq: could not map parent interrupt\n"); 44 return irqno; 45 } 46 // 這里設置irqno對應的irq_desc的handle_irq為s3c_irq_demux 47 // 從函數名稱中就可以看出, 這個函數會再次進行檢測該主中斷的哪個子中斷被請求 48 irq_set_chained_handler(irqno, s3c_irq_demux); 49 } 50 51 return 0; 52 }
從這里我們知道,interrupts屬性中的四個參數中的含義:
三、中斷控制器驅動

1 static struct s3c24xx_irq_of_ctrl s3c2410_ctrl[] = { 2 { // 主中斷 3 .name = "intc", 4 .offset = 0, 5 }, { // 子中斷 6 .name = "subintc", 7 .offset = 0x18, // 寄存器地址偏移 8 .parent = &s3c_intc[0], 9 } 10 }; 11 12 int __init s3c2410_init_intc_of(struct device_node *np, 13 struct device_node *interrupt_parent) 14 { 15 return s3c_init_intc_of(np, interrupt_parent, 16 s3c2410_ctrl, ARRAY_SIZE(s3c2410_ctrl)); 17 } 18 IRQCHIP_DECLARE(s3c2410_irq, "samsung,s3c2410-irq", s3c2410_init_intc_of);
在內核啟動的時候,函數s3c2410_init_intc_of會被調用。

1 static int __init s3c_init_intc_of(struct device_node *np, 2 struct device_node *interrupt_parent, 3 struct s3c24xx_irq_of_ctrl *s3c_ctrl, int num_ctrl) 4 { 5 struct s3c_irq_intc *intc; 6 struct s3c24xx_irq_of_ctrl *ctrl; 7 struct irq_domain *domain; 8 void __iomem *reg_base; 9 int i; 10 11 // 對中斷控制器的reg屬性所表示地址空間進行映射 12 reg_base = of_iomap(np, 0); 13 14 // 為該中斷控制器創建irq_domain,一共支持64個中斷,對應的irq_domain_ops是s3c24xx_irq_ops_of 15 domain = irq_domain_add_linear(np, num_ctrl * 32, 16 &s3c24xx_irq_ops_of, NULL); 17 // 依次處理兩個中斷控制器 18 for (i = 0; i < num_ctrl; i++) { 19 ctrl = &s3c_ctrl[i]; 20 21 pr_debug("irq: found controller %s\n", ctrl->name); 22 // 主和子中斷控制器各分配一個s3c_irq_intc 23 intc = kzalloc(sizeof(struct s3c_irq_intc), GFP_KERNEL); 24 // 主和子中斷控制器共享一個irq_domain 25 intc->domain = domain; 26 // 為主和子中斷控制器下的每個中斷各分配一個s3c_irq_data結構體 27 intc->irqs = kzalloc(sizeof(struct s3c_irq_data) * 32, 28 GFP_KERNEL); 29 30 if (ctrl->parent) { // 子中斷控制器 31 intc->reg_pending = reg_base + ctrl->offset; // SUBSRCPND 32 intc->reg_mask = reg_base + ctrl->offset + 0x4; // INTSUBMSK 33 34 // 由於先處理的是s3c2410_ctrl[0],所以在處理子中斷的時候,*(ctrl->parent)非空 35 // 是主中斷控制器對應的s3c_irq_intc 36 if (*(ctrl->parent)) { 37 intc->parent = *(ctrl->parent); 38 } else { 39 pr_warn("irq: parent of %s missing\n", 40 ctrl->name); 41 kfree(intc->irqs); 42 kfree(intc); 43 continue; 44 } 45 } else { // 主中斷控制器 46 intc->reg_pending = reg_base + ctrl->offset; //SRCPND 47 intc->reg_mask = reg_base + ctrl->offset + 0x08; //INTMSK 48 intc->reg_intpnd = reg_base + ctrl->offset + 0x10; //INTPND 49 } 50 51 s3c24xx_clear_intc(intc); // 清除中斷 52 s3c_intc[i] = intc; 53 } 54 // 將handle_arch_irq設置為s3c24xx_handle_irq, 這樣在發生中斷后,會從匯編entry-armv.S中 55 // 調轉到s3c24xx_handle_irq 56 set_handle_irq(s3c24xx_handle_irq); 57 58 return 0; 59 }
中斷控制啟動的初始化,結構體s3c24xx_irq_ops_of中的map和xlate還沒有分析,放到下面分析。
四、interrupts屬性的解析
---> of_platform_populate
---> 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
詳細分析請參考:基於tiny4412的Linux內核移植 --- 實例學習中斷背后的知識(1)
這里我們只看看s3c24xx_irq_map_of。
在調用這個函數時,已經創建號hwirq對應的virq了:

1 static int s3c24xx_irq_map_of(struct irq_domain *h, unsigned int virq, 2 irq_hw_number_t hw) 3 { 4 unsigned int ctrl_num = hw / 32; //判斷該hwirq屬於主還是子中斷控制器 5 unsigned int intc_hw = hw % 32; // 得到跟寄存器對應的硬件中斷號 6 struct s3c_irq_intc *intc = s3c_intc[ctrl_num]; // 獲得中斷控制器對應的結構體 7 struct s3c_irq_intc *parent_intc = intc->parent; 8 struct s3c_irq_data *irq_data = &intc->irqs[intc_hw]; // 每個中斷都會有一個s3c_irq_data結構體 9 10 /* attach controller pointer to irq_data */ 11 irq_data->intc = intc; 12 irq_data->offset = intc_hw; //跟硬件寄存器對應的硬件中斷號,也就是對應的位號 13 14 // 將virq對應的irq_desc的handle_irq初始化為handle_edge_irq, 15 // 下面在s3c_irq_type中會根據中斷觸發類型再次修改 16 if (!parent_intc) // 主中斷控制器 17 irq_set_chip_and_handler(virq, &s3c_irq_chip, handle_edge_irq); 18 else // 子中斷控制器 19 irq_set_chip_and_handler(virq, &s3c_irq_level_chip, 20 handle_edge_irq); 21 22 irq_set_chip_data(virq, irq_data); 23 24 return 0; 25 }
上面主和子中斷控制雖然使用的時同一個irq domain但是對應的irq_chip卻是各自的。
1 static struct irq_chip s3c_irq_chip = { 2 .name= "s3c", 3 .irq_ack= s3c_irq_ack, // 清除中斷 4 .irq_mask= s3c_irq_mask, // 屏蔽中斷 5 .irq_unmask= s3c_irq_unmask, // 打開中斷 6 .irq_set_type= s3c_irq_type, //根據不同的觸發類型設置不同的處理函數 7 .irq_set_wake= s3c_irq_wake // 8 };
子irq_chip:
1 static struct irq_chip s3c_irq_level_chip = { 2 .name= "s3c-level", 3 .irq_mask= s3c_irq_mask, 4 .irq_unmask= s3c_irq_unmask, 5 .irq_ack= s3c_irq_ack, 6 .irq_set_type= s3c_irq_type, 7 };
在s3c_irq_mask中屏蔽中斷的時候,如果屏蔽的是子中斷,還需要判斷目前該子中斷所隸屬的主中斷是不是還有其他子中斷在使用,如果沒有的話,也會把該主中斷也給屏蔽了。
同樣,s3c_irq_unmask在打開子中斷時也會將其所隸屬的主中斷也打開。
五、子中斷分發s3c_irq_demux
在含有子中斷的主中斷被觸發后,會執行主中斷對應的virq的irq_desc的handle_irq,也就是這里的s3c_irq_demux:

1 static void s3c_irq_demux(struct irq_desc *desc) // 這里的desc是主中斷的virq的irq_desc 2 { 3 struct irq_chip *chip = irq_desc_get_chip(desc); //獲得主中斷的irq_chip 4 struct s3c_irq_data *irq_data = irq_desc_get_chip_data(desc); 5 struct s3c_irq_intc *intc = irq_data->intc; // 參考s3c24xx_irq_xlate_of函數 6 struct s3c_irq_intc *sub_intc = irq_data->sub_intc; // 參考s3c24xx_irq_xlate_of函數 7 unsigned int n, offset, irq; 8 unsigned long src, msk; 9 10 /* we're using individual domains for the non-dt case 11 * and one big domain for the dt case where the subintc 12 * starts at hwirq number 32. 13 */ 14 // 由於我們使用的是設備樹,所以這里的offset是32,也就是子中斷的起始號 15 offset = irq_domain_get_of_node(intc->domain) ? 32 : 0; 16 17 // 屏蔽該主中斷,並清中斷 18 chained_irq_enter(chip, desc); 19 // 讀取子中斷的pending寄存器,看那些子中斷被觸發了 20 src = readl_relaxed(sub_intc->reg_pending); 21 msk = readl_relaxed(sub_intc->reg_mask); 22 23 src &= ~msk; // 去掉被屏蔽的 24 src &= irq_data->sub_bits; // 去掉沒有初始化的 25 // 此時src中存放的就是將要被處理的子中斷,下面會一一處理 26 while (src) { 27 n = __ffs(src); 28 src &= ~(1 << n); 29 irq = irq_find_mapping(sub_intc->domain, offset + n); //根據hwirq找到virq 30 generic_handle_irq(irq); // 處理 31 } 32 // 打開該主中斷 33 chained_irq_exit(chip, desc); 34 }
六、設備驅動申請中斷
1、串口驅動
drivers/tty/serial/samsung.c
函數調用:
s3c24xx_serial_probe
---> s3c24xx_serial_init_port
在函數s3c24xx_serial_init_port會申請中斷
ret = platform_get_irq(platdev, 0); // UART0 receive interrupt
ret = platform_get_irq(platdev, 1); // UART0 transmit interrupt
2、I2C控制器
drivers/i2c/busses/i2c-s3c2410.c
在函數s3c24xx_i2c_probe中會申請中斷:
i2c->irq = ret = platform_get_irq(pdev, 0)
七、查看系統信息
開機后,可以從/proc/interrupts中看到當前的中斷資源申請信息:
[root@tq2440 ]# cat /proc/interrupts CPU0 7: 926 s3c-eint 7 Edge eth0 8: 0 s3c 8 Edge s3c2410-rtc tick 13: 567308 s3c 13 Edge samsung_time_irq 26: 0 s3c 26 Edge ohci_hcd:usb1 27: 4 s3c 27 Edge 54000000.i2c //I2C 30: 0 s3c 30 Edge s3c2410-rtc alarm 32: 53 s3c-level 32 Level 50000000.serial //UART0 RXD 33: 230 s3c-level 33 Level 50000000.serial //UART0 TXD 59: 0 s3c-level 59 Edge 53000000.watchdog

[ 1.851407] [<c035004c>] (s3c24xx_i2c_irq) from [<c00447f0>] (__handle_irq_event_percpu+0x3c/0x130) [ 1.851457] [<c00447f0>] (__handle_irq_event_percpu) from [<c0044900>] (handle_irq_event_percpu+0x1c/0x54) [ 1.851490] [<c0044900>] (handle_irq_event_percpu) from [<c0044960>] (handle_irq_event+0x28/0x3c) [ 1.851526] [<c0044960>] (handle_irq_event) from [<c00476c0>] (handle_edge_irq+0xbc/0x190) [ 1.851558] [<c00476c0>] (handle_edge_irq) from [<c00441c4>] (__handle_domain_irq+0x6c/0xcc) [ 1.851589] [<c00441c4>] (__handle_domain_irq) from [<c0009444>] (s3c24xx_handle_irq+0x6c/0x12c) [ 1.851622] [<c0009444>] (s3c24xx_handle_irq) from [<c000e5fc>] (__irq_svc+0x5c/0x78)
[ 2.393176] [<c0293410>] (s3c24xx_serial_tx_chars) from [<c00447f0>] (__handle_irq_event_percpu+0x3c/0x130) [ 2.393218] [<c00447f0>] (__handle_irq_event_percpu) from [<c0044900>] (handle_irq_event_percpu+0x1c/0x54) [ 2.393249] [<c0044900>] (handle_irq_event_percpu) from [<c0044960>] (handle_irq_event+0x28/0x3c) [ 2.393283] [<c0044960>] (handle_irq_event) from [<c004740c>] (handle_level_irq+0x90/0x114) [ 2.393312] [<c004740c>] (handle_level_irq) from [<c0043fd8>] (generic_handle_irq+0x2c/0x40) [ 2.393379] [<c0043fd8>] (generic_handle_irq) from [<c0242868>] (s3c_irq_demux+0xc8/0x140) [ 2.393424] [<c0242868>] (s3c_irq_demux) from [<c00441c4>] (__handle_domain_irq+0x6c/0xcc) [ 2.393460] [<c00441c4>] (__handle_domain_irq) from [<c0009444>] (s3c24xx_handle_irq+0x6c/0x12c) [ 2.393492] [<c0009444>] (s3c24xx_handle_irq) from [<c000e5fc>] (__irq_svc+0x5c/0x78)
[ 9.223769] [<c0294570>] (s3c24xx_serial_rx_chars) from [<c00447f0>] (__handle_irq_event_percpu+0x3c/0x130) [ 9.223817] [<c00447f0>] (__handle_irq_event_percpu) from [<c0044900>] (handle_irq_event_percpu+0x1c/0x54) [ 9.223851] [<c0044900>] (handle_irq_event_percpu) from [<c0044960>] (handle_irq_event+0x28/0x3c) [ 9.223883] [<c0044960>] (handle_irq_event) from [<c004740c>] (handle_level_irq+0x90/0x114) [ 9.223915] [<c004740c>] (handle_level_irq) from [<c0043fd8>] (generic_handle_irq+0x2c/0x40) [ 9.223978] [<c0043fd8>] (generic_handle_irq) from [<c0242868>] (s3c_irq_demux+0xc8/0x140) [ 9.224017] [<c0242868>] (s3c_irq_demux) from [<c00441c4>] (__handle_domain_irq+0x6c/0xcc) [ 9.224050] [<c00441c4>] (__handle_domain_irq) from [<c0009444>] (s3c24xx_handle_irq+0x6c/0x12c) [ 9.224081] [<c0009444>] (s3c24xx_handle_irq) from [<c000e5fc>] (__irq_svc+0x5c/0x78)
完。