RK3568 PCIE2.0 x1 RC模式控制器驱动代码流程
一、rk_pcie_probe函数的执行内容
二、dw_pcie_host_init函数的执行内容
三、PCIE链路训练的代码内容
四、PCIE扫描流程
五、访问下游设备配置空间的接口函数内容
Pcie2x1接口对应的硬件原理图,开发板是用于接Intel I210-T1 PCIE 网卡

对应的设备树节点:
pcie2x1: pcie@fe260000 { compatible = "rockchip,rk3568-pcie", "snps,dw-pcie"; #address-cells = <3>; #size-cells = <2>; bus-range = <0x0 0xf>; clocks = <&cru ACLK_PCIE20_MST>, <&cru ACLK_PCIE20_SLV>, <&cru ACLK_PCIE20_DBI>, <&cru PCLK_PCIE20>, <&cru CLK_PCIE20_AUX_NDFT>; clock-names = "aclk_mst", "aclk_slv", "aclk_dbi", "pclk", "aux"; device_type = "pci"; interrupts = <GIC_SPI 75 IRQ_TYPE_LEVEL_HIGH>, <GIC_SPI 74 IRQ_TYPE_LEVEL_HIGH>, <GIC_SPI 73 IRQ_TYPE_LEVEL_HIGH>, <GIC_SPI 72 IRQ_TYPE_LEVEL_HIGH>, <GIC_SPI 71 IRQ_TYPE_LEVEL_HIGH>; interrupt-names = "sys", "pmc", "msg", "legacy", "err"; #interrupt-cells = <1>; interrupt-map-mask = <0 0 0 7>; interrupt-map = <0 0 0 1 &pcie2x1_intc 0>, <0 0 0 2 &pcie2x1_intc 1>, <0 0 0 3 &pcie2x1_intc 2>, <0 0 0 4 &pcie2x1_intc 3>; linux,pci-domain = <0>; num-ib-windows = <6>; num-viewport = <8>; num-ob-windows = <2>; max-link-speed = <2>; msi-map = <0x0 &its 0x0 0x1000>; num-lanes = <1>; phys = <&combphy2_psq PHY_TYPE_PCIE>; phy-names = "pcie-phy"; power-domains = <&power RK3568_PD_PIPE>; ranges = <0x00000800 0x0 0xf4000000 0x0 0xf4000000 0x0 0x100000 0x81000000 0x0 0xf4100000 0x0 0xf4100000 0x0 0x100000 0x82000000 0x0 0xf4200000 0x0 0xf4200000 0x0 0x1e00000 0xc3000000 0x3 0x00000000 0x3 0x00000000 0x0 0x40000000>; reg = <0x3 0xc0000000 0x0 0x400000>, <0x0 0xfe260000 0x0 0x10000>; reg-names = "pcie-dbi", "pcie-apb"; resets = <&cru SRST_PCIE20_POWERUP>; reset-names = "pipe"; status = "disabled"; pcie2x1_intc: legacy-interrupt-controller { interrupt-controller; #address-cells = <0>; #interrupt-cells = <1>; interrupt-parent = <&gic>; interrupts = <GIC_SPI 72 IRQ_TYPE_EDGE_RISING>; }; };
设备树的compatible属性值是"rockchip,rk3568-pcie",
和pci_driver\pci\controller\dwc\pcie-dw-rockchip.c驱动匹配,进入probe函数rk_pcie_probe
一、rk_pcie_probe函数的执行内容
1、从设备树获取"pcie-dbi"和"pcie-apb"寄存器的基地址
2、循环10秒等待vpcie3v3驱动加载完成,然后调用电源管理子系统的接口regulator_enable使能vpcie3v3
3、初始化phy
4、从设备树获取复位信息resets = <&cru SRST_PCIE20_POWERUP>,对控制器进行解复位reset_control_deassert
5、从设备树获取sys中断信息interrupts = <GIC_SPI 75 IRQ_TYPE_LEVEL_HIGH>,注册sys中断的处理函数
6、使能所有时钟clock-names = "aclk_mst", "aclk_slv", "aclk_dbi", "pclk", "aux"
7、操作pcie控制器的dbi寄存器使能读写
8、配置Rockchip PCIe控制器的LTSSM(链路训练与状态机)在热复位后能快速、可控地重新建立链路
9、创建一个新的irq_domain,将pcie控制器里面的中断映射到内核的虚拟中断
10、通过设置apb寄存器,将pcie控制器初始化为RC模式
11、调用Pcie控制器初始化函数,然后等待控制器回调,开始链路训练Enable LTSSM(Link Training and Status State Machine)
12、pcie的dma初始化、挂接dma的启动和配置函数
13、创建sysfs调试设备文件、写寄存器开启pcie控制器的调试寄存器
/* * probe函数 * rk_pcie_probe * 分配私有结构体内存struct rk_pcie *rk_pcie;、struct dw_pcie *pci; * 获取"pcie-dbi"寄存器地址、获取"pcie-apb"寄存器地址 * 循环10秒等待vpcie3v3驱动加载完成 * 使能电源 * 初始化phy * 获取复位信息、进行解复位操作 * 注册sys中断 * 使能所有时钟clock-names = "aclk_mst", "aclk_slv", "aclk_dbi", "pclk", "aux"; * 操作pcie控制器的dbi寄存器使能读写 * 操作寄存器,配置Rockchip PCIe控制器的LTSSM(链路训练与状态机)在热复位后能快速、可控地重新建立链路 * 创建一个新的irq_domain,将pcie控制器里面的中断映射到内核的虚拟中断, * pcie控制器里面的中断汇聚到GIC 72 SPI中断,72中断回调后,就可以通过虚拟中断号回调具体的处理函数,并且通过虚拟中断号,可以使用内核接口管理中断,比如中断屏蔽使能等 * 将pcie控制器初始化为RC模式 * 调用Pcie控制器初始化函数,然后等待控制器回调,开始链路训练Enable LTSSM(Link Training and Status State Machine) * pcie的dma初始化 * 挂接dma的启动和配置函数 * 关闭dbi寄存器可读写功能 * 告诉内核,这个设备具有唤醒系统的能力 * Enable async system PM for multiports SoC * 调试设备文件、写寄存器开启pcie控制器的调试信息 */ static int rk_pcie_probe(struct platform_device *pdev) rk_pcie_really_probe(pdev); /* 获取rk_pcie_of_match,这里面存的是pcie控制器的工作模式,RC或者EP */ const struct of_device_id *match; match = of_match_device(rk_pcie_of_match, dev); const struct rk_pcie_of_data *data; enum rk_pcie_device_mode mode; data = (struct rk_pcie_of_data *)match->data; mode = (enum rk_pcie_device_mode)data->mode; /* 驱动的私有结构体 */ struct rk_pcie *rk_pcie; rk_pcie = devm_kzalloc(dev, sizeof(*rk_pcie), GFP_KERNEL); struct dw_pcie *pci; pci = devm_kzalloc(dev, sizeof(*pci), GFP_KERNEL); /* * 绑定两个操作函数 * rk_pcie_establish_link、rk_pcie_link_up */ pci->dev = dev; pci->ops = &dw_pcie_ops; /* "rockchip,rk3568-pcie"驱动没有使用msi中断,这里是0 */ rk_pcie->msi_vector_num = data->msi_vector_num; rk_pcie->mode = mode; rk_pcie->pci = pci; /* * 从设备树获取资源 * 获取"pcie-dbi"寄存器地址 * 获取"pcie-apb"寄存器地址 */ ret = rk_pcie_resource_get(pdev, rk_pcie); /* 获取"pcie-dbi"寄存器地址 */ struct resource *dbi_base; dbi_base = platform_get_resource_byname(pdev, IORESOURCE_MEM, "pcie-dbi"); rk_pcie->dbi_base = devm_ioremap_resource(&pdev->dev, dbi_base); rk_pcie->pci->dbi_base = rk_pcie->dbi_base; /* 获取"pcie-apb"寄存器地址 */ apb_base = platform_get_resource_byname(pdev, IORESOURCE_MEM, "pcie-apb"); rk_pcie->apb_base = devm_ioremap_resource(&pdev->dev, apb_base); /* * 循环10秒等待vpcie3v3驱动加载完成 */ retry_regulator: rk_pcie->vpcie3v3 = devm_regulator_get_optional(dev, "vpcie3v3"); if (IS_ERR(rk_pcie->vpcie3v3)) if (PTR_ERR(rk_pcie->vpcie3v3) != -ENODEV) if (IS_ENABLED(CONFIG_PCIE_RK_THREADED_INIT)) /* Deferred but in threaded context for most 10s */ msleep(20); if (++val < 500) goto retry_regulator; ret = PTR_ERR(rk_pcie->vpcie3v3); goto release_driver; /* * 使能电源 */ ret = rk_pcie_enable_power(rk_pcie); /* 调用电源管理子系统的函数使能3v3电源 */ ret = regulator_enable(rk_pcie->vpcie3v3); /* * 初始化phy */ ret = rk_pcie_phy_init(rk_pcie); /* 获取phy句柄,该句柄已经和combphy2_psq设备节点的Phy驱动绑定 */ struct device *dev = rk_pcie->pci->dev; rk_pcie->phy = devm_phy_optional_get(dev, "pcie-phy"); /* * 设置phy模式 * phy_set_mode_ext() 是 PHY 框架提供的扩展 API,向 PHY 驱动传递主模式 + 子模式 * 在 Rockchip naneng-combphy 驱动中,该函数会将模式保存到 PHY 私有数据中,供后续 phy_init 使用。 */ switch (rk_pcie->mode) case RK_PCIE_RC_TYPE: rk_pcie->phy_mode = PHY_MODE_PCIE; /* make no sense */ rk_pcie->phy_sub_mode = PHY_MODE_PCIE_RC; break; ret = phy_set_mode_ext(rk_pcie->phy, rk_pcie->phy_mode, rk_pcie->phy_sub_mode); /* * 调用phy的初始化接口 * 根据之前设置的模式配置 PHY 寄存器(PLL 分频系数、Tx/Rx 参数、阻抗校准等) * 使能并配置参考时钟(100MHz,由设备树 assigned-clock-rates 保证) * 释放复位信号(reset_control_deassert,对应设备树中的 resets) */ ret = phy_init(rk_pcie->phy); /* * 调用phy的上电接口 * phy_power_on() 调用 PHY 驱动的 .power_on 回调 * 该回调通常使能 PHY 的高速 SerDes 发送器和接收器,让 PHY 开始发送训练序列(TS1/TS2),为链路训练做准备 * 此时 PCIe 控制器才能检测到对端设备(如果对端也上电并应答) */ phy_power_on(rk_pcie->phy); /* * 获取复位信息 * 对应设备树属性:resets = <&cru SRST_PCIE20_POWERUP>; */ rk_pcie->rsts = devm_reset_control_array_get_exclusive(dev); /* * 释放数组中所有(这里是 1 个)复位信号,使 PCIe 控制器脱离复位状态 * 也就是解复位,让pcie相关模块工作起来 */ reset_control_deassert(rk_pcie->rsts); /* * 注册sys中断 * 对应设备树的属性:interrupts = <GIC_SPI 75 IRQ_TYPE_LEVEL_HIGH>, */ ret = rk_pcie_request_sys_irq(rk_pcie, pdev); int irq; /* 获取中断号 */ irq = platform_get_irq_byname(pdev, "sys"); /* 注册中断回调函数 */ ret = devm_request_irq(rk_pcie->pci->dev, irq, rk_pcie_sys_irq_handler, IRQF_SHARED, "pcie-sys", rk_pcie); /* 将rk_pcie设置为驱动的私有数据 */ platform_set_drvdata(pdev, rk_pcie); /* * 初始化时钟 * 从设备树获取所有时钟,对时钟进行prepare和enable * 对应的设备树节点属性:clocks = <&cru ACLK_PCIE20_MST>, <&cru ACLK_PCIE20_SLV>, <&cru ACLK_PCIE20_DBI>, <&cru PCLK_PCIE20>, <&cru CLK_PCIE20_AUX_NDFT>; clock-names = "aclk_mst", "aclk_slv", "aclk_dbi", "pclk", "aux"; * 这些时钟在硬件上必须全部使能,PCIe 控制器才能正确响应配置空间访问、DMA 传输以及中断处理 */ ret = rk_pcie_clk_init(rk_pcie); struct device *dev = rk_pcie->pci->dev; /* * 查找 dev 对应的设备树节点 * 解析其 clocks 属性,得到 &cru 和数字ID(如 42) * 通过 &cru 找到对应的时钟控制器驱动 * 调用该驱动的 .get() 钩子函数,传入数字ID 42 * 时钟控制器驱动根据ID 42,在内部查找并返回代表 ACLK_PCIE20_MST 这个实际时钟的 struct clk 指针 * 使用时钟:获取到 struct clk 指针后,驱动就可以通过 clk_bulk_prepare_enable() 等标准API来操作时钟了 */ rk_pcie->clk_cnt = devm_clk_bulk_get_all(dev, &rk_pcie->clks); ret = clk_bulk_prepare_enable(rk_pcie->clk_cnt, rk_pcie->clks); /* 操作pcie控制器的dbi寄存器使能读写 */ dw_pcie_dbi_ro_wr_en(pci); u32 reg; u32 val; reg = PCIE_MISC_CONTROL_1_OFF; val = dw_pcie_readl_dbi(pci, reg); val |= PCIE_DBI_RO_WR_EN; dw_pcie_writel_dbi(pci, reg, val); /* * 操作apb寄存器 * 配置Rockchip PCIe控制器的LTSSM(链路训练与状态机)在热复位后能快速、可控地重新建立链路 */ rk_pcie_fast_link_setup(rk_pcie); u32 val; /* LTSSM EN ctrl mode */ val = rk_pcie_readl_apb(rk_pcie, PCIE_CLIENT_HOT_RESET_CTRL); val |= PCIE_LTSSM_ENABLE_ENHANCE | (PCIE_LTSSM_ENABLE_ENHANCE << 16); rk_pcie_writel_apb(rk_pcie, PCIE_CLIENT_HOT_RESET_CTRL, val); /* * 为 PCIe 控制器下的传统 INTx 中断(INTA~INTD)建立 IRQ 映射域,从而将 PCIe 设备发出的中断信号路由到 GIC * PCIe设备A触发 INTA * PCIe控制器将 INTA 合并,向GIC发送 SPI 72 信号 * CPU跳转到Linux内核的GIC总处理函数 * GIC根据IRQ号(SPI 72),调用Rockchip驱动注册的 rk_pcie_irq_handler * rk_pcie_irq_handler 读取寄存器,发现是 INTA 触发的 * 调用 generic_handle_domain_irq(domain, INTA_INDEX)、将 INTA_INDEX 映射为具体的Linux IRQ号(如 201) * 内核调用IRQ 201上挂载中断处理函数 */ ret = rk_pcie_init_irq_domain(rk_pcie); struct device *dev = rockchip->pci->dev; struct device_node *intc = of_get_next_child(dev->of_node, NULL); raw_spin_lock_init(&rockchip->intx_lock); /* * 通过“创建容器 + 填充映射表 + 绑定硬件操作”三步,完美地实现了“将物理硬件中断线(INTX 0~3)转换成 Linux 内核虚拟 IRQ 号”的功能 * static const struct irq_domain_ops intx_domain_ops = * .map = rk_pcie_intx_map, * 建立一个新的irq_domain,基于intc节点建立,这个域里面的中断数量是PCI_NUM_INTX * irq_domain绑定的操作函数是intx_domain_ops * */ rockchip->irq_domain = irq_domain_add_linear(intc, PCI_NUM_INTX, &intx_domain_ops, rockchip); static int rk_pcie_intx_map(struct irq_domain *domain, unsigned int irq, irq_hw_number_t hwirq) /* static struct irq_chip rk_pcie_legacy_irq_chip = { .name = "rk-pcie-legacy-int", .irq_enable = rk_pcie_legacy_irq_unmask, .irq_disable = rk_pcie_legacy_irq_mask, .irq_mask = rk_pcie_legacy_irq_mask, .irq_unmask = rk_pcie_legacy_irq_unmask, .flags = IRQCHIP_SKIP_SET_WAKE | IRQCHIP_MASK_ON_SUSPEND, }; * 把虚拟中断号 201 和硬件操作集 rk_pcie_legacy_irq_chip 绑定。以后用户 enable_irq(201) 时,内核就知道去调用 rk_pcie_legacy_irq_unmask 写 PCIe 寄存器 * 这样就实现了pcie控制器上的中断映射到内核的虚拟中断号,并挂接硬件操作函数,内核就可以通过虚拟中断号,调用内核的中断操作函数进行中断处理 * 整个流程: * 系统启动:rk_pcie_init_irq_domain 执行,irq_domain 容器创建成功 * Linux PCI 核心发现设备需要 INTA、PCI 核心调用 irq_create_mapping(domain, 0)(hwirq=0) * 内核分配一个虚拟 IRQ 号(比如 201)、触发 .map 回调,即执行 rk_pcie_intx_map(domain, 201, 0)、将 201 绑定 rk_pcie_legacy_irq_chip 和 rockchip 数据 * 设备驱动申请中断,request_irq(201, handler, ...),此 handler 被挂入 irq 201 的动作链表 * 中断发生、总入口 rk_pcie_irq_handler(注册在 SPI 72 上)运行、读寄存器发现是 INTA,调用 generic_handle_domain_irq(domain, 0) * 内核通过 domain 查到 hwirq 0 对应虚拟 IRQ 201、内核调用 irq 201 的流控函数(handle_simple_irq)、handle_simple_irq调用201的handler */ irq_set_chip_and_handler(irq, &rk_pcie_legacy_irq_chip, handle_simple_irq); irq_set_chip_data(irq, domain->host_data); /* Set PCIe mode */ rk_pcie_set_mode(rk_pcie); /* 将pcie控制器初始化为RC模式 */ switch (rk_pcie->mode) case RK_PCIE_RC_TYPE: /* Pull down CLKREQ# to assert the connecting CLOCK_GEN OE */ rk_pcie_writel_apb(rk_pcie, PCIE_CLIENT_POWER, 0x30011000); disable_aspm_l1ss(rk_pcie); rk_pcie_writel_apb(rk_pcie, 0x0, 0xf00040); /* * 要梳理清楚pcie的工作原理 */ switch (rk_pcie->mode) case RK_PCIE_RC_TYPE: ret = rk_add_pcie_port(rk_pcie, pdev); struct dw_pcie *pci = rk_pcie->pci; struct pcie_port *pp = &pci->pp; struct device *dev = pci->dev; /* * 注册给控制器的操作函数 * static struct dw_pcie_host_ops rk_pcie_host_ops = * .host_init = rk_pcie_host_init, */ pp->ops = &rk_pcie_host_ops; /* pcie控制器的初始化函数 */ ret = dw_pcie_host_init(pp); /* * 回调函数rk_pcie_host_init的执行内容 */ static int rk_pcie_host_init(struct pcie_port *pp) struct dw_pcie *pci = to_dw_pcie_from_pp(pp); dw_pcie_setup_rc(pp); ret = rk_pcie_establish_link(pci); struct rk_pcie *rk_pcie = to_rk_pcie(pci); bool std_rc = rk_pcie->mode == RK_PCIE_RC_TYPE && !rk_pcie->dma_obj; /* EP设备已经up这里直接返回,RC设备往下走继续重新初始化 */ if (dw_pcie_link_up(pci) && !std_rc) dev_err(pci->dev, "link is already up\n"); return 0; rk_pcie_disable_ltssm(rk_pcie); rk_pcie_writel_apb(rk_pcie, 0x0, 0xc0008); rk_pcie_link_status_clear(rk_pcie); rk_pcie_writel_apb(rk_pcie, PCIE_CLIENT_GENERAL_DEBUG, 0x0); rk_pcie_enable_debug(rk_pcie); rk_pcie_writel_apb(rk_pcie, PCIE_CLIENT_DBG_FIFO_PTN_HIT_D0, PCIE_CLIENT_DBG_TRANSITION_DATA); rk_pcie_writel_apb(rk_pcie, PCIE_CLIENT_DBG_FIFO_PTN_HIT_D1, PCIE_CLIENT_DBG_TRANSITION_DATA); rk_pcie_writel_apb(rk_pcie, PCIE_CLIENT_DBG_FIFO_TRN_HIT_D0, PCIE_CLIENT_DBG_TRANSITION_DATA); rk_pcie_writel_apb(rk_pcie, PCIE_CLIENT_DBG_FIFO_TRN_HIT_D1, PCIE_CLIENT_DBG_TRANSITION_DATA); rk_pcie_writel_apb(rk_pcie, PCIE_CLIENT_DBG_FIFO_MODE_CON, PCIE_CLIENT_DBF_EN); /* Enable client reset or link down interrupt */ rk_pcie_writel_apb(rk_pcie, PCIE_CLIENT_INTR_MASK, 0x40000); /* Enable LTSSM(Link Training and Status State Machine) */ rk_pcie_enable_ltssm(rk_pcie); rk_pcie_writel_apb(rk_pcie, 0x0, 0xC000C); /* * Add this 1ms delay because we observe link is always up stably after it and * could help us save 20ms for scanning devices. */ usleep_range(1000, 1100); /* 轮询等待PCIe链路建立 */ for (retries = 0; retries < 100; retries++) if (dw_pcie_link_up(pci)) msleep(50); if (dw_pcie_link_up(pci)) dev_info(pci->dev, "PCIe Link up, LTSSM is 0x%x\n", rk_pcie_readl_apb(rk_pcie, PCIE_CLIENT_LTSSM_STATUS)); rk_pcie_debug_dump(rk_pcie); return 0; dev_info_ratelimited(pci->dev, "PCIe Linking... LTSSM is 0x%x\n", rk_pcie_readl_apb(rk_pcie, PCIE_CLIENT_LTSSM_STATUS)); rk_pcie_debug_dump(rk_pcie); msleep(20); break; /* 如果当前配置的是信号测试模式,这里直接返回 */ if (rk_pcie->is_signal_test == true) return 0; /* * pcie的dma初始化 */ ret = rk_pcie_init_dma_trx(rk_pcie); /* 读寄存器判断是否启用dma */ if (!rk_pcie_udma_enabled(rk_pcie)) return 0; /* 调用dma模块的接口 */ rk_pcie->dma_obj = rk_pcie_dma_obj_probe(rk_pcie->pci->dev); /* Enable client write and read interrupt */ rk_pcie_writel_apb(rk_pcie, PCIE_CLIENT_INTR_MASK, 0xc000000); /* Enable core write interrupt */ dw_pcie_writel_dbi(rk_pcie->pci, PCIE_DMA_OFFSET + PCIE_DMA_WR_INT_MASK, 0x0); /* Enable core read interrupt */ dw_pcie_writel_dbi(rk_pcie->pci, PCIE_DMA_OFFSET + PCIE_DMA_RD_INT_MASK, 0x0); /* * 挂接dma的启动和配置函数 */ if (rk_pcie->dma_obj) rk_pcie->dma_obj->start_dma_func = rk_pcie_start_dma_dwc; rk_pcie->dma_obj->config_dma_func = rk_pcie_config_dma_dwc; /* 关闭dbi寄存器可读写功能 */ dw_pcie_dbi_ro_wr_dis(pci); /* 告诉内核,这个设备具有唤醒系统的能力 */ device_init_wakeup(dev, true); /* Enable async system PM for multiports SoC */ device_enable_async_suspend(dev); /* * 调试文件系统 */ if (IS_ENABLED(CONFIG_DEBUG_FS)) /* 创建sysfs调试设备文件 */ ret = rockchip_pcie_debugfs_init(rk_pcie); struct dentry *file; pcie->debugfs = debugfs_create_dir(dev_name(pcie->pci->dev), NULL); debugfs_create_devm_seqfile(pcie->pci->dev, "dumpfifo", pcie->debugfs, rockchip_pcie_fifo_show); file = debugfs_create_file("err_event", 0644, pcie->debugfs, pcie, &rockchip_pcie_rasdes_ops); /* 写寄存器开启pcie控制器的调试寄存器 */ /* Enable RASDES Error event by default */ val = dw_pcie_find_ext_capability(rk_pcie->pci, PCI_EXT_CAP_ID_VNDR); dw_pcie_writel_dbi(rk_pcie->pci, val + 8, 0x1c); dw_pcie_writel_dbi(rk_pcie->pci, val + 8, 0x3);
二、dw_pcie_host_init函数的执行内容
1、创建桥结构体struct pci_host_bridge
2、从设备树读取ranges属性地址空间信息:配置空间、IO空间、32位内存BAR空间、64位内存BAR空间,把这些地址空间信息保存到桥结构体
3、向内存申请占用这些内存资源devm_request_pci_bus_resources
4、将IO空间映射到虚拟地址空间、将配置空间映射到虚拟地址pp->va_cfg0_base
5、创建msi中断的中断域、注册msi控制器级别的处理函数
6、调用rk注册的回调函数rk_pcie_host_init,开始链路训练
7、调用pci_host_probe函数,这个函数会扫描下挂的所有pcie设备、重新分配空间资源(比如给下挂的EP设备分配BAR空间)、把扫描到的设备添加到内核子系统
/* * pcie控制器初始化 * dw_pcie_host_init * 控制器桥结构体初始化struct pci_host_bridge *bridge; * 从设备树读信息,然后保存到桥结构体 * 从设备树读取ranges属性地址空间信息:配置空间、IO空间、32位内存BAR空间、64位内存BAR空间 * 向内存申请占用这些内存资源 * 将IO空间映射到虚拟地址空间 * msi中断创建中断域、注册msi控制器级别的处理函数 * 两个访问控制器和下挂子设备的操作函数, * 调用rk注册的回调函数rk_pcie_host_init,开始链路训练 * 开始probe */ int dw_pcie_host_init(struct pcie_port *pp) /* * 控制器桥结构体初始化struct pci_host_bridge *bridge; */ struct pci_host_bridge *bridge; /* * 仅仅分配并初始化pci_host_bridge,没有做其他操作 * 注册一个释放资源的回调 * 从设备树读信息,然后保存到桥结构体 * 从设备树读取ranges属性地址空间信息:配置空间、IO空间、32位内存BAR空间、64位内存BAR空间 * 向内存申请占用这些内存资源 * 将IO空间映射到虚拟地址空间 */ bridge = devm_pci_alloc_host_bridge(dev, 0); struct pci_host_bridge *bridge; /* * 仅仅分配并初始化pci_host_bridge,没有做其他操作 */ bridge = pci_alloc_host_bridge(priv); struct pci_host_bridge *bridge; bridge = kzalloc(sizeof(*bridge) + priv, GFP_KERNEL); pci_init_host_bridge(bridge); INIT_LIST_HEAD(&bridge->windows); INIT_LIST_HEAD(&bridge->dma_ranges); bridge->native_aer = 1; bridge->native_pcie_hotplug = 1; bridge->native_shpc_hotplug = 1; bridge->native_pme = 1; bridge->native_ltr = 1; bridge->native_dpc = 1; device_initialize(&bridge->dev); /* 绑定一个释放资源的回调 */ bridge->dev.release = pci_release_host_bridge_dev; return bridge; bridge->dev.parent = dev; /* 注册一个释放资源的回调 */ ret = devm_add_action_or_reset(dev, devm_pci_alloc_host_bridge_release, bridge); /* * 从设备树读信息,然后保存到桥结构体 --- 加点打印,编个版本跑一下加深理解 * 从设备树读取ranges属性地址空间信息:配置空间、IO空间、32位内存BAR空间、64位内存BAR空间 * 向内存申请占用这些内存资源 * 将IO空间映射到虚拟地址空间 */ ret = devm_of_pci_bridge_init(dev, bridge); /* 设备树节点为空,则直接返回 */ if (!dev->of_node) return 0; /* pci_common_swizzle 正是 Linux 内核提供的一个标准、通用的中断重映射实现 */ bridge->swizzle_irq = pci_common_swizzle; /* 硬件中断号映射到虚拟中断号 */ bridge->map_irq = of_irq_parse_and_map_pci; return pci_parse_request_of_pci_ranges(dev, bridge); resource_size_t iobase; struct resource_entry *win, *tmp; INIT_LIST_HEAD(&bridge->windows); INIT_LIST_HEAD(&bridge->dma_ranges); /* * 从设备树读取ranges属性地址空间信息:配置空间、IO空间、32内存BAR空间、64位内存BAR空间 */ err = devm_of_pci_get_host_bridge_resources(dev, 0, 0xff, &bridge->windows, &bridge->dma_ranges, &iobase); /* 向内存申请占用这些内存资源 */ err = devm_request_pci_bus_resources(dev, &bridge->windows); /* * 将IO空间映射到虚拟地址空间 */ resource_list_for_each_entry_safe(win, tmp, &bridge->windows) struct resource *res = win->res; switch (resource_type(res)) case IORESOURCE_IO: err = devm_pci_remap_iospace(dev, res, iobase); break; case IORESOURCE_MEM: res_valid |= !(res->flags & IORESOURCE_PREFETCH); break; /* * pci协议规范规定,pci桥设备一定要有一段非预取的内存空间 */ if (!res_valid) dev_warn(dev, "non-prefetchable memory resource required\n"); /* 保存桥结构体到私有数据结构 */ pp->bridge = bridge; resource_list_for_each_entry(win, &bridge->windows) switch (resource_type(win->res)) case IORESOURCE_IO: /* IO空间大小 */ pp->io_size = resource_size(win->res); /* IO空间在Pcie域的基地址 */ pp->io_bus_addr = win->res->start - win->offset; /* IO空间在cpu域的基地址 */ pp->io_base = pci_pio_to_address(win->res->start); break; case 0: /* 配置空间的大小0x100000 */ pp->cfg0_size = resource_size(win->res); /* 配置空间在cpu空间的基地址 */ pp->cfg0_base = win->res->start; break; /* 如果配置空间没有映射到虚拟地址,那么进行配置空间的虚拟地址映射 */ if (!pp->va_cfg0_base) pp->va_cfg0_base = devm_pci_remap_cfgspace(dev, pp->cfg0_base, pp->cfg0_size); /* 从设备树获取窗口数量 */ ret = of_property_read_u32(np, "num-viewport", &pci->num_viewport); /* 从设备树获取链路速率max-link-speed,也就是gen2,max-link-speed = <2>; */ pci->link_gen = of_pci_get_max_link_speed(np); /* * msi中断的处理,将msi中断的处理逻辑和its控制器关联,后续EP设备都可以使用msi中断, * RC会将EP设备的MSI写事务映射到写its寄存器,从而触发对应的msi中断到cpu */ /* 已经使能msi中断 */ if (pci_msi_enabled()) /* rk的pcie控制器驱动没有注册这个回调 */ if (!pp->ops->set_num_vectors) pp->num_vectors = MSI_DEF_NUM_VECTORS; /* rk的pcie控制器驱动没有注册这个回调 */ if (!pp->ops->msi_host_init) pp->msi_irq_chip = &dw_pci_msi_bottom_irq_chip; /* * 重点、重点、重点 * 创建中断域,待研究 */ ret = dw_pcie_allocate_domains(pp); /* * 注册msi的中断处理函数 * 重点、重点、重点 */ if (pp->msi_irq) irq_set_chained_handler_and_data(pp->msi_irq, dw_chained_msi_isr, pp); pp->msi_data = dma_map_single_attrs(pci->dev, &pp->msi_msg, sizeof(pp->msi_msg), DMA_FROM_DEVICE, DMA_ATTR_SKIP_CPU_SYNC); ret = dma_mapping_error(pci->dev, pp->msi_data); /* * 两个访问控制器和下挂子设备的操作函数, * 重点、重点、重点 * 已完成研究 */ bridge->ops = &dw_pcie_ops; bridge->child_ops = &dw_child_pcie_ops; /* 调用rk注册的回调函数rk_pcie_host_init,开始链路训练 */ ret = pp->ops->host_init(pp); bridge->sysdata = pp; /* * 开始probe */ ret = pci_host_probe(bridge); /* 进行扫描动作,这个动作会给每个下挂设备分配BDF号 */ ret = pci_scan_root_bus_bridge(bridge); bus = bridge->bus; /* * 重新分配空间资源,比如给每个下挂设备设置BAR空间基地址 */ if (pci_has_flag(PCI_PROBE_ONLY)) pci_bus_claim_resources(bus); else pci_bus_size_bridges(bus); pci_bus_assign_resources(bus); list_for_each_entry(child, &bus->children, node) pcie_bus_configure_settings(child); /* * 把扫描到的设备的添加到内核子系统 */ pci_bus_add_devices(bus);
三、PCIE链路训练的代码内容
1、设置链路最大的速率,设备树设置的是GEN2
2、配置 N_FTS(Gen1 和 Gen2+)、N_FTS(Number of Fast Training Sequences)是 PCIe 链路退出低功耗状态(L0s 或 L1)后,重新进入 L0 时所需的 FTS 数量,用来保证接收端 PLL 锁定
3、链路控制寄存器(PCIE_PORT_LINK_CONTROL)设置、禁止快速训练模式、启用 DLL(Data Link Layer)链路使能,允许 Data Link 层进行链路初始化
4、设置lane的数量,这里是x1
5、配置内存空间和IO空间的窗口映射
6、将 RC 的 PCI 类别寄存器 (PCI_CLASS_DEVICE) 设置为 PCI_CLASS_BRIDGE_PCI (0x0604),表明这是一个 PCI-to-PCI 桥设备
7、使能链路速率改变
8、关闭链路训练状态机、清除链路状态、使能调试寄存器、使能链路训练状态机LTSSM开始链路训练、循环等待链路训练完成
/* * pcie链路训练代码流程 * rk_pcie_host_init * 代码段1配置RC的DBI寄存器,配置的寄存器内容如下: * 使能dbi寄存器可读写 * 设置链路最大的速率,设备树设置的是GEN2 * 配置 N_FTS(Gen1 和 Gen2+)、N_FTS(Number of Fast Training Sequences)是 PCIe 链路退出低功耗状态(L0s 或 L1)后,重新进入 L0 时所需的 FTS 数量,用来保证接收端 PLL 锁定 * 链路控制寄存器(PCIE_PORT_LINK_CONTROL)设置、禁止快速训练模式、启用 DLL(Data Link Layer)链路使能,允许 Data Link 层进行链路初始化 * 配置链路宽度(Lane Width) * 配置msi中断 * Setup RC BARs * Setup interrupt pins * Setup bus numbers * Setup command register * 配置内存空间和IO空间的窗口映射 * 将BAR0地址再写入0 * 设置设备类别:将 RC 的 PCI 类别寄存器 (PCI_CLASS_DEVICE) 设置为 PCI_CLASS_BRIDGE_PCI (0x0604),表明这是一个 PCI-to-PCI 桥设备。这是 RC 作为根端口所必需的标准标识。 * 使能链路速率改变 * 关闭dbi读写使能 * 代码段2: * 判断是否标准的rc * Rest the device * 关闭链路训练状态机 * 清除链路状态 * 使能调试寄存器 * Enable client reset or link down interrupt * 使能链路训练状态机LTSSM开始链路训练 * 系统恢复(Resume)特殊处理 * 循环等待链路训练完成 * 代码段3: * Program the msi_data,待研究 */ static int rk_pcie_host_init(struct pcie_port *pp) int ret; struct dw_pcie *pci = to_dw_pcie_from_pp(pp); /* * 代码段1 * 初始化DBI相关的硬件寄存器 */ dw_pcie_setup_rc(pp); u32 val, ctrl, num_ctrls; struct dw_pcie *pci = to_dw_pcie_from_pp(pp); /* 使能dbi寄存器可读写 */ dw_pcie_dbi_ro_wr_en(pci); u32 reg; u32 val; reg = PCIE_MISC_CONTROL_1_OFF; val = dw_pcie_readl_dbi(pci, reg); dw_pcie_read(pci->dbi_base + reg, size, &val); val |= PCIE_DBI_RO_WR_EN; dw_pcie_writel_dbi(pci, reg, val); dw_pcie_write(pci->dbi_base + reg, size, val); /* * 设置链路最大的速率,设备树设置的是GEN2 * 配置 N_FTS(Gen1 和 Gen2+)、N_FTS(Number of Fast Training Sequences)是 PCIe 链路退出低功耗状态(L0s 或 L1)后,重新进入 L0 时所需的 FTS 数量,用来保证接收端 PLL 锁定 * 链路控制寄存器(PCIE_PORT_LINK_CONTROL)设置、禁止快速训练模式、启用 DLL(Data Link Layer)链路使能,允许 Data Link 层进行链路初始化 * 配置链路宽度(Lane Width) */ dw_pcie_setup(pci); /* * 设置链路最大的速率,设备树设置的是GEN2 */ dw_pcie_link_set_max_speed(pci, pci->link_gen); u32 cap, ctrl2, link_speed; /* 读寄存器查找所有能力 */ u8 offset = dw_pcie_find_capability(pci, PCI_CAP_ID_EXP); reg = dw_pcie_readw_dbi(pci, PCI_CAPABILITY_LIST); next_cap_ptr = (reg & 0x00ff); __dw_pcie_find_next_cap(pci, next_cap_ptr, cap); dw_pcie_readw_dbi(pci, cap_ptr); cap = dw_pcie_readl_dbi(pci, offset + PCI_EXP_LNKCAP); ctrl2 = dw_pcie_readl_dbi(pci, offset + PCI_EXP_LNKCTL2); ctrl2 &= ~PCI_EXP_LNKCTL2_TLS; switch (pcie_link_speed[link_gen]) case PCIE_SPEED_2_5GT: link_speed = PCI_EXP_LNKCTL2_TLS_2_5GT; break; case PCIE_SPEED_5_0GT: link_speed = PCI_EXP_LNKCTL2_TLS_5_0GT; break; case PCIE_SPEED_8_0GT: link_speed = PCI_EXP_LNKCTL2_TLS_8_0GT; break; case PCIE_SPEED_16_0GT: link_speed = PCI_EXP_LNKCTL2_TLS_16_0GT; break; default: /* Use hardware capability */ link_speed = FIELD_GET(PCI_EXP_LNKCAP_SLS, cap); ctrl2 &= ~PCI_EXP_LNKCTL2_HASD; break; dw_pcie_writel_dbi(pci, offset + PCI_EXP_LNKCTL2, ctrl2 | link_speed); cap &= ~((u32)PCI_EXP_LNKCAP_SLS); dw_pcie_writel_dbi(pci, offset + PCI_EXP_LNKCAP, cap | link_speed); /* * 配置 N_FTS(Gen1 和 Gen2+) * N_FTS(Number of Fast Training Sequences)是 PCIe 链路退出低功耗状态(L0s 或 L1)后,重新进入 L0 时所需的 FTS 数量,用来保证接收端 PLL 锁定 */ /* Configure Gen1 N_FTS */ if (pci->n_fts[0]) val = dw_pcie_readl_dbi(pci, PCIE_PORT_AFR); val &= ~(PORT_AFR_N_FTS_MASK | PORT_AFR_CC_N_FTS_MASK); val |= PORT_AFR_N_FTS(pci->n_fts[0]); val |= PORT_AFR_CC_N_FTS(pci->n_fts[0]); dw_pcie_writel_dbi(pci, PCIE_PORT_AFR, val); /* Configure Gen2+ N_FTS */ if (pci->n_fts[1]) val = dw_pcie_readl_dbi(pci, PCIE_LINK_WIDTH_SPEED_CONTROL); val &= ~PORT_LOGIC_N_FTS_MASK; val |= pci->n_fts[pci->link_gen - 1]; dw_pcie_writel_dbi(pci, PCIE_LINK_WIDTH_SPEED_CONTROL, val); /* * 链路控制寄存器(PCIE_PORT_LINK_CONTROL)设置 * 清除 PORT_LINK_FAST_LINK_MODE 位:该位通常用于启用“快速链路模式”(可能跳过某些训练步骤),清除后采用标准训练流程 * 置位 PORT_LINK_DLL_LINK_EN:启用 DLL(Data Link Layer)链路使能,允许 Data Link 层进行链路初始化 */ val = dw_pcie_readl_dbi(pci, PCIE_PORT_LINK_CONTROL); val &= ~PORT_LINK_FAST_LINK_MODE; val |= PORT_LINK_DLL_LINK_EN; dw_pcie_writel_dbi(pci, PCIE_PORT_LINK_CONTROL, val); /* * num-lanes = <1>; --- x1 * 配置链路宽度(Lane Width) */ /* 从设备树读取lane数量 */ of_property_read_u32(np, "num-lanes", &pci->num_lanes); /* Set the number of lanes */ val &= ~PORT_LINK_FAST_LINK_MODE; val &= ~PORT_LINK_MODE_MASK; val |= PORT_LINK_MODE_1_LANES; dw_pcie_writel_dbi(pci, PCIE_PORT_LINK_CONTROL, val); /* Set link width speed control register */ val = dw_pcie_readl_dbi(pci, PCIE_LINK_WIDTH_SPEED_CONTROL); val &= ~PORT_LOGIC_LINK_WIDTH_MASK; val |= PORT_LOGIC_LINK_WIDTH_1_LANES; dw_pcie_writel_dbi(pci, PCIE_LINK_WIDTH_SPEED_CONTROL, val); /* * 配置msi中断 */ if (pci_msi_enabled() && !pp->ops->msi_host_init) num_ctrls = DIV_ROUND_UP(pp->num_vectors, MAX_MSI_IRQS_PER_CTRL); /* Initialize IRQ Status array */ for (ctrl = 0; ctrl < num_ctrls; ctrl++) pp->irq_mask[ctrl] = ~0; dw_pcie_writel_dbi(pci, PCIE_MSI_INTR0_MASK + (ctrl * MSI_REG_CTRL_BLOCK_SIZE), pp->irq_mask[ctrl]); dw_pcie_writel_dbi(pci, PCIE_MSI_INTR0_ENABLE + (ctrl * MSI_REG_CTRL_BLOCK_SIZE), ~0); /* Setup RC BARs */ dw_pcie_writel_dbi(pci, PCI_BASE_ADDRESS_0, 0x00000004); dw_pcie_writel_dbi(pci, PCI_BASE_ADDRESS_1, 0x00000000); /* Setup interrupt pins */ val = dw_pcie_readl_dbi(pci, PCI_INTERRUPT_LINE); val &= 0xffff00ff; val |= 0x00000100; dw_pcie_writel_dbi(pci, PCI_INTERRUPT_LINE, val); /* Setup bus numbers */ val = dw_pcie_readl_dbi(pci, PCI_PRIMARY_BUS); val &= 0xff000000; val |= 0x00ff0100; dw_pcie_writel_dbi(pci, PCI_PRIMARY_BUS, val); /* Setup command register */ val = dw_pcie_readl_dbi(pci, PCI_COMMAND); val &= 0xffff0000; val |= PCI_COMMAND_IO | PCI_COMMAND_MEMORY | PCI_COMMAND_MASTER | PCI_COMMAND_SERR; dw_pcie_writel_dbi(pci, PCI_COMMAND, val); /* * 配置内存空间和IO空间的窗口映射 */ if (pp->bridge->child_ops == &dw_child_pcie_ops) int atu_idx = 0; struct resource_entry *entry; /* 遍历,将所有内存空间都进行窗口映射 */ /* Get last memory resource entry */ resource_list_for_each_entry(entry, &pp->bridge->windows) if (resource_type(entry->res) != IORESOURCE_MEM) continue; if (pci->num_viewport <= ++atu_idx) break; dw_pcie_prog_outbound_atu(pci, atu_idx, PCIE_ATU_TYPE_MEM, entry->res->start, entry->res->start - entry->offset, resource_size(entry->res)); /* 进行IO空间的窗口映射 */ if (pp->io_size) if (pci->num_viewport > ++atu_idx) dw_pcie_prog_outbound_atu(pci, atu_idx, PCIE_ATU_TYPE_IO, pp->io_base, pp->io_bus_addr, pp->io_size); /* 将BAR0地址再写入0 */ dw_pcie_writel_dbi(pci, PCI_BASE_ADDRESS_0, 0); /* * 设置设备类别:将 RC 的 PCI 类别寄存器 (PCI_CLASS_DEVICE) 设置为 PCI_CLASS_BRIDGE_PCI (0x0604),表明这是一个 PCI-to-PCI 桥设备。这是 RC 作为根端口所必需的标准标识。 */ dw_pcie_writew_dbi(pci, PCI_CLASS_DEVICE, PCI_CLASS_BRIDGE_PCI); /* * 使能链路速率改变 */ val = dw_pcie_readl_dbi(pci, PCIE_LINK_WIDTH_SPEED_CONTROL); val |= PORT_LOGIC_SPEED_CHANGE; dw_pcie_writel_dbi(pci, PCIE_LINK_WIDTH_SPEED_CONTROL, val); /* * 关闭dbi读写使能 */ dw_pcie_dbi_ro_wr_dis(pci); /* * 代码段2 * 判断是否标准的rc * Rest the device * 关闭链路训练状态机 * 清除链路状态 * 使能调试寄存器 * Enable client reset or link down interrupt * 使能链路训练状态机LTSSM开始链路训练 * 系统恢复(Resume)特殊处理 * 循环等待链路训练完成 */ ret = rk_pcie_establish_link(pci); int retries, power; struct rk_pcie *rk_pcie = to_rk_pcie(pci); /* 判断是否标准的rc */ bool std_rc = rk_pcie->mode == RK_PCIE_RC_TYPE && !rk_pcie->dma_obj; /* 判断链路是否已经up,也就是是否处于L0状态 */ if (dw_pcie_link_up(pci) && !std_rc) dev_err(pci->dev, "link is already up\n"); return 0; /* Rest the device */ printk("lzt lzt:rk_pcie->rst_gpio is %p\r\n", rk_pcie->rst_gpio); gpiod_set_value_cansleep(rk_pcie->rst_gpio, 0); /* 关闭链路训练状态机 */ rk_pcie_disable_ltssm(rk_pcie); rk_pcie_writel_apb(rk_pcie, 0x0, 0xc0008); /* 清除链路状态 */ rk_pcie_link_status_clear(rk_pcie); rk_pcie_writel_apb(rk_pcie, PCIE_CLIENT_GENERAL_DEBUG, 0x0); /* 使能调试寄存器 */ rk_pcie_enable_debug(rk_pcie); rk_pcie_writel_apb(rk_pcie, PCIE_CLIENT_DBG_FIFO_PTN_HIT_D0, PCIE_CLIENT_DBG_TRANSITION_DATA); rk_pcie_writel_apb(rk_pcie, PCIE_CLIENT_DBG_FIFO_PTN_HIT_D1, PCIE_CLIENT_DBG_TRANSITION_DATA); rk_pcie_writel_apb(rk_pcie, PCIE_CLIENT_DBG_FIFO_TRN_HIT_D0, PCIE_CLIENT_DBG_TRANSITION_DATA); rk_pcie_writel_apb(rk_pcie, PCIE_CLIENT_DBG_FIFO_TRN_HIT_D1, PCIE_CLIENT_DBG_TRANSITION_DATA); rk_pcie_writel_apb(rk_pcie, PCIE_CLIENT_DBG_FIFO_MODE_CON, PCIE_CLIENT_DBF_EN); /* Enable client reset or link down interrupt */ rk_pcie_writel_apb(rk_pcie, PCIE_CLIENT_INTR_MASK, 0x40000); /* 使能链路训练状态机LTSSM开始链路训练 */ /* Enable LTSSM */ rk_pcie_enable_ltssm(rk_pcie); /* * 系统恢复(Resume)特殊处理 * 在系统从挂起(suspend)恢复时,若驱动标记为“跳过恢复后的扫描”,且 WiFi 模块未上电,则直接释 * 放复位(拉高 PERST#)并返回,不等待链路训练完成。这是因为此时下游设备(如 WiFi 模块)可能未 * 供电,等待链路训练会超时,但设备驱动后续会自行处理重扫描。 * 如何进行重扫描操作 */ if (rk_pcie->in_suspend && rk_pcie->skip_scan_in_resume) rfkill_get_wifi_power_state(&power); if (!power) gpiod_set_value_cansleep(rk_pcie->rst_gpio, 1); return 0; msleep(rk_pcie->perst_inactive_ms); gpiod_set_value_cansleep(rk_pcie->rst_gpio, 1); usleep_range(1000, 1100); /* * 循环等待链路训练完成 */ for (retries = 0; retries < 100; retries++) if (dw_pcie_link_up(pci)) msleep(50); if (dw_pcie_link_up(pci)) rk_pcie_debug_dump(rk_pcie); return 0; rk_pcie_debug_dump(rk_pcie); msleep(20); /* * 代码段3 */ if (pp->msi_irq > 0) dw_pcie_msi_init(pp); struct dw_pcie *pci = to_dw_pcie_from_pp(pp); u64 msi_target = (u64)pp->msi_data; /* Program the msi_data */ dw_pcie_writel_dbi(pci, PCIE_MSI_ADDR_LO, lower_32_bits(msi_target)); dw_pcie_writel_dbi(pci, PCIE_MSI_ADDR_HI, upper_32_bits(msi_target));
四、PCIE扫描流程
链路训练完成后,驱动代码调用pci_host_probe函数进行链路扫描,函数内容如下:
1、把桥设备注册到内核子系统
2、扫描所有下挂的子设备pci_scan_child_bus
3、计算每个桥所需要的IO和MEM空间的大小pci_bus_size_bridges
4、实际分配具体的物理地址,并将这些地址写入设备的 BAR 寄存器和桥的基址/限址寄存器中,让硬件真正生效
5、在所有设备被发现、地址被分配后,统一配置 MPS、MRRS 等性能参数
6、将所有设备添加到pci子系统
7、在 /sys/bus/pci/devices/ 下为设备创建一系列属性文件
8、在 /proc/bus/pci/ 下创建设备对应的文件,提供原始配置空间访问接口
9、更新 PCIe 桥的 D3 状态,也就是低功耗策略
10、触发驱动绑定,让设备可以和驱动程序绑定
11、标记设备已添加
/* * pci_host_probe代码分析 * 把桥设备注册到内核子系统 * 扫描所有下挂的子设备pci_scan_child_bus * 计算每个桥所需要的IO和MEM空间的大小 * 实际分配具体的物理地址,并将这些地址写入设备的 BAR 寄存器和桥的基址/限址寄存器中,让硬件真正生效 */ int pci_host_probe(struct pci_host_bridge *bridge) struct pci_bus *bus, *child; /* * 扫描所有下挂设备 * 扫描pci_host_bridge *bridge根总线及其下挂的所有下游设备 * */ ret = pci_scan_root_bus_bridge(bridge); struct resource_entry *window; // 资源结构体 bool found = false; struct pci_bus *b; // 总线结构体 int max, bus, ret; /* * 查找总线资源,把总线号赋值给bridge->busnr * IORESOURCE_BUS标志表示该资源描述的是一个总线号区间(而非内存或 I/O 端口) */ resource_list_for_each_entry(window, &bridge->windows) if (window->res->flags & IORESOURCE_BUS) bridge->busnr = window->res->start; found = true; break; /* * 把桥设备注册到内核子系统 */ ret = pci_register_host_bridge(bridge); /* 分配并初始化一个 pci_bus 结构,代表根总线,并关联到 bridge */ struct pci_bus *bus, *b; bus = pci_alloc_bus(NULL); bridge->bus = bus; /* * list_splice_init() 将 bridge->windows 链表中的所有节点移动到临时链表 resources,并清空原链表。这样做是为了在注册过程中安全地遍历和处理资源,避免与后续操作冲突。 */ /* Temporarily move resources off the list */ list_splice_init(&bridge->windows, &resources); bus->sysdata = bridge->sysdata; bus->msi = bridge->msi; bus->ops = bridge->ops; bus->number = bus->busn_res.start = bridge->busnr; /* pcie的domain号,设备树配置的是0 */ bus->domain_nr = pci_bus_find_domain_nr(bus, parent); /* * 检查总线是否已被注册 * pci_find_bus() 根据域号和总线号在全局总线链表中查找是否已存在相同标识的总线 */ b = pci_find_bus(pci_domain_nr(bus), bridge->busnr); if (b) dev_dbg(&b->dev, "bus already known\n"); err = -EEXIST; goto free; /* * dev_set_name() 为主机桥的设备对象(bridge->dev)设置名称,格式为 pci<域号>:<总线号>,例如 pci0000:00 */ dev_set_name(&bridge->dev, "pci%04x:%02x", pci_domain_nr(bus), bridge->busnr); /* * 添加主机桥设备 * device_add() 将主机桥设备添加到 Linux 设备模型,使其在 sysfs 中可见(例如 /sys/devices/pci0000:00) * 成功后,将 bridge->dev 的引用赋给 bus->bridge,并启用异步挂起支持 */ err = device_add(&bridge->dev); if (err) put_device(&bridge->dev); goto free; bus->bridge = get_device(&bridge->dev); device_enable_async_suspend(bus->bridge); /* * 设置总线的设备树节点和 MSI 域 * pci_set_bus_of_node():若系统使用设备树(Device Tree),则将相应的 OF 节点关联到总线 * pci_set_bus_msi_domain():设置总线的 MSI 中断域(用于 PCIe 消息信号中断) */ pci_set_bus_of_node(bus); pci_set_bus_msi_domain(bus); /* * 设置 NUMA 节点信息 * 若没有父设备(即主机桥是顶层),则尝试根据总线信息(如 ACPI 或设备树提供的 NUMA 节点)设置桥设备的 NUMA 节点 */ if (!parent) set_dev_node(bus->bridge, pcibus_to_node(bus)); /* * 初始化总线设备并注册 * 设置总线设备的 class 为 pcibus_class(PCI 总线设备类),父设备为之前注册的主机桥设备 * 置总线设备的名称(格式如 0000:00)并保存名称字符串 * 调用 device_register() 将总线设备注册到设备模型,这将创建 /sys/devices/pci0000:00/0000:00 等目录 */ bus->dev.class = &pcibus_class; bus->dev.parent = bus->bridge; dev_set_name(&bus->dev, "%04x:%02x", pci_domain_nr(bus), bus->number); name = dev_name(&bus->dev); err = device_register(&bus->dev); /* * 如果总线操作函数(bus->ops)定义了 add_bus 回调,则调用它,并在失败时打印警告 */ if (bus->ops->add_bus) err = bus->ops->add_bus(bus); if (WARN_ON(err < 0)) dev_err(&bus->dev, "failed to add bus: %d\n", err); /* * 创建 legacy I/O 和内存文件 * 在 sysfs 中创建 legacy_io 和 legacy_mem 文件,用于向后兼容的应用程序访问 PCI I/O 和内存空间 */ /* Create legacy_io and legacy_mem files for this bus */ pci_create_legacy_files(bus); /* * bridge->windows 中的所有资源条目已被 list_splice_init 移动到 resources 链表,以便在注册过程中安全处理。现在需要将它们逐一处理并移回原链表 * window:当前遍历到的资源条目指针。 * n:临时存储下一个条目的指针,用于在删除或移动当前节点时安全地继续遍历 * &resources:待遍历的链表头,此处指向之前从 bridge->windows 暂移过来的临时链表 */ resource_list_for_each_entry_safe(window, n, &resources) /* list_move_tail 将当前条目(window->node)从 resources 链表中移除,并添加到 bridge->windows 链表的尾部 */ list_move_tail(&window->node, &bridge->windows); /* window->offset:该资源在 CPU 地址与 PCI 总线地址之间的偏移量。例如,若 CPU 地址空间起始为 0x80000000,而总线地址起始为 0x00000000,则 offset = 0x80000000 */ offset = window->offset; /* 指向 struct resource 结构,描述一个具体的资源区间(I/O 端口、内存区域或总线号范围) */ res = window->res; if (res->flags & IORESOURCE_BUS) /* 此函数将当前总线的 busn_res(总线号资源)插入到该总线的资源管理结构中 */ pci_bus_insert_busn_res(bus, bus->number, res->end); else /* 该函数将资源添加到总线的资源列表(bus->resources)中,供后续 PCI 设备进行资源分配时使用 */ pci_bus_add_resource(bus, res, 0); /* * 格式化总线地址信息(若存在偏移) */ if (offset) /* 若cpu地址和pcie域地址存在偏移 */ if (resource_type(res) == IORESOURCE_IO) fmt = " (bus address [%#06llx-%#06llx])"; else fmt = " (bus address [%#010llx-%#010llx])"; snprintf(addr, sizeof(addr), fmt, (unsigned long long)(res->start - offset), (unsigned long long)(res->end - offset)); else /* 不存在偏移 */ addr[0] = '\0'; /* 打印出来 */ dev_info(&bus->dev, "root bus resource %pR%s\n", res, addr); /* 将根总线加入全局列表 */ down_write(&pci_bus_sem); list_add_tail(&bus->node, &pci_root_buses); up_write(&pci_bus_sem); /* end of pci_register_host_bridge */ /* 总线结构体和总线号 */ b = bridge->bus; bus = bridge->busnr; /* * 扫描所有下挂的子设备 */ max = pci_scan_child_bus(b); pci_scan_child_bus_extend(bus, 0); /* 计算每个桥所需要的IO和MEM空间的大小 */ pci_bus_size_bridges(bus); /* 实际分配具体的物理地址,并将这些地址写入设备的 BAR 寄存器和桥的基址/限址寄存器中,让硬件真正生效 */ pci_bus_assign_resources(bus); /* 在所有设备被发现、地址被分配后,统一配置 MPS、MRRS 等性能参数 */ list_for_each_entry(child, &bus->children, node) pcie_bus_configure_settings(child); /* * 将所有设备添加到pci子系统 * 在 /sys/bus/pci/devices/ 下为设备创建一系列属性文件 * 在 /proc/bus/pci/ 下创建设备对应的文件,提供原始配置空间访问接口 * 更新 PCIe 桥的 D3 状态,也就是低功耗策略 * 触发驱动绑定,让设备可以和驱动程序绑定 * 标记设备已添加 */ pci_bus_add_devices(bus); pci_bus_add_device(dev); /* 在 /sys/bus/pci/devices/ 下为设备创建一系列属性文件 */ pci_create_sysfs_dev_files(dev); /* 在 /proc/bus/pci/ 下创建设备对应的文件,提供原始配置空间访问接口 */ pci_proc_attach_device(dev); /* 更新 PCIe 桥的 D3 状态,也就是低功耗策略 */ pci_bridge_d3_update(dev); /* 触发驱动绑定,让设备可以和驱动程序绑定 */ dev->match_driver = true; retval = device_attach(&dev->dev); /* 标记设备已添加 */ pci_dev_assign_added(dev, true);
五、访问下游设备配置空间的接口函数内容
/* * 通过窗口映射的方式 * 将下游设备的配置空间映射到cpu地址空间 * 然后读写对应的cpu地址空间来访问下游设备的配置空间 */ static struct pci_ops dw_child_pcie_ops = { .map_bus = dw_pcie_other_conf_map_bus, .read = dw_pcie_rd_other_conf, .write = dw_pcie_wr_other_conf, }; /* * dw_child_pcie_ops回调函数分析 */ /* * dw_pcie_other_conf_map_bus * 访问下游设备配置空间的函数 * struct pci_bus *bus:当前要访问的目标pci总线 * unsigned int devfn:目标设备的设备号(高 5 位)和功能号(低 3 位)的编码组合 * int where:在配置空间内的偏移量(即想要读写的寄存器偏移地址,范围 0~4095) */ static void __iomem *dw_pcie_other_conf_map_bus(struct pci_bus *bus, unsigned int devfn, int where) int type; u32 busdev; struct pcie_port *pp = bus->sysdata; struct dw_pcie *pci = to_dw_pcie_from_pp(pp); /* 检查总线是否up */ if (!dw_pcie_link_up(pci)) return NULL; /* 把BDF号组装起来 */ busdev = PCIE_ATU_BUS(bus->number) | PCIE_ATU_DEV(PCI_SLOT(devfn)) | PCIE_ATU_FUNC(PCI_FUNC(devfn)); /* * 如果是挂在根总线下面的设备,那么用type 0的TLP报文访问 * 如果是挂在下级总线下面的设备,那么用type 1的TLP报文访问 */ if (pci_is_root_bus(bus->parent)) type = PCIE_ATU_TYPE_CFG0; else type = PCIE_ATU_TYPE_CFG1; /* * 进行outbound窗口映射,cpu->pcie域地址空间的访问 * void dw_pcie_prog_outbound_atu(struct dw_pcie *pci, int index, int type, u64 cpu_addr, u64 pci_addr, u32 size) * struct dw_pcie *pci:控制器核心结构体 * int index:固定传0 * int type:type 0或type 1 * u64 cpu_addr:配置寄存器在cpu域的物理地址 * u64 pci_addr:busdev,也就是BDF号组装起来的 * pp->cfg0_size:配置空间的大小 */ dw_pcie_prog_outbound_atu(pci, 0, type, pp->cfg0_base, busdev, pp->cfg0_size); __dw_pcie_prog_outbound_atu(pci, 0, index, type, cpu_addr, pci_addr, size); u32 retries, val; /* 地址调制,这里没有注册,不会执行 */ if (pci->ops->cpu_addr_fixup) cpu_addr = pci->ops->cpu_addr_fixup(pci, cpu_addr); /* * 使用展开模式进行窗口映射,也就是每个映射窗口有独立的寄存器,不需要操作窗口切换寄存器 */ if (pci->iatu_unroll_enabled & DWC_IATU_UNROLL_EN) dw_pcie_prog_outbound_atu_unroll(pci, func_no, index, type, cpu_addr, pci_addr, size); u32 retries, val; /* cpu_addr是起始地址,limit_addr是映射的末尾地址 */ u64 limit_addr = cpu_addr + size - 1; /* 写入cpu的起始地址 */ dw_pcie_writel_ob_unroll(pci, index, PCIE_ATU_UNR_LOWER_BASE, lower_32_bits(cpu_addr)); /* * 宏定义:#define PCIE_GET_ATU_OUTB_UNR_REG_OFFSET(region) ((region) << 9) */ u32 offset = PCIE_GET_ATU_OUTB_UNR_REG_OFFSET(index); dw_pcie_writel_atu(pci, offset + reg, val); ret = dw_pcie_write(pci->atu_base + reg, 4, val); if (size == 4) writel(val, addr); else if (size == 2) writew(val, addr); else if (size == 1) writeb(val, addr); else return PCIBIOS_BAD_REGISTER_NUMBER; return PCIBIOS_SUCCESSFUL; dw_pcie_writel_ob_unroll(pci, index, PCIE_ATU_UNR_UPPER_BASE, upper_32_bits(cpu_addr)); /* 写入cpu的末尾地址 */ dw_pcie_writel_ob_unroll(pci, index, PCIE_ATU_UNR_LOWER_LIMIT, lower_32_bits(limit_addr)); dw_pcie_writel_ob_unroll(pci, index, PCIE_ATU_UNR_UPPER_LIMIT, upper_32_bits(limit_addr)); /* 写入对应的pcie域地址,这里是BDF号组装起来的 */ dw_pcie_writel_ob_unroll(pci, index, PCIE_ATU_UNR_LOWER_TARGET, lower_32_bits(pci_addr)); dw_pcie_writel_ob_unroll(pci, index, PCIE_ATU_UNR_UPPER_TARGET, upper_32_bits(pci_addr)); /* * 配置控制寄存器 1(CR1)—— 类型与功能号 * PCIE_ATU_UNR_REGION_CTRL1:控制寄存器 1 * type:决定事务类型,例如:PCIE_ATU_TYPE_MEM:内存读写事务、PCIE_ATU_TYPE_IO:I/O 事务、PCIE_ATU_TYPE_CFG0:配置 Type 0(访问本总线设备) * PCIE_ATU_TYPE_CFG1:配置 Type 1(访问下游总线设备) * PCIE_ATU_FUNC_NUM(func_no):将功能号写入 CR1 的特定位,当类型为 CFG0/CFG1 时,硬件会将该功能号填入配置请求包中 */ dw_pcie_writel_ob_unroll(pci, index, PCIE_ATU_UNR_REGION_CTRL1, type | PCIE_ATU_FUNC_NUM(func_no)); /* 启用 ATU 窗口(CR2) */ dw_pcie_writel_ob_unroll(pci, index, PCIE_ATU_UNR_REGION_CTRL2, PCIE_ATU_ENABLE); /* 轮询读寄存器等待使能成功 */ for (retries = 0; retries < LINK_WAIT_MAX_IATU_RETRIES; retries++) val = dw_pcie_readl_ob_unroll(pci, index, PCIE_ATU_UNR_REGION_CTRL2); if (val & PCIE_ATU_ENABLE) return; mdelay(LINK_WAIT_IATU); return; /* 选择操作的窗口 */ dw_pcie_writel_dbi(pci, PCIE_ATU_VIEWPORT, PCIE_ATU_REGION_OUTBOUND | index); /* 写入cpu的起始地址 */ dw_pcie_writel_dbi(pci, PCIE_ATU_LOWER_BASE, lower_32_bits(cpu_addr)); dw_pcie_writel_dbi(pci, PCIE_ATU_UPPER_BASE, upper_32_bits(cpu_addr)); /* 写入cpu的末尾地址 */ dw_pcie_writel_dbi(pci, PCIE_ATU_LIMIT, lower_32_bits(cpu_addr + size - 1)); dw_pcie_writel_dbi(pci, PCIE_ATU_LOWER_TARGET, lower_32_bits(pci_addr)); /* 写入pcie域地址 */ dw_pcie_writel_dbi(pci, PCIE_ATU_UPPER_TARGET, upper_32_bits(pci_addr)); /* 配置控制寄存器 1(CR1)—— 类型与功能号 */ dw_pcie_writel_dbi(pci, PCIE_ATU_CR1, type | PCIE_ATU_FUNC_NUM(func_no)); /* 启用 ATU 窗口(CR2) */ dw_pcie_writel_dbi(pci, PCIE_ATU_CR2, PCIE_ATU_ENABLE); /* 轮询读寄存器等待使能成功 */ for (retries = 0; retries < LINK_WAIT_MAX_IATU_RETRIES; retries++) val = dw_pcie_readl_dbi(pci, PCIE_ATU_CR2); if (val & PCIE_ATU_ENABLE) return; mdelay(LINK_WAIT_IATU); /* 返回对应的虚拟地址 */ return pp->va_cfg0_base + where; /* * dw_pcie_rd_other_conf * 读下游设备配置空间 */ static int dw_pcie_rd_other_conf(struct pci_bus *bus, unsigned int devfn, int where, int size, u32 *val) int ret; struct pcie_port *pp = bus->sysdata; struct dw_pcie *pci = to_dw_pcie_from_pp(pp); ret = pci_generic_config_read(bus, devfn, where, size, val); void __iomem *addr; /* 对应函数dw_pcie_other_conf_map_bus */ addr = bus->ops->map_bus(bus, devfn, where); if (size == 1) *val = readb(addr); else if (size == 2) *val = readw(addr); else *val = readl(addr); return PCIBIOS_SUCCESSFUL; /* * 如果使能了IO和配置空间共享,那么这里要将窗口0映射成IO空间 */ if (!ret && (pci->iatu_unroll_enabled & DWC_IATU_IOCFG_SHARED)) /* 进行cpu的IO空间地址pp->io_base到pcie域地址pp->io_bus_addr的窗口映射 */ dw_pcie_prog_outbound_atu(pci, 0, PCIE_ATU_TYPE_IO, pp->io_base, pp->io_bus_addr, pp->io_size); return ret; /* * dw_pcie_wr_other_conf * 写下游设备配置空间 */ static int dw_pcie_wr_other_conf(struct pci_bus *bus, unsigned int devfn, int where, int size, u32 val) int ret; struct pcie_port *pp = bus->sysdata; struct dw_pcie *pci = to_dw_pcie_from_pp(pp); ret = pci_generic_config_write(bus, devfn, where, size, val); void __iomem *addr; /* * 对应函数dw_pcie_other_conf_map_bus * 进行outbound窗口映射,并获取对应的虚拟地址 */ addr = bus->ops->map_bus(bus, devfn, where); if (!addr) return PCIBIOS_DEVICE_NOT_FOUND; if (size == 1) writeb(val, addr); else if (size == 2) writew(val, addr); else writel(val, addr); return PCIBIOS_SUCCESSFUL; /* * IO和配置空间共享窗口0,将窗口0重新配置为IO空间的映射 */ if (!ret && (pci->iatu_unroll_enabled & DWC_IATU_IOCFG_SHARED)) dw_pcie_prog_outbound_atu(pci, 0, PCIE_ATU_TYPE_IO, pp->io_base, pp->io_bus_addr, pp->io_size); return ret;
参考资料
《22_【北京迅为】itop-3568开发板pcie2.0接口快速测试手册_v1.0.pdf》
浙公网安备 33010602011771号