【PCI】ARM架构——PCI总线驱动、RC驱动、Host Bridge驱动、xilinx xdma ip驱动(八)

这篇具有很好参考价值的文章主要介绍了【PCI】ARM架构——PCI总线驱动、RC驱动、Host Bridge驱动、xilinx xdma ip驱动(八)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

本文以xilinx RC IP为例,讲解ARM的RC驱动(PL)。

IP例程参考网址:https://xilinx-wiki.atlassian.net/wiki/spaces/A/pages/18842034/Xilinx+Linux+PL+PCIe+Root+Port

IP文档文档参考网址:https://docs.xilinx.com/v/u/en-US/pg194-axi-bridge-pcie-gen3和https://docs.xilinx.com/r/en-US/pg213-pcie4-ultrascale-plus

使用平台参考文档网址:https://docs.xilinx.com/v/u/en-US/ug1085-zynq-ultrascale-trm

RC驱动代码参考网址:https://github.com/Xilinx/linux-xlnx/blob/master/drivers/pci/controller/pcie-xdma-pl.c

参考代码:pcie-xdma-pl.c

内核版本:linux5.4

RC需要完成哪些工作?

先看RC连接简图。
【PCI】ARM架构——PCI总线驱动、RC驱动、Host Bridge驱动、xilinx xdma ip驱动(八)

RC需要完成以下工作内容。

初始化阶段:

​ ①能向CPU申请资源,如PCI总线号、pci内存空间、pci I/O空间。

​ ②能扫描到下游设备,并为设备分配资源。

工作阶段:

​ ③能转换PCI地址空间与CPU地址空间。

​ ④能将CPU的AXI访问信号转换成pcie TLP总线事务发送到下游总线(信号类型和地址空间转换)。

​ ⑤能将pcie设备发出的MR/MW TLP事务转换成AXI访问信号送给CPU(信号类型和地址空间转换)。

​ ⑥能接收PCIe设备中断(INTx、MSI、MSI-X),并反馈给CPU处理(级联中断)。

​ ⑦能向CPU报告RC自身的异常状态。

后续陆续讲解到对应实现。

RC初始化

只有当RC初始化完成之后,才有扫描下游PCIe设备的能力。那么,RC是如何被系统发现并初始化的呢?

RC对应的设备树

X86平台,linux内核可以使用BIOS上报设备信息来初始化一系列设备,而ARM平台,则使用设备树告诉linux内核有哪些设备。

使用的ZU19EG平台没有BIOS,因此RC的信息需要添加到设备树中,以下是RC对应的设备树信息(参考官网例程)。

{
	amba_pl: amba_pl@0 {
		#address-cells = <2>;
		#size-cells = <2>;
		compatible = "simple-bus";
		ranges;

		xdma_0: axi-pcie@a0000000 {
			compatible = "xlnx,xdma-host-3.00";
			device_type = "pci";
			#address-cells = <3>;
			#size-cells = <2>;

			interrupt-names = "misc", "msi0", "msi1";
			interrupts = <0 89 4>, <0 90 4>, <0 91 4>;
			interrupt-parent = <&gic>;

			ranges = <0x02000000 0x00000000 0x00000000 0x0 0xA0000000 0x00000000 0x10000000>; 
			//pci总线0地址映射到CPU的0xA0000000地址,大小为0x10000000
			
			reg = <0x00000004 0x00000000 0x0 0x10000000>;

			#interrupt-cells = <1>;
			interrupt-map-mask = <0 0 0 7>;
			interrupt-map = <0 0 0 1 &pcie_intc_0 1>, 
							<0 0 0 2 &pcie_intc_0 2>, 
							<0 0 0 3 &pcie_intc_0 3>,
                            <0 0 0 4 &pcie_intc_0 4>;
			pcie_intc_0: interrupt-controller {
				#address-cells = <0>;
				#interrupt-cells = <1>;
				interrupt-controller;
			};
		};
	};
};

RC作为platform设备挂在platform总线上,依靠compatible字段与RC驱动(pcie-xdma-pl.c)完成匹配。

设备树详解:

普通中断

interrupts为三个元素时格式为:

<type, interrupt number, trigger type>

第一个参数表示是PPI、SPI、SGI其中的一个

【??】SGI: software generated interrupts 中断号 0~15
【1】PPI: per processor inerrupts 中断号 16~31
【0】SPI: shared processor interrupts 32~1019

第二个参数表示:是第一个参数类型中的第几个中断号

第三个参数表示:中断触发的类型。(上升沿、下降沿等)

#define IRQ_TYPE_NONE        0
#define IRQ_TYPE_EDGE_RISING    1
#define IRQ_TYPE_EDGE_FALLING    2
#define IRQ_TYPE_EDGE_BOTH    (IRQ_TYPE_EDGE_FALLING | IRQ_TYPE_EDGE_RISING)
#define IRQ_TYPE_LEVEL_HIGH    4
#define IRQ_TYPE_LEVEL_LOW    8

示例:

interrupt-names = "misc";
interrupts = <0 89 4>;
interrupt-parent = <&gic>;
0表示中断类型为共享处理器中断(SPI),89表示中断号为SPI中断类型中的第89号中断号,计算出来的实际中断号即为32+89=121号中断,4表示高电平沿触发中断。
中断的父节点是gic,也就是中断引脚连在gic上。
驱动中通过platform_get_irq_byname函数根据"misc"名字获取对应的中断号(32+89=121)。

中断映射

interrupt-cells表示定义了一个中断说明符(中断域)所需要的单元数。

interrupt-controller表示将包含该属性的节点定义为中断控制器,也就是说这个RC包含中断控制器。

interrupt-map-mask指定了设备树中的链接节点计算使用的掩码。

interrupt-map将一个中断域与一组父中断域桥接,并指定子域中的中断说明符如何映射到其各自的父域,每一行有5项格式如下:

  • child unit address: 子节点的单元地址
    【PCI】ARM架构——PCI总线驱动、RC驱动、Host Bridge驱动、xilinx xdma ip驱动(八)

    比如描述pci bus为0、设备号为0x11、功能号为0的设备,(0x0<<16)|(0x11<<11)|(0x0<<8)=0x8800,则描述符为<0x8800 0 0>

  • child interrupt specifier: 子节点的中断说明符

    在pci总线上就是:1(INTA)、2(INTB)、3(INTC)、4(INTD)

  • interrupt-parent: 该值指向子节点将被映射到的中断父域

  • parent unit address: 中断父域的地址

  • parent interrupt specifier: 父域中的中断描述符

    在pci总线上就是:1(INTA)、2(INTB)、3(INTC)、4(INTD)

示例:

