本文以xilinx RC IP為例,講解ARM的RC驅(qū)動(dòng)(PL)。
IP例程參考網(wǎng)址:https://xilinx-wiki.atlassian.net/wiki/spaces/A/pages/18842034/Xilinx+Linux+PL+PCIe+Root+Port
IP文檔文檔參考網(wǎng)址:https://docs.xilinx.com/v/u/en-US/pg194-axi-bridge-pcie-gen3和https://docs.xilinx.com/r/en-US/pg213-pcie4-ultrascale-plus
使用平臺(tái)參考文檔網(wǎng)址:https://docs.xilinx.com/v/u/en-US/ug1085-zynq-ultrascale-trm
RC驅(qū)動(dòng)代碼參考網(wǎng)址:https://github.com/Xilinx/linux-xlnx/blob/master/drivers/pci/controller/pcie-xdma-pl.c
參考代碼:pcie-xdma-pl.c
內(nèi)核版本:linux5.4
RC需要完成哪些工作?
先看RC連接簡(jiǎn)圖。
RC需要完成以下工作內(nèi)容。
初始化階段:
? ①能向CPU申請(qǐng)資源,如PCI總線號(hào)、pci內(nèi)存空間、pci I/O空間。
? ②能掃描到下游設(shè)備,并為設(shè)備分配資源。
工作階段:
? ③能轉(zhuǎn)換PCI地址空間與CPU地址空間。
? ④能將CPU的AXI訪問信號(hào)轉(zhuǎn)換成pcie TLP總線事務(wù)發(fā)送到下游總線(信號(hào)類型和地址空間轉(zhuǎn)換)。
? ⑤能將pcie設(shè)備發(fā)出的MR/MW TLP事務(wù)轉(zhuǎn)換成AXI訪問信號(hào)送給CPU(信號(hào)類型和地址空間轉(zhuǎn)換)。
? ⑥能接收PCIe設(shè)備中斷(INTx、MSI、MSI-X),并反饋給CPU處理(級(jí)聯(lián)中斷)。
? ⑦能向CPU報(bào)告RC自身的異常狀態(tài)。文章來源:http://www.zghlxwxcb.cn/news/detail-400688.html
后續(xù)陸續(xù)講解到對(duì)應(yīng)實(shí)現(xiàn)。
RC初始化
只有當(dāng)RC初始化完成之后,才有掃描下游PCIe設(shè)備的能力。那么,RC是如何被系統(tǒng)發(fā)現(xiàn)并初始化的呢?
RC對(duì)應(yīng)的設(shè)備樹
X86平臺(tái),linux內(nèi)核可以使用BIOS上報(bào)設(shè)備信息來初始化一系列設(shè)備,而ARM平臺(tái),則使用設(shè)備樹告訴linux內(nèi)核有哪些設(shè)備。
使用的ZU19EG平臺(tái)沒有BIOS,因此RC的信息需要添加到設(shè)備樹中,以下是RC對(duì)應(yīng)的設(shè)備樹信息(參考官網(wǎng)例程)。
{
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設(shè)備掛在platform總線上,依靠compatible字段與RC驅(qū)動(dòng)(pcie-xdma-pl.c)完成匹配。
設(shè)備樹詳解:
普通中斷
interrupts為三個(gè)元素時(shí)格式為:
<type, interrupt number, trigger type>
第一個(gè)參數(shù)表示是PPI、SPI、SGI其中的一個(gè)
【??】SGI: software generated interrupts 中斷號(hào) 0~15
【1】PPI: per processor inerrupts 中斷號(hào) 16~31
【0】SPI: shared processor interrupts 32~1019
第二個(gè)參數(shù)表示:是第一個(gè)參數(shù)類型中的第幾個(gè)中斷號(hào)
第三個(gè)參數(shù)表示:中斷觸發(fā)的類型。(上升沿、下降沿等)
#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表示中斷號(hào)為SPI中斷類型中的第89號(hào)中斷號(hào),計(jì)算出來的實(shí)際中斷號(hào)即為32+89=121號(hào)中斷,4表示高電平沿觸發(fā)中斷。
中斷的父節(jié)點(diǎn)是gic,也就是中斷引腳連在gic上。
驅(qū)動(dòng)中通過platform_get_irq_byname函數(shù)根據(jù)"misc"名字獲取對(duì)應(yīng)的中斷號(hào)(32+89=121)。
中斷映射
interrupt-cells表示定義了一個(gè)中斷說明符(中斷域)所需要的單元數(shù)。
interrupt-controller表示將包含該屬性的節(jié)點(diǎn)定義為中斷控制器,也就是說這個(gè)RC包含中斷控制器。
interrupt-map-mask指定了設(shè)備樹中的鏈接節(jié)點(diǎn)計(jì)算使用的掩碼。
interrupt-map將一個(gè)中斷域與一組父中斷域橋接,并指定子域中的中斷說明符如何映射到其各自的父域,每一行有5項(xiàng)格式如下:
-
child unit address: 子節(jié)點(diǎn)的單元地址
比如描述pci bus為0、設(shè)備號(hào)為0x11、功能號(hào)為0的設(shè)備,(0x0<<16)|(0x11<<11)|(0x0<<8)=0x8800,則描述符為<0x8800 0 0>
-
child interrupt specifier: 子節(jié)點(diǎn)的中斷說明符
在pci總線上就是:1(INTA)、2(INTB)、3(INTC)、4(INTD)
-
interrupt-parent: 該值指向子節(jié)點(diǎn)將被映射到的中斷父域
-
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,第一個(gè)0x0000是BDF號(hào))
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結(jié)合使用,比如pci bus為0、設(shè)備號(hào)為0x11、功能號(hào)為0的設(shè)備(根據(jù)pci總線規(guī)定function為0的設(shè)備使用INTA中斷線),其<child unit address ,child interrupt specifier>描述為<0x8800 0 0 1>,和interrupt-map-mask <0 0 0 7>對(duì)應(yīng)進(jìn)行與運(yùn)算后結(jié)果為<0 0 0 1>,也就是匹配<0 0 0 1 &pcie_intc_0 1>節(jié)點(diǎn),這就表示pci bus為0、設(shè)備號(hào)為0x11、功能號(hào)為0的設(shè)備中斷引腳INTA連接在PCI控制器的INTA引腳上,那么該設(shè)備的硬件中斷號(hào)就是0(INTA - INTA)。
注意:對(duì)于pci總線號(hào)非0的設(shè)備,在計(jì)算之前需要轉(zhuǎn)換到最上游的briege設(shè)備才能計(jì)算,轉(zhuǎn)換關(guān)系如下圖所示。
怎么轉(zhuǎn)換呢?舉個(gè)例子。
pcie設(shè)備B的INTA中斷線對(duì)應(yīng)硬件中斷號(hào)是多少呢?設(shè)備B的bus不為0,因此需要轉(zhuǎn)換中斷引腳。首先,設(shè)備B的設(shè)備號(hào)為1(AD引腳與IDSEL引腳連線決定設(shè)備號(hào)是多少),根據(jù)轉(zhuǎn)換關(guān)系,設(shè)備B的中斷INTA應(yīng)該連接上游橋A的INTB,所以設(shè)備B的INTA觸發(fā)中斷將傳導(dǎo)至橋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>對(duì)應(yīng)進(jìn)行與運(yùn)算后結(jié)果為<0x0 0x0 0x0 2>,所以設(shè)備B的INTA中斷線對(duì)應(yīng)的硬件中斷號(hào)為1(INTB-INTA)。注意:上面INTA~INTD的中斷線連接是虛擬的,因?yàn)閜cie是使用message上報(bào)INTx中斷,但映射關(guān)系是一樣的,此外,ADx總線與IDSEL線都是虛擬的,這里只是為了方便理解而借用PCI總線定義。
地址映射
PCI地址空間與CPU地址空間是完全分離的,所以需要通過定義ranges屬性進(jìn)行地址轉(zhuǎn)化。
其對(duì)應(yīng)格式為<pci addr, cpu addr, size>
- pci addr使用3個(gè)cell描述
3個(gè)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:代表重申請(qǐng)空間標(biāo)志(這里沒有使用)
? p:代表預(yù)讀空間(緩存)標(biāo)志
? t:別名地址標(biāo)志(這里沒有使用)
? ss:空間代碼
? 00: 設(shè)置空間
? 01:IO空間
? 10:32位空間
? 11:64位空間
? bbbbbbbb: PCI總線號(hào)。PCI有可能是層次性架構(gòu),所以我們可能需要區(qū)分一些子-總線
? ddddd:設(shè)備號(hào),通常由初始化設(shè)備選擇信號(hào)IDSEL連接時(shí)申請(qǐng)。
? fff:功能序號(hào),有些多功能PCI設(shè)備可能用到。
? rrrrrrrr:注冊(cè)號(hào),在設(shè)置周期使用。
hhhhhhhh hhhhhhhh hhhhhhhh hhhhhhhh:PCI地址的高32位。
llllllll llllllll llllllll llllllll:PCI地址的低32位。
- cpu addr則根據(jù)父節(jié)點(diǎn)的#address-cells屬性來決定使用多少cell描述
- size根據(jù)自身節(jié)點(diǎn)的#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 地址空間長(zhǎng)度
先看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個(gè)cell組成,高位在前地位在后,即CPU地址為0xA0000000
地址長(zhǎng)度
xdma_0.#size-cells為2,所以地址長(zhǎng)度由2個(gè)cell組成,,高位在前地位在后,即地址空間長(zhǎng)度為0x10000000
整個(gè)ranges表示:從PCI地址空間地址為0開始,申請(qǐng)0x10000000空間大小,映射到CPU0xA0000000地址空間上,后續(xù)CPU訪問0xA0000000開始的地址,就是訪問PCI空間
區(qū)域空間
設(shè)備樹使用reg標(biāo)明了一塊區(qū)域空間,即reg = <0x00000004 0x00000000 0x0 0x10000000>;
這個(gè)空間將在RC的驅(qū)動(dòng)中解析,用來當(dāng)做pcie配置空間(ECAM機(jī)制)。
xilinx_pcie_probe初始化過程
linux系統(tǒng)啟動(dòng)過程中會(huì)解析設(shè)備樹,并為各節(jié)點(diǎn)創(chuàng)建對(duì)應(yīng)的實(shí)例,然后掛在對(duì)應(yīng)的總線上,對(duì)于上面的rc設(shè)備樹來說,linux系統(tǒng)將會(huì)創(chuàng)建一個(gè)rc平臺(tái)設(shè)備掛在plaform總線上,進(jìn)而去匹配RC驅(qū)動(dòng),執(zhí)行RC驅(qū)動(dòng)中的probe函數(shù),也就是xilinx_pcie_probe函數(shù)。
probe簡(jiǎn)化流程如下圖所示。
來看一下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);
//分配并初始化一個(gè)基礎(chǔ)的pci_hsot_bridge結(jié)構(gòu),尾部多申請(qǐng)sizeof(*port)
bridge = devm_pci_alloc_host_bridge(dev, sizeof(*port));
if (!bridge)
return -ENODEV;
//port為掛在bridge尾部的私有數(shù)據(jù),之前多申請(qǐng)了sizeof(*port)空間
port = pci_host_bridge_priv(bridge);
port->dev = dev;
//解析設(shè)備樹,獲取資源信息和申請(qǐng)中斷,如寄存器基地址
err = xilinx_pcie_parse_dt(port);
if (err) {
dev_err(dev, "Parsing DT failed\n");
return err;
}
//初始化寄存器
xilinx_pcie_init_port(port);
//創(chuàng)建INTx中斷控制器域,創(chuàng)建MSI中斷控制器域
err = xilinx_pcie_init_irq_domain(port);
if (err) {
dev_err(dev, "Failed creating IRQ Domain\n");
return err;
}
//將bus號(hào)資源和內(nèi)存資源加入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;
}
//向系統(tǒng)資源樹申請(qǐng)資源,會(huì)檢查資源沖突
err = devm_request_pci_bus_resources(dev, &res);
if (err)
goto error;
//將資源掛到RC bridge資源樹上,后續(xù)pci資源都從該資源樹上申請(qǐng)
list_splice_init(&res, &bridge->windows);
bridge->dev.parent = dev;
bridge->sysdata = port;
bridge->busnr = port->root_busno;//未初始化,默認(rèn)為0,此處為坑點(diǎn),多個(gè)RC務(wù)必小心(同一個(gè)pcie域下,默認(rèn)情況下每個(gè)RC有各自的域,因?yàn)槊總€(gè)RC由不同的平臺(tái)總線設(shè)備注冊(cè),但也可以自定義一個(gè)平臺(tái)總線設(shè)備,然后在驅(qū)動(dòng)中注冊(cè)多個(gè)RC,這樣就屬于一個(gè)pcie總線域)
bridge->ops = &xilinx_pcie_ops;
//中斷映射函數(shù),用于解析設(shè)備樹,獲取設(shè)備與INTx中斷映射關(guān)系
bridge->map_irq = of_irq_parse_and_map_pci;
//查找中斷映射表,返回pcie設(shè)備最終連接到host橋上的INTx中斷線
bridge->swizzle_irq = pci_common_swizzle;
//掃描根總線分配bus號(hào),填充信息,但未分配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);
//執(zhí)行設(shè)備與驅(qū)動(dòng)的匹配操作
pci_bus_add_devices(bus);
return 0;
error:
pci_free_resource_list(&res);
return err;
}
主要干的事都注釋了,來看看主要函數(shù)內(nèi)部實(shí)現(xiàn)。
主要函數(shù)
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, ®s);//解析設(shè)備樹中reg區(qū)域
if (err) {
dev_err(dev, "missing \"reg\" property\n");
return err;
}
//將reg映射到系統(tǒng),該區(qū)域小部分地址用來訪問RC控制器寄存器,
//大部分地址用于ECAM機(jī)制訪問外設(shè)配置空間
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) {//最重要的步驟
//申請(qǐng)中斷用于處理RC核內(nèi)部事件,包含下游設(shè)備的INTx中斷
err = xilinx_request_misc_irq(port);
if (err)
return err;
//填充通用中斷處理函數(shù)和通用數(shù)據(jù)
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;
}
將設(shè)備樹中reg屬性解析出來,即從0x4_0000_0000地址開始的0x10000000大小空間(256M),該區(qū)域小部分用于RC控制操作(pg194的34頁),大部分用于ECAM機(jī)制(pg194的55頁)訪問下游pcie設(shè)備的配置空間。ECAM解析格式如下。
IP Core會(huì)將訪問地址按上面格式解析,轉(zhuǎn)換成配置訪問,就如同X86的配置訪問一樣。
上面最重要的步驟是申請(qǐng)中斷,即以下兩函數(shù)。
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,看設(shè)備樹解析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自身申請(qǐng)中斷,包含處理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,看設(shè)備樹解析32+90=122
if (port->msi.irq_msi0 <= 0) {
dev_err(dev, "Unable to find msi0 IRQ line\n");
return port->msi.irq_msi0;
}
//給指定軟中斷號(hào)填充共享數(shù)據(jù)port及通用中斷處理函數(shù)xilinx_pcie_msi_handler_low
irq_set_chained_handler_and_data(port->msi.irq_msi0,
xilinx_pcie_msi_handler_low,
port);//設(shè)置級(jí)聯(lián)中斷
port->msi.irq_msi1 = platform_get_irq_byname(pdev, "msi1");//123,看設(shè)備樹解析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);//設(shè)置級(jí)聯(lián)中斷
return 0;
}
上面兩個(gè)函數(shù)申請(qǐng)了3個(gè)中斷,121軟中斷號(hào)對(duì)應(yīng)的中斷用于處理RC自身上報(bào)的異常以及pcie設(shè)備觸發(fā)的INTx中斷,122和123軟中斷號(hào)用于處理下游pcie設(shè)備觸發(fā)的MSI/MSI-X中斷。有點(diǎn)迷糊?沒關(guān)系,在pcie設(shè)備申請(qǐng)中斷章節(jié)詳細(xì)講解。
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);//關(guān)閉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)//啟動(dòng)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) {//啟動(dòng)所有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);
}
}
該部分代碼主要就是配置中斷相關(guān)寄存器,然后使能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);
}
// 向系統(tǒng)注冊(cè)irq domain并創(chuàng)建映射
// 線性映射。其實(shí)就是一個(gè)lookup table,HW interrupt ID作為index,通過查表可以獲取對(duì)應(yīng)的軟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);
//創(chuàng)建irq_domain,并添加到系統(tǒng)
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;
}
//創(chuàng)建msi_domain,下游pcie設(shè)備使用該域申請(qǐng)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;
//向系統(tǒng)申請(qǐng)一塊內(nèi)存頁,用于監(jiān)聽pcie設(shè)備的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);//申請(qǐng)內(nèi)存頁
msg_addr = virt_to_phys((void *)msi->msi_pages);//轉(zhuǎn)換為CPU物理地址
//設(shè)置MSI/MSI-X中斷監(jiān)聽起始地址,范圍為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);
}
上述代碼申請(qǐng)了兩個(gè)中斷域,leg_domain中斷域用來處理pcie設(shè)備的INTx中斷,msi_domain中斷域用來處理pcie設(shè)備的MSI/MSI-X中斷。
linux中斷子系統(tǒng)使用中斷域方式擴(kuò)展中斷,方便中斷控制器之間級(jí)聯(lián),對(duì)于本文來說,就是pci控制器中斷級(jí)聯(lián)在gic中斷控制器上,該部分詳解看pcie設(shè)備申請(qǐng)中斷章節(jié)。
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);
//解析設(shè)備樹中定義的pci bus范圍,目前設(shè)備樹中未定義,使用默認(rèn)的
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號(hào)資源添加到鏈表
/* 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) {//解析設(shè)備樹中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;
}
//將內(nèi)存加入鏈表,即設(shè)備樹中定義的映射,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設(shè)備樹中的bus-ranges屬性和ranges屬性,由于rc設(shè)備樹中未定義bus-ranges屬性,所以使用默認(rèn)的bus范圍,即0~0xff。ranges屬性解析出來就是將pci地址空間0地址映射到CPU地址空間0xA0000000地址,大小為0x10000000。
其他函數(shù)
devm_request_pci_bus_resources
linux系統(tǒng)使用資源樹的方式管理所有可用資源,所有的子系統(tǒng)使用某個(gè)資源區(qū)域之前都應(yīng)向資源樹申請(qǐng)。在devm_of_pci_get_host_bridge_resources函數(shù)中解析了RC的設(shè)備樹,獲取了pci bus區(qū)間和pci空間映射的CPU地址空間,在使用這兩個(gè)資源區(qū)間之前,需要向資源樹申請(qǐng),由系統(tǒng)檢查資源區(qū)間是否被占用,若兩個(gè)資源區(qū)間未被占用,則資源能申請(qǐng)成功,并將這兩個(gè)資源區(qū)間標(biāo)記被RC占用。
pci_scan_root_bus_bridge
將RC注冊(cè)為host bridge,然后掃描RC下的pcie設(shè)備和橋,為它們創(chuàng)建對(duì)應(yīng)結(jié)構(gòu)并獲取它們的信息填充到結(jié)構(gòu)中。注意:該函數(shù)只會(huì)給pcie橋分配bus號(hào),而不會(huì)給pcie設(shè)備分配memory space和I/O space。
pci_assign_unassigned_bus_resources
為RC下的pcie設(shè)備分配可用的資源。
pci_bus_add_devices
讓pcie設(shè)備和橋挨個(gè)匹配pci總線上的驅(qū)動(dòng),若有驅(qū)動(dòng)匹配成功,則先執(zhí)行pci總線的probe函數(shù)(pci_device_probe),然后再執(zhí)行設(shè)備驅(qū)動(dòng)的probe函數(shù)
pcie設(shè)備申請(qǐng)中斷
級(jí)聯(lián)中斷
在X86架構(gòu)中,使用APIC上報(bào)中斷給CPU,而在ARM架構(gòu)則使用GIC上報(bào)中斷給CPU。
先看看非級(jí)聯(lián)情況下,中斷是如何處理的,如下圖所示。
設(shè)備0和設(shè)備1共用GIC的0號(hào)中斷線,當(dāng)設(shè)備1觸發(fā)中斷時(shí),由CPU讀取GIC狀態(tài)獲取對(duì)應(yīng)的硬件中斷號(hào),再根據(jù)硬件中斷號(hào)找到對(duì)應(yīng)的irq_desc結(jié)構(gòu),執(zhí)行結(jié)構(gòu)內(nèi)部的通用中斷處理函數(shù)(共享中斷),然后通用中斷處理函數(shù)會(huì)依次執(zhí)行action鏈表上的設(shè)備中斷處理函數(shù),在設(shè)備中斷處理函數(shù)中會(huì)檢查是否是自身觸發(fā)了中斷,如果是,則進(jìn)行中斷處理,如果不是,則直接返回,所以action鏈表上只有設(shè)備1的handler正常執(zhí)行。
linux系統(tǒng)內(nèi)部維護(hù)一個(gè)irq_desc數(shù)組,該數(shù)組的下標(biāo)作為linux系統(tǒng)的軟中斷號(hào),數(shù)組的前32個(gè)元素用于CPU內(nèi)部使用(ARM架構(gòu)),而外部設(shè)備共享中斷使用32~1019。對(duì)于上圖來說,因?yàn)橹挥幸粋€(gè)中斷控制器,所以每個(gè)硬件中斷引腳可以線性對(duì)應(yīng)一個(gè)軟件中斷號(hào),即硬件中斷號(hào)與軟件中斷號(hào)有一個(gè)固定偏移。那么,如果多個(gè)硬件中斷控制器呢?比如,增加一個(gè)PCIe中斷控制器,如下圖所示。
如上圖所示,存在兩個(gè)中斷控制器,每個(gè)中斷控制器都有自己獨(dú)立的硬件中斷號(hào),所以無法簡(jiǎn)單的線性映射軟件中斷號(hào)。linux系統(tǒng)為了方便中斷級(jí)聯(lián)擴(kuò)展,采用irq_domian方式管理一個(gè)個(gè)中斷控制器,在irq_domian內(nèi)部維護(hù)著一個(gè)硬件中斷號(hào)與軟件中斷號(hào)映射關(guān)系表,如下圖所示。
映射方式有三種,分別為linear map、Radix tree map、no map。linear map:維護(hù)固定大小的表,索引是硬件中斷號(hào),如果硬件中斷最大數(shù)量固定,并且數(shù)值不大,硬件中斷號(hào)連續(xù),可以選擇線性映射;Radix tree map:硬件中斷號(hào)可能很大,可以選擇樹映射;no map:硬件中斷號(hào)直接就是Linux的中斷號(hào)。
級(jí)聯(lián)PCIE中斷控制器后,中斷處理如下圖所示。
以本文所講解的RC驅(qū)動(dòng)為例,pcie中斷控制器使用interrupt_out引腳上報(bào)自身異常中斷和下游設(shè)備的INTx中斷,在RC驅(qū)動(dòng)xilinx_request_misc_irq函數(shù)中,將使用硬件中斷號(hào)89向GIC irq_domian申請(qǐng)中斷(GIC 89硬件中斷號(hào)對(duì)應(yīng)121軟件中斷號(hào)),xilinx_pcie_intr_handler中斷函數(shù)將掛在121軟件中斷號(hào)irq_desc結(jié)構(gòu)下的action鏈表上。當(dāng)pcie設(shè)備觸發(fā)INTA中斷時(shí),pcie中斷控制器將拉動(dòng)interrupt_out引腳向GIC上報(bào)中斷, CPU查找GIC irq_domian域確認(rèn)89硬件中斷號(hào)對(duì)應(yīng)軟中斷為121,然后執(zhí)行irq_desc[121].handle_irq函數(shù),handle_irq又會(huì)依次去執(zhí)行action鏈表上注冊(cè)的設(shè)備中斷函數(shù),其中就有xilinx_pcie_intr_handler函數(shù)。在xilinx_pcie_intr_handler函數(shù)中,將讀取INTx狀態(tài)寄存器,確認(rèn)是INTA硬件中斷線觸發(fā)了,然后使用硬件中斷號(hào)0(INTA-INTA)查找INTx irq_domain內(nèi)部映射表獲取軟件中斷號(hào)233(假設(shè)),最后執(zhí)行irq_desc[233].handle_irq函數(shù),handle_irq又會(huì)依次去執(zhí)行action鏈表上注冊(cè)的設(shè)備中斷函數(shù),其中就有PCIe設(shè)備注冊(cè)的中斷處理函數(shù)。
注意:對(duì)于級(jí)聯(lián)中斷,可以將irq_desc[xxx].handle_irq函數(shù)替換成下級(jí)中斷分發(fā)函數(shù),比如上圖121軟中斷handle_irq函數(shù)替換成pcie中斷控制器注冊(cè)的handler函數(shù),但RC驅(qū)動(dòng)并未這樣做,這是因?yàn)閜cie中斷控制器注冊(cè)的handler函數(shù)不僅僅分發(fā)INTx中斷(事實(shí)上pcie設(shè)備使用INTx中斷較少),更多的是處理自身的異常中斷狀態(tài)(設(shè)備中斷處理)。
我們知道,RC不僅需要處理INTx中斷,還需要處理MSI/MSI-X中斷,所以真實(shí)的連接圖如下。
在RC驅(qū)動(dòng)中為pcie中斷控制器申請(qǐng)了兩個(gè)中斷域,msi irq_domain域用來處理MSI/MSI-X中斷,中斷輸出級(jí)聯(lián)引腳interrupt_out_msi_vec0to31、interrupt_out_msi_vec32to63分別連接GIC 90、91引腳(對(duì)應(yīng)軟中斷號(hào)122、123)。在申請(qǐng)中斷時(shí),將MSI級(jí)聯(lián)分發(fā)函數(shù)xilinx_pcie_msi_handler_low、xilinx_pcie_msi_handler_high填充到irq_desc[122]、rq_desc[123]的handle_irq函數(shù)(注意不是掛在action鏈表上)。
pcie設(shè)備申請(qǐng)INTx中斷
如果pcie設(shè)備支持INTx中斷,其配置空間寄存器PCI_INTERRUPT_PIN(0x3d)中需要填寫該設(shè)備將使用哪條INTx線上報(bào)中斷。
RC驅(qū)動(dòng)中將調(diào)用pci_scan_root_bus_bridge函數(shù)掃描下游所有設(shè)備,然后調(diào)用pci_bus_add_devices函數(shù)讓設(shè)備驅(qū)匹配pci總線上驅(qū)動(dòng),如果匹配成功,則先執(zhí)行pci總線的probe函數(shù),再執(zhí)行驅(qū)動(dòng)的probe函數(shù),過程如下。
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//執(zhí)行總線的probe函數(shù),也就是pci_device_probe函數(shù)
pci_device_probe
pci_assign_irq
pci_common_swizzle//查找映射表(pcie體系架構(gòu)23頁,表1-3),確定最終連接的上游橋設(shè)備號(hào)及中斷引腳
of_irq_parse_and_map_pci//解析interrupt-map 和 interrupt-map-mask 建立映射
pci_write_config_byte(dev, PCI_INTERRUPT_LINE, irq)//軟件中斷號(hào)寫入配置空間,供后續(xù)設(shè)備驅(qū)動(dòng)使用
__pci_device_probe
pci_match_device//再次確認(rèn)能驅(qū)動(dòng)與設(shè)備匹配
pci_call_probe
local_pci_probe
pci_drv->probe
//來看看pci總線的probe實(shí)現(xiàn)
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設(shè)備的INTx分配軟件中斷號(hào)
error = pcibios_alloc_irq(pci_dev);
if (error < 0)
return error;
pci_dev_get(pci_dev);
error = __pci_device_probe(drv, pci_dev);//執(zhí)行驅(qū)動(dòng)的probe函數(shù)
if (error) {
pcibios_free_irq(pci_dev);
pci_dev_put(pci_dev);
}
return error;
}
//看看怎么分配INTx對(duì)應(yīng)軟件中斷號(hào)
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. */
//根據(jù)設(shè)備PCI_INTERRUPT_PIN值和設(shè)備號(hào),查找映射表(pcie體系架構(gòu)23頁,表1-3),確定最終連接的上游橋設(shè)備號(hào)及中斷引腳
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 獲取真實(shí)的irq domain并建立當(dāng)前irq domain的硬件中斷號(hào)與linux軟中斷號(hào)的關(guān)聯(lián)
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;//轉(zhuǎn)換中斷引腳,按pcie體系架構(gòu)23頁表1-3關(guān)系(本文設(shè)備樹講解章節(jié)也有)
}
//已經(jīng)獲得了最上游橋的硬件中斷號(hào),再按設(shè)備樹章節(jié)講解的方式計(jì)算一下就獲得了pcie設(shè)備對(duì)應(yīng)的硬件中斷號(hào)
of_irq_parse_and_map_pci
of_irq_parse_pci//解析設(shè)備樹,計(jì)算硬件中斷號(hào)(設(shè)備樹章節(jié)有講解計(jì)算方式)
irq_create_of_mapping//創(chuàng)建映射,硬件中斷號(hào)與軟件中斷號(hào)關(guān)聯(lián)
irq_create_fwspec_mapping
irq_find_mapping//先查詢是否之前映射過
irq_create_mapping//沒有映射則建立映射
irq_domain_alloc_descs//申請(qǐng)一個(gè)軟中斷號(hào),即irq_desc結(jié)構(gòu)
irq_domain_associate
domain->ops->map//xilinx_pcie_intx_map,填充rq_desc.handle_irq函數(shù)為通用中斷處理函數(shù)
irq_domain_set_mapping//建立映射關(guān)系
//再來看看怎么將硬件中斷號(hào)與軟中斷號(hào)關(guān)聯(lián)的
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) {//如果是線性映射,則硬件中斷號(hào)為數(shù)組索引,軟件中斷號(hào)為存儲(chǔ)的值
domain->linear_revmap[hwirq] = irq_data->irq;
} else {
mutex_lock(&domain->revmap_tree_mutex);
radix_tree_insert(&domain->revmap_tree, hwirq, irq_data);//樹型映射,硬件中斷號(hào)為索引,軟件中斷號(hào)為存儲(chǔ)的值
mutex_unlock(&domain->revmap_tree_mutex);
}
}
所以,pcie設(shè)備在執(zhí)行設(shè)備驅(qū)動(dòng)的probe函數(shù)前就已經(jīng)為INTx中斷申請(qǐng)好了軟中斷號(hào),后續(xù)設(shè)備驅(qū)動(dòng)中就可以直接使用軟中斷號(hào)注冊(cè)設(shè)備中斷處理函數(shù)。
pcie設(shè)備申請(qǐng)MSI/MSI-X中斷
pcie設(shè)備申請(qǐng)MSI/MSI-X中斷需要在設(shè)備驅(qū)動(dòng)中顯性調(diào)用申請(qǐng)函數(shù),比如pci_alloc_irq_vectors函數(shù)。
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中斷個(gè)數(shù)
msi_capability_init
//從系統(tǒng)中申請(qǐng)中斷desc,從MSI_DOMAIN中分配硬件中斷號(hào)及對(duì)應(yīng)地址,并使desc與硬件中斷號(hào)關(guān)聯(lián),然后將地址寫入設(shè)備的MSI capability中
pci_msi_setup_msi_irqs
dev_get_msi_domain//獲取設(shè)備的msi_domain域,也就是rc創(chuàng)建的msi_domain域
msi_domain_alloc_irqs
__irq_domain_alloc_irqs
irq_domain_alloc_descs//申請(qǐng)軟中斷號(hào),即irq_desc
irq_domain_alloc_irqs_hierarchy
domain->ops->alloc//msi_domain_alloc函數(shù)
irq_domain_alloc_irqs_parent//分配MSI_domain硬件中斷號(hào)
domain->ops->alloc//xilinx_irq_domain_alloc函數(shù),看下面
ops->msi_init//msi_domain_ops_init函數(shù),設(shè)置設(shè)備的irq_data結(jié)構(gòu),將設(shè)備的BDF號(hào)關(guān)聯(lián)軟中斷號(hào),注意這個(gè)不是中斷時(shí)查找使用的
irq_domain_insert_irq
irq_domain_set_mapping//將硬件中斷號(hào)與軟中斷號(hào)關(guān)聯(lián),上面已經(jīng)講解過
irq_domain_activate_irq//激活MSI中斷,即向pcie設(shè)備的MSI結(jié)構(gòu)寫addr、data
__irq_domain_activate_irq
domain->ops->activate//msi_domain_activate函數(shù)
irq_chip_compose_msi_msg
pos->chip->irq_compose_msi_msg(pos, msg)//xilinx_compose_msi_msg函數(shù),看下面
irq_chip_write_msi_msg
data->chip->irq_write_msi_msg(data, msg)//pci_msi_domain_write_msg函數(shù),看下面
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 調(diào)用 irq_domain_set_hwirq_and_chip,
// 然后通過 virq 獲取 irq_data 結(jié)構(gòu)體,并將 hwirq 設(shè)置到 irq_data->hwirq 中,
// 后面調(diào)用irq_domain_set_mapping函數(shù)將硬件中斷號(hào)與軟中斷號(hào)關(guān)聯(lián)
/*
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驅(qū)動(dòng)中申請(qǐng)的一塊內(nèi)存地址加上硬件中斷號(hào)寫入pcie設(shè)備的MSI addr、data中
//RC監(jiān)控總線上memory write地址,當(dāng)寫的地址屬于msi->msi_pages~msi->msi_pages+4k范圍內(nèi)時(shí),認(rèn)為觸發(fā)MSI中斷,詳見pg194手冊(cè)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
}
//將獲取的消息信息寫到設(shè)備pcie設(shè)備的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);
}
簡(jiǎn)單來說,就是rc驅(qū)動(dòng)中申請(qǐng)了一塊內(nèi)存頁,并將起始地址寫入Root Port MSI Base 1、Root Port MSI Base 2寄存器中(pg194-41頁),這樣RC就知道哪段地址的memory write操作表示MSI中斷(PCIe設(shè)備發(fā)出的MWR TLP報(bào)文)。pcie設(shè)備申請(qǐng)中斷時(shí),先申請(qǐng)軟件中斷號(hào),然后回調(diào)RC驅(qū)動(dòng)中函數(shù)獲取硬件中斷號(hào)并關(guān)聯(lián)軟件中斷號(hào)。激活MSI中斷時(shí),回調(diào)RC驅(qū)動(dòng)中xilinx_compose_msi_msg函數(shù)中組裝msg消息(data為硬件中斷號(hào)),然后寫入pcie設(shè)備的MSI/MSI-X結(jié)構(gòu)中。
pcie設(shè)備觸發(fā)中斷
觸發(fā)INTx中斷
pcie設(shè)備通過發(fā)送message觸發(fā)INTx中斷,消息類型如下圖所示。
RC接受到消息后拉動(dòng)interrupt_out引腳上報(bào)中斷,進(jìn)而執(zhí)行xilinx_pcie_intr_handler中斷處理函數(shù),該函數(shù)代碼如下。
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中映射關(guān)系,返回軟件中斷號(hào),再執(zhí)行軟件中斷號(hào)對(duì)應(yīng)中斷函數(shù)鏈表
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;
}
函數(shù)中讀取INTx狀態(tài)寄存器,獲取硬件中斷號(hào),然后使用irq_find_mapping查找leg_domain中斷域中該硬件中斷號(hào)對(duì)應(yīng)的軟件中斷號(hào),最后去執(zhí)行軟件中斷號(hào)對(duì)應(yīng)的irq_desc[xxx].handle_irq函數(shù)。
觸發(fā)MSI/MSI-X中斷
RC有兩個(gè)寄存器可以配置監(jiān)控pcie總線上的MSI中斷,配置說明如下圖所示。
當(dāng)pcie設(shè)備發(fā)出的memory write TLP地址屬于 寄存器基地址~寄存器基地址+4k 范圍時(shí),則RC認(rèn)為接受到了MSI中斷,并根據(jù)TLP中data值(硬件中斷號(hào))置位MSI中斷狀態(tài)寄存器(0x174、0x178),然后拉動(dòng)interrupt_out_msi_vec0to31或interrupt_out_msi_vec32to63引腳向GIC上報(bào)中斷,進(jìn)而執(zhí)行xilinx_pcie_msi_handler_low或xilinx_pcie_msi_handler_high函數(shù),函數(shù)內(nèi)部讀取MSI中斷狀態(tài)寄存器獲取硬件中斷號(hào),最后根據(jù)硬件中斷號(hào)找到軟件中斷號(hào),執(zhí)行irq_desc[xxx].handle_irq函數(shù)。
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);//查找硬件中斷號(hào)對(duì)應(yīng)的軟件中斷號(hào)
if (virq)//執(zhí)行軟件中斷
generic_handle_irq(virq);
}
}
}
IP Core配置及連線
地址轉(zhuǎn)換及AXI連線
? 地址轉(zhuǎn)換查看pg194手冊(cè)65頁。
? AXI連線查看pg194手冊(cè)8頁、ug1085手冊(cè)29頁、ug1085手冊(cè)231頁。
? 設(shè)備樹中配置將pci總線空間地址0映射到CPU地址0xA000_0000上,大小為256M,這段空間作為pcie設(shè)備的bar空間。
? 設(shè)備樹中配置將CPU地址空間0x4_0000_0000開始的256M作為pcie配置空間,CPU訪問這塊地址空間將觸發(fā)ip core的ECAM機(jī)制,進(jìn)而訪問對(duì)應(yīng)pcie設(shè)備的配置空間。
中斷連線
? interrupt_out:需要連接到GIC中斷控制器上,如PL_PS_Group0、PL_PS_Group1,查看ug1085手冊(cè)321頁
? interrupt_out_msi_vec0to31:需要連接到GIC中斷控制器上,如PL_PS_Group0、PL_PS_Group1,查看ug1085手冊(cè)321頁
? interrupt_out_msi_vec32to63:需要連接到GIC中斷控制器上,如PL_PS_Group0、PL_PS_Group1,查看ug1085手冊(cè)321頁
? 解碼模式配置查看pg194手冊(cè)85頁,解碼模式介紹查看pg194手冊(cè)87頁
奇怪的寄存器
? pg194手冊(cè)33頁顯示寄存器map如下圖所示。
? 也就是說0x000~0x12F范圍是bridge橋的配置空間,查看pg213手冊(cè),如下。
? 但實(shí)際讀出來和代碼中都會(huì)使用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需要額外配置?這是一個(gè)待填坑的地方,先留著吧~~~
續(xù)寫:經(jīng)驗(yàn)證,手冊(cè)與最新ip core不匹配~~~吐槽一下,ip core更新了,手冊(cè)居然不更新
clk連線
sys_clk
sys_clk_gt
使用PL內(nèi)部時(shí)鐘。
ECAM配置
查看pg194手冊(cè)55頁。
RC需要完成的工作實(shí)現(xiàn)
初始化階段:
? ①能向CPU申請(qǐng)資源,如PCI總線號(hào)、pci內(nèi)存空間、pci I/O空間。
? 答:資源解析和申請(qǐng)請(qǐng)查看xilinx_pcie_parse_dt、devm_of_pci_get_host_bridge_resources、devm_request_pci_bus_resources函數(shù)說明。
? ②能掃描到下游設(shè)備,并為設(shè)備分配資源。
? 答:RC借助ECAM機(jī)制來配置訪問下游設(shè)備的配置空間,該機(jī)制有IP core提供,查看查看pg194手冊(cè)55頁。
工作階段:
? ③能轉(zhuǎn)換PCI地址空間與CPU地址空間。
? ④能將CPU的AXI訪問信號(hào)轉(zhuǎn)換成pcie TLP總線事務(wù)發(fā)送到下游總線(信號(hào)類型和地址空間轉(zhuǎn)換)。
? ⑤能將pcie設(shè)備發(fā)出的MR/MW TLP事務(wù)轉(zhuǎn)換成AXI訪問信號(hào)送給CPU(信號(hào)類型和地址空間轉(zhuǎn)換)。
? 答:③④⑤均由ip core提供的能力,配置方法查看pg194手冊(cè)65頁,簡(jiǎn)要說明查看pg194手冊(cè)11頁。
? ⑥能接收PCIe設(shè)備中斷(INTx、MSI、MSI-X),并反饋給CPU處理(級(jí)聯(lián)中斷)。
? ⑦能向CPU報(bào)告RC自身的異常狀態(tài)。
? 答:⑥⑦查看pcie設(shè)備申請(qǐng)中斷章節(jié)。文章來源地址http://www.zghlxwxcb.cn/news/detail-400688.html
到了這里,關(guān)于【PCI】ARM架構(gòu)——PCI總線驅(qū)動(dòng)、RC驅(qū)動(dòng)、Host Bridge驅(qū)動(dòng)、xilinx xdma ip驅(qū)動(dòng)(八)的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!