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

本文以AM5728 GPIO中斷爲例,簡單介紹有關從註冊GIC中斷到 驅動使用GPIO中斷的整個過程,主要關注中斷相關處理流程,爲後續ARM平臺xenomai IPIPE中斷處理流程做鋪墊。

第一部分: GIC中斷控制器的註冊。

第二部分:設備樹的device node在向platform_device轉化的過程中節點的interrupts屬性的處理。

第三部分:platform_device註冊添加。

第四部分:GPIO控制器驅動的註冊,大部分GPIO控制器同時具備interrupt controller的功能。

第五部分:引用GPIO中斷的節點的解析。

/ {
	#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 = <GIC_PPI 9 (GIC_CPU_MASK_SIMPLE(2) | IRQ_TYPE_LEVEL_HIGH)>;
		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 = <GIC_SPI 30 IRQ_TYPE_LEVEL_HIGH>;
			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設備樹來添加,由解析設備樹到設備註冊添加的流程如下:

GIC設備樹信息如下

/*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 = <GIC_PPI 9 (GIC_CPU_MASK_SIMPLE(2) | IRQ_TYPE_LEVEL_HIGH)>;
		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

  • 首先需要了解一下鏈接腳本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驅動的初始化入口函數了;

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的映射;
	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屬性的處理,函數調用關係:

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_countof_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

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的含義如下:

#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函數完成。

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箇中斷。

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代碼分析一下:

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插入基數樹。

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);
}
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的過程:

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;

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

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中

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的映射關係:

[19491.235350] virq is 43,hwirq is 30

of_device_alloc

需要關注的是 domain->ops->map(),該函數中戶設置該中斷的desc->handle_irq(),對於GIC來說,map函數爲gic_irq_domain_map,SPI中斷handle_irq()設置爲handle_fasteoi_irq。

第三部分:platform_device註冊添加

device_add

platform_driver的probe就會被調用。

第四部分 GPIO控制器驅動

相關代碼:

drivers\gpio\gpio-omap.c

gpio@48051000節點轉化成的platform_device被註冊的時候,omap_gpio_probe()會被調用。這個函數目前我們先只分析跟中斷相關的。

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中斷控制器。

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

#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)

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的實現,這個在下面的分析中會被回調.

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流程:

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;

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;

ret = devm_request_irq(bank->chip.parent, bank->irq,
			       omap_gpio_irq_handler,
			       0, dev_name(bank->chip.parent), bank);

bank->irq創建一個action,設置該action–>handleromap_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

這裏用掉電保護功能的驅動爲例,掉電保護功能設備樹節點入下:

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當作中斷引腳來使用。

下面是掉電檢測的驅動:

.....
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_gpiogpio_to_irq.

of_get_named_gpio

這個函數的作用是根據傳遞的屬性的name和索引號,得到一個gpio號

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);
}
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;
}
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

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.

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;
}
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定義如下

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

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;
....
}

最後將註冊該中斷的中斷處理函數:

    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()

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

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.

[   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

第六部分 GPIO中斷處理流程

回顧上述分析流程,GPIO7的hwirq:30,其virq:43,維護映射關係的爲中斷控制器GIC的irq_domain,中斷處理函數在GPIO控制器驅動中設置:

/*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的中斷處理函數在掉電保護驅動中設置:

/*drivers/gree/gree_power_down.c*/
ret = request_irq(irq_num, powerdown_detect_irq, IRQFLAGS, IRQDESC, pdev);

檢測到中斷事件後:

image-20200806165455785

generic_handle_irq

  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

  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/wsg1100/

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章