[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: [PATCH v3 3/4] xen/arm: add support for PCI child bus



On Wed, 9 Apr 2025, Mykyta Poturai wrote:
> From: Oleksandr Andrushchenko <oleksandr_andrushchenko@xxxxxxxx>
> 
> 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@xxxxxxxx>
> Signed-off-by: Mykyta Poturai <mykyta_poturai@xxxxxxxx>
> ---
> v2->v3:
> * no change
> 
> v1->v2:
> * fixed compilation issues
> ---
>  xen/arch/arm/include/asm/pci.h      | 12 +++-
>  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                 | 83 ++++++++++++++++++++++------
>  7 files changed, 192 insertions(+), 47 deletions(-)
> 
> diff --git a/xen/arch/arm/include/asm/pci.h b/xen/arch/arm/include/asm/pci.h
> index 3d2ca9b5b0..addec31791 100644
> --- a/xen/arch/arm/include/asm/pci.h
> +++ b/xen/arch/arm/include/asm/pci.h
> @@ -67,6 +67,9 @@ struct pci_host_bridge {
>      uint16_t segment;                /* Segment number */
>      struct pci_config_window* cfg;   /* Pointer to the bridge config window 
> */
>      const struct pci_ops *ops;
> +    /* Child bus */
> +    struct pci_config_window *child_cfg;
> +    const struct pci_ops *child_ops;
>      void *priv;                      /* Private data of the bridge. */
>  };
>  
> @@ -96,14 +99,19 @@ 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);
> +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);
>  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..ad0b2c35f9 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);
> +}

It doesn't look like this change is necessary? It doesn't seem that
pci_ecam_map_bus_generic is used anywhere?


>  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..cc4787f2b5 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)

NIT: I would do static inline here


> +{
> +    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 7ce9675551..2b058b5f5e 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,8 +233,10 @@ 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)
> +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)
>  {
>      struct pci_host_bridge *bridge;
>      struct pci_config_window *cfg;
> @@ -224,7 +251,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;
> @@ -242,10 +269,29 @@ struct pci_host_bridge *pci_host_common_probe(struct 
> dt_device_node *dev,
>          BUG();
>      }
>      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;
> +    }
> +
>      pci_add_host_bridge(bridge);
>  
>      return bridge;
>  
> +err_child:
> +    xfree(bridge->cfg);
> +
>  err_exit:
>      xfree(bridge);
>  
> @@ -280,9 +326,12 @@ 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;
> @@ -348,6 +397,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++ )
>          {
> @@ -363,7 +413,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);

Is this because the child bus address range is always a subset of the
parent address range?

If not, we would need to check either of them independently (|| instead
of &&).


> +            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 a6ffbda174..47cf144831 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));
> +    return PTR_RET(pci_host_common_probe(dev, &pci_generic_ecam_ops, NULL));
>  }
>  
>  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 a38f2e019e..75d1235aaf 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));
> +    return PTR_RET(pci_host_common_probe(dev, &nwl_pcie_ops, NULL));
>  }
>  
>  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..468aee1db7 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,78 @@ 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 = vpci_sbdf_from_gpa(bridge, info->gpa, true);
> +
> +    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 = vpci_sbdf_from_gpa(bridge, info->gpa, false);
> +
> +    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 +148,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)
> -- 
> 2.34.1
> 



 


Rackspace

Lists.xenproject.org is hosted with RackSpace, monitoring our
servers 24x7x365 and backed by RackSpace's Fanatical Support®.