XEN during boot will read the PCI device tree node “reg” property and will map the PCI config space to the XEN memory.
XEN will read the “linux, pci-domain” property from the device tree node and configure the host bridge segment number accordingly. As of now "pci-host-ecam-generic" compatible board is supported. Change-Id: If32f7748b7dc89dd37114dc502943222a2a36c49 Signed-off-by: Rahul Singh <rahul.si...@arm.com> --- xen/arch/arm/Kconfig | 7 + xen/arch/arm/Makefile | 1 + xen/arch/arm/pci/Makefile | 4 + xen/arch/arm/pci/pci-access.c | 101 ++++++++++++++ xen/arch/arm/pci/pci-host-common.c | 198 ++++++++++++++++++++++++++++ xen/arch/arm/pci/pci-host-generic.c | 131 ++++++++++++++++++ xen/arch/arm/pci/pci.c | 112 ++++++++++++++++ xen/arch/arm/setup.c | 2 + xen/include/asm-arm/device.h | 7 +- xen/include/asm-arm/pci.h | 97 +++++++++++++- 10 files changed, 654 insertions(+), 6 deletions(-) create mode 100644 xen/arch/arm/pci/Makefile create mode 100644 xen/arch/arm/pci/pci-access.c create mode 100644 xen/arch/arm/pci/pci-host-common.c create mode 100644 xen/arch/arm/pci/pci-host-generic.c create mode 100644 xen/arch/arm/pci/pci.c diff --git a/xen/arch/arm/Kconfig b/xen/arch/arm/Kconfig index 2777388265..ee13339ae9 100644 --- a/xen/arch/arm/Kconfig +++ b/xen/arch/arm/Kconfig @@ -31,6 +31,13 @@ menu "Architecture Features" source "arch/Kconfig" +config ARM_PCI + bool "PCI Passthrough Support" + depends on ARM_64 + ---help--- + + PCI passthrough support for Xen on ARM64. + config ACPI bool "ACPI (Advanced Configuration and Power Interface) Support" if EXPERT depends on ARM_64 diff --git a/xen/arch/arm/Makefile b/xen/arch/arm/Makefile index 7e82b2178c..345cb83eed 100644 --- a/xen/arch/arm/Makefile +++ b/xen/arch/arm/Makefile @@ -6,6 +6,7 @@ ifneq ($(CONFIG_NO_PLAT),y) obj-y += platforms/ endif obj-$(CONFIG_TEE) += tee/ +obj-$(CONFIG_ARM_PCI) += pci/ obj-$(CONFIG_HAS_ALTERNATIVE) += alternative.o obj-y += bootfdt.init.o diff --git a/xen/arch/arm/pci/Makefile b/xen/arch/arm/pci/Makefile new file mode 100644 index 0000000000..358508b787 --- /dev/null +++ b/xen/arch/arm/pci/Makefile @@ -0,0 +1,4 @@ +obj-y += pci.o +obj-y += pci-host-generic.o +obj-y += pci-host-common.o +obj-y += pci-access.o diff --git a/xen/arch/arm/pci/pci-access.c b/xen/arch/arm/pci/pci-access.c new file mode 100644 index 0000000000..c53ef58336 --- /dev/null +++ b/xen/arch/arm/pci/pci-access.c @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2020 Arm Ltd. + * + * 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/pci.h> +#include <xen/rwlock.h> + +static uint32_t pci_config_read(pci_sbdf_t sbdf, unsigned int reg, + unsigned int len) +{ + int rc; + uint32_t val = GENMASK(0, len * 8); + + struct pci_host_bridge *bridge = pci_find_host_bridge(sbdf.seg, sbdf.bus); + + if ( unlikely(!bridge) ) + { + printk(XENLOG_ERR "Unable to find bridge for "PRI_pci"\n", + sbdf.seg, sbdf.bus, sbdf.dev, sbdf.fn); + return val; + } + + if ( unlikely(!bridge->ops->read) ) + return val; + + rc = bridge->ops->read(bridge, (uint32_t) sbdf.sbdf, reg, len, &val); + if ( rc ) + printk(XENLOG_ERR "Failed to read reg %#x len %u for "PRI_pci"\n", + reg, len, sbdf.seg, sbdf.bus, sbdf.dev, sbdf.fn); + + return val; +} + +static void pci_config_write(pci_sbdf_t sbdf, unsigned int reg, + unsigned int len, uint32_t val) +{ + int rc; + struct pci_host_bridge *bridge = pci_find_host_bridge(sbdf.seg, sbdf.bus); + + if ( unlikely(!bridge) ) + { + printk(XENLOG_ERR "Unable to find bridge for "PRI_pci"\n", + sbdf.seg, sbdf.bus, sbdf.dev, sbdf.fn); + return; + } + + if ( unlikely(!bridge->ops->write) ) + return; + + rc = bridge->ops->write(bridge, (uint32_t) sbdf.sbdf, reg, len, val); + if ( rc ) + printk(XENLOG_ERR "Failed to write reg %#x len %u for "PRI_pci"\n", + reg, len, sbdf.seg, sbdf.bus, sbdf.dev, sbdf.fn); +} + +/* + * Wrappers for all PCI configuration access functions. + */ + +#define PCI_OP_WRITE(size, type) \ + void pci_conf_write##size (pci_sbdf_t sbdf,unsigned int reg, type val) \ +{ \ + pci_config_write(sbdf, reg, size / 8, val); \ +} + +#define PCI_OP_READ(size, type) \ + type pci_conf_read##size (pci_sbdf_t sbdf, unsigned int reg) \ +{ \ + return pci_config_read(sbdf, reg, size / 8); \ +} + +PCI_OP_READ(8, u8) +PCI_OP_READ(16, u16) +PCI_OP_READ(32, u32) +PCI_OP_WRITE(8, u8) +PCI_OP_WRITE(16, u16) +PCI_OP_WRITE(32, u32) + +/* + * Local variables: + * mode: C + * c-file-style: "BSD" + * c-basic-offset: 4 + * tab-width: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/xen/arch/arm/pci/pci-host-common.c b/xen/arch/arm/pci/pci-host-common.c new file mode 100644 index 0000000000..c5f98be698 --- /dev/null +++ b/xen/arch/arm/pci/pci-host-common.c @@ -0,0 +1,198 @@ +/* + * Copyright (C) 2020 Arm Ltd. + * + * Based on Linux drivers/pci/ecam.c + * Copyright 2016 Broadcom. + * + * Based on Linux drivers/pci/controller/pci-host-common.c + * Based on Linux drivers/pci/controller/pci-host-generic.c + * Copyright (C) 2014 ARM Limited Will Deacon <will.dea...@arm.com> + * + * + * 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/pci.h> +#include <xen/rwlock.h> +#include <xen/vmap.h> + +/* + * List for all the pci host bridges. + */ + +static LIST_HEAD(pci_host_bridges); + +static bool __init dt_pci_parse_bus_range(struct dt_device_node *dev, + struct pci_config_window *cfg) +{ + const __be32 *cells; + uint32_t len; + + cells = dt_get_property(dev, "bus-range", &len); + /* bus-range should at least be 2 cells */ + if ( !cells || (len < (sizeof(*cells) * 2)) ) + return false; + + cfg->busn_start = dt_next_cell(1, &cells); + cfg->busn_end = dt_next_cell(1, &cells); + + return true; +} + +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 *gen_pci_init(struct dt_device_node *dev, + struct pci_ecam_ops *ops) +{ + int err; + struct pci_config_window *cfg; + paddr_t addr, size; + + cfg = xzalloc(struct pci_config_window); + if ( !cfg ) + return NULL; + + err = dt_pci_parse_bus_range(dev, cfg); + if ( !err ) { + cfg->busn_start = 0; + cfg->busn_end = 0xff; + printk(XENLOG_ERR "No bus range found for pci controller\n"); + } else { + if ( cfg->busn_end > cfg->busn_start + 0xff ) + cfg->busn_end = cfg->busn_start + 0xff; + } + + /* Parse our PCI ecam register address*/ + err = dt_device_get_address(dev, 0, &addr, &size); + if ( err ) + goto err_exit; + + cfg->phys_addr = addr; + cfg->size = size; + cfg->ops = ops; + + /* + * 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. + */ + cfg->win = pci_remap_cfgspace(cfg->phys_addr, cfg->size); + if ( !cfg->win ) + goto err_exit_remap; + + printk("ECAM at [mem %lx-%lx] 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_remap: + printk(XENLOG_ERR "ECAM ioremap failed\n"); +err_exit: + pci_ecam_free(cfg); + return NULL; +} + +static 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; +} + +int pci_host_common_probe(struct dt_device_node *dev, + struct pci_ecam_ops *ops) +{ + struct pci_host_bridge *bridge; + struct pci_config_window *cfg; + u32 segment; + + bridge = pci_alloc_host_bridge(); + if ( !bridge ) + return -ENOMEM; + + /* Parse and map our Configuration Space windows */ + cfg = gen_pci_init(dev, ops); + if ( !cfg ) + return -ENOMEM; + + bridge->dt_node = dev; + bridge->sysdata = cfg; + bridge->ops = &ops->pci_ops; + + if( !dt_property_read_u32(dev, "linux,pci-domain", &segment) ) + { + printk(XENLOG_ERR "\"linux,pci-domain\" property in not available in DT\n"); + return -ENODEV; + } + + bridge->segment = (u16)segment; + + list_add_tail(&bridge->node, &pci_host_bridges); + + return 0; +} + +/* + * This function will lookup an hostbridge based on the segment and bus + * number. + */ +struct pci_host_bridge *pci_find_host_bridge(uint16_t segment, uint8_t bus) +{ + struct pci_host_bridge *bridge; + bool found = false; + + list_for_each_entry( bridge, &pci_host_bridges, node ) + { + if ( bridge->segment != segment ) + continue; + + found = true; + break; + } + + return (found) ? bridge : NULL; +} +/* + * Local variables: + * mode: C + * c-file-style: "BSD" + * c-basic-offset: 4 + * tab-width: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/xen/arch/arm/pci/pci-host-generic.c b/xen/arch/arm/pci/pci-host-generic.c new file mode 100644 index 0000000000..cd67b3dec6 --- /dev/null +++ b/xen/arch/arm/pci/pci-host-generic.c @@ -0,0 +1,131 @@ +/* + * Copyright (C) 2020 Arm Ltd. + * + * Based on Linux drivers/pci/controller/pci-host-common.c + * Based on Linux drivers/pci/controller/pci-host-generic.c + * Copyright (C) 2014 ARM Limited Will Deacon <will.dea...@arm.com> + * + * 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 <asm/device.h> +#include <asm/io.h> +#include <xen/pci.h> +#include <asm/pci.h> + +/* + * Function to get the config space base. + */ +static void __iomem *pci_config_base(struct pci_host_bridge *bridge, + uint32_t sbdf, int where) +{ + struct pci_config_window *cfg = bridge->sysdata; + unsigned int devfn_shift = cfg->ops->bus_shift - 8; + + pci_sbdf_t sbdf_t = (pci_sbdf_t) sbdf ; + + unsigned int busn = sbdf_t.bus; + void __iomem *base; + + if ( busn < cfg->busn_start || busn > cfg->busn_end ) + return NULL; + + base = cfg->win + (busn << cfg->ops->bus_shift); + + return base + (PCI_DEVFN(sbdf_t.dev, sbdf_t.fn) << devfn_shift) + where; +} + +int pci_ecam_config_write(struct pci_host_bridge *bridge, uint32_t sbdf, + int where, int size, u32 val) +{ + void __iomem *addr; + + addr = pci_config_base(bridge, sbdf, where); + if ( !addr ) + return -ENODEV; + + if ( size == 1 ) + writeb(val, addr); + else if ( size == 2 ) + writew(val, addr); + else + writel(val, addr); + + return 0; +} + +int pci_ecam_config_read(struct pci_host_bridge *bridge, uint32_t sbdf, + int where, int size, u32 *val) +{ + void __iomem *addr; + + addr = pci_config_base(bridge, sbdf, where); + if ( !addr ) { + *val = ~0; + return -ENODEV; + } + + if ( size == 1 ) + *val = readb(addr); + else if ( size == 2 ) + *val = readw(addr); + else + *val = readl(addr); + + return 0; +} + +/* ECAM ops */ +struct pci_ecam_ops pci_generic_ecam_ops = { + .bus_shift = 20, + .pci_ops = { + .read = pci_ecam_config_read, + .write = pci_ecam_config_write, + } +}; + +static const struct dt_device_match gen_pci_dt_match[] = { + { .compatible = "pci-host-ecam-generic", + .data = &pci_generic_ecam_ops }, + + { }, +}; + +static int gen_pci_dt_init(struct dt_device_node *dev, const void *data) +{ + const struct dt_device_match *of_id; + struct pci_ecam_ops *ops; + + of_id = dt_match_node(gen_pci_dt_match, dev->dev.of_node); + ops = (struct pci_ecam_ops *) of_id->data; + + printk(XENLOG_INFO "Found PCI host bridge %s compatible:%s \n", + dt_node_full_name(dev), of_id->compatible); + + return pci_host_common_probe(dev, ops); +} + +DT_DEVICE_START(pci_gen, "PCI HOST GENERIC", DEVICE_PCI) +.dt_match = gen_pci_dt_match, +.init = gen_pci_dt_init, +DT_DEVICE_END + +/* + * Local variables: + * mode: C + * c-file-style: "BSD" + * c-basic-offset: 4 + * tab-width: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/xen/arch/arm/pci/pci.c b/xen/arch/arm/pci/pci.c new file mode 100644 index 0000000000..f8cbb99591 --- /dev/null +++ b/xen/arch/arm/pci/pci.c @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2020 Arm Ltd. + * + * 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/acpi.h> +#include <xen/device_tree.h> +#include <xen/errno.h> +#include <xen/init.h> +#include <xen/pci.h> +#include <xen/param.h> + +static int __init dt_pci_init(void) +{ + struct dt_device_node *np; + int rc; + + dt_for_each_device_node(dt_host, np) + { + rc = device_init(np, DEVICE_PCI, NULL); + if( !rc ) + continue; + /* + * Ignore the following error codes: + * - EBADF: Indicate the current is not an pci + * - ENODEV: The pci device is not present or cannot be used by + * Xen. + */ + else if ( rc != -EBADF && rc != -ENODEV ) + { + printk(XENLOG_ERR "No driver found in XEN or driver init error.\n"); + return rc; + } + } + + return 0; +} + +#ifdef CONFIG_ACPI +static void __init acpi_pci_init(void) +{ + printk(XENLOG_ERR "ACPI pci init not supported \n"); + return; +} +#else +static inline void __init acpi_pci_init(void) { } +#endif + +static bool __initdata param_pci_enable; +static int __init parse_pci_param(const char *arg) +{ + if ( !arg ) + { + param_pci_enable = false; + return 0; + } + + switch ( parse_bool(arg, NULL) ) + { + case 0: + param_pci_enable = false; + return 0; + case 1: + param_pci_enable = true; + return 0; + } + + return -EINVAL; +} +custom_param("pci", parse_pci_param); + +void __init pci_init(void) +{ + /* + * Enable PCI when has been enabled explicitly (pci=on) + */ + if ( !param_pci_enable) + goto disable; + + if ( acpi_disabled ) + dt_pci_init(); + else + acpi_pci_init(); + +#ifdef CONFIG_HAS_PCI + pci_segments_init(); +#endif + +disable: + return; +} + +/* + * Local variables: + * mode: C + * c-file-style: "BSD" + * c-basic-offset: 4 + * tab-width: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/xen/arch/arm/setup.c b/xen/arch/arm/setup.c index 7968cee47d..2d7f1db44f 100644 --- a/xen/arch/arm/setup.c +++ b/xen/arch/arm/setup.c @@ -930,6 +930,8 @@ void __init start_xen(unsigned long boot_phys_offset, setup_virt_paging(); + pci_init(); + do_initcalls(); /* diff --git a/xen/include/asm-arm/device.h b/xen/include/asm-arm/device.h index ee7cff2d44..28f8049cfd 100644 --- a/xen/include/asm-arm/device.h +++ b/xen/include/asm-arm/device.h @@ -4,6 +4,7 @@ enum device_type { DEV_DT, + DEV_PCI, }; struct dev_archdata { @@ -25,15 +26,15 @@ typedef struct device device_t; #include <xen/device_tree.h> -/* TODO: Correctly implement dev_is_pci when PCI is supported on ARM */ -#define dev_is_pci(dev) ((void)(dev), 0) -#define dev_is_dt(dev) ((dev->type == DEV_DT) +#define dev_is_pci(dev) (dev->type == DEV_PCI) +#define dev_is_dt(dev) (dev->type == DEV_DT) enum device_class { DEVICE_SERIAL, DEVICE_IOMMU, DEVICE_GIC, + DEVICE_PCI, /* Use for error */ DEVICE_UNKNOWN, }; diff --git a/xen/include/asm-arm/pci.h b/xen/include/asm-arm/pci.h index de13359f65..94fd00360a 100644 --- a/xen/include/asm-arm/pci.h +++ b/xen/include/asm-arm/pci.h @@ -1,7 +1,98 @@ -#ifndef __X86_PCI_H__ -#define __X86_PCI_H__ +/* + * Copyright (C) 2020 Arm Ltd. + * + * Based on Linux drivers/pci/ecam.c + * Copyright 2016 Broadcom. + * + * Based on Linux drivers/pci/controller/pci-host-common.c + * Based on Linux drivers/pci/controller/pci-host-generic.c + * Copyright (C) 2014 ARM Limited Will Deacon <will.dea...@arm.com> + * + * 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/>. + */ +#ifndef __ARM_PCI_H__ +#define __ARM_PCI_H__ + +#include <xen/pci.h> +#include <xen/device_tree.h> +#include <asm/device.h> + +#ifdef CONFIG_ARM_PCI + +/* Arch pci dev struct */ struct arch_pci_dev { + struct device dev; +}; + +#define PRI_pci "%04x:%02x:%02x.%u" +#define pci_to_dev(pcidev) (&(pcidev)->arch.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; + struct pci_ecam_ops *ops; + void __iomem *win; +}; + +/* Forward declaration as pci_host_bridge and pci_ops depend on each other. */ +struct pci_host_bridge; + +struct pci_ops { + int (*read)(struct pci_host_bridge *bridge, + uint32_t sbdf, int where, int size, u32 *val); + int (*write)(struct pci_host_bridge *bridge, + uint32_t sbdf, int where, int size, u32 val); +}; + +/* + * 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 (*init)(struct pci_config_window *); +}; + +/* + * 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 */ + void *sysdata; /* Pointer to the config space window*/ + const struct pci_ops *ops; }; -#endif /* __X86_PCI_H__ */ +struct pci_host_bridge *pci_find_host_bridge(uint16_t segment, uint8_t bus); + +int pci_host_common_probe(struct dt_device_node *dev, + struct pci_ecam_ops *ops); + +void pci_init(void); + +#else /*!CONFIG_ARM_PCI*/ +struct arch_pci_dev { }; +static inline void pci_init(void) { } +#endif /*!CONFIG_ARM_PCI*/ +#endif /* __ARM_PCI_H__ */ -- 2.17.1