interrupt-map = <0 0 0 1 &pcie_intc_0 1>
child unit address: 0x0000 0x0 0x0 (xdma_0.#address-cells属性是3,第一个0x0000是BDF号)
child interrupt specifier: 1 (xdma_0.#interrupt-cells属性是1),在pci总线上就是:1(INTA)、2(INTB)、3(INTC)、4(INTD)
interrupt parent: &pcie_intc_0
parent unit address:是空的 (pcie_intc_0.address-cells为0)
parent interrupt specifier: 1 (pcie_intc_0.#interrupt-cells属性是1),在pci总线上就是:1(INTA)、2(INTB)、3(INTC)、4(INTD)

interrupt-map需要与interrupt-map-mask结合使用,比如pci bus为0、设备号为0x11、功能号为0的设备(根据pci总线规定function为0的设备使用INTA中断线),其<child unit address ,child interrupt specifier>描述为<0x8800 0 0 1>,和interrupt-map-mask <0 0 0 7>对应进行与运算后结果为<0 0 0 1>,也就是匹配<0 0 0 1 &pcie_intc_0 1>节点,这就表示pci bus为0、设备号为0x11、功能号为0的设备中断引脚INTA连接在PCI控制器的INTA引脚上,那么该设备的硬件中断号就是0(INTA - INTA)。

注意:对于pci总线号非0的设备,在计算之前需要转换到最上游的briege设备才能计算,转换关系如下图所示。
【PCI】ARM架构——PCI总线驱动、RC驱动、Host Bridge驱动、xilinx xdma ip驱动(八)

怎么转换呢?举个例子。
【PCI】ARM架构——PCI总线驱动、RC驱动、Host Bridge驱动、xilinx xdma ip驱动(八)

pcie设备B的INTA中断线对应硬件中断号是多少呢?设备B的bus不为0,因此需要转换中断引脚。首先,设备B的设备号为1(AD引脚与IDSEL引脚连线决定设备号是多少),根据转换关系,设备B的中断INTA应该连接上游桥A的INTB,所以设备B的INTA触发中断将传导至桥A的INTB上,然后,桥A的bus为0,所以得出<child unit address ,child interrupt specifier>描述为<0x0 0x0 0x0 2>,最后,让<0x0 0x0 0x0 2>与interrupt-map-mask <0 0 0 7>对应进行与运算后结果为<0x0 0x0 0x0 2>,所以设备B的INTA中断线对应的硬件中断号为1(INTB-INTA)。注意:上面INTA~INTD的中断线连接是虚拟的,因为pcie是使用message上报INTx中断,但映射关系是一样的,此外,ADx总线与IDSEL线都是虚拟的,这里只是为了方便理解而借用PCI总线定义。

地址映射

PCI地址空间与CPU地址空间是完全分离的,所以需要通过定义ranges属性进行地址转化。

其对应格式为<pci addr, cpu addr, size>

  • pci addr使用3个cell描述
  3个ell分别代表物理地址高位、中位、低位:
  ​		phys.high cell : npt000ss bbbbbbbb dddddfff rrrrrrrr
  ​		phys.mid cell : hhhhhhhh hhhhhhhh hhhhhhhh hhhhhhhh 
  ​		phys.low cell : llllllll llllllll llllllll llllllll 

  PCI地址为64位宽度,编码在phys.mid和phys.low中。真正重要的东西在于phys.high这一位空间中:
  ​		n:代表重申请空间标志(这里没有使用)
  ​		p:代表预读空间(缓存)标志
  ​		t:别名地址标志(这里没有使用)
  ​		ss:空间代码
  ​			00: 设置空间
  ​			01:IO空间
  ​			10:32位空间
  ​			11:64位空间
​		bbbbbbbb: PCI总线号。PCI有可能是层次性架构,所以我们可能需要区分一些子-总线
​		ddddd:设备号,通常由初始化设备选择信号IDSEL连接时申请。
​		fff:功能序号,有些多功能PCI设备可能用到。
​		rrrrrrrr:注册号,在设置周期使用。

		hhhhhhhh hhhhhhhh hhhhhhhh hhhhhhhh:PCI地址的高32位。
		llllllll llllllll llllllll llllllll:PCI地址的低32位。
  • cpu addr则根据父节点的#address-cells属性来决定使用多少cell描述
  • size根据自身节点的#size-cells属性来决定使用多少cell描述

示例:

{
	amba_pl: amba_pl@0 {
		#address-cells = <2>;
		#size-cells = <2>;
		ranges;

		xdma_0: axi-pcie@a0000000 {
			#address-cells = <3>;
			#size-cells = <2>;

			ranges = <0x02000000 0x00000000 0x00000000 0x0 0xA0000000 0x00000000 0x10000000>; 
			};
		};
	};
};
划分如下
0x02000000 0 0x00000000 PCI地址, 0x0 0xA0000000 CPU地址, 0 0x10000000 地址空间长度

先看pci地址
phys.high cell : 00000010 00000000 00000000 00000000
phys.mid cell : 00000000 00000000 00000000 00000000
phys.low cell : 00000000 00000000 00000000 00000000
表示32位PCI空间地址为0

CPU地址
amba_pl.#address-cells为2,所以CPU地址由2个cell组成,高位在前地位在后,即CPU地址为0xA0000000

地址长度
xdma_0.#size-cells为2,所以地址长度由2个cell组成,,高位在前地位在后,即地址空间长度为0x10000000

整个ranges表示:从PCI地址空间地址为0开始,申请0x10000000空间大小,映射到CPU0xA0000000地址空间上,后续CPU访问0xA0000000开始的地址,就是访问PCI空间

区域空间

设备树使用reg标明了一块区域空间,即reg = <0x00000004 0x00000000 0x0 0x10000000>;

这个空间将在RC的驱动中解析,用来当做pcie配置空间(ECAM机制)。

xilinx_pcie_probe初始化过程

linux系统启动过程中会解析设备树,并为各节点创建对应的实例,然后挂在对应的总线上,对于上面的rc设备树来说,linux系统将会创建一个rc平台设备挂在plaform总线上,进而去匹配RC驱动,执行RC驱动中的probe函数,也就是xilinx_pcie_probe函数。

probe简化流程如下图所示。
【PCI】ARM架构——PCI总线驱动、RC驱动、Host Bridge驱动、xilinx xdma ip驱动(八)

来看一下probe代码。

static int xilinx_pcie_probe(struct platform_device *pdev)
{
	struct xilinx_pcie_port *port;
	struct device *dev = &pdev->dev;
	struct pci_bus *bus;
	struct pci_bus *child;
	struct pci_host_bridge *bridge;
	int err;
	resource_size_t iobase = 0;
	LIST_HEAD(res);

    //分配并初始化一个基础的pci_hsot_bridge结构,尾部多申请sizeof(*port)
	bridge = devm_pci_alloc_host_bridge(dev, sizeof(*port));
	if (!bridge)
		return -ENODEV;

    //port为挂在bridge尾部的私有数据,之前多申请了sizeof(*port)空间
	port = pci_host_bridge_priv(bridge);
	port->dev = dev;

    //解析设备树,获取资源信息和申请中断,如寄存器基地址
	err = xilinx_pcie_parse_dt(port);
	if (err) {
		dev_err(dev, "Parsing DT failed\n");
		return err;
	}

    //初始化寄存器
	xilinx_pcie_init_port(port);

    //创建INTx中断控制器域,创建MSI中断控制器域
	err = xilinx_pcie_init_irq_domain(port);
	if (err) {
		dev_err(dev, "Failed creating IRQ Domain\n");
		return err;
	}

    //将bus号资源和内存资源加入res链表中
	err = devm_of_pci_get_host_bridge_resources(dev, 0, 0xff, &res, &iobase);
	if (err) {
		dev_err(dev, "Getting bridge resources failed\n");
		return err;
	}

    //向系统资源树申请资源,会检查资源冲突
	err = devm_request_pci_bus_resources(dev, &res);
	if (err)
		goto error;

    //将资源挂到RC bridge资源树上,后续pci资源都从该资源树上申请
	list_splice_init(&res, &bridge->windows);
	bridge->dev.parent = dev;
	bridge->sysdata = port;
	bridge->busnr = port->root_busno;//未初始化,默认为0,此处为坑点,多个RC务必小心(同一个pcie域下,默认情况下每个RC有各自的域,因为每个RC由不同的平台总线设备注册,但也可以自定义一个平台总线设备,然后在驱动中注册多个RC,这样就属于一个pcie总线域)
	bridge->ops = &xilinx_pcie_ops;

    //中断映射函数,用于解析设备树,获取设备与INTx中断映射关系
	bridge->map_irq = of_irq_parse_and_map_pci;

    //查找中断映射表,返回pcie设备最终连接到host桥上的INTx中断线
	bridge->swizzle_irq = pci_common_swizzle;

    //扫描根总线分配bus号,填充信息,但未分配memory、io资源
	err = pci_scan_root_bus_bridge(bridge);
	if (err)
		goto error;

	bus = bridge->bus;

    //分配资源,memory、io资源
	pci_assign_unassigned_bus_resources(bus);
	list_for_each_entry(child, &bus->children, node)
		pcie_bus_configure_settings(child);

    //执行设备与驱动的匹配操作
	pci_bus_add_devices(bus);
	return 0;

error:
	pci_free_resource_list(&res);
	return err;
}

主要干的事都注释了,来看看主要函数内部实现。

主要函数

xilinx_pcie_parse_dt

static int xilinx_pcie_parse_dt(struct xilinx_pcie_port *port)
{
	struct device *dev = port->dev;
	struct device_node *node = dev->of_node;
	struct resource regs;
	const char *type;
	int err, mode_val, val;

	if (of_device_is_compatible(node, "xlnx,xdma-host-3.00"))
		port->xdma_config = XDMA_ZYNQMP_PL;
	else if (of_device_is_compatible(node, "xlnx,pcie-dma-versal-2.0"))
		port->xdma_config = XDMA_VERSAL_PL;
	else if (of_device_is_compatible(node, "xlnx,versal-cpm-host-1.00"))
		port->xdma_config = XDMA_VERSAL_CPM;

	if (port->xdma_config == XDMA_ZYNQMP_PL ||
	    port->xdma_config == XDMA_VERSAL_PL) {
		type = of_get_property(node, "device_type", NULL);
		if (!type || strcmp(type, "pci")) {
			dev_err(dev, "invalid \"device_type\" %s\n", type);
			return -EINVAL;
		}

        //regs:0x4_0000_0000, size:0x20000000
		err = of_address_to_resource(node, 0, &regs);//解析设备树中reg区域
		if (err) {
			dev_err(dev, "missing \"reg\" property\n");
			return err;
		}

        //将reg映射到系统,该区域小部分地址用来访问RC控制器寄存器,
        //大部分地址用于ECAM机制访问外设配置空间
		port->reg_base = devm_ioremap_resource(dev, &regs);
		if (IS_ERR(port->reg_base))
			return PTR_ERR(port->reg_base);

		if (port->xdma_config == XDMA_ZYNQMP_PL) {
			val = pcie_read(port, XILINX_PCIE_REG_BIR);//read 0x130
			val = (val >> XILINX_PCIE_FIFO_SHIFT) & MSI_DECD_MODE;
			mode_val = pcie_read(port, XILINX_PCIE_REG_VSEC) &
					XILINX_PCIE_VSEC_REV_MASK;//read 0x12c
			mode_val = mode_val >> XILINX_PCIE_VSEC_REV_SHIFT;
			if (mode_val && !val) {//使用解码模式
				port->msi_mode = MSI_DECD_MODE;
				dev_info(dev, "Using MSI Decode mode\n");
			} else {
				port->msi_mode = MSI_FIFO_MODE;
				dev_info(dev, "Using MSI FIFO mode\n");
			}
		}

		if (port->xdma_config == XDMA_VERSAL_PL)
			port->msi_mode = MSI_DECD_MODE;

		if (port->msi_mode == MSI_DECD_MODE) {//最重要的步骤
            //申请中断用于处理RC核内部事件,包含下游设备的INTx中断
			err = xilinx_request_misc_irq(port);
			if (err)
				return err;

            //填充通用中断处理函数和通用数据
			err = xilinx_request_msi_irq(port);
			if (err)
				return err;

		} else if (port->msi_mode == MSI_FIFO_MODE) {
			port->irq = irq_of_parse_and_map(node, 0);
			if (!port->irq) {
				dev_err(dev, "Unable to find IRQ line\n");
				return -ENXIO;
			}

			err = devm_request_irq(dev, port->irq,
					       xilinx_pcie_intr_handler,
					       IRQF_SHARED | IRQF_NO_THREAD,
					       "xilinx-pcie", port);
			if (err) {
				dev_err(dev, "unable to request irq %d\n",
					port->irq);
				return err;
			}
		}
	} else if (port->xdma_config == XDMA_VERSAL_CPM) {
		struct resource *res;
		struct platform_device *pdev = to_platform_device(dev);

		res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "cfg");
		port->reg_base = devm_ioremap_resource(dev, res);
		if (IS_ERR(port->reg_base))
			return PTR_ERR(port->reg_base);

		res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
						   "cpm_slcr");
		port->cpm_base = devm_ioremap_resource(dev, res);
		if (IS_ERR(port->cpm_base))
			return PTR_ERR(port->cpm_base);

		err = xilinx_request_misc_irq(port);
		if (err)
			return err;
	}

	return 0;
}

