【原創】中斷子系統-ARM GPIO中斷處理流程
阿新 • • 發佈:2021-01-10
[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/