访问 EP 的配置空间方法
- 内存映射
- IO 访问
内存访问配置空间
-
前置知识
PCIe 设备的寻址是按照 BDF 即 Bus-Device-Function 来组织的。访问某个设备则需要根据BDF计算偏移地址。 -
两种不同的内存访问配置空间方法
- 类 xilinx,基地址 + 偏移地址访问
// linux-5.10\drivers\pci\controller\pcie-xilinx.c
/* ECAM definitions */
#define ECAM_BUS_NUM_SHIFT 20
#define ECAM_DEV_NUM_SHIFT 12
/**
* xilinx_pcie_map_bus - Get configuration base
* @bus: PCI Bus structure
* @devfn: Device/function
* @where: Offset from base
*
* Return: Base address of the configuration space needed to be
* accessed.
*/
static void __iomem *xilinx_pcie_map_bus(struct pci_bus *bus,
unsigned int devfn, int where)
{
struct xilinx_pcie_port *port = bus->sysdata;
int relbus;
if (!xilinx_pcie_valid_device(bus, devfn))
return NULL;
relbus = (bus->number << ECAM_BUS_NUM_SHIFT) |
(devfn << ECAM_DEV_NUM_SHIFT);
return port->reg_base + relbus + where;
}
这个函数是转换配置空间的地址,port->reg_base = devm_pci_remap_cfg_resource(dev, ®s);
从设备树获取的基地址,然后加上 bus->number 与 devfn 计算得到的偏移地址。这个地址可以直接通过 readl 与 writel 直接访问。
- 类 designware,变基地址 + 偏移地址访问
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);
/*
* Checking whether the link is up here is a last line of defense
* against platforms that forward errors on the system bus as
* SError upon PCI configuration transactions issued when the link
* is down. This check is racy by definition and does not stop
* the system from triggering an SError if the link goes down
* after this check is performed.
*/
if (!dw_pcie_link_up(pci))
return NULL;
busdev = PCIE_ATU_BUS(bus->number) | PCIE_ATU_DEV(PCI_SLOT(devfn)) |
PCIE_ATU_FUNC(PCI_FUNC(devfn));
if (pci_is_root_bus(bus->parent))
type = PCIE_ATU_TYPE_CFG0;
else
type = PCIE_ATU_TYPE_CFG1;
dw_pcie_prog_outbound_atu(pci, PCIE_ATU_REGION_INDEX1,
type, pp->cfg0_base,
busdev, pp->cfg0_size);
return pp->va_cfg0_base + where;
}
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);
if (!ret && pci->num_viewport <= 2)
dw_pcie_prog_outbound_atu(pci, PCIE_ATU_REGION_INDEX1,
PCIE_ATU_TYPE_IO, pp->io_base,
pp->io_bus_addr, pp->io_size);
return ret;
}
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);
if (!ret && pci->num_viewport <= 2)
dw_pcie_prog_outbound_atu(pci, PCIE_ATU_REGION_INDEX1,
PCIE_ATU_TYPE_IO, pp->io_base,
pp->io_bus_addr, pp->io_size);
return ret;
}
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_pcie_other_conf_map_bus()
函数,这个是用来计算访问 EP 配置空间地址。同样也是通过 BDF 计算出 busdev 的偏移,但是它调用了 dw_pcie_prog_outbound_atu(busdev)
做了地址转换,所以它每次返回的转换后的地址都是 pp->va_cfg0_base + where
,而 pp->va_cfg0_base
是固定的值,它是由 pp->va_cfg0_base = devm_pci_remap_cfgspace(dev, pp->cfg0_base, pp->cfg0_size);
从设备树里读出来的固定地址空间。
总结
两种 MEM 访问 EP 配置空间的方式都是通过基地址 + 偏移地址访问的。类 xilinx 的方式是全映射,需要 256M 的空间,将所有的 PCIe 设备的配置空间都映射到 CPU 的地址空间来。类 designware 则取巧,使用少量的 CPU 地址空间,但是每次访问之前需要重新映射 EP 的地址,使得相同的 CPU 地址空间可以放到的不同的 EP 设备的配置空间。