将设备树中reg属性解析出来,即从0x4_0000_0000地址开始的0x10000000大小空间(256M),该区域小部分用于RC控制操作(pg194的34页),大部分用于ECAM机制(pg194的55页)访问下游pcie设备的配置空间。ECAM解析格式如下。
【PCI】ARM架构——PCI总线驱动、RC驱动、Host Bridge驱动、xilinx xdma ip驱动(八)

IP Core会将访问地址按上面格式解析,转换成配置访问,就如同X86的配置访问一样。

上面最重要的步骤是申请中断,即以下两函数。

static int xilinx_request_misc_irq(struct xilinx_pcie_port *port)
{
	struct device *dev = port->dev;
	struct platform_device *pdev = to_platform_device(dev);
	int err;

	port->irq_misc = platform_get_irq_byname(pdev, "misc");//121,看设备树解析32+89=121
	if (port->irq_misc <= 0) {
		dev_err(dev, "Unable to find misc IRQ line\n");
		return port->irq_misc;
	}
	err = devm_request_irq(dev, port->irq_misc,
			       xilinx_pcie_intr_handler,
			       IRQF_SHARED | IRQF_NO_THREAD,
			       "xilinx-pcie", port);//为rc自身申请中断,包含处理INTx中断
	if (err) {
		dev_err(dev, "unable to request misc IRQ line %d\n",
			port->irq_misc);
		return err;
	}

	return 0;
}

static int xilinx_request_msi_irq(struct xilinx_pcie_port *port)
{
	struct device *dev = port->dev;
	struct platform_device *pdev = to_platform_device(dev);

	port->msi.irq_msi0 = platform_get_irq_byname(pdev, "msi0");//122,看设备树解析32+90=122
	if (port->msi.irq_msi0 <= 0) {
		dev_err(dev, "Unable to find msi0 IRQ line\n");
		return port->msi.irq_msi0;
	}

    //给指定软中断号填充共享数据port及通用中断处理函数xilinx_pcie_msi_handler_low
	irq_set_chained_handler_and_data(port->msi.irq_msi0,
					 xilinx_pcie_msi_handler_low,
					 port);//设置级联中断

	port->msi.irq_msi1 = platform_get_irq_byname(pdev, "msi1");//123,看设备树解析32+91=123
	if (port->msi.irq_msi1 <= 0) {
		dev_err(dev, "Unable to find msi1 IRQ line\n");
		return port->msi.irq_msi1;
	}

	irq_set_chained_handler_and_data(port->msi.irq_msi1,
					 xilinx_pcie_msi_handler_high,
					 port);//设置级联中断

	return 0;
}

上面两个函数申请了3个中断,121软中断号对应的中断用于处理RC自身上报的异常以及pcie设备触发的INTx中断,122和123软中断号用于处理下游pcie设备触发的MSI/MSI-X中断。有点迷糊?没关系,在pcie设备申请中断章节详细讲解。

xilinx_pcie_init_port

代码如下。

static void xilinx_pcie_init_port(struct xilinx_pcie_port *port)
{
	if (xilinx_pcie_link_is_up(port))
		dev_info(port->dev, "PCIe Link is UP\n");
	else
		dev_info(port->dev, "PCIe Link is DOWN\n");

	/* Disable all interrupts */
	pcie_write(port, ~XILINX_PCIE_IDR_ALL_MASK,
		   XILINX_PCIE_REG_IMR);//关闭rc所有自身中断(包含INTx)

	/* Clear pending interrupts */
	pcie_write(port, pcie_read(port, XILINX_PCIE_REG_IDR) &
			 XILINX_PCIE_IMR_ALL_MASK,
		   XILINX_PCIE_REG_IDR);//清除rc自身所有中断(包含INTx)

	/* Enable all interrupts */
	if (!port->cpm_base)//启动RC所有自身中断(包含INTx)
		pcie_write(port, XILINX_PCIE_IMR_ALL_MASK,
			   XILINX_PCIE_REG_IMR);
	pcie_write(port, XILINX_PCIE_IDRN_MASK, XILINX_PCIE_REG_IDRN_MASK);
	if (port->msi_mode == MSI_DECD_MODE) {//启动所有MSI中断
		pcie_write(port, XILINX_PCIE_IDR_ALL_MASK,
			   XILINX_PCIE_REG_MSI_LOW_MASK);
		pcie_write(port, XILINX_PCIE_IDR_ALL_MASK,
			   XILINX_PCIE_REG_MSI_HI_MASK);
	}
	/* Enable the Bridge enable bit */
	pcie_write(port, pcie_read(port, XILINX_PCIE_REG_RPSC) |
			 XILINX_PCIE_REG_RPSC_BEN,
		   XILINX_PCIE_REG_RPSC);

	if (port->cpm_base) {
		writel(XILINX_PCIE_MISC_IR_LOCAL,
		       port->cpm_base + XILINX_PCIE_MISC_IR_ENABLE);
		pcie_write(port, XILINX_PCIE_IMR_ALL_MASK_CPM,
			   XILINX_PCIE_REG_IMR);
	}
}

该部分代码主要就是配置中断相关寄存器,然后使能host bridge。

xilinx_pcie_init_irq_domain

代码如下。

static int xilinx_pcie_init_irq_domain(struct xilinx_pcie_port *port)
{
	struct device *dev = port->dev;
	struct device_node *node = dev->of_node;
	struct device_node *pcie_intc_node;

	/* Setup INTx */
	pcie_intc_node = of_get_next_child(node, NULL);
	if (!pcie_intc_node) {
		dev_err(dev, "No PCIe Intc node found\n");
		return PTR_ERR(pcie_intc_node);
	}

    // 向系统注册irq domain并创建映射
    // 线性映射。其实就是一个lookup table,HW interrupt ID作为index,通过查表可以获取对应的软IRQ number
	port->leg_domain = irq_domain_add_linear(pcie_intc_node, INTX_NUM,
						 &intx_domain_ops,
						 port);
	if (!port->leg_domain) {
		dev_err(dev, "Failed to get a INTx IRQ domain\n");
		return PTR_ERR(port->leg_domain);
	}

    //初始化MSI中断域
	xilinx_pcie_init_msi_irq_domain(port);

	return 0;
}

