diff mbox series

[3/7] xen/arm: add support for PCI child bus

Message ID c6706d49eaf5e2cab29c47c346b5cb8cda20a943.1740382735.git.mykyta_poturai@epam.com (mailing list archive)
State New
Headers show
Series Add support for R-Car Gen4 PCI host controller | expand

Commit Message

Mykyta Poturai Feb. 24, 2025, 9:18 a.m. UTC
From: Oleksandr Andrushchenko <oleksandr_andrushchenko@epam.com>

PCI host bridges often have different ways to access the root and child
bus configuration spaces. One of the examples is Designware's host bridge
and its multiple clones [1].

Linux kernel implements this by instantiating a child bus when device
drivers provide not only the usual pci_ops to access ECAM space (this is
the case for the generic host bridge), but also means to access the child
bus which has a dedicated configuration space and own implementation for
accessing the bus, e.g. child_ops.

For Xen it is not feasible to fully implement PCI bus infrastructure as
Linux kernel does, but still child bus can be supported.

Add support for the PCI child bus which includes the following changes:
- introduce bus mapping functions depending on SBDF
- assign bus start and end for the child bus and re-configure the same for
  the parent (root) bus
- make pci_find_host_bridge be aware of multiple busses behind the same bridge
- update pci_host_bridge_mappings, so it also doesn't map to guest the memory
  spaces belonging to the child bus
- make pci_host_common_probe accept one more pci_ops structure for the child bus
- install MMIO handlers for the child bus
- re-work vpci_mmio_{write|read} with parent and child approach in mind

[1] https://elixir.bootlin.com/linux/v5.15/source/drivers/pci/controller/dwc

Signed-off-by: Oleksandr Andrushchenko <oleksandr_andrushchenko@epam.com>
Signed-off-by: Mykyta Poturai <mykyta_poturai@epam.com>
---
 xen/arch/arm/include/asm/pci.h      | 14 ++++-
 xen/arch/arm/pci/ecam.c             | 17 ++++--
 xen/arch/arm/pci/pci-access.c       | 37 ++++++++++--
 xen/arch/arm/pci/pci-host-common.c  | 86 +++++++++++++++++++++------
 xen/arch/arm/pci/pci-host-generic.c |  2 +-
 xen/arch/arm/pci/pci-host-zynqmp.c  |  2 +-
 xen/arch/arm/vpci.c                 | 91 ++++++++++++++++++++++++-----
 7 files changed, 200 insertions(+), 49 deletions(-)
diff mbox series

Patch

