xen/arm: PCI host bridge discovery within XEN on ARM

XEN during boot will read the PCI device tree node “reg” property
and will map the PCI config space to the XEN memory.

As of now only "pci-host-ecam-generic" compatible board is supported.

"linux,pci-domain" device tree property assigns a fixed PCI domain
number to a host bridge, otherwise an unstable (across boots) unique
number will be assigned by Linux. XEN access the PCI devices based on
Segment:Bus:Device:Function. A Segment number in the XEN is same as a
domain number in Linux. Segment number and domain number has to be in
sync to access the correct PCI devices.

XEN will read the “linux,pci-domain” property from the device tree node
and configure the host bridge segment number accordingly. If this
property is not available XEN will allocate the unique segment number
to the host bridge.

Signed-off-by: Rahul Singh <rahul.singh@arm.com>
Reviewed-by: Stefano Stabellini <sstabellini@kernel.org>
This commit is contained in:
Rahul Singh authored and Stefano Stabellini committed 2021-10-07 12:47:25 -07:00
1 parent 15517ed61f
commit 6ec9176d94
6 files changed
+499

No files matched your search

+4
View File
@@ -1 +1,5 @@
obj-y += pci.o
obj-y += pci-access.o
obj-y += pci-host-generic.o
obj-y += pci-host-common.o
obj-y += ecam.o
+61
View File
@@ -0,0 +1,61 @@
/*
* Based on Linux drivers/pci/ecam.c
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <xen/pci.h>
#include <xen/sched.h>
/*
* 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)
{
const struct pci_config_window *cfg = bridge->cfg;
struct pci_ecam_ops *ops =
container_of(bridge->ops, struct pci_ecam_ops, pci_ops);
unsigned int devfn_shift = ops->bus_shift - 8;
void __iomem *base;
unsigned int busn = PCI_BUS(sbdf.bdf);
if ( busn < cfg->busn_start || busn > cfg->busn_end )
return NULL;
busn -= cfg->busn_start;
base = cfg->win + (busn << ops->bus_shift);
return base + (PCI_DEVFN2(sbdf.bdf) << devfn_shift) + where;
}
/* ECAM ops */
const struct pci_ecam_ops pci_generic_ecam_ops = {
.bus_shift = 20,
.pci_ops = {
.map_bus = pci_ecam_map_bus,
.read = pci_generic_config_read,
.write = pci_generic_config_write,
}
};
/*
* Local variables:
* mode: C
* c-file-style: "BSD"
* c-basic-offset: 4
* tab-width: 4
* indent-tabs-mode: nil
* End:
*/
+83
View File
@@ -0,0 +1,83 @@
/*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <xen/pci.h>
#include <asm/io.h>
#define INVALID_VALUE (~0U)
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);
if ( !addr )
{
*value = INVALID_VALUE;
return -ENODEV;
}
switch ( len )
{
case 1:
*value = readb(addr);
break;
case 2:
*value = readw(addr);
break;
case 4:
*value = readl(addr);
break;
default:
ASSERT_UNREACHABLE();
}
return 0;
}
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);
if ( !addr )
return -ENODEV;
switch ( len )
{
case 1:
writeb(value, addr);
break;
case 2:
writew(value, addr);
break;
case 4:
writel(value, addr);
break;
default:
ASSERT_UNREACHABLE();
}
return 0;
}
/*
* Local variables:
* mode: C
* c-file-style: "BSD"
* c-basic-offset: 4
* tab-width: 4
* indent-tabs-mode: nil
* End:
*/
+247
View File
@@ -0,0 +1,247 @@
/*
* Based on Linux drivers/pci/ecam.c
* Based on Linux drivers/pci/controller/pci-host-common.c
* Based on Linux drivers/pci/controller/pci-host-generic.c
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <xen/init.h>
#include <xen/pci.h>
#include <xen/rwlock.h>
#include <xen/sched.h>
#include <xen/vmap.h>
/*
* List for all the pci host bridges.
*/
static LIST_HEAD(pci_host_bridges);
static atomic_t domain_nr = ATOMIC_INIT(-1);
static inline void __iomem *pci_remap_cfgspace(paddr_t start, size_t len)
{
return ioremap_nocache(start, len);
}
static void pci_ecam_free(struct pci_config_window *cfg)
{
if ( cfg->win )
iounmap(cfg->win);
xfree(cfg);
}
static struct pci_config_window * __init
gen_pci_init(struct dt_device_node *dev, const struct pci_ecam_ops *ops)
{
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;
err = dt_property_read_u32_array(dev, "bus-range", bus_range,
ARRAY_SIZE(bus_range));
if ( err ) {
cfg->busn_start = 0;
cfg->busn_end = 0xff;
printk(XENLOG_INFO "%s: No bus range found for pci controller\n",
dt_node_full_name(dev));
} else {
cfg->busn_start = bus_range[0];
cfg->busn_end = bus_range[1];
if ( cfg->busn_end > cfg->busn_start + 0xff )
cfg->busn_end = cfg->busn_start + 0xff;
}
if ( ops->cfg_reg_index )
{
cfg_reg_idx = ops->cfg_reg_index(dev);
if ( cfg_reg_idx < 0 )
goto err_exit;
}
else
cfg_reg_idx = 0;
/* Parse our PCI ecam register address */
err = dt_device_get_address(dev, cfg_reg_idx, &addr, &size);
if ( err )
goto err_exit;
cfg->phys_addr = addr;
cfg->size = size;
/*
* On 64-bit systems, we do a single ioremap for the whole config space
* since we have enough virtual address range available. On 32-bit, we
* ioremap the config space for each bus individually.
* As of now only 64-bit is supported 32-bit is not supported.
*
* TODO: For 32-bit implement the ioremap/iounmap of config space
* dynamically for each read/write call.
*/
cfg->win = pci_remap_cfgspace(cfg->phys_addr, cfg->size);
if ( !cfg->win )
{
printk(XENLOG_ERR "ECAM ioremap failed\n");
goto err_exit;
}
printk("ECAM at [mem 0x%"PRIpaddr"-0x%"PRIpaddr"] for [bus %x-%x] \n",
cfg->phys_addr, cfg->phys_addr + cfg->size - 1,
cfg->busn_start, cfg->busn_end);
if ( ops->init )
{
err = ops->init(cfg);
if ( err )
goto err_exit;
}
return cfg;
err_exit:
pci_ecam_free(cfg);
return NULL;
}
struct pci_host_bridge *pci_alloc_host_bridge(void)
{
struct pci_host_bridge *bridge = xzalloc(struct pci_host_bridge);
if ( !bridge )
return NULL;
INIT_LIST_HEAD(&bridge->node);
return bridge;
}
void pci_add_host_bridge(struct pci_host_bridge *bridge)
{
list_add_tail(&bridge->node, &pci_host_bridges);
}
static int pci_get_new_domain_nr(void)
{
return atomic_inc_return(&domain_nr);
}
static int pci_bus_find_domain_nr(struct dt_device_node *dev)
{
static int use_dt_domains = -1;
int domain;
domain = dt_get_pci_domain_nr(dev);
/*
* Check DT domain and use_dt_domains values.
*
* If DT domain property is valid (domain >= 0) and
* use_dt_domains != 0, the DT assignment is valid since this means
* we have not previously allocated a domain number by using
* pci_get_new_domain_nr(); we should also update use_dt_domains to
* 1, to indicate that we have just assigned a domain number from
* DT.
*
* If DT domain property value is not valid (ie domain < 0), and we
* have not previously assigned a domain number from DT
* (use_dt_domains != 1) we should assign a domain number by
* using the:
*
* pci_get_new_domain_nr()
*
* API and update the use_dt_domains value to keep track of method we
* are using to assign domain numbers (use_dt_domains = 0).
*
* All other combinations imply we have a platform that is trying
* to mix domain numbers obtained from DT and pci_get_new_domain_nr(),
* which is a recipe for domain mishandling and it is prevented by
* invalidating the domain value (domain = -1) and printing a
* corresponding error.
*/
if ( domain >= 0 && use_dt_domains )
{
use_dt_domains = 1;
}
else if ( domain < 0 && use_dt_domains != 1 )
{
use_dt_domains = 0;
domain = pci_get_new_domain_nr();
}
else
{
domain = -1;
}
return domain;
}
int pci_host_common_probe(struct dt_device_node *dev, const void *data)
{
struct pci_host_bridge *bridge;
struct pci_config_window *cfg;
struct pci_ecam_ops *ops;
int err;
if ( dt_device_for_passthrough(dev) )
return 0;
ops = (struct pci_ecam_ops *) data;
bridge = pci_alloc_host_bridge();
if ( !bridge )
return -ENOMEM;
/* Parse and map our Configuration Space windows */
cfg = gen_pci_init(dev, ops);
if ( !cfg )
{
err = -ENOMEM;
goto err_exit;
}
bridge->dt_node = dev;
bridge->cfg = cfg;
bridge->ops = &ops->pci_ops;
bridge->segment = pci_bus_find_domain_nr(dev);
if ( bridge->segment < 0 )
{
printk(XENLOG_ERR "Inconsistent \"linux,pci-domain\" property in DT\n");
BUG();
}
pci_add_host_bridge(bridge);
return 0;
err_exit:
xfree(bridge);
return err;
}
/*
* Local variables:
* mode: C
* c-file-style: "BSD"
* c-basic-offset: 4
* tab-width: 4
* indent-tabs-mode: nil
* End:
*/
+48
View File
@@ -0,0 +1,48 @@
/*
* Based on Linux drivers/pci/controller/pci-host-common.c
* Based on Linux drivers/pci/controller/pci-host-generic.c
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <xen/init.h>
#include <xen/pci.h>
#include <asm/device.h>
#include <asm/pci.h>
static const struct dt_device_match __initconstrel gen_pci_dt_match[] =
{
{ .compatible = "pci-host-ecam-generic" },
{ },
};
static int __init pci_host_generic_probe(struct dt_device_node *dev,
const void *data)
{
return pci_host_common_probe(dev, &pci_generic_ecam_ops);
}
DT_DEVICE_START(pci_gen, "PCI HOST GENERIC", DEVICE_PCI)
.dt_match = gen_pci_dt_match,
.init = pci_host_generic_probe,
DT_DEVICE_END
/*
* Local variables:
* mode: C
* c-file-style: "BSD"
* c-basic-offset: 4
* tab-width: 4
* indent-tabs-mode: nil
* End:
*/
+56
View File
@@ -26,6 +26,62 @@ struct arch_pci_dev {
struct device dev;
};
/*
* struct to hold the mappings of a config space window. This
* is expected to be used as sysdata for PCI controllers that
* use ECAM.
*/
struct pci_config_window {
paddr_t phys_addr;
paddr_t size;
uint8_t busn_start;
uint8_t busn_end;
void __iomem *win;
};
/*
* struct to hold pci host bridge information
* for a PCI controller.
*/
struct pci_host_bridge {
struct dt_device_node *dt_node; /* Pointer to the associated DT node */
struct list_head node; /* Node in list of host bridges */
uint16_t segment; /* Segment number */
struct pci_config_window* cfg; /* Pointer to the bridge config window */
struct pci_ops *ops;
};
struct pci_ops {
void __iomem *(*map_bus)(struct pci_host_bridge *bridge, pci_sbdf_t sbdf,
uint32_t offset);
int (*read)(struct pci_host_bridge *bridge, pci_sbdf_t sbdf,
uint32_t reg, uint32_t len, uint32_t *value);
int (*write)(struct pci_host_bridge *bridge, pci_sbdf_t sbdf,
uint32_t reg, uint32_t len, uint32_t value);
};
/*
* struct to hold pci ops and bus shift of the config window
* for a PCI controller.
*/
struct pci_ecam_ops {
unsigned int bus_shift;
struct pci_ops pci_ops;
int (*cfg_reg_index)(struct dt_device_node *dev);
int (*init)(struct pci_config_window *);
};
/* Default ECAM ops */
extern const struct pci_ecam_ops pci_generic_ecam_ops;
int pci_host_common_probe(struct dt_device_node *dev, const void *data);
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);
static always_inline bool is_pci_passthrough_enabled(void)
{
return pci_passthrough_enabled;
Reference in new issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.