static int xilinx_pcie_init_msi_irq_domain(struct xilinx_pcie_port *port)
{
	struct fwnode_handle *fwnode = of_node_to_fwnode(port->dev->of_node);
	struct xilinx_msi *msi = &port->msi;
	int size = BITS_TO_LONGS(XILINX_NUM_MSI_IRQS) * sizeof(long);

    //创建irq_domain,并添加到系统
	msi->dev_domain = irq_domain_add_linear(NULL, XILINX_NUM_MSI_IRQS,
						&dev_msi_domain_ops, port);
	if (!msi->dev_domain) {
		dev_err(port->dev, "failed to create dev IRQ domain\n");
		return -ENOMEM;
	}

    //创建msi_domain,下游pcie设备使用该域申请MSI中断
	msi->msi_domain = pci_msi_create_irq_domain(fwnode,
						    &xilinx_msi_domain_info,
						    msi->dev_domain);
	if (!msi->msi_domain) {
		dev_err(port->dev, "failed to create msi IRQ domain\n");
		irq_domain_remove(msi->dev_domain);
		return -ENOMEM;
	}

	mutex_init(&msi->lock);
	msi->bitmap = kzalloc(size, GFP_KERNEL);
	if (!msi->bitmap)
		return -ENOMEM;

    //向系统申请一块内存页,用于监听pcie设备的MSI/MSI-X中断
	xilinx_pcie_enable_msi(port);

	return 0;
}

static void xilinx_pcie_enable_msi(struct xilinx_pcie_port *port)
{
	struct xilinx_msi *msi = &port->msi;
	phys_addr_t msg_addr;

	msi->msi_pages = __get_free_pages(GFP_KERNEL, 0);//申请内存页
	msg_addr = virt_to_phys((void *)msi->msi_pages);//转换为CPU物理地址

    //设置MSI/MSI-X中断监听起始地址,范围为4kb,详情查阅pg194的41页
	pcie_write(port, upper_32_bits(msg_addr), XILINX_PCIE_REG_MSIBASE1);
	pcie_write(port, lower_32_bits(msg_addr), XILINX_PCIE_REG_MSIBASE2);
}

上述代码申请了两个中断域,leg_domain中断域用来处理pcie设备的INTx中断,msi_domain中断域用来处理pcie设备的MSI/MSI-X中断。

linux中断子系统使用中断域方式扩展中断,方便中断控制器之间级联,对于本文来说,就是pci控制器中断级联在gic中断控制器上,该部分详解看pcie设备申请中断章节。

devm_of_pci_get_host_bridge_resources

int devm_of_pci_get_host_bridge_resources(struct device *dev,
			unsigned char busno, unsigned char bus_max,
			struct list_head *resources, resource_size_t *io_base)
{
	struct device_node *dev_node = dev->of_node;
	struct resource *res, tmp_res;
	struct resource *bus_range;
	struct of_pci_range range;
	struct of_pci_range_parser parser;
	char range_type[4];
	int err;

	if (io_base)
		*io_base = (resource_size_t)OF_BAD_ADDR;

	bus_range = devm_kzalloc(dev, sizeof(*bus_range), GFP_KERNEL);
	if (!bus_range)
		return -ENOMEM;

	dev_info(dev, "host bridge %pOF ranges:\n", dev_node);

    //解析设备树中定义的pci bus范围,目前设备树中未定义,使用默认的
	err = of_pci_parse_bus_range(dev_node, bus_range);
	if (err) {
		bus_range->start = busno;//0
		bus_range->end = bus_max;//0xff
		bus_range->flags = IORESOURCE_BUS;
		dev_info(dev, "  No bus range found for %pOF, using %pR\n",
			 dev_node, bus_range);
	} else {
		if (bus_range->end > bus_range->start + bus_max)
			bus_range->end = bus_range->start + bus_max;
	}
	pci_add_resource(resources, bus_range);//将bus号资源添加到链表

	/* Check for ranges property */
	err = of_pci_range_parser_init(&parser, dev_node);//获取ranges属性
	if (err)
		goto failed;

	dev_dbg(dev, "Parsing ranges property...\n");
	for_each_of_pci_range(&parser, &range) {//解析设备树中range属性
		/* Read next ranges element */
		if ((range.flags & IORESOURCE_TYPE_BITS) == IORESOURCE_IO)
			snprintf(range_type, 4, " IO");
		else if ((range.flags & IORESOURCE_TYPE_BITS) == IORESOURCE_MEM)
			snprintf(range_type, 4, "MEM");
		else
			snprintf(range_type, 4, "err");
		dev_info(dev, "  %s %#010llx..%#010llx -> %#010llx\n",
			 range_type, range.cpu_addr,
			 range.cpu_addr + range.size - 1, range.pci_addr);

		/*
		 * If we failed translation or got a zero-sized region
		 * then skip this range
		 */
		if (range.cpu_addr == OF_BAD_ADDR || range.size == 0)
			continue;

		err = of_pci_range_to_resource(&range, dev_node, &tmp_res);
		if (err)
			continue;

		res = devm_kmemdup(dev, &tmp_res, sizeof(tmp_res), GFP_KERNEL);
		if (!res) {
			err = -ENOMEM;
			goto failed;
		}

		if (resource_type(res) == IORESOURCE_IO) {
			if (!io_base) {
				dev_err(dev, "I/O range found for %pOF. Please provide an io_base pointer to save CPU base address\n",
					dev_node);
				err = -EINVAL;
				goto failed;
			}
			if (*io_base != (resource_size_t)OF_BAD_ADDR)
				dev_warn(dev, "More than one I/O resource converted for %pOF. CPU base address for old range lost!\n",
					 dev_node);
			*io_base = range.cpu_addr;
		}

        //将内存加入链表,即设备树中定义的映射,pci地址空间0地址映射到CPU地址空间0xA0000000地址,大小为0x10000000
		pci_add_resource_offset(resources, res,	res->start - range.pci_addr);
	}

	return 0;

failed:
	pci_free_resource_list(resources);
	return err;
}

解析rc设备树中的bus-ranges属性和ranges属性,由于rc设备树中未定义bus-ranges属性,所以使用默认的bus范围,即0~0xff。ranges属性解析出来就是将pci地址空间0地址映射到CPU地址空间0xA0000000地址,大小为0x10000000。

其他函数

devm_request_pci_bus_resources
linux系统使用资源树的方式管理所有可用资源,所有的子系统使用某个资源区域之前都应向资源树申请。在devm_of_pci_get_host_bridge_resources函数中解析了RC的设备树,获取了pci bus区间和pci空间映射的CPU地址空间,在使用这两个资源区间之前,需要向资源树申请,由系统检查资源区间是否被占用,若两个资源区间未被占用,则资源能申请成功,并将这两个资源区间标记被RC占用。
    
pci_scan_root_bus_bridge
将RC注册为host bridge,然后扫描RC下的pcie设备和桥,为它们创建对应结构并获取它们的信息填充到结构中。注意:该函数只会给pcie桥分配bus号,而不会给pcie设备分配memory space和I/O space。
    
pci_assign_unassigned_bus_resources
为RC下的pcie设备分配可用的资源。
    
pci_bus_add_devices
让pcie设备和桥挨个匹配pci总线上的驱动,若有驱动匹配成功,则先执行pci总线的probe函数(pci_device_probe),然后再执行设备驱动的probe函数

pcie设备申请中断

级联中断

在X86架构中,使用APIC上报中断给CPU,而在ARM架构则使用GIC上报中断给CPU。

先看看非级联情况下,中断是如何处理的,如下图所示。
【PCI】ARM架构——PCI总线驱动、RC驱动、Host Bridge驱动、xilinx xdma ip驱动(八)

设备0和设备1共用GIC的0号中断线,当设备1触发中断时,由CPU读取GIC状态获取对应的硬件中断号,再根据硬件中断号找到对应的irq_desc结构,执行结构内部的通用中断处理函数(共享中断),然后通用中断处理函数会依次执行action链表上的设备中断处理函数,在设备中断处理函数中会检查是否是自身触发了中断,如果是,则进行中断处理,如果不是,则直接返回,所以action链表上只有设备1的handler正常执行。

linux系统内部维护一个irq_desc数组,该数组的下标作为linux系统的软中断号,数组的前32个元素用于CPU内部使用(ARM架构),而外部设备共享中断使用32~1019。对于上图来说,因为只有一个中断控制器,所以每个硬件中断引脚可以线性对应一个软件中断号,即硬件中断号与软件中断号有一个固定偏移。那么,如果多个硬件中断控制器呢?比如,增加一个PCIe中断控制器,如下图所示。
【PCI】ARM架构——PCI总线驱动、RC驱动、Host Bridge驱动、xilinx xdma ip驱动(八)