diff --git a/xen/arch/arm/include/asm/pci.h b/xen/arch/arm/include/asm/pci.h
index e1f63d75e3..0012c2ae9e 100644
--- a/xen/arch/arm/include/asm/pci.h
+++ b/xen/arch/arm/include/asm/pci.h
@@ -69,6 +69,9 @@  struct pci_host_bridge {
     struct pci_config_window* cfg;   /* Pointer to the bridge config window */
     const struct pci_ops *ops;
     void *priv;                      /* Private data of the bridge. */
+    /* Child bus */
+    struct pci_config_window* child_cfg;
+    const struct pci_ops *child_ops;
 };
 
 struct pci_ops {
@@ -97,15 +100,20 @@  struct pci_ecam_ops {
 /* Default ECAM ops */
 extern const struct pci_ecam_ops pci_generic_ecam_ops;
 
-struct pci_host_bridge *pci_host_common_probe(struct dt_device_node *dev,
-                                              const struct pci_ecam_ops *ops,
-                                              size_t priv_sz);
+struct pci_host_bridge *
+pci_host_common_probe(struct dt_device_node *dev,
+                      const struct pci_ecam_ops *ops,
+                      const struct pci_ecam_ops *child_ops,
+                      size_t priv_sz);
 int pci_generic_config_read(struct pci_host_bridge *bridge, pci_sbdf_t sbdf,
                             uint32_t reg, uint32_t len, uint32_t *value);
 int pci_generic_config_write(struct pci_host_bridge *bridge, pci_sbdf_t sbdf,
                              uint32_t reg, uint32_t len, uint32_t value);
 void __iomem *pci_ecam_map_bus(struct pci_host_bridge *bridge,
                                pci_sbdf_t sbdf, uint32_t where);
+void __iomem *pci_ecam_map_bus_generic(const struct pci_config_window *cfg,
+                                       const struct pci_ecam_ops *ops,
+                                       pci_sbdf_t sbdf, uint32_t where);
 bool pci_ecam_need_p2m_hwdom_mapping(struct domain *d,
                                      struct pci_host_bridge *bridge,
                                      uint64_t addr);
diff --git a/xen/arch/arm/pci/ecam.c b/xen/arch/arm/pci/ecam.c
index 3987f96b01..5486881c5c 100644
--- a/xen/arch/arm/pci/ecam.c
+++ b/xen/arch/arm/pci/ecam.c
@@ -20,12 +20,10 @@ 
 /*
  * Function to implement the pci_ops->map_bus method.
  */
-void __iomem *pci_ecam_map_bus(struct pci_host_bridge *bridge,
-                               pci_sbdf_t sbdf, uint32_t where)
+void __iomem *pci_ecam_map_bus_generic(const struct pci_config_window *cfg,
+                                       const struct pci_ecam_ops *ops,
+                                       pci_sbdf_t sbdf, uint32_t where)
 {
-    const struct pci_config_window *cfg = bridge->cfg;
-    const struct pci_ecam_ops *ops =
-        container_of(bridge->ops, const struct pci_ecam_ops, pci_ops);
     unsigned int devfn_shift = ops->bus_shift - 8;
     void __iomem *base;
     unsigned int busn = sbdf.bus;
@@ -39,6 +37,15 @@  void __iomem *pci_ecam_map_bus(struct pci_host_bridge *bridge,
     return base + (sbdf.devfn << devfn_shift) + where;
 }
 
+void __iomem *pci_ecam_map_bus(struct pci_host_bridge *bridge,
+                               pci_sbdf_t sbdf, uint32_t where)
+{
+    const struct pci_ecam_ops *ops =
+        container_of(bridge->ops, const struct pci_ecam_ops, pci_ops);
+
+    return pci_ecam_map_bus_generic(bridge->cfg, ops, sbdf, where);
+}
+
 bool __init pci_ecam_need_p2m_hwdom_mapping(struct domain *d,
                                             struct pci_host_bridge *bridge,
                                             uint64_t addr)
diff --git a/xen/arch/arm/pci/pci-access.c b/xen/arch/arm/pci/pci-access.c
index 9f9aac43d7..4da607ac3f 100644
--- a/xen/arch/arm/pci/pci-access.c
+++ b/xen/arch/arm/pci/pci-access.c
@@ -18,10 +18,31 @@ 
 #define INVALID_VALUE (~0U)
 #define PCI_ERR_VALUE(len) GENMASK(0, len * 8)
 
+static const struct pci_ops *get_ops(struct pci_host_bridge *bridge,
+                                     pci_sbdf_t sbdf)
+{
+    if ( bridge->child_ops )
+    {
+        struct pci_config_window* cfg = bridge->child_cfg;
+
+        if ( (sbdf.bus >= cfg->busn_start) && (sbdf.bus <= cfg->busn_end) )
+            return bridge->child_ops;
+    }
+    return bridge->ops;
+}
+
+static void __iomem *map_bus(struct pci_host_bridge *bridge, pci_sbdf_t sbdf,
+                            uint32_t reg)
+{
+    const struct pci_ops *ops = get_ops(bridge, sbdf);
+
+    return ops->map_bus(bridge, sbdf, reg);
+}
+
 int pci_generic_config_read(struct pci_host_bridge *bridge, pci_sbdf_t sbdf,
                             uint32_t reg, uint32_t len, uint32_t *value)
 {
-    void __iomem *addr = bridge->ops->map_bus(bridge, sbdf, reg);
+    void __iomem *addr = map_bus(bridge, sbdf, reg);
 
     if ( !addr )
     {
@@ -50,7 +71,7 @@  int pci_generic_config_read(struct pci_host_bridge *bridge, pci_sbdf_t sbdf,
 int pci_generic_config_write(struct pci_host_bridge *bridge, pci_sbdf_t sbdf,
                              uint32_t reg, uint32_t len, uint32_t value)
 {
-    void __iomem *addr = bridge->ops->map_bus(bridge, sbdf, reg);
+    void __iomem *addr = map_bus(bridge, sbdf, reg);
 
     if ( !addr )
         return -ENODEV;
@@ -78,14 +99,16 @@  static uint32_t pci_config_read(pci_sbdf_t sbdf, unsigned int reg,
 {
     uint32_t val = PCI_ERR_VALUE(len);
     struct pci_host_bridge *bridge = pci_find_host_bridge(sbdf.seg, sbdf.bus);
+    const struct pci_ops *ops;
 
     if ( unlikely(!bridge) )
         return val;
 
-    if ( unlikely(!bridge->ops->read) )
+    ops = get_ops(bridge, sbdf);
+    if ( unlikely(!ops->read) )
         return val;
 
-    bridge->ops->read(bridge, sbdf, reg, len, &val);
+    ops->read(bridge, sbdf, reg, len, &val);
 
     return val;
 }
@@ -94,14 +117,16 @@  static void pci_config_write(pci_sbdf_t sbdf, unsigned int reg,
                              unsigned int len, uint32_t val)
 {
     struct pci_host_bridge *bridge = pci_find_host_bridge(sbdf.seg, sbdf.bus);
+    const struct pci_ops *ops;
 
     if ( unlikely(!bridge) )
         return;
 
-    if ( unlikely(!bridge->ops->write) )
+    ops = get_ops(bridge, sbdf);
+    if ( unlikely(!ops->write) )
         return;
 
-    bridge->ops->write(bridge, sbdf, reg, len, val);
+    ops->write(bridge, sbdf, reg, len, val);
 }
 
 /*
diff --git a/xen/arch/arm/pci/pci-host-common.c b/xen/arch/arm/pci/pci-host-common.c
index e4e05d1176..3824146561 100644
--- a/xen/arch/arm/pci/pci-host-common.c
+++ b/xen/arch/arm/pci/pci-host-common.c
@@ -57,17 +57,12 @@  static void pci_ecam_free(struct pci_config_window *cfg)
     xfree(cfg);
 }
 
-static struct pci_config_window * __init
-gen_pci_init(struct dt_device_node *dev, const struct pci_ecam_ops *ops)
+static void __init gen_pci_init_bus_range(struct dt_device_node *dev,
+                                          struct pci_host_bridge *bridge,
+                                          struct pci_config_window *cfg)
 {
-    int err, cfg_reg_idx;
     u32 bus_range[2];
-    paddr_t addr, size;
-    struct pci_config_window *cfg;
-
-    cfg = xzalloc(struct pci_config_window);
-    if ( !cfg )
-        return NULL;
+    int err;
 
     err = dt_property_read_u32_array(dev, "bus-range", bus_range,
                                      ARRAY_SIZE(bus_range));
@@ -82,6 +77,36 @@  gen_pci_init(struct dt_device_node *dev, const struct pci_ecam_ops *ops)
         if ( cfg->busn_end > cfg->busn_start + 0xff )
             cfg->busn_end = cfg->busn_start + 0xff;
     }
+}
+
+static void __init gen_pci_init_bus_range_child(struct dt_device_node *dev,
+                                                struct pci_host_bridge *bridge,
+                                                struct pci_config_window *cfg)
+{
+    cfg->busn_start = bridge->cfg->busn_start + 1;
+    cfg->busn_end = bridge->cfg->busn_end;
+    bridge->cfg->busn_end = bridge->cfg->busn_start;
+
+    printk(XENLOG_INFO "Root bus end updated: [bus %x-%x]\n",
+           bridge->cfg->busn_start, bridge->cfg->busn_end);
+}
+
+static struct pci_config_window * __init
+gen_pci_init(struct dt_device_node *dev, struct pci_host_bridge *bridge,
+             const struct pci_ecam_ops *ops,
+             void (*init_bus_range)(struct dt_device_node *dev,
+                                    struct pci_host_bridge *bridge,
+                                    struct pci_config_window *cfg))
+{
+    int err, cfg_reg_idx;
+    paddr_t addr, size;
+    struct pci_config_window *cfg;
+
+    cfg = xzalloc(struct pci_config_window);
+    if ( !cfg )
+        return NULL;
+
+    init_bus_range(dev, bridge, cfg);
 
     if ( ops->cfg_reg_index )
     {
@@ -208,9 +233,11 @@  static int pci_bus_find_domain_nr(struct dt_device_node *dev)
     return domain;
 }
 
-struct pci_host_bridge *pci_host_common_probe(struct dt_device_node *dev,
-                                              const struct pci_ecam_ops *ops,
-                                              size_t priv_sz)
+struct pci_host_bridge *
+pci_host_common_probe(struct dt_device_node *dev,
+                      const struct pci_ecam_ops *ops,
+                      const struct pci_ecam_ops *child_ops,
+                      size_t priv_sz)
 {
     struct pci_host_bridge *bridge;
     struct pci_config_window *cfg;
@@ -225,7 +252,7 @@  struct pci_host_bridge *pci_host_common_probe(struct dt_device_node *dev,
         return ERR_PTR(-ENOMEM);
 
     /* Parse and map our Configuration Space windows */
-    cfg = gen_pci_init(dev, ops);
+    cfg = gen_pci_init(dev, bridge, ops, gen_pci_init_bus_range);
     if ( !cfg )
     {
         err = -ENOMEM;
@@ -245,6 +272,21 @@  struct pci_host_bridge *pci_host_common_probe(struct dt_device_node *dev,
 
     bridge->segment = domain;
 
+    if ( child_ops )
+    {
+        /* Parse and map child's Configuration Space windows */
+        cfg = gen_pci_init(dev, bridge, child_ops,
+                           gen_pci_init_bus_range_child);
+        if ( !cfg )
+        {
+            err = -ENOMEM;
+            goto err_child;
+        }
+
+        bridge->child_cfg = cfg;
+        bridge->child_ops = &child_ops->pci_ops;
+    }
+
     if ( priv_sz )
     {
         bridge->priv = xzalloc_bytes(priv_sz);
@@ -262,6 +304,9 @@  struct pci_host_bridge *pci_host_common_probe(struct dt_device_node *dev,
 err_priv:
     xfree(bridge->priv);
 
+err_child:
+    xfree(bridge->cfg);
+
 err_exit:
     xfree(bridge);
 
@@ -296,9 +341,11 @@  struct pci_host_bridge *pci_find_host_bridge(uint16_t segment, uint8_t bus)
     {
         if ( bridge->segment != segment )
             continue;
-        if ( (bus < bridge->cfg->busn_start) || (bus > bridge->cfg->busn_end) )
-            continue;
-        return bridge;
+        if ( bridge->child_cfg && (bus >= bridge->child_cfg->busn_start) &&
+             (bus <= bridge->child_cfg->busn_end) )
+             return bridge;
+        if ( (bus >= bridge->cfg->busn_start) && (bus <= bridge->cfg->busn_end) )
+             return bridge;
     }
 
     return NULL;
@@ -364,6 +411,7 @@  int __init pci_host_bridge_mappings(struct domain *d)
     {
         const struct dt_device_node *dev = bridge->dt_node;
         unsigned int i;
+        bool need_mapping;
 
         for ( i = 0; i < dt_number_of_address(dev); i++ )
         {
@@ -379,7 +427,11 @@  int __init pci_host_bridge_mappings(struct domain *d)
                 return err;
             }
 
-            if ( bridge->ops->need_p2m_hwdom_mapping(d, bridge, addr) )
+            need_mapping = bridge->ops->need_p2m_hwdom_mapping(d, bridge, addr);
+            if ( need_mapping && bridge->child_ops )
+                  need_mapping = bridge->child_ops->
+                      need_p2m_hwdom_mapping(d, bridge, addr);
+            if ( need_mapping )
             {
                 err = map_range_to_domain(dev, addr, size, &mr_data);
                 if ( err )
diff --git a/xen/arch/arm/pci/pci-host-generic.c b/xen/arch/arm/pci/pci-host-generic.c
index dde6a79a8e..08d94879b7 100644
--- a/xen/arch/arm/pci/pci-host-generic.c
+++ b/xen/arch/arm/pci/pci-host-generic.c
@@ -29,7 +29,7 @@  static const struct dt_device_match __initconstrel gen_pci_dt_match[] =
 static int __init pci_host_generic_probe(struct dt_device_node *dev,
                                          const void *data)
 {
-    return PTR_RET(pci_host_common_probe(dev, &pci_generic_ecam_ops, 0));
+    return PTR_RET(pci_host_common_probe(dev, &pci_generic_ecam_ops, NULL, 0));
 }
 
 DT_DEVICE_START(pci_gen, "PCI HOST GENERIC", DEVICE_PCI_HOSTBRIDGE)
diff --git a/xen/arch/arm/pci/pci-host-zynqmp.c b/xen/arch/arm/pci/pci-host-zynqmp.c
index c796447ac4..0b51ff6bd9 100644
--- a/xen/arch/arm/pci/pci-host-zynqmp.c
+++ b/xen/arch/arm/pci/pci-host-zynqmp.c
@@ -47,7 +47,7 @@  static const struct dt_device_match __initconstrel nwl_pcie_dt_match[] =
 static int __init pci_host_generic_probe(struct dt_device_node *dev,
                                          const void *data)
 {
-    return PTR_RET(pci_host_common_probe(dev, &nwl_pcie_ops, 0));
+    return PTR_RET(pci_host_common_probe(dev, &nwl_pcie_ops, NULL, 0));
 }
 
 DT_DEVICE_START(pci_gen, "PCI HOST ZYNQMP", DEVICE_PCI_HOSTBRIDGE)
diff --git a/xen/arch/arm/vpci.c b/xen/arch/arm/vpci.c
index b63a356bb4..05af4ef390 100644
--- a/xen/arch/arm/vpci.c
+++ b/xen/arch/arm/vpci.c
@@ -8,15 +8,17 @@ 
 #include <asm/mmio.h>
 
 static pci_sbdf_t vpci_sbdf_from_gpa(const struct pci_host_bridge *bridge,
-                                     paddr_t gpa)
+                                     paddr_t gpa, bool use_root)
 {
     pci_sbdf_t sbdf;
 
     if ( bridge )
     {
-        sbdf.sbdf = VPCI_ECAM_BDF(gpa - bridge->cfg->phys_addr);
+        const struct pci_config_window *cfg = use_root ? bridge->cfg :
+                                                         bridge->child_cfg;
+        sbdf.sbdf = VPCI_ECAM_BDF(gpa - cfg->phys_addr);
         sbdf.seg = bridge->segment;
-        sbdf.bus += bridge->cfg->busn_start;
+        sbdf.bus += cfg->busn_start;
     }
     else
         sbdf.sbdf = VPCI_ECAM_BDF(gpa - GUEST_VPCI_ECAM_BASE);
@@ -24,11 +26,9 @@  static pci_sbdf_t vpci_sbdf_from_gpa(const struct pci_host_bridge *bridge,
     return sbdf;
 }
 
-static int vpci_mmio_read(struct vcpu *v, mmio_info_t *info,
-                          register_t *r, void *p)
+static int vpci_mmio_read(struct vcpu *v, mmio_info_t *info, register_t *r,
+                          pci_sbdf_t sbdf)
 {
-    struct pci_host_bridge *bridge = p;
-    pci_sbdf_t sbdf = vpci_sbdf_from_gpa(bridge, info->gpa);
     const unsigned int access_size = (1U << info->dabt.size) * 8;
     const register_t invalid = GENMASK_ULL(access_size - 1, 0);
     /* data is needed to prevent a pointer cast on 32bit */
@@ -46,31 +46,86 @@  static int vpci_mmio_read(struct vcpu *v, mmio_info_t *info,
     return 0;
 }
 
-static int vpci_mmio_write(struct vcpu *v, mmio_info_t *info,
-                           register_t r, void *p)
+static int vpci_mmio_read_root(struct vcpu *v, mmio_info_t *info,
+                          register_t *r, void *p)
+{
+    struct pci_host_bridge *bridge = p;
+    pci_sbdf_t sbdf = vpci_sbdf_from_gpa(bridge, info->gpa, true);
+
+    return vpci_mmio_read(v, info, r, sbdf);
+}
+
+static int vpci_mmio_read_child(struct vcpu *v, mmio_info_t *info,
+                          register_t *r, void *p)
 {
     struct pci_host_bridge *bridge = p;
-    pci_sbdf_t sbdf = vpci_sbdf_from_gpa(bridge, info->gpa);
+    pci_sbdf_t sbdf = vpci_sbdf_from_gpa(bridge, info->gpa, false);
 
+    return vpci_mmio_read(v, info, r, sbdf);
+}
+
+static int vpci_mmio_write(struct vcpu *v, mmio_info_t *info,
+                           register_t r, pci_sbdf_t sbdf)
+{
     return vpci_ecam_write(sbdf, ECAM_REG_OFFSET(info->gpa),
                            1U << info->dabt.size, r);
 }
 
+static int vpci_mmio_write_root(struct vcpu *v, mmio_info_t *info,
+                                register_t r, void *p)
+{
+    struct pci_host_bridge *bridge = p;
+    pci_sbdf_t sbdf;
+
+    if ( !vpci_sbdf_from_gpa(v->domain, bridge, info->gpa,
+                             true, &sbdf) )
+        return 0;
+
+    return vpci_mmio_write(v, info, r, sbdf);
+}
+
+static int vpci_mmio_write_child(struct vcpu *v, mmio_info_t *info,
+                                register_t r, void *p)
+{
+    struct pci_host_bridge *bridge = p;
+    pci_sbdf_t sbdf;
+
+    if ( !vpci_sbdf_from_gpa(v->domain, bridge, info->gpa,
+                             false, &sbdf) )
+        return 0;
+
+    return vpci_mmio_write(v, info, r, sbdf);
+}
+
 static const struct mmio_handler_ops vpci_mmio_handler = {
-    .read  = vpci_mmio_read,
-    .write = vpci_mmio_write,
+    .read  = vpci_mmio_read_root,
+    .write = vpci_mmio_write_root,
+};
+
+static const struct mmio_handler_ops vpci_mmio_handler_child = {
+    .read  = vpci_mmio_read_child,
+    .write = vpci_mmio_write_child,
 };
 
 static int vpci_setup_mmio_handler_cb(struct domain *d,
                                       struct pci_host_bridge *bridge)
 {
     struct pci_config_window *cfg = bridge->cfg;
+    int count = 1;
 
     register_mmio_handler(d, &vpci_mmio_handler,
                           cfg->phys_addr, cfg->size, bridge);
 
-    /* We have registered a single MMIO handler. */
-    return 1;
+    if ( bridge->child_ops )
+    {
+        struct pci_config_window *cfg = bridge->child_cfg;
+
+        register_mmio_handler(d, &vpci_mmio_handler_child,
+                              cfg->phys_addr, cfg->size, bridge);
+        count++;
+    }
+
+    return count;
 }
 
 int domain_vpci_init(struct domain *d)
@@ -101,8 +156,12 @@  int domain_vpci_init(struct domain *d)
 static int vpci_get_num_handlers_cb(struct domain *d,
                                     struct pci_host_bridge *bridge)
 {
-    /* Each bridge has a single MMIO handler for the configuration space. */
-    return 1;
+    int count = 1;
+
+    if ( bridge->child_cfg )
+        count++;
+
+    return count;
 }
 
 unsigned int domain_vpci_get_num_mmio_handlers(struct domain *d)