1. 程式人生 > >【原創】中斷子系統-ARM GPIO中斷處理流程

【原創】中斷子系統-ARM GPIO中斷處理流程

[TOC] 本文以AM5728 GPIO中斷為例,簡單介紹有關從註冊GIC中斷到 驅動使用GPIO中斷的整個過程,主要關注中斷相關處理流程,為後續ARM平臺xenomai IPIPE中斷處理流程做鋪墊。 第一部分: GIC中斷控制器的註冊。 第二部分:裝置樹的device node在向platform_device轉化的過程中節點的interrupts屬性的處理。 第三部分:platform_device註冊新增。 第四部分:GPIO控制器驅動的註冊,大部分GPIO控制器同時具備interrupt controller的功能。 第五部分:引用GPIO中斷的節點的解析。 ```C / { #address-cells = <2>; #size-cells = <2>; compatible = "ti,dra7xx"; interrupt-parent = <&crossbar_mpu>; chosen { }; gic: interrupt-controller@48211000 { compatible = "arm,cortex-a15-gic"; interrupt-controller; #interrupt-cells = <3>; reg = <0x0 0x48211000 0x0 0x1000>, <0x0 0x48212000 0x0 0x2000>, <0x0 0x48214000 0x0 0x2000>, <0x0 0x48216000 0x0 0x2000>; interrupts = ; interrupt-parent = <&gic>; }; ocp { compatible = "ti,dra7-l3-noc", "simple-bus"; #address-cells = <1>; #size-cells = <1>; ranges = <0x0 0x0 0x0 0xc0000000>; ti,hwmods = "l3_main_1", "l3_main_2"; reg = <0x0 0x44000000 0x0 0x1000000>, <0x0 0x45000000 0x0 0x1000>; interrupts-extended = <&crossbar_mpu GIC_SPI 4 IRQ_TYPE_LEVEL_HIGH>, <&wakeupgen GIC_SPI 10 IRQ_TYPE_LEVEL_HIGH>; gpio1: gpio@4ae10000 { ...... }; gpio2: gpio@48055000 { ...... }; gpio3: gpio@48057000 { ...... }; gpio4: gpio@48059000 { ...... }; gpio5: gpio@4805b000 { ...... }; gpio6: gpio@4805d000 { ...... }; gpio7: gpio@48051000 { compatible = "ti,omap4-gpio"; reg = <0x48051000 0x200>; interrupts = ; ti,hwmods = "gpio7"; gpio-controller; #gpio-cells = <2>; interrupt-controller; #interrupt-cells = <2>; }; gpio8: gpio@48053000 { ...... }; }; }; ``` - 由於中斷級聯,對於GPIO控制器`gpio@48051000`下的每個GPIO來說,它們產生中斷後,不能直接通知GIC,而是先通知中斷控制器`gpio@48051000`,然後`gpio@48051000`再通過SPI-30通知GIC,然後GIC會通過irq或者firq觸發某個CPU中斷。 - root gic就是上面的"arm,cortex-a15-gic",它的interrupt cells是3, 表示引用gic上的一箇中斷需要三個引數 - Linux中每一個irq_domain都對應一個irq_chip,irq_chip是kernel對中斷控制器的軟體抽象。 ## 第一部分 GIC中斷控制器的註冊 ### 1. GIC驅動分析 ARM平臺的裝置資訊,都是通過`Device Tree`裝置樹來新增,由解析裝置樹到設備註冊新增的流程如下: ![](https://wsg-blogs-pic.oss-cn-beijing.aliyuncs.com/xenomai/dts-platform_device.png) GIC裝置樹資訊如下 ```C /*arch\arm\boot\dts\dra7.dtsi*/ gic: interrupt-controller@48211000 { compatible = "arm,cortex-a15-gic"; interrupt-controller; #interrupt-cells = <3>; reg = <0x0 0x48211000 0x0 0x1000>, <0x0 0x48212000 0x0 0x2000>, <0x0 0x48214000 0x0 0x2000>, <0x0 0x48216000 0x0 0x2000>; interrupts = ; interrupt-parent = <&gic>; }; ``` - `compatible`欄位:用於與具體的驅動來進行匹配,比如圖片中`arm,cortex-a15-gic`,可以根據這個名字去匹配對應的驅動程式; - `interrupt-cells`欄位:用於指定編碼一箇中斷源所需要的單元個數,這個值為3。比如在外設在裝置樹中新增中斷訊號時,通常能看到類似`interrupts = <0 23 4>;`的資訊,第一個單元0,表示的是中斷型別(`1:PPI,0:SPI`),第二個單元23表示的是中斷號,第三個單元4表示的是中斷觸發的型別(電平觸發OR邊緣觸發); - `reg`欄位:描述中斷控制器的地址資訊以及地址範圍,比如圖片中分別制定了`GIC Distributor(GICD)`和`GIC CPU Interface(GICC)`的地址資訊; - `interrupt-controller`欄位:表示該裝置是一箇中斷控制器,外設可以連線在該中斷控制器上; - 關於裝置數的各個欄位含義,詳細可以參考`Documentation/devicetree/bindings`下的對應資訊; 裝置樹的資訊,是怎麼新增到系統中的呢?`Device Tree`最終會編譯成`dtb`檔案,並通過Uboot傳遞給核心,在核心啟動後會將`dtb`檔案解析成`device_node`結構。 ![](D:\文件\原始碼筆記\xenomai blogs\blogs\unflatten_device_tree.png) - 裝置樹的節點資訊,最終會變成`device_node`結構,在記憶體中維持一個樹狀結構; - 裝置與驅動,會根據`compatible`欄位進行匹配; ### 2.GIC驅動流程分析 ![img](https://wsg-blogs-pic.oss-cn-beijing.aliyuncs.com/xenomai/1771657-20200531111308070-739998455.png) - 首先需要了解一下連結指令碼`vmlinux.lds`,指令碼中定義了一個`__irqchip_of_table`段,該段用於存放中斷控制器資訊,用於最終來匹配裝置; - 在GIC驅動程式中,使用`IRQCHIP_DECLARE`巨集來宣告結構資訊,包括`compatible`欄位和回撥函式,該巨集會將這個結構放置到`__irqchip_of_table`欄位中; - 在核心啟動初始化中斷的函式中,`of_irq_init`函式會去查詢裝置節點資訊,該函式的傳入引數就是`__irqchip_of_table`段,由於`IRQCHIP_DECLARE`已經將資訊填充好了,`of_irq_init`就會遍歷`__irqchip_of_table`,按照interrupt controller的連線關係從root開始,依次初始化每一個interrupt controller,`of_irq_init`函式會根據`arm,gic-400`去查詢對應的裝置節點,並獲取裝置的資訊。 - `or_irq_init`函式中,最終會回撥`IRQCHIP_DECLARE`宣告的回撥函式,也就是`gic_of_init`,而這個函式就是GIC驅動的初始化入口函數了; ```C IRQCHIP_DECLARE(cortex_a15_gic, "arm,cortex-a15-gic", gic_of_init); IRQCHIP_DECLARE(cortex_a9_gic, "arm,cortex-a9-gic", gic_of_init); IRQCHIP_DECLARE(cortex_a7_gic, "arm,cortex-a7-gic", gic_of_init); ``` - GIC的工作,本質上是由中斷訊號來驅動,因此驅動本身的工作就是完成各類資訊的初始化,註冊好相應的回撥函式,以便能在訊號到來之時去執行; - `set_smp_process_call`設定`__smp_cross_call`函式指向`gic_raise_softirq`,本質上就是通過軟體來觸發GIC的`SGI中斷`,用於核間互動; - `cpuhp_setup_state_nocalls`函式,設定好CPU進行熱插拔時GIC的回撥函式,以便在CPU熱插拔時做相應處理; - `set_handle_irq`函式的設定很關鍵,它將全域性函式指標`handle_arch_irq`指向了`gic_handle_irq`,而處理器在進入中斷異常時,會跳轉到`handle_arch_irq`執行,所以,可以認為它就是中斷處理的入口函數了; - 驅動中完成了各類函式的註冊,此外還完成了`irq_chip`, `irq_domain`等結構體的初始化,計算這個GIC模組所支援的中斷個數gic_irqs,然後建立一個linear irq domain。此時尚未分配virq,也沒有建立hwirq跟virq的對映; ```C 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->domain = irq_domain_create_linear(handle, gic_irqs, &gic_irq_domain_hierarchy_ops, gic); ``` 在初始化的時候既沒有給hwirq分配對應的virq,也沒有建立二者之間的對映,這部分工作會到後面有人引用GIC上的某個中斷時再分配和建立。 - 最後,完成GIC硬體模組的初始化設定,以及電源管理相關的註冊等工作; ## 第二部分 device node轉化為platform_device 相關程式碼: drivers/of/platform.c 這個轉化過程是呼叫`of_platform_populate`開始的。以`gpio1: gpio@4ae10000`為例,暫時只關心interrupts屬性的處理,函式呼叫關係: ![](https://wsg-blogs-pic.oss-cn-beijing.aliyuncs.com/xenomai/dev_node-platfrom_dev-1.png) ```C struct platform_device *of_device_alloc(struct device_node *np, const char *bus_id, struct device *parent) { struct platform_device *dev; int rc, i, num_reg = 0, num_irq; struct resource *res, temp_res; dev = platform_device_alloc("", PLATFORM_DEVID_NONE); if (!dev) return NULL; /* count the io and irq resources */ while (of_address_to_resource(np, num_reg, &temp_res) == 0) num_reg++; num_irq = of_irq_count(np);/* 統計這個節點的interrupts屬性中描述了幾個中斷*/ /* Populate the resource table */ if (num_irq || num_reg) { res = kzalloc(sizeof(*res) * (num_irq + num_reg), GFP_KERNEL); if (!res) { platform_device_put(dev); return NULL; } dev->num_resources = num_reg + num_irq; dev->resource = res; for (i = 0; i < num_reg; i++, res++) { rc = of_address_to_resource(np, i, res); WARN_ON(rc); } /*解析interrupts屬性,將每一箇中斷轉化為resource結構體*/ if (of_irq_to_resource_table(np, res, num_irq) != num_irq) pr_debug("not all legacy IRQ resources mapped for %s\n", np->
name); } dev->dev.of_node = of_node_get(np); dev->dev.fwnode = &np->fwnode; dev->dev.parent = parent ? : &platform_bus; if (bus_id) dev_set_name(&dev->dev, "%s", bus_id); else of_device_make_bus_id(&dev->dev); return dev; } ``` 這裡主要涉及到兩個函式`of_irq_count`和`of_irq_to_resource_table`,傳入的np就是`gpio1: gpio@4ae10000`節點。 - of_irq_count 這個函式會解析interrupts屬性,並統計其中描述了幾個中斷。 簡化如下:找到`gpio1: gpio@4ae10000`節點的所隸屬的interrupt-controller,即interrupt-controller@10490000節點,然後獲得其#interrupt-cells屬性的值,因為只要知道了這個值,也就知道了在interrupts屬性中描述一箇中斷需要幾個引數,也就很容易知道interrupts所描述的中斷個數。這裡關鍵的函式是`of_irq_parse_one`: ```C int of_irq_count(struct device_node *dev) { struct of_phandle_args irq; int nr = 0; while (of_irq_parse_one(dev, nr, &irq) == 0) nr++; return nr; } ``` nr表示的是index,of_irq_parse_one每次成功返回,都表示成功從interrupts屬性中解析到了第nr箇中斷,同時將關於這個中斷的資訊存放到irq中,struct of_phandle_args的含義如下: ```C #define MAX_PHANDLE_ARGS 16 struct of_phandle_args { struct device_node *np; // 用於存放賦值處理這個中斷的中斷控制器的節點 int args_count;// 就是interrupt-controller的#interrupt-cells的值 uint32_t args[MAX_PHANDLE_ARGS];// 用於存放具體描述某一箇中斷的引數的值 }; ``` 最後將解析到的中斷個數返回。 - **of_irq_to_resource_table** 知道interrupts中描述了幾個中斷後,這個函式開始將這些中斷轉換為resource,這個是由of_irq_to_resource函式完成。 ```C int of_irq_to_resource_table(struct device_node *dev, struct resource *res, int nr_irqs) { int i; for (i = 0; i < nr_irqs; i++, res++) if (of_irq_to_resource(dev, i, res) <= 0)//將這些中斷轉換為resource break; return i; } ``` 第二個引數i表示的是index,即interrupts屬性中的第i箇中斷。 ```C int of_irq_to_resource(struct device_node *dev, int index, struct resource *r) { int irq = of_irq_get(dev, index);// 返回interrupts中第index個hwirq中斷對映到的virq if (irq < 0) return irq; /* Only dereference the resource if both the * resource and the irq are valid. */ if (r && irq) { // 將這個irq封裝成resource const char *name = NULL; memset(r, 0, sizeof(*r)); /* * Get optional "interrupt-names" property to add a name * to the resource. 獲取可選的“中斷名稱”屬性,以向資源新增名稱。*/ of_property_read_string_index(dev, "interrupt-names", index, &name); r->
start = r->end = irq; // 全域性唯一的virq r->flags = IORESOURCE_IRQ | irqd_get_trigger_type(irq_get_irq_data(irq));// 這個中斷的屬性,如上升沿還是下降沿觸發 r->name = name ? name : of_node_full_name(dev); } return irq; } ``` 所以,分析重點是irq_of_parse_and_map,這個函式會獲得`gpio@4ae10000`節點的interrupts屬性的第index箇中斷的引數,這是通過`of_irq_parse_one`完成的,然後獲得該中斷所隸屬的interrupt-controller的irq domain,也就是前面GIC註冊的那個irq domain,利用該domain的`of_xlate`函式從前面表示第index箇中斷的引數中解析出hwirq和中斷型別,最後從系統中為該hwriq分配一個全域性唯一的virq,並將對映關係存放到中斷控制器的irq domain中,也就是gic的irq domain。 下面結合kernel程式碼分析一下: ```C int of_irq_get(struct device_node *dev, int index) { int rc; struct of_phandle_args oirq; struct irq_domain *domain; rc = of_irq_parse_one(dev, index, &oirq);// 獲得interrupts的第index箇中斷引數,並封裝到oirq中 if (rc) return rc; domain = irq_find_host(oirq.np); if (!domain) return -EPROBE_DEFER; return irq_create_of_mapping(&oirq); //返回對映到的virq } ``` 獲取裝置資料中的引數,然後呼叫irq_create_of_mapping對映hwirq到virq,這個過程中先分配virq、分配irq_desc,然後呼叫domain的map函式建立hwirq到該virq的對映,最後以virq為索引將irq_desc插入基數樹。 ```C unsigned int irq_create_of_mapping(struct of_phandle_args *irq_data) { struct irq_fwspec fwspec; of_phandle_args_to_fwspec(irq_data, &fwspec);// 將irq_data中的資料轉存到fwspec return irq_create_fwspec_mapping(&fwspec); } ``` ```C unsigned int irq_create_fwspec_mapping(struct irq_fwspec *fwspec) { struct irq_domain *domain; struct irq_data *irq_data; irq_hw_number_t hwirq; unsigned int type = IRQ_TYPE_NONE; int virq; if (fwspec->
fwnode) { /*這裡的程式碼主要是找到irq domain。這是根據上一個函式傳遞進來的引數irq_data的np成員來尋找的*/ domain = irq_find_matching_fwspec(fwspec, DOMAIN_BUS_WIRED); if (!domain) domain = irq_find_matching_fwspec(fwspec, DOMAIN_BUS_ANY); } else { domain = irq_default_domain; } ...... /*如果沒有定義xlate函式,那麼取interrupts屬性的第一個cell作為HW interrupt ID。*/ if (irq_domain_translate(domain, fwspec, &hwirq, &type)) return 0; ...... /* 解析完了,最終還是要呼叫irq_create_mapping函式來建立HW interrupt ID和IRQ number的對映關係。*/ virq = irq_find_mapping(domain, hwirq); if (virq) { if (type == IRQ_TYPE_NONE || type == irq_get_trigger_type(virq)) return virq; if (irq_get_trigger_type(virq) == IRQ_TYPE_NONE) { irq_data = irq_get_irq_data(virq); if (!irq_data) return 0; /*如果有需要,呼叫irq_set_irq_type函式設定trigger type*/ irqd_set_trigger_type(irq_data, type); return virq; } pr_warn("type mismatch, failed to map hwirq-%lu for %s!\n", hwirq, of_node_full_name(to_of_node(fwspec->fwnode))); return 0; } if (irq_domain_is_hierarchy(domain)) { // 對於GIC的irq domain這樣定義了alloc的domain來說,走這個分支 virq = irq_domain_alloc_irqs(domain, 1, NUMA_NO_NODE, fwspec); if (virq <= 0) return 0; } else { /* Create mapping 建立HW interrupt ID和IRQ number的對映關係。 */ virq = irq_create_mapping(domain, hwirq); if (!virq) return virq; } irq_data = irq_get_irq_data(virq); if (!irq_data) { if (irq_domain_is_hierarchy(domain)) irq_domain_free_irqs(virq, 1); else irq_dispose_mapping(virq); return 0; } /* Store trigger type */ irqd_set_trigger_type(irq_data, type); return virq; //返回對映到的virq } ``` 看一下gic irq domain的translate的過程: ```C static int gic_irq_domain_translate(struct irq_domain *d, struct irq_fwspec *fwspec, unsigned long *hwirq, unsigned int *type) { if (is_of_node(fwspec->fwnode)) { if (fwspec->param_count < 3)// 檢查描述中斷的引數個數是否合法 return -EINVAL; /* 這裡加16的目的是跳過SGI中斷,因為SGI用於CPU之間通訊,不歸中斷子系統管 10;GIC支援的中斷中從0-15號屬於SGI,16-32屬於PPI,32-1020屬於SPI*/ *hwirq = fwspec->param[1] + 16; /*從這裡可以看到,描述GIC中斷的三個引數中第一個表示中斷種類,0表示的是SPI,非0表示PPI; 這裡加16的意思是跳過PPI; 同時我們也知道了,第二個引數表示某種型別的中斷(PPI or SPI)中的第幾個(從0開始)*/ if (!fwspec->param[0]) *hwirq += 16; // 第三個引數表示的中斷的型別,如上升沿、下降沿或者高低電平觸發 *type = fwspec->param[2] & IRQ_TYPE_SENSE_MASK; return 0; } ...... return -EINVAL; } ``` 通過這個函式,我們就獲得了fwspec所表示的hwirq和type 接著看一下irq_find_mapping,如果hwirq之前跟virq之間發生過對映,會存放到irq domain中,這個函式就是查詢irq domain,以hwirq為索引,尋找virq; ```C unsigned int irq_find_mapping(struct irq_domain *domain, irq_hw_number_t hwirq) { struct irq_data *data; ...... if (hwirq < domain->revmap_direct_max_irq) { data = irq_domain_get_irq_data(domain, hwirq); if (data && data->hwirq == hwirq) return hwirq; } /* Check if the hwirq is in the linear revmap. */ if (hwirq < domain->revmap_size)//如果是線性對映irq domain的條件,hwirq作為數字下標 return domain->linear_revmap[hwirq]; ...... data = radix_tree_lookup(&domain->revmap_tree, hwirq);// hwirq作為key return data ? data->irq : 0; } ``` 下面分析virq的分配以及對映,對於GIC irq domain,由於其ops定義了alloc,在註冊irq domain的時候會執行`domain->flags |= IRQ_DOMAIN_FLAG_HIERARCHY` ```C int __irq_domain_alloc_irqs(struct irq_domain *domain, int irq_base, unsigned int nr_irqs, int node, void *arg, bool realloc, const struct cpumask *affinity) { int i, ret, virq; /* 下面這個函式會從系統中一個唯一的virq,其實就是全域性變數allocated_irqs從低位到高位第一個為0的位的位號. 然後將allocated_irqs的第virq位置為1, 然後會為這個virq分配一個irq_desc, virq會存放到irq_desc的irq_data.irq中. 最後將這個irq_desc存放到irq_desc_tree中,以virq為key,函式irq_to_desc就是以virq為key,查詢irq_desc_tree 迅速定位到irq_desc*/ virq = irq_domain_alloc_descs(irq_base, nr_irqs, 0, node, affinity); irq_domain_alloc_irq_data(domain, virq, nr_irqs); ...... ret = irq_domain_alloc_irqs_hierarchy(domain, virq, nr_irqs, arg); ...... for (i = 0; i < nr_irqs; i++) // 將virq跟hwirq的對映關係存放到irq domain中,這樣就可以通過hwirq在該irq_domain中快速找到virq irq_domain_insert_irq(virq + i); ..... return virq; } ``` `irq_domain_alloc_irq_data` 會根據virq獲得對應的`irq_desc`,然後將`domain`賦值給`irq_desc->irq_data->domain`. `irq_domain_alloc_irqs_recursive` 這個函式會呼叫gic irq domain的`domain->ops->alloc`,即`gic_irq_domain_alloc` 下面分析irq_create_mapping,對於irq domain的ops中沒有定義alloc的domain,會執行這個函式 ​ ---> irq_create_mapping 為hwirq分配virq,並存放對映到irq domain中 ```C unsigned int irq_create_mapping(struct irq_domain *domain, irq_hw_number_t hwirq) { struct device_node *of_node; int virq; ...... of_node = irq_domain_get_of_node(domain); /* Check if mapping already exists 如果對映已經存在,那麼不需要對映,直接返回 */ virq = irq_find_mapping(domain, hwirq); if (virq) { pr_debug("-> existing mapping on virq %d\n", virq); return virq; } /* Allocate a virtual interrupt number 分配虛擬中斷號*/ virq = irq_domain_alloc_descs(-1, 1, hwirq, of_node_to_nid(of_node), NULL); ..... if (irq_domain_associate(domain, virq, hwirq)) {//建立mapping irq_free_desc(virq); return 0; } ..... return virq; } ``` 至此,device node在轉化為platform_device過程中的interrupts屬性的處理就暫時分析完畢,後面會呼叫`device_add()`註冊該platform_device,然後匹配到的platform_driver的probe就會被呼叫。 通過列印資訊可知GPIO7的hwirq與virq的對映關係: ```shell [19491.235350] virq is 43,hwirq is 30 ``` ![of_device_alloc](中斷子系統-GPIO中斷處理流程/of_device_alloc.png) ==需要關注的是 `domain->ops->map()`,該函式中戶設定該中斷的`desc->handle_irq()`,對於GIC來說,map函式為`gic_irq_domain_map`,SPI中斷handle_irq()設定為handle_fasteoi_irq。== ## 第三部分:platform_device註冊新增 ![device_add](https://wsg-blogs-pic.oss-cn-beijing.aliyuncs.com/xenomai/of_device_alloc.png) platform_driver的probe就會被呼叫。 ![](https://wsg-blogs-pic.oss-cn-beijing.aliyuncs.com/xenomai/platform-bus.png) ## 第四部分 GPIO控制器驅動 相關程式碼: drivers\gpio\gpio-omap.c 在`gpio@48051000`節點轉化成的platform_device被註冊的時候,`omap_gpio_probe()`會被呼叫。這個函式目前我們先只分析跟中斷相關的。 ```C static int omap_gpio_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct device_node *node = dev->of_node; const struct of_device_id *match; const struct omap_gpio_platform_data *pdata; struct resource *res; struct gpio_bank *bank; struct irq_chip *irqc; int ret; match = of_match_device(of_match_ptr(omap_gpio_match), dev); ...... pdata = match ? match->data : dev_get_platdata(dev); ...... bank = devm_kzalloc(dev, sizeof(struct gpio_bank), GFP_KERNEL); ...... /*irq_chip用於抽象該GPIO中斷控制器*/ irqc = devm_kzalloc(dev, sizeof(*irqc), GFP_KERNEL); ...... irqc->irq_startup = omap_gpio_irq_startup, irqc->irq_shutdown = omap_gpio_irq_shutdown, irqc->irq_ack = omap_gpio_ack_irq, irqc->irq_mask = omap_gpio_mask_irq, irqc->irq_mask_ack = omap_gpio_mask_ack_irq, irqc->irq_unmask = omap_gpio_unmask_irq, irqc->irq_set_type = omap_gpio_irq_type, irqc->irq_set_wake = omap_gpio_wake_enable, irqc->irq_bus_lock = omap_gpio_irq_bus_lock, irqc->irq_bus_sync_unlock = gpio_irq_bus_sync_unlock, irqc->name = dev_name(&pdev->dev); irqc->flags = IRQCHIP_MASK_ON_SUSPEND | IRQCHIP_PIPELINE_SAFE; bank->irq = platform_get_irq(pdev, 0);/*該irq已經是虛擬的了 詳見of_irq_to_resource*/ ...... bank->chip.parent = dev; bank->chip.owner = THIS_MODULE; bank->dbck_flag = pdata->dbck_flag; bank->stride = pdata->bank_stride; bank->width = pdata->bank_width;/*該bank GPIO數*/ bank->is_mpuio = pdata->is_mpuio; bank->non_wakeup_gpios = pdata->non_wakeup_gpios; bank->regs = pdata->regs; #ifdef CONFIG_OF_GPIO bank->chip.of_node = of_node_get(node); #endif ...... platform_set_drvdata(pdev, bank); ...... ret = omap_gpio_chip_init(bank, irqc);/*完成GPIO中斷控制器註冊*/ ...... omap_gpio_show_rev(bank); ...... list_add_tail(&bank->node, &omap_gpio_list); return 0; } ``` 需要注意的是,通過`platform_get_irq(pdev, 0)`獲取該bank對應的中斷時,已經是virq了,不是裝置樹裡指定的GIC hwirq。 `omap_gpio_chip_init`為該bank註冊GPIO中斷控制器。 ```C static int omap_gpio_chip_init(struct gpio_bank *bank, struct irq_chip *irqc) { struct gpio_irq_chip *irq; static int gpio; const char *label; int irq_base = 0; int ret; /*GPIO操作回撥函式*/ bank->chip.request = omap_gpio_request; bank->chip.free = omap_gpio_free; bank->chip.get_direction = omap_gpio_get_direction; bank->chip.direction_input = omap_gpio_input; bank->chip.get = omap_gpio_get; bank->chip.get_multiple = omap_gpio_get_multiple; bank->chip.direction_output = omap_gpio_output; bank->chip.set_config = omap_gpio_set_config; bank->chip.set = omap_gpio_set; bank->chip.set_multiple = omap_gpio_set_multiple; label = devm_kasprintf(bank->chip.parent, GFP_KERNEL, "gpio-%d-%d", gpio, gpio + bank->width - 1); bank->chip.label = label; bank->chip.base = gpio;//該bank中的第一個gpio的邏輯gpio號 bank->chip.ngpio = bank->width;//該bank GPIO數 irq = &bank->chip.irq; irq->chip = irqc; //設定該bank 的irq_chip irq->handler = handle_bad_irq; //該中斷控制器預設中斷處理函式 irq->default_type = IRQ_TYPE_NONE; //中斷預設觸發方式 irq->num_parents = 1; irq->parents = &bank->irq; irq->first = irq_base;//該GPIO中斷控制器的起始中斷號0 ret = gpiochip_add_data(&bank->chip, bank); // 這裡的d->irq是節點gpio@48051000的interrupts屬性所對映到的virq,對應的hwirq就是SPI-30 // 這裡申請了中斷,在中斷處理函式omap_gpio_irq_handler中會獲得發生中斷的引腳,轉化為該GPIO控制器的hwirq,再進行一步處理 ret = devm_request_irq(bank->chip.parent, bank->irq, omap_gpio_irq_handler, 0, dev_name(bank->chip.parent), bank); ank->width; return ret; } ``` `gpiochip_add_data` ```C #define gpiochip_add_data(chip, data) gpiochip_add_data_with_key(chip, data, NULL, NULL) int gpiochip_add_data_with_key(struct gpio_chip *chip, void *data, struct lock_class_key *lock_key, struct lock_class_key *request_key) { unsigned long flags; int status = 0; unsigned i; int base = chip->base; struct gpio_device *gdev; // 每一個bank都都應一個唯一的gpio_device和gpio_chip gdev = kzalloc(sizeof(*gdev), GFP_KERNEL); gdev->dev.bus = &gpio_bus_type; gdev->chip = chip; chip->gpiodev = gdev; if (chip->parent) { gdev->dev.parent = chip->parent; gdev->dev.of_node = chip->parent->of_node; } #ifdef CONFIG_OF_GPIO /* If the gpiochip has an assigned OF node this takes precedence */ if (chip->of_node) gdev->dev.of_node = chip->of_node; else chip->of_node = gdev->dev.of_node; #endif // 分配一個唯一的id gdev->id = ida_simple_get(&gpio_ida, 0, 0, GFP_KERNEL); dev_set_name(&gdev->dev, "gpiochip%d", gdev->id); device_initialize(&gdev->dev); dev_set_drvdata(&gdev->dev, gdev); if (chip->parent && chip->parent->driver) gdev->owner = chip->parent->driver->owner; else if (chip->owner) /* TODO: remove chip->owner */ gdev->owner = chip->owner; else gdev->owner = THIS_MODULE; // 為這個chip下的每一個gpio都要分配一個gpio_desc結構體 gdev->descs = kcalloc(chip->ngpio, sizeof(gdev->descs[0]), GFP_KERNEL); gdev->label = kstrdup_const(chip->label ?: "unknown", GFP_KERNEL); // 這個chip中含有的gpio的個數 gdev->ngpio = chip->ngpio; //gdev->data代表這個bank gdev->data = data; spin_lock_irqsave(&gpio_lock, flags); // base表示的是這個bank在系統中的邏輯gpio號 gdev->base = base; // 將這個bank對應的gpio_device新增到全域性連結串列gpio_devices中 // 在新增的時候會根據gdev->base和ngpio在gpio_devices連結串列中找到合適的位置 status = gpiodev_add_to_list(gdev); spin_unlock_irqrestore(&gpio_lock, flags); /*為每個GPIO分配gpio_desc,建立與gdev的聯絡*/ for (i = 0; i < chip->ngpio; i++) { struct gpio_desc *desc = &gdev->descs[i]; desc->gdev = gdev; desc->flags = !chip->direction_input ? (1 << FLAG_IS_OUT) : 0; } // 預設這個chip下的所有gpio都是可以產生中斷 status = gpiochip_irqchip_init_valid_mask(chip); status = gpiochip_init_valid_mask(chip); /*為該bank新增irq_chip,並建立一個irq_domain 只是建立了irq domain,還沒有存放任何中斷對映關係,在需要的時候才會對映。*/ status = gpiochip_add_irqchip(chip, lock_key, request_key); status = of_gpiochip_add(chip); acpi_gpiochip_add(chip); machine_gpiochip_add(chip); if (gpiolib_initialized) { status = gpiochip_setup_dev(gdev); } return 0; } ``` ---> of_gpiochip_add(struct gpio_chip *chip) ```C int of_gpiochip_add(struct gpio_chip *chip) { int status; ...... if (!chip->of_xlate) { /*pio_chip的of_gpio_n_cells被賦值為2,表示引用一個gpio資源需要兩個引數, 負責解析這兩個引數函式以的of_xlate函式為of_gpio_simple_xlate, 其中第一個引數表示gpio號(在對應的bank中),第二個表示flag*/ chip->of_gpio_n_cells = 2; chip->of_xlate = of_gpio_simple_xlate; } ``` 這裡需要看一下of_gpio_simple_xlate的實現,這個在下面的分析中會被回撥. ```C int of_gpio_simple_xlate(struct gpio_chip *gc, const struct of_phandle_args *gpiospec, u32 *flags) { ...... if (flags) // 第二個引數表示的是flag *flags = gpiospec->args[1]; // 第一個引數表示的是gpio號 return gpiospec->args[0]; } ``` 下看建立domain流程: ```C static int gpiochip_add_irqchip(struct gpio_chip *gpiochip, struct lock_class_key *lock_key, struct lock_class_key *request_key) { struct irq_chip *irqchip = gpiochip->irq.chip; const struct irq_domain_ops *ops; struct device_node *np; unsigned int type; unsigned int i; ...... np = gpiochip->gpiodev->dev.of_node; type = gpiochip->irq.default_type; //預設觸發型別 ..... gpiochip->to_irq = gpiochip_to_irq; /*驅動request irq時呼叫*/ gpiochip->irq.default_type = type; gpiochip->irq.lock_key = lock_key; gpiochip->irq.request_key = request_key; if (gpiochip->irq.domain_ops) ops = gpiochip->irq.domain_ops; else ops = &gpiochip_domain_ops; /* 建立一個linear irq domain,從這裡看到,每一個bank都會有一個irq domain,ngpio是這個bank含有的gpio的個數,也是這個irq domain支援的中斷的個數*/ gpiochip->irq.domain = irq_domain_add_simple(np, gpiochip->ngpio, gpiochip->irq.first, ops, gpiochip); ...... return 0; } ``` 上面也只是建立了irq domain,還沒有存放任何中斷對映關係,在需要的時候才會對映。 該irq domain的irq_domain_ops為`gpiochip_domain_ops`; ```C static const struct irq_domain_ops gpiochip_domain_ops = { .map = gpiochip_irq_map, .unmap = gpiochip_irq_unmap, /* Virtually all GPIO irqchips are twocell:ed */ .xlate = irq_domain_xlate_twocell, }; ``` gpio7這個中斷在GIC級的處理函式註冊為`omap_gpio_irq_handler`; ```C ret = devm_request_irq(bank->chip.parent, bank->irq, omap_gpio_irq_handler, 0, dev_name(bank->chip.parent), bank); ``` 為`bank->irq`建立一個action,設定該`action–>handler`為`omap_gpio_irq_handler`,將該action新增到`bank->irq`對應的irq_desc的actions連結串列。 ## 第五部分 引用GPIO中斷的節點的解析 從上面的分析中我們知道了如下幾點: 1. 每一個bank都對應一個gpio_chip和gpio_device 2. 這個bank下的每一個gpio都會對應一個唯一的gpio_desc結構體,這些結構提的首地址存放在gpio_device的desc中 3. 上面的gpio_device會加入到全域性gpio_devices連結串列中 4. gpio_chip的of_gpio_n_cells被賦值為2,表示引用一個gpio資源需要兩個引數,負責解析這兩個引數函式以的of_xlate函式為of_gpio_simple_xlate,其中第一個引數表示gpio號(在對應的bank中),第二個表示flag 這裡用掉電保護功能的驅動為例,掉電保護功能裝置樹節點入下: ```C powerdown_protect__pins_default: powerdown_protect__pins_default { pinctrl-single,pins = < DRA7XX_CORE_IOPAD(0x37a4, (PIN_INPUT_PULLUP | MUX_MODE14)) /* gpio7_7 */ DRA7XX_CORE_IOPAD(0x34fc, (PIN_OUTPUT | MUX_MODE14)) /* gpio3_6 */ >; }; powerdown_protect { compatible = "greerobot,powerdown_protect"; pinctrl-names = "default"; pinctrl-0 = <&powerdown_protect__pins_default>; powerdown_detect_gpio = <&gpio7 7 GPIO_ACTIVE_HIGH>; powerdown_ssd_en = <&gpio3 6 GPIO_ACTIVE_HIGH>; }; ``` 上面的節點powerdown_protect中引用了gpio3、gpio7,而且在驅動中打算將這個gpio當作中斷引腳來使用。 下面是掉電檢測的驅動: ```C ..... int gpio_id = -1; int ssd_en = -1; int irq_num = -1; ...... static int powerdown_protect_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct device_node *node = dev->of_node; int ret = -1; gpio_id = of_get_named_gpio(node, "powerdown_detect_gpio", 0); ..... ret = gpio_request(gpio_id, "powerdown_detect"); ..... irq_num = gpio_to_irq(gpio_id); ..... ret = request_irq(irq_num, powerdown_detect_irq, IRQFLAGS, IRQDESC, pdev); ..... ret = misc_register(&pwd_miscdev); .... return 0; fail: gpio_free(gpio_id); return ret; } static int powerdown_protect_remove(struct platform_device *pdev) { free_irq(irq_num, pdev); gpio_free(gpio_id); return 0; } static const struct of_device_id powerdown_protect_match[] = { { .compatible = "greerobot,powerdown_protect", }, {} }; static struct platform_driver powerdown_protect_driver = { .probe = powerdown_protect_probe, .remove = powerdown_protect_remove, .driver = { .name = "greerobot_powerdown_protect", .owner = THIS_MODULE, .of_match_table = powerdown_protect_match, }, }; static __init int powerdown_protect_init(void) { return platform_driver_register(&powerdown_protect_driver); } module_init(powerdown_protect_init); ``` 其中我們只需要分析兩個關鍵的函式:`of_get_named_gpio` 和 `gpio_to_irq`. **of_get_named_gpio** 這個函式的作用是根據傳遞的屬性的name和索引號,得到一個gpio號 ```C int of_get_named_gpio_flags(struct device_node *np, const char *list_name, int index, enum of_gpio_flags *flags) { struct gpio_desc *desc; desc = of_get_named_gpiod_flags(np, list_name, index, flags); ..... return desc_to_gpio(desc); } ``` ```C struct gpio_desc *of_get_named_gpiod_flags(struct device_node *np, const char *propname, int index, enum of_gpio_flags *flags) { struct of_phandle_args gpiospec; struct gpio_chip *chip; struct gpio_desc *desc; int ret; /* 解析"powerdown_detect_gpio"屬性中第index欄位,將解析結果存放到gpiospec中 struct of_phandle_args { struct device_node *np; // int-gpio屬性所引用的gpio-controller的node--gpio7 int args_count; // gpio7這個gpio-controller的#gpio-cells屬性的值 uint32_t args[MAX_PHANDLE_ARGS]; // 具體描述這個gpio屬性的每一個引數 }; */ ret = of_parse_phandle_with_args_map(np, propname, "gpio", index, &gpiospec); // 上面gpiospec的np存放的索引用的gpio-controller的node, // 遍歷gpio_devices連結串列,找到對應的gpio_device,也就找到了gpio_chip chip = of_find_gpiochip_by_xlate(&gpiospec); // 呼叫chip->of_xlate解析gpiospec,返回gpiospec的args中的第一個引數args[0], // 也就是前面分析的在bank中的邏輯gpio號 // 知道了gpio號,就可以在gpio_device->desc中索引到對應的gpio_desc desc = of_xlate_and_get_gpiod_flags(chip, &gpiospec, flags); ..... return desc; } ``` ```C int desc_to_gpio(const struct gpio_desc *desc) { // 獲得這個gpio_desc對應的gpio在系統中的邏輯gpio號 return desc->gdev->base + (desc - &desc->gdev->descs[0]); } ``` **gpio_to_irq** 將這個gpio轉換成對應的virq ```c gpio_to_irq(irq_gpio) ---> __gpio_to_irq(gpio) ​ ---> gpiod_to_irq(gpio_to_desc(gpio)) ``` 這裡呼叫了兩個函式,函式`gpio_to_desc`根據傳入的全域性邏輯gpio號找到對應的`gpio_desc`,原理是:遍歷`gpio_devices`連結串列,根據傳入的邏輯gpio號,就可以定位到所屬的gpio_device,前面說過,在將gpio_device加入到`gpio_devices`連結串列的時候,不是亂加的,而是根據gpio_device的base和ngpio找到一個合適的位置。找到了gpio_device,那麼通過索引它的desc成員,就可以找到對應的gpio_desc. ```C struct gpio_desc *gpio_to_desc(unsigned gpio) { struct gpio_device *gdev; unsigned long flags; spin_lock_irqsave(&gpio_lock, flags); list_for_each_entry(gdev, &gpio_devices, list) { if (gdev->base <= gpio && gdev->base + gdev->ngpio > gpio) { spin_unlock_irqrestore(&gpio_lock, flags); return &gdev->descs[gpio - gdev->base]; } } ...... return NULL; } ``` ```C int gpiod_to_irq(const struct gpio_desc *desc) { struct gpio_chip *chip; int offset; ....... chip = desc->gdev->chip; offset = gpio_chip_hwgpio(desc); if (chip->to_irq) { int retirq = chip->to_irq(chip, offset); ... return retirq; } return -ENXIO; } ``` 其to_irq定義如下 ```C static int gpiochip_to_irq(struct gpio_chip *chip, unsigned offset) { .... return irq_create_mapping(chip->irq.domain, offset); } ``` 需要注意的是offset,比如對於gpio7.7,那麼offset就是7,這裡的offset就是GPIO7這個控制器的hwirq,呼叫irq_create_mapping可以為該hwirq在kernel中分配一個唯一的virq,同時將hwirq和virq的對映關係存放到bank->irq_domain中。 對映過程中會設定該virq的higth level handler函式,上節我們在GPIO控制器驅動中註冊了gpio_chip的handler為`handle_bad_irq`,此時我們還沒有呼叫`request_irq()`來設定該中斷的處理函式,所以disable該中斷,desc->handler_irq也只能是`handle_bad_irq`。 ```C void __irq_do_set_handler(struct irq_desc *desc, irq_flow_handler_t handle, int is_chained, const char *name) { .... if (handle == handle_bad_irq) { .... irq_state_set_disabled(desc); if (is_chained) desc->action = NULL; desc->depth = 1; } desc->handle_irq = handle; desc->name = name; .... } ``` 最後將註冊該中斷的中斷處理函式: ```C irq_num = gpio_to_irq(gpio_id); ..... ret = request_irq(irq_num, powerdown_detect_irq, IRQFLAGS, IRQDESC, pdev); ``` 建立一個action,設定該action-handler為`powerdown_detect_irq`,將該action新增到irq_num對應的irq_desc的actions連結串列,然後`__setup_irq()`,在沒呼叫request_irq前desc->handler_irq是`handle_bad_irq`,現在我們要根據具體的中斷觸發方式來設定了,最終呼叫上面gpio中斷控制器中註冊的函式`omap_gpio_irq_type()`。 ```C request_irq -->__setup_irq -->__irq_set_trigger ->ret = chip->irq_set_type(&desc->irq_data, flags); /*gpio控制器註冊的irq_set_type回撥函式omap_gpio_irq_type()*/ ``` `omap_gpio_irq_type()`根據中斷類型別來設定相應的`desc->handler_irq`,即`handle_simple_irq` ```C static int omap_gpio_irq_type(struct irq_data *d, unsigned type) { struct gpio_bank *bank = omap_irq_data_get_bank(d); int retval; unsigned long flags; unsigned offset = d->hwirq; if (type & ~IRQ_TYPE_SENSE_MASK) return -EINVAL; if (!bank->regs->leveldetect0 && (type & (IRQ_TYPE_LEVEL_LOW|IRQ_TYPE_LEVEL_HIGH))) return -EINVAL; raw_spin_lock_irqsave(&bank->lock, flags); retval = omap_set_gpio_triggering(bank, offset, type); if (retval) { raw_spin_unlock_irqrestore(&bank->lock, flags); goto error; } omap_gpio_init_irq(bank, offset); if (!omap_gpio_is_input(bank, offset)) { raw_spin_unlock_irqrestore(&bank->lock, flags); retval = -EINVAL; goto error; } raw_spin_unlock_irqrestore(&bank->lock, flags); if (type & (IRQ_TYPE_LEVEL_LOW | IRQ_TYPE_LEVEL_HIGH)) irq_set_handler_locked(d, handle_level_irq); else if (type & (IRQ_TYPE_EDGE_FALLING | IRQ_TYPE_EDGE_RISING)) /* * Edge IRQs are already cleared/acked in irq_handler and * not need to be masked, as result handle_edge_irq() * logic is excessed here and may cause lose of interrupts. * So just use handle_simple_irq. */ irq_set_handler_locked(d, handle_simple_irq); return 0; error: return retval; } ``` 可以看到載入掉電保護驅動後,執行gpio_to_irq時建立了hwirq和virq之間的對映,分配到的virq是176. ```shell [ 12.189454] __irq_alloc_descs: alloc virq: 176, cnt: 1 [ 12.195729] irq: irq 7 on domain gpio@48051000 mapped to virtual irq 176 [ 12.195850] powerdown irq is 176 ``` 到此可以得到以下對映圖: ![gpio-map-virq](https://wsg-blogs-pic.oss-cn-beijing.aliyuncs.com/xenomai/gpio-map-virq-1596697674078.png) ## 第六部分 GPIO中斷處理流程 回顧上述分析流程,GPIO7的hwirq:30,其virq:43,維護對映關係的為中斷控制器GIC的irq_domain,中斷處理函式在GPIO控制器驅動中設定: ```C /*drivers/gpio/gpio-omap.c*/ ret = devm_request_irq(bank->chip.parent, bank->irq, omap_gpio_irq_handler, 0, dev_name(bank->chip.parent), bank); ``` 掉電檢測引腳GPIO7_7,其中斷控制器為GPIO7,hwirq:7,其virq為:176,維護對映關係的為GPIO中斷控制器的irq_domain,GPIO7_7的中斷處理函式在掉電保護驅動中設定: ```C /*drivers/gree/gree_power_down.c*/ ret = request_irq(irq_num, powerdown_detect_irq, IRQFLAGS, IRQDESC, pdev); ``` 檢測到中斷事件後: ![image-20200806165455785](https://wsg-blogs-pic.oss-cn-beijing.aliyuncs.com/xenomai/image-20200806165455785.png) ![generic_handle_irq](https://wsg-blogs-pic.oss-cn-beijing.aliyuncs.com/xenomai/generic_handle_irq-1596711245942.png) 1. 先找到root interrupt controler(GIC)對應的irq_domain; 2. 根據HW暫存器資訊和irq_domain資訊獲取hwirq,即30; 3. 呼叫`handle_IRQ`來處理該hwirq; 4. 呼叫`irq_find_mapping`找到hwirq對應的IRQ NUMBER 43; 5. 最終呼叫到`generic_handle_irq`來進行中斷處理,即`desc->handle_irq()`。 `desc->handle_irq()`在GPIO7的裝置節點轉換為platform_device過程中已設定為`handle_fasteoi_irq`,==可能是其他函式==; `handle_fasteoi_irq()`進一步得到virq 43對應的irq_desc,並遍歷執行連結串列desc->actions內的action函式,`omap_gpio_irq_handler`得到執行。 以上是GIC中斷控制器層處理硬體中斷號30的流程,`generic_handle_irq`最終會處理GPIO7 驅動註冊的處理函式`omap_gpio_irq_handler`,流程如下: `omap_gpio_irq_handler`中重複上面`generic_handle_irq`步驟: ![2level-int](https://wsg-blogs-pic.oss-cn-beijing.aliyuncs.com/xenomai/handle_irq_type.png) 1. 找到GPIO7 interrupt controler對應的irq_domain 2. 根據HW暫存器資訊和irq_domain資訊獲取offset,即7; 3. 呼叫`irq_find_mapping`找到hwirq對應的IRQ Number 176; 4. 呼叫`generic_handle_irq`處理該IRQ Number 176. 5. 根據virq得到irq_desc,執行`desc->handle_irq()`; `desc->handle_irq`在掉電檢測驅動中request_irq時根據irq來設定,具體為`handle_simple_irq()`,`handle_simple_irq()`中最終遍歷action list,呼叫specific handler,也就是我們掉電檢測驅動註冊的中斷處理函式`powerdown_detect_irq()`。 版權宣告:本文為本文為博主原創文章,轉載請註明出處。如有問題,歡迎指正。部落格地址:https://www.cnblogs.com/