如上图所示,存在两个中断控制器,每个中断控制器都有自己独立的硬件中断号,所以无法简单的线性映射软件中断号。linux系统为了方便中断级联扩展,采用irq_domian方式管理一个个中断控制器,在irq_domian内部维护着一个硬件中断号与软件中断号映射关系表,如下图所示。
【PCI】ARM架构——PCI总线驱动、RC驱动、Host Bridge驱动、xilinx xdma ip驱动(八)

映射方式有三种,分别为linear map、Radix tree map、no map。linear map:维护固定大小的表,索引是硬件中断号,如果硬件中断最大数量固定,并且数值不大,硬件中断号连续,可以选择线性映射;Radix tree map:硬件中断号可能很大,可以选择树映射;no map:硬件中断号直接就是Linux的中断号。

级联PCIE中断控制器后,中断处理如下图所示。
【PCI】ARM架构——PCI总线驱动、RC驱动、Host Bridge驱动、xilinx xdma ip驱动(八)

以本文所讲解的RC驱动为例,pcie中断控制器使用interrupt_out引脚上报自身异常中断和下游设备的INTx中断,在RC驱动xilinx_request_misc_irq函数中,将使用硬件中断号89向GIC irq_domian申请中断(GIC 89硬件中断号对应121软件中断号),xilinx_pcie_intr_handler中断函数将挂在121软件中断号irq_desc结构下的action链表上。当pcie设备触发INTA中断时,pcie中断控制器将拉动interrupt_out引脚向GIC上报中断, CPU查找GIC irq_domian域确认89硬件中断号对应软中断为121,然后执行irq_desc[121].handle_irq函数,handle_irq又会依次去执行action链表上注册的设备中断函数,其中就有xilinx_pcie_intr_handler函数。在xilinx_pcie_intr_handler函数中,将读取INTx状态寄存器,确认是INTA硬件中断线触发了,然后使用硬件中断号0(INTA-INTA)查找INTx irq_domain内部映射表获取软件中断号233(假设),最后执行irq_desc[233].handle_irq函数,handle_irq又会依次去执行action链表上注册的设备中断函数,其中就有PCIe设备注册的中断处理函数。

注意:对于级联中断,可以将irq_desc[xxx].handle_irq函数替换成下级中断分发函数,比如上图121软中断handle_irq函数替换成pcie中断控制器注册的handler函数,但RC驱动并未这样做,这是因为pcie中断控制器注册的handler函数不仅仅分发INTx中断(事实上pcie设备使用INTx中断较少),更多的是处理自身的异常中断状态(设备中断处理)。

我们知道,RC不仅需要处理INTx中断,还需要处理MSI/MSI-X中断,所以真实的连接图如下。
【PCI】ARM架构——PCI总线驱动、RC驱动、Host Bridge驱动、xilinx xdma ip驱动(八)

在RC驱动中为pcie中断控制器申请了两个中断域,msi irq_domain域用来处理MSI/MSI-X中断,中断输出级联引脚interrupt_out_msi_vec0to31、interrupt_out_msi_vec32to63分别连接GIC 90、91引脚(对应软中断号122、123)。在申请中断时,将MSI级联分发函数xilinx_pcie_msi_handler_low、xilinx_pcie_msi_handler_high填充到irq_desc[122]、rq_desc[123]的handle_irq函数(注意不是挂在action链表上)。

pcie设备申请INTx中断

如果pcie设备支持INTx中断,其配置空间寄存器PCI_INTERRUPT_PIN(0x3d)中需要填写该设备将使用哪条INTx线上报中断。

RC驱动中将调用pci_scan_root_bus_bridge函数扫描下游所有设备,然后调用pci_bus_add_devices函数让设备驱匹配pci总线上驱动,如果匹配成功,则先执行pci总线的probe函数,再执行驱动的probe函数,过程如下。

pci_bus_add_devices
	pci_bus_add_device
		device_attach
			__device_attach
				__device_attach_driver
					driver_match_device//检查是否能匹配
					driver_probe_device
    					really_probe
    						dev->bus->probe//执行总线的probe函数,也就是pci_device_probe函数
    							pci_device_probe
    								pci_assign_irq
    									pci_common_swizzle//查找映射表(pcie体系架构23页,表1-3),确定最终连接的上游桥设备号及中断引脚
    									of_irq_parse_and_map_pci//解析interrupt-map 和 interrupt-map-mask 建立映射
    									pci_write_config_byte(dev, PCI_INTERRUPT_LINE, irq)//软件中断号写入配置空间,供后续设备驱动使用
    								__pci_device_probe
    									pci_match_device//再次确认能驱动与设备匹配
    									pci_call_probe
    										local_pci_probe
    											pci_drv->probe
    
//来看看pci总线的probe实现
static int pci_device_probe(struct device *dev)
{
	int error;
	struct pci_dev *pci_dev = to_pci_dev(dev);
	struct pci_driver *drv = to_pci_driver(dev->driver);

	if (!pci_device_can_probe(pci_dev))
		return -ENODEV;

	pci_assign_irq(pci_dev);//为pcie设备的INTx分配软件中断号

	error = pcibios_alloc_irq(pci_dev);
	if (error < 0)
		return error;

	pci_dev_get(pci_dev);
	error = __pci_device_probe(drv, pci_dev);//执行驱动的probe函数
	if (error) {
		pcibios_free_irq(pci_dev);
		pci_dev_put(pci_dev);
	}

	return error;
}

//看看怎么分配INTx对应软件中断号
void pci_assign_irq(struct pci_dev *dev)
{
	u8 pin;
	u8 slot = -1;
	int irq = 0;
	struct pci_host_bridge *hbrg = pci_find_host_bridge(dev->bus);

	if (!(hbrg->map_irq)) {
		pci_dbg(dev, "runtime IRQ mapping not provided by arch\n");
		return;
	}

	/* If this device is not on the primary bus, we need to figure out
	   which interrupt pin it will come in on.   We know which slot it
	   will come in on 'cos that slot is where the bridge is.   Each
	   time the interrupt line passes through a PCI-PCI bridge we must
	   apply the swizzle function.  */

	pci_read_config_byte(dev, PCI_INTERRUPT_PIN, &pin);
	/* Cope with illegal. */
	if (pin > 4)
		pin = 1;

	if (pin) {
		/* Follow the chain of bridges, swizzling as we go.  */
        //根据设备PCI_INTERRUPT_PIN值和设备号,查找映射表(pcie体系架构23页,表1-3),确定最终连接的上游桥设备号及中断引脚
		if (hbrg->swizzle_irq)//pci_common_swizzle
			slot = (*(hbrg->swizzle_irq))(dev, &pin);

		/*
		 * If a swizzling function is not used map_irq must
		 * ignore slot
		 */
		 //解析  interrupt-map 和 interrupt-map-mask 获取真实的irq domain并建立当前irq domain的硬件中断号与linux软中断号的关联
		irq = (*(hbrg->map_irq))(dev, slot, pin);//of_irq_parse_and_map_pci
		if (irq == -1)
			irq = 0;
	}
	dev->irq = irq;

	pci_dbg(dev, "assign IRQ: got %d\n", dev->irq);

	/* Always tell the device, so the driver knows what is
	   the real IRQ to use; the device does not use it. */
	pci_write_config_byte(dev, PCI_INTERRUPT_LINE, irq);
}

u8 pci_common_swizzle(struct pci_dev *dev, u8 *pinp)
{
	u8 pin = *pinp;

	while (!pci_is_root_bus(dev->bus)) {//直到找到最上游桥
		pin = pci_swizzle_interrupt_pin(dev, pin);
		dev = dev->bus->self;
	}
	*pinp = pin;
	return PCI_SLOT(dev->devfn);
}

u8 pci_swizzle_interrupt_pin(const struct pci_dev *dev, u8 pin)
{
	int slot;

	if (pci_ari_enabled(dev->bus))
		slot = 0;
	else
		slot = PCI_SLOT(dev->devfn);

	return (((pin - 1) + slot) % 4) + 1;//转换中断引脚,按pcie体系架构23页表1-3关系(本文设备树讲解章节也有)
}


//已经获得了最上游桥的硬件中断号,再按设备树章节讲解的方式计算一下就获得了pcie设备对应的硬件中断号
of_irq_parse_and_map_pci
    of_irq_parse_pci//解析设备树,计算硬件中断号(设备树章节有讲解计算方式)
    irq_create_of_mapping//创建映射,硬件中断号与软件中断号关联
    	irq_create_fwspec_mapping
    		irq_find_mapping//先查询是否之前映射过
    		irq_create_mapping//没有映射则建立映射
    			irq_domain_alloc_descs//申请一个软中断号,即irq_desc结构
    			irq_domain_associate
    				domain->ops->map//xilinx_pcie_intx_map,填充rq_desc.handle_irq函数为通用中断处理函数
    				irq_domain_set_mapping//建立映射关系
    
