本文以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需要完成以下工作内容。
初始化阶段:
①能向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初始化完成之后,才有扫描下游PCIe设备的能力。那么,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为三个元素时格式为:
第一个参数表示是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项格式如下:
比如描述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中断线),其
注意:对于pci总线号非0的设备,在计算之前需要转换到最上游的briege设备才能计算,转换关系如下图所示。
pcie设备B的INTA中断线对应硬件中断号是多少呢?设备B的bus不为0,因此需要转换中断引脚。首先,设备B的设备号为1(AD引脚与IDSEL引脚连线决定设备号是多少),根据转换关系,设备B的中断INTA应该连接上游桥A的INTB,所以设备B的INTA触发中断将传导至桥A的INTB上,然后,桥A的bus为0,所以得出
地址映射
PCI地址空间与CPU地址空间是完全分离的,所以需要通过定义ranges属性进行地址转化。
其对应格式为
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位。
示例:
{
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机制)。
linux系统启动过程中会解析设备树,并为各节点创建对应的实例,然后挂在对应的总线上,对于上面的rc设备树来说,linux系统将会创建一个rc平台设备挂在plaform总线上,进而去匹配RC驱动,执行RC驱动中的probe函数,也就是xilinx_pcie_probe函数。
来看一下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;
}
主要干的事都注释了,来看看主要函数内部实现。
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, ®s);//解析设备树中reg区域
if (err) {
dev_err(dev, "missing \"reg\" property\n");
return err;
}
//将reg映射到系统,该区域小部分地址用来访问RC控制器寄存器,
//大部分地址用于ECAM机制访问外设配置空间
port->reg_base = devm_ioremap_resource(dev, ®s);
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解析格式如下。
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设备申请中断章节详细讲解。
代码如下。
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。
代码如下。
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设备申请中断章节。
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函数
在X86架构中,使用APIC上报中断给CPU,而在ARM架构则使用GIC上报中断给CPU。
设备0和设备1共用GIC的0号中断线,当设备1触发中断时,由CPU读取GIC状态获取对应的硬件中断号,再根据硬件中断号找到对应的irq_desc结构,执行结构内部的通用中断处理函数(共享中断),然后通用中断处理函数会依次执行action链表上的设备中断处理函数,在设备中断处理函数中会检查是否是自身触发了中断,如果是,则进行中断处理,如果不是,则直接返回,所以action链表上只有设备1的handler正常执行。
linux系统内部维护一个irq_desc数组,该数组的下标作为linux系统的软中断号,数组的前32个元素用于CPU内部使用(ARM架构),而外部设备共享中断使用32~1019。对于上图来说,因为只有一个中断控制器,所以每个硬件中断引脚可以线性对应一个软件中断号,即硬件中断号与软件中断号有一个固定偏移。那么,如果多个硬件中断控制器呢?比如,增加一个PCIe中断控制器,如下图所示。
如上图所示,存在两个中断控制器,每个中断控制器都有自己独立的硬件中断号,所以无法简单的线性映射软件中断号。linux系统为了方便中断级联扩展,采用irq_domian方式管理一个个中断控制器,在irq_domian内部维护着一个硬件中断号与软件中断号映射关系表,如下图所示。
映射方式有三种,分别为linear map、Radix tree map、no map。linear map:维护固定大小的表,索引是硬件中断号,如果硬件中断最大数量固定,并且数值不大,硬件中断号连续,可以选择线性映射;Radix tree map:硬件中断号可能很大,可以选择树映射;no map:硬件中断号直接就是Linux的中断号。
以本文所讲解的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中断,所以真实的连接图如下。
在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中断,其配置空间寄存器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中断需要在设备驱动中显性调用申请函数,比如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设备通过发送message触发INTx中断,消息类型如下图所示。
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函数。
RC有两个寄存器可以配置监控pcie总线上的MSI中断,配置说明如下图所示。
当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);
}
}
}
地址转换及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设备的配置空间。
中断连线
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页
奇怪的寄存器
也就是说0x000~0x12F范围是bridge桥的配置空间,查看pg213手册,如下。
但实际读出来和代码中都会使用0x128、0x12C地址?
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页。
初始化阶段:
①能向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设备申请中断章节。