//再来看看怎么将硬件中断号与软中断号关联的
static void irq_domain_set_mapping(struct irq_domain *domain,
				   irq_hw_number_t hwirq,
				   struct irq_data *irq_data)
{
	if (hwirq < domain->revmap_size) {//如果是线性映射,则硬件中断号为数组索引,软件中断号为存储的值
		domain->linear_revmap[hwirq] = irq_data->irq;
	} else {
		mutex_lock(&domain->revmap_tree_mutex);
		radix_tree_insert(&domain->revmap_tree, hwirq, irq_data);//树型映射,硬件中断号为索引,软件中断号为存储的值
		mutex_unlock(&domain->revmap_tree_mutex);
	}
}

所以,pcie设备在执行设备驱动的probe函数前就已经为INTx中断申请好了软中断号,后续设备驱动中就可以直接使用软中断号注册设备中断处理函数。

pcie设备申请MSI/MSI-X中断

pcie设备申请MSI/MSI-X中断需要在设备驱动中显性调用申请函数,比如pci_alloc_irq_vectors函数。

pci_alloc_irq_vectors
	pci_alloc_irq_vectors_affinity
		__pci_enable_msix_range
		__pci_enable_msi_range//以MSI为例
    		pci_msi_vec_count//查询MSI Capability获取MSI中断个数
    		msi_capability_init
				//从系统中申请中断desc,从MSI_DOMAIN中分配硬件中断号及对应地址,并使desc与硬件中断号关联,然后将地址写入设备的MSI capability中
    			pci_msi_setup_msi_irqs
    				dev_get_msi_domain//获取设备的msi_domain域,也就是rc创建的msi_domain域
    				msi_domain_alloc_irqs
    					__irq_domain_alloc_irqs
    						irq_domain_alloc_descs//申请软中断号,即irq_desc
    						irq_domain_alloc_irqs_hierarchy
    							domain->ops->alloc//msi_domain_alloc函数
    								irq_domain_alloc_irqs_parent//分配MSI_domain硬件中断号
    									domain->ops->alloc//xilinx_irq_domain_alloc函数,看下面
    							ops->msi_init//msi_domain_ops_init函数,设置设备的irq_data结构,将设备的BDF号关联软中断号,注意这个不是中断时查找使用的
    						irq_domain_insert_irq
    							irq_domain_set_mapping//将硬件中断号与软中断号关联,上面已经讲解过
    						irq_domain_activate_irq//激活MSI中断,即向pcie设备的MSI结构写addr、data
    							__irq_domain_activate_irq
    								domain->ops->activate//msi_domain_activate函数
    									irq_chip_compose_msi_msg
    										pos->chip->irq_compose_msi_msg(pos, msg)//xilinx_compose_msi_msg函数,看下面
    									irq_chip_write_msi_msg
    										data->chip->irq_write_msi_msg(data, msg)//pci_msi_domain_write_msg函数,看下面
    
static int xilinx_irq_domain_alloc(struct irq_domain *domain, unsigned int virq,
				   unsigned int nr_irqs, void *args)
{
	struct xilinx_pcie_port *pcie = domain->host_data;
	struct xilinx_msi *msi = &pcie->msi;
	int bit;
	int i;

	mutex_lock(&msi->lock);
	bit = bitmap_find_free_region(msi->bitmap, XILINX_NUM_MSI_IRQS,
				      get_count_order(nr_irqs));
	if (bit < 0) {
		mutex_unlock(&msi->lock);
		return -ENOSPC;
	}

    // irq_domain_set_info 调用 irq_domain_set_hwirq_and_chip,
    // 然后通过 virq 获取 irq_data 结构体,并将 hwirq 设置到 irq_data->hwirq 中,
    // 后面调用irq_domain_set_mapping函数将硬件中断号与软中断号关联
    /*
        irq_data->hwirq = hwirq;
        irq_data->chip = chip ? chip : &no_irq_chip;
        irq_data->chip_data = chip_data;
    */
	for (i = 0; i < nr_irqs; i++) {
		irq_domain_set_info(domain, virq + i, bit + i, &xilinx_irq_chip,
				    domain->host_data, handle_simple_irq,
				    NULL, NULL);
	}
	mutex_unlock(&msi->lock);
	return 0;
}

//将RC驱动中申请的一块内存地址加上硬件中断号写入pcie设备的MSI addr、data中
//RC监控总线上memory write地址,当写的地址属于msi->msi_pages~msi->msi_pages+4k范围内时,认为触发MSI中断,详见pg194手册41页
static void xilinx_compose_msi_msg(struct irq_data *data, struct msi_msg *msg)
{
	struct xilinx_pcie_port *pcie = irq_data_get_irq_chip_data(data);
	struct xilinx_msi *msi = &pcie->msi;
	phys_addr_t msi_addr;

	msi_addr = virt_to_phys((void *)msi->msi_pages);
	msg->address_lo = lower_32_bits(msi_addr);
	msg->address_hi = upper_32_bits(msi_addr);
	msg->data = data->hwirq;//hwirq = bit + i
}

//将获取的消息信息写到设备pcie设备的MSI addr、data中
void pci_msi_domain_write_msg(struct irq_data *irq_data, struct msi_msg *msg)
{
	struct msi_desc *desc = irq_data_get_msi_desc(irq_data);

	/*
	 * For MSI-X desc->irq is always equal to irq_data->irq. For
	 * MSI only the first interrupt of MULTI MSI passes the test.
	 */
	if (desc->irq == irq_data->irq)
		__pci_write_msi_msg(desc, msg);
}

void __pci_write_msi_msg(struct msi_desc *entry, struct msi_msg *msg)
{
	struct pci_dev *dev = msi_desc_to_pci_dev(entry);

	if (dev->current_state != PCI_D0 || pci_dev_is_disconnected(dev)) {
		/* Don't touch the hardware now */
	} else if (entry->msi_attrib.is_msix) {
		void __iomem *base = pci_msix_desc_addr(entry);

		if (!base)
			goto skip;

		writel(msg->address_lo, base + PCI_MSIX_ENTRY_LOWER_ADDR);
		writel(msg->address_hi, base + PCI_MSIX_ENTRY_UPPER_ADDR);
		writel(msg->data, base + PCI_MSIX_ENTRY_DATA);
	} else {
		int pos = dev->msi_cap;
		u16 msgctl;

		pci_read_config_word(dev, pos + PCI_MSI_FLAGS, &msgctl);
		msgctl &= ~PCI_MSI_FLAGS_QSIZE;
		msgctl |= entry->msi_attrib.multiple << 4;
		pci_write_config_word(dev, pos + PCI_MSI_FLAGS, msgctl);

		pci_write_config_dword(dev, pos + PCI_MSI_ADDRESS_LO,
				       msg->address_lo);
		if (entry->msi_attrib.is_64) {
			pci_write_config_dword(dev, pos + PCI_MSI_ADDRESS_HI,
					       msg->address_hi);
			pci_write_config_word(dev, pos + PCI_MSI_DATA_64,
					      msg->data);
		} else {
			pci_write_config_word(dev, pos + PCI_MSI_DATA_32,
					      msg->data);
		}
	}

skip:
	entry->msg = *msg;

	if (entry->write_msi_msg)
		entry->write_msi_msg(entry, entry->write_msi_msg_data);

}

简单来说,就是rc驱动中申请了一块内存页,并将起始地址写入Root Port MSI Base 1、Root Port MSI Base 2寄存器中(pg194-41页),这样RC就知道哪段地址的memory write操作表示MSI中断(PCIe设备发出的MWR TLP报文)。pcie设备申请中断时,先申请软件中断号,然后回调RC驱动中函数获取硬件中断号并关联软件中断号。激活MSI中断时,回调RC驱动中xilinx_compose_msi_msg函数中组装msg消息(data为硬件中断号),然后写入pcie设备的MSI/MSI-X结构中。

pcie设备触发中断

触发INTx中断

pcie设备通过发送message触发INTx中断,消息类型如下图所示。
【PCI】ARM架构——PCI总线驱动、RC驱动、Host Bridge驱动、xilinx xdma ip驱动(八)

RC接受到消息后拉动interrupt_out引脚上报中断,进而执行xilinx_pcie_intr_handler中断处理函数,该函数代码如下。

static irqreturn_t xilinx_pcie_intr_handler(int irq, void *data)
{
	struct xilinx_pcie_port *port = (struct xilinx_pcie_port *)data;
	u32 val, mask, status, msi_data, bit;
	unsigned long intr_val;

	/* Read interrupt decode and mask registers */
	val = pcie_read(port, XILINX_PCIE_REG_IDR);
	mask = pcie_read(port, XILINX_PCIE_REG_IMR);

	status = val & mask;
	if (!status)
		return IRQ_NONE;

	if (status & XILINX_PCIE_INTR_LINK_DOWN)
		dev_warn(port->dev, "Link Down\n");

	if (status & XILINX_PCIE_INTR_HOT_RESET)
		dev_info(port->dev, "Hot reset\n");

	if (status & XILINX_PCIE_INTR_CFG_TIMEOUT)
		dev_warn(port->dev, "ECAM access timeout\n");

	if (status & XILINX_PCIE_INTR_CORRECTABLE) {
		dev_warn(port->dev, "Correctable error message\n");
		xilinx_pcie_clear_err_interrupts(port);
	}

	if (status & XILINX_PCIE_INTR_NONFATAL) {
		dev_warn(port->dev, "Non fatal error message\n");
		xilinx_pcie_clear_err_interrupts(port);
	}

	if (status & XILINX_PCIE_INTR_FATAL) {
		dev_warn(port->dev, "Fatal error message\n");
		xilinx_pcie_clear_err_interrupts(port);
	}

	if (status & XILINX_PCIE_INTR_INTX) {
		/* Handle INTx Interrupt */
		intr_val = pcie_read(port, XILINX_PCIE_REG_IDRN);
		intr_val = intr_val >> XILINX_PCIE_IDRN_SHIFT;

        // irq_find_mapping 查找irq_domian中映射关系,返回软件中断号,再执行软件中断号对应中断函数链表
		for_each_set_bit(bit, &intr_val, INTX_NUM)
			generic_handle_irq(irq_find_mapping(port->leg_domain,
							    bit));
	}

	if (port->msi_mode == MSI_FIFO_MODE &&
	    (status & XILINX_PCIE_INTR_MSI) && (!port->cpm_base)) {
		/* MSI Interrupt */
		val = pcie_read(port, XILINX_PCIE_REG_RPIFR1);

		if (!(val & XILINX_PCIE_RPIFR1_INTR_VALID)) {
			dev_warn(port->dev, "RP Intr FIFO1 read error\n");
			goto error;
		}

		if (val & XILINX_PCIE_RPIFR1_MSI_INTR) {
			msi_data = pcie_read(port, XILINX_PCIE_REG_RPIFR2) &
				   XILINX_PCIE_RPIFR2_MSG_DATA;

			/* Clear interrupt FIFO register 1 */
			pcie_write(port, XILINX_PCIE_RPIFR1_ALL_MASK,
				   XILINX_PCIE_REG_RPIFR1);

			if (IS_ENABLED(CONFIG_PCI_MSI)) {
				/* Handle MSI Interrupt */
				val = irq_find_mapping(port->msi.dev_domain,
						       msi_data);
				if (val)
					generic_handle_irq(val);
			}
		}
	}

	if (status & XILINX_PCIE_INTR_SLV_UNSUPP)
		dev_warn(port->dev, "Slave unsupported request\n");

	if (status & XILINX_PCIE_INTR_SLV_UNEXP)
		dev_warn(port->dev, "Slave unexpected completion\n");

	if (status & XILINX_PCIE_INTR_SLV_COMPL)
		dev_warn(port->dev, "Slave completion timeout\n");

	if (status & XILINX_PCIE_INTR_SLV_ERRP)
		dev_warn(port->dev, "Slave Error Poison\n");

	if (status & XILINX_PCIE_INTR_SLV_CMPABT)
		dev_warn(port->dev, "Slave Completer Abort\n");

	if (status & XILINX_PCIE_INTR_SLV_ILLBUR)
		dev_warn(port->dev, "Slave Illegal Burst\n");

	if (status & XILINX_PCIE_INTR_MST_DECERR)
		dev_warn(port->dev, "Master decode error\n");

	if (status & XILINX_PCIE_INTR_MST_SLVERR)
		dev_warn(port->dev, "Master slave error\n");

	if (port->cpm_base) {
		if (status & XILINX_PCIE_INTR_CFG_PCIE_TIMEOUT)
			dev_warn(port->dev, "PCIe ECAM access timeout\n");

		if (status & XILINX_PCIE_INTR_CFG_ERR_POISON)
			dev_warn(port->dev, "ECAM poisoned completion received\n");

		if (status & XILINX_PCIE_INTR_PME_TO_ACK_RCVD)
			dev_warn(port->dev, "PME_TO_ACK message received\n");

		if (status & XILINX_PCIE_INTR_PM_PME_RCVD)
			dev_warn(port->dev, "PM_PME message received\n");

		if (status & XILINX_PCIE_INTR_SLV_PCIE_TIMEOUT)
			dev_warn(port->dev, "PCIe completion timeout received\n");
	}

error:
	/* Clear the Interrupt Decode register */
	pcie_write(port, status, XILINX_PCIE_REG_IDR);
	if (port->cpm_base) {
		val = readl(port->cpm_base + XILINX_PCIE_MISC_IR_STATUS);
		if (val)
			writel(val,
			       port->cpm_base + XILINX_PCIE_MISC_IR_STATUS);
	}

	return IRQ_HANDLED;
}

函数中读取INTx状态寄存器,获取硬件中断号,然后使用irq_find_mapping查找leg_domain中断域中该硬件中断号对应的软件中断号,最后去执行软件中断号对应的irq_desc[xxx].handle_irq函数。

触发MSI/MSI-X中断

RC有两个寄存器可以配置监控pcie总线上的MSI中断,配置说明如下图所示。
【PCI】ARM架构——PCI总线驱动、RC驱动、Host Bridge驱动、xilinx xdma ip驱动(八)

当pcie设备发出的memory write TLP地址属于 寄存器基地址~寄存器基地址+4k 范围时,则RC认为接受到了MSI中断,并根据TLP中data值(硬件中断号)置位MSI中断状态寄存器(0x174、0x178),然后拉动interrupt_out_msi_vec0to31或interrupt_out_msi_vec32to63引脚向GIC上报中断,进而执行xilinx_pcie_msi_handler_low或xilinx_pcie_msi_handler_high函数,函数内部读取MSI中断状态寄存器获取硬件中断号,最后根据硬件中断号找到软件中断号,执行irq_desc[xxx].handle_irq函数。

static void xilinx_pcie_msi_handler_low(struct irq_desc *desc)
{
	struct irq_chip *chip = irq_desc_get_chip(desc);
	struct xilinx_pcie_port *port = irq_desc_get_handler_data(desc);

	chained_irq_enter(chip, desc);
	xilinx_pcie_handle_msi_irq(port, XILINX_PCIE_REG_MSI_LOW);
	chained_irq_exit(chip, desc);
}

static void xilinx_pcie_msi_handler_high(struct irq_desc *desc)
{
	struct irq_chip *chip = irq_desc_get_chip(desc);
	struct xilinx_pcie_port *port = irq_desc_get_handler_data(desc);

	chained_irq_enter(chip, desc);
	xilinx_pcie_handle_msi_irq(port, XILINX_PCIE_REG_MSI_HI);
	chained_irq_exit(chip, desc);
}

static void xilinx_pcie_handle_msi_irq(struct xilinx_pcie_port *port,
				       u32 status_reg)
{
	struct xilinx_msi *msi;
	unsigned long status;
	u32 bit;
	u32 virq;

	msi = &port->msi;

	while ((status = pcie_read(port, status_reg)) != 0) {
		for_each_set_bit(bit, &status, 32) {
			pcie_write(port, 1 << bit, status_reg);
			if (status_reg == XILINX_PCIE_REG_MSI_HI)
				bit = bit + 32;
			virq = irq_find_mapping(msi->dev_domain, bit);//查找硬件中断号对应的软件中断号
			if (virq)//执行软件中断
				generic_handle_irq(virq);
		}
	}
}

IP Core配置及连线

地址转换及AXI连线

​ 地址转换查看pg194手册65页。

​ AXI连线查看pg194手册8页、ug1085手册29页、ug1085手册231页。

​ 设备树中配置将pci总线空间地址0映射到CPU地址0xA000_0000上,大小为256M,这段空间作为pcie设备的bar空间。

​ 设备树中配置将CPU地址空间0x4_0000_0000开始的256M作为pcie配置空间,CPU访问这块地址空间将触发ip core的ECAM机制,进而访问对应pcie设备的配置空间。
【PCI】ARM架构——PCI总线驱动、RC驱动、Host Bridge驱动、xilinx xdma ip驱动(八)

中断连线

​ interrupt_out:需要连接到GIC中断控制器上,如PL_PS_Group0、PL_PS_Group1,查看ug1085手册321页

​ interrupt_out_msi_vec0to31:需要连接到GIC中断控制器上,如PL_PS_Group0、PL_PS_Group1,查看ug1085手册321页

​ interrupt_out_msi_vec32to63:需要连接到GIC中断控制器上,如PL_PS_Group0、PL_PS_Group1,查看ug1085手册321页

​ 解码模式配置查看pg194手册85页,解码模式介绍查看pg194手册87页

奇怪的寄存器

​ pg194手册33页显示寄存器map如下图所示。
【PCI】ARM架构——PCI总线驱动、RC驱动、Host Bridge驱动、xilinx xdma ip驱动(八)

​ 也就是说0x000~0x12F范围是bridge桥的配置空间,查看pg213手册,如下。
【PCI】ARM架构——PCI总线驱动、RC驱动、Host Bridge驱动、xilinx xdma ip驱动(八)

​ 但实际读出来和代码中都会使用0x128、0x12C地址?
【PCI】ARM架构——PCI总线驱动、RC驱动、Host Bridge驱动、xilinx xdma ip驱动(八)

if (port->xdma_config == XDMA_ZYNQMP_PL) {
			val = pcie_read(port, XILINX_PCIE_REG_BIR);//read 0x130地址
			val = (val >> XILINX_PCIE_FIFO_SHIFT) & MSI_DECD_MODE;
			mode_val = pcie_read(port, XILINX_PCIE_REG_VSEC) &
					XILINX_PCIE_VSEC_REV_MASK;//read 0x12c地址,XILINX_PCIE_VSEC_REV_MASK:GENMASK(19, 16)
			mode_val = mode_val >> XILINX_PCIE_VSEC_REV_SHIFT;//XILINX_PCIE_VSEC_REV_SHIFT:16
			if (mode_val && !val) {//使用解码模式
				port->msi_mode = MSI_DECD_MODE;
				dev_info(dev, "Using MSI Decode mode\n");
			} else {
				port->msi_mode = MSI_FIFO_MODE;
				dev_info(dev, "Using MSI FIFO mode\n");
			}
		}

ip core需要额外配置?这是一个待填坑的地方,先留着吧~~~
续写:经验证,手册与最新ip core不匹配~~~吐槽一下,ip core更新了,手册居然不更新

clk连线

sys_clk
sys_clk_gt

使用PL内部时钟。

ECAM配置

查看pg194手册55页。

RC需要完成的工作实现

初始化阶段:

​ ①能向CPU申请资源,如PCI总线号、pci内存空间、pci I/O空间。

​ 答:资源解析和申请请查看xilinx_pcie_parse_dt、devm_of_pci_get_host_bridge_resources、devm_request_pci_bus_resources函数说明。

​ ②能扫描到下游设备,并为设备分配资源。

​ 答:RC借助ECAM机制来配置访问下游设备的配置空间,该机制有IP core提供,查看查看pg194手册55页。

工作阶段:

​ ③能转换PCI地址空间与CPU地址空间。

​ ④能将CPU的AXI访问信号转换成pcie TLP总线事务发送到下游总线(信号类型和地址空间转换)。

​ ⑤能将pcie设备发出的MR/MW TLP事务转换成AXI访问信号送给CPU(信号类型和地址空间转换)。

​ 答:③④⑤均由ip core提供的能力,配置方法查看pg194手册65页,简要说明查看pg194手册11页。

​ ⑥能接收PCIe设备中断(INTx、MSI、MSI-X),并反馈给CPU处理(级联中断)。

​ ⑦能向CPU报告RC自身的异常状态。

​ 答:⑥⑦查看pcie设备申请中断章节。文章来源地址https://www.toymoban.com/news/detail-400688.html

到了这里,关于【PCI】ARM架构——PCI总线驱动、RC驱动、Host Bridge驱动、xilinx xdma ip驱动(八)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Xilinx PCIe XDMA IP中断

    IRQ Module Legacy Interrupts (1)usr_irq_req拉高导致中断发送到PCIe Host,可以同时拉高多个bit; (2)等到ack后,相应的usr_irq_req才可以拉低; 第一个 ack 表示 INT 消息已经被发送到了 PCIe block ; (3) 经过 (2) 后 ,FPGA XDMA 内的 interrupt pending register 将会拉高 , 等待 ISR 的查和处理

    2024年01月16日
    浏览(42)
  • Xilinx XDMA 上位机应用程序控制逻辑

    1. 驱动安装的参数 关于驱动的编译和安装这里就不多讲了,无非就是make 和 insmod 。这里讲一下驱动安装时,控制驱动属性的几个参数: 1.中断模式 中断模式分为三种,MSIX是最新的中断模式,老版本的内核可能不支持。就比如说我的内核。如果不指定驱动安装额中断参数,那

    2024年02月09日
    浏览(45)
  • Arm 推出新的总线互联 SoC架构:CI-700 和 NI-700

    快速链接: . 👉👉👉 个人博客笔记导读目录(全部) 👈👈👈 付费专栏-付费课程 【购买须知】: 【精选】ARMv8/ARMv9架构入门到精通-[目录] 👈👈👈 联系方式-加入交流群 ---- 联系方式-加入交流群 AMBA、AXI、AHB、APB、ACE、CHI、ACE-Lite、AXI-Stream、CCI、CMN、CCN、CN、CI、NIC、NOC、NI

    2024年02月05日
    浏览(38)
  • 《PCI Express体系结构导读》随记 —— 第I篇 第2章 PCI总线的桥与配置(20)

    接前一篇文章:《PCI Express体系结构导读》随记 —— 第I篇 第2章 PCI总线的桥与配置(19) PCI总线定义了两类配置请求,一个是Type 00h配置请求,另一个是Type 01h配置请求。PCI总线使用这些配置请求访问PCI总线树上的设备配置空间,包括PCI桥和PCI Agent设备的配置空间。 其中,

    2024年01月21日
    浏览(41)
  • FPGA(基于xilinx)中PCIe介绍以及IP核XDMA的使用

    例如:第一章 PCIe简介以及IP核的使用 PCIe 总线架构与以太网的 OSI 模型类似,是一种分层协议架构, 分为事务层(Transaction Layer)、 数据链路层(Data Link Layer) 和物理层(Physical Layer)。 这些层中的每一层都分为两部分:一部分处理出站(要发送的)信息,另一部分处理入站(接收

    2024年02月08日
    浏览(46)
  • 手把手教你学会 Xilinx PCIE/XDMA 读写DDR系列(三) ——XDMA读写DDR项目工程讲解和下板测试

    因最近想通过PCIE把数据从FPGA传到PC,借此机会和大家一起学习XDMA读写DDR 制作不易,记得三连哦,给我动力,持续更新!!! 完整工程文件下载:XDMA读写DDR工程   提取码:4sxh 在前两篇文章的学习中,我们已经成功配置了XDMA读写DDR所需的两个关键IP核,并深入学习了XDMA读写

    2024年03月12日
    浏览(62)
  • Qemu虚拟arm开发板驱动开发详解(一)——驱动基本架构

            此前在《WSL2下Ubuntu22.04使用Qemu搭建虚拟Vexpress-A9开发板》系列文章中,我们已建立好Linux最小系统的运行环境,并将其成功移植到了由Qemu模拟的arm32开发板上。接下来将介绍如何基于上述环境进行驱动开发。         本节主要带各位读者了解Linux内核驱动的基本架

    2024年02月05日
    浏览(46)
  • XILINX ZYNQ 7000 AXI总线 (二)

    了解了AXI 的大部分内容后,开始做一个实战,就是完成AXI 结构的输入输出搭建。 一.创建一个IP 3. 4. 5. 6.选择AXI FULL,创界主接口和从接口 7. 8.可以看到XILINX AXI FULL 的源代码 二.创建一个新的工程,把IP导入到这个工程 2.创建 block design 放入两个AXI IP 三 设计 创建两个 接口进行

    2024年02月16日
    浏览(59)
  • Linux下PCI设备驱动开发详解(一)

    PCI总线是目前应用最广泛的计算机总线标准,而且是一种兼容性最强,功能最全的计算机总线。 而linux作为一种开源的操作系统,同时也为PCI总线与各种新型设备互联成为可能。尤其被现在的异构计算GPU/FPGA、软硬结合新的方向广泛运用。 应用程序位于用户空间,驱动程序位

    2024年02月04日
    浏览(43)
  • Linux下PCI设备驱动开发详解(二)

    根据上一章的概念,PCI驱动包括PCI通用的驱动,以及根据实际需要设备本身的驱动。 所谓的编写设备驱动,其实就是编写设备本身驱动,因为linux内核的PCI驱动是内核自带的。 为了更好的学习PCI设备驱动,我们需要明白内核具体做了什么,下面我们研究一下,linux PCI通用的驱

    2024年01月19日
    浏览(48)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包