|
[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index] [Minios-devel] [UNIKRAFT PATCH RFC 09/23] plat/pci_ecam: Introduce pci ecam skeleton
This patch adds support for a generic PCI host controller. The controller
itself has no configuration registers and has its address spaces described
entirely by the device-tree. Currently, only ECAM is supported.
Signed-off-by: Jia He <justin.he@xxxxxxx>
---
plat/common/pci_ecam.c | 524 +++++++++++++++++++++++++++++++++++++++++
1 file changed, 524 insertions(+)
create mode 100644 plat/common/pci_ecam.c
diff --git a/plat/common/pci_ecam.c b/plat/common/pci_ecam.c
new file mode 100644
index 00000000..b97a5800
--- /dev/null
+++ b/plat/common/pci_ecam.c
@@ -0,0 +1,524 @@
+/* SPDX-License-Identifier: BSD-3-Clause */
+/*
+ * Authors: Jia He <justin.he@xxxxxxx>
+ *
+ * Copyright (c) 2018, Arm Ltd. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the copyright holder nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ *
+ * THIS HEADER MAY NOT BE EXTRACTED OR MODIFIED IN ANY WAY.
+ */
+
+#include <stdbool.h>
+#include <pci/pci_ecam.h>
+#include <platform_bus.h>
+#include <uk/list.h>
+#include <uk/alloc.h>
+#include <uk/print.h>
+#include <kvm/config.h>
+#include <uk/plat/common/cpu.h>
+#include <libfdt_env.h>
+#include <ofw/fdt.h>
+#include <libfdt.h>
+
+static struct uk_alloc *a;
+static struct pci_ecam_ops pci_generic_ecam_ops;
+static const struct device_match_table gen_pci_match_table[];
+
+struct pci_config_window pcw = {
+ .ops = &pci_generic_ecam_ops
+};
+
+struct pci_range_parser {
+ int offset; /* in fdt */
+ const fdt32_t *range; /* start offset in offset */
+ const fdt32_t *end; /* end offset in offset */
+ int np; /* number of property */
+ int pna; /* parent number of address cell */
+};
+
+struct pci_range {
+ __u32 pci_space;
+ __u64 pci_addr;
+ __u64 cpu_addr;
+ __u64 size;
+ __u32 flags;
+};
+
+#define fdt_start (_libkvmplat_cfg.dtb)
+
+static int gen_pci_fdt; // start offset of pci-ecam in fdt
+/*
+ * Function to implement the pci_ops ->map_bus method
+ */
+static void *pci_ecam_map_bus(__u8 bus, __u8 devfn, int where)
+{
+ __u8 bus_shift = pcw.ops->bus_shift;
+ __u8 devfn_shift = bus_shift - 8;
+ __u8 bus_start = pcw.br.bus_start;
+ __u8 bus_end = pcw.br.bus_end;
+ void *base = (void *)pcw.config_base;
+
+ if (bus < bus_start || bus > bus_end)
+ return NULL;
+
+ return base + (bus << bus_shift) + (devfn << devfn_shift) + where;
+}
+
+int pci_generic_config_read(__u8 bus, __u8 devfn,
+ int where, int size, __u32 *val)
+{
+ void *addr;
+
+ /* add rmb before io read */
+ rmb();
+ addr = pci_ecam_map_bus(bus, devfn, where);
+ if (!addr) {
+ *val = ~0;
+ return -1;
+ }
+
+ if (size == 1)
+ *val = ioreg_read8(addr);
+ else if (size == 2)
+ *val = ioreg_read16(addr);
+ else if (size == 4)
+ *val = ioreg_read32(addr);
+ else
+ uk_pr_err("not support size pci config read\n");
+
+ return 0;
+}
+
+int pci_generic_config_write(__u8 bus, __u8 devfn,
+ int where, int size, __u32 val)
+{
+ void *addr;
+ __u32 mask, tmp;
+
+ addr = pci_ecam_map_bus(bus, devfn, where);
+ if (!addr)
+ return -1;
+
+ if (size == 1)
+ ioreg_write8(addr, val);
+ else if (size == 2)
+ ioreg_write16(addr, val);
+ else if (size == 4)
+ ioreg_write32(addr, val);
+ else
+ uk_pr_err("not support size pci config write\n");
+
+ /* add wmb after io write */
+ wmb();
+
+ return 0;
+}
+
+/* ECAM cfg */
+static struct pci_ecam_ops pci_generic_ecam_ops = {
+ .bus_shift = 20
+};
+
+static unsigned int pci_get_flags(const fdt32_t *addr)
+{
+ unsigned int flags = 0;
+ __u32 w = fdt32_to_cpu(*addr);
+
+ /* only IORESOURCE_IO is supported currently */
+ switch ((w >> 24) & 0x03) {
+ case 0x01:
+ flags |= IORESOURCE_IO;
+ break;
+ case 0x02: /* 32 bits */
+ case 0x03: /* 64 bits */
+ default:
+ uk_pr_err("only supported pci ioresource flags\n");
+ break;
+ }
+
+ return flags;
+}
+
+static int gen_pci_parser_range(struct pci_range_parser *parser, int offset)
+{
+ const int na = 3, ns = 2;
+ int rlen;
+
+ parser->pna = fdt_address_cells(fdt_start, offset);
+ parser->np = parser->pna + na + ns;
+ parser->range = fdt_getprop(fdt_start, offset, "ranges", &rlen);
+ if (parser->range == NULL)
+ return -ENOENT;
+
+ parser->end = parser->range + rlen / sizeof(fdt32_t);
+
+ return 0;
+}
+
+struct pci_range *pci_range_parser_one(struct pci_range_parser *parser,
+ struct pci_range *range, int offset)
+{
+ const int na = 3, ns = 2; /* address and size */
+
+ if (!range)
+ return NULL;
+
+ if (!parser->range || parser->range + parser->np > parser->end)
+ return NULL;
+
+ range->pci_space = fdt32_to_cpu(*parser->range);
+ range->flags = pci_get_flags(parser->range);
+ range->pci_addr = fdt_reg_read_number(parser->range + 1, ns);
+ range->cpu_addr = fdt_translate_address_by_ranges(fdt_start, offset,
+ parser->range + na);
+ range->size = fdt_reg_read_number(parser->range + parser->pna + na, ns);
+
+ parser->range += parser->np;
+
+ return range;
+}
+
+static int irq_find_parent(const void *fdt, int child)
+{
+ int p;
+ int plen;
+ fdt32_t *prop;
+ fdt32_t parent;
+
+ do {
+ prop = fdt_getprop(fdt, child, "interrupt-parent", &plen);
+ if (prop == NULL) {
+ p = fdt_parent_offset(fdt, child);
+ } else {
+ parent = fdt32_to_cpu(prop[0]);
+ p = fdt_node_offset_by_phandle(fdt, parent);
+ }
+ child = p;
+ } while (p >= 0 && fdt_getprop(fdt, p, "#interrupt-cells", NULL) ==
NULL);
+
+ return p;
+}
+
+int gen_pci_irq_parse(const fdt32_t *addr, struct fdt_phandle_args *out_irq)
+{
+ int ipar, tnode, old = 0, newpar = 0;
+ fdt32_t initial_match_array[16];
+ const fdt32_t *match_array = initial_match_array;
+ const fdt32_t *tmp, *imap, *imask;
+ const fdt32_t dummy_imask[] = { [0 ... 16] = cpu_to_fdt32(~0) };
+ int intsize, newintsize;
+ int addrsize, newaddrsize = 0;
+ int imaplen, match, i, rc = -EINVAL;
+ int plen;
+ int *prop;
+
+ ipar = gen_pci_fdt;
+
+ /* First get the #interrupt-cells property of the current cursor
+ * that tells us how to interpret the passed-in intspec. If there
+ * is none, we are nice and just walk up the tree
+ */
+ do {
+ prop = fdt_getprop(fdt_start, ipar, "#interrupt-cells", &plen);
+ if (prop != NULL)
+ break;
+ ipar = irq_find_parent(fdt_start, ipar);
+ } while (ipar >= 0);
+
+ if (ipar < 0) {
+ uk_pr_info(" -> no parent found !\n");
+ goto fail;
+ }
+
+ intsize = fdt32_to_cpu(prop[0]);
+ uk_pr_info("irq_parse_raw: ipar=%p, size=%d\n", ipar, intsize);
+
+ if (out_irq->args_count != intsize)
+ goto fail;
+
+ /* Look for this #address-cells. We have to implement the old linux
+ * trick of looking for the parent here as some device-trees rely on it
+ */
+ old = ipar;
+ do {
+ tmp = fdt_getprop(fdt_start, old, "#address-cells", NULL);
+ tnode = fdt_parent_offset(fdt_start, old);
+ old = tnode;
+ } while (old >= 0 && tmp == NULL);
+
+ old = 0;
+ addrsize = (tmp == NULL) ? 2 : fdt32_to_cpu(*tmp);
+ uk_pr_info(" -> addrsize=%d\n", addrsize);
+
+ /* Precalculate the match array - this simplifies match loop */
+ for (i = 0; i < addrsize; i++)
+ initial_match_array[i] = addr ? addr[i] : 0;
+ for (i = 0; i < intsize; i++)
+ initial_match_array[addrsize + i] =
cpu_to_fdt32(out_irq->args[i]);
+
+ /* Now start the actual "proper" walk of the interrupt tree */
+ while (ipar >= 0) {
+ /* Now check if cursor is an interrupt-controller and if it is
+ * then we are done
+ */
+ if (fdt_prop_read_bool(fdt_start, ipar,
"interrupt-controller")) {
+ uk_pr_info(" -> got it !\n");
+ return 0;
+ }
+
+ /*
+ * interrupt-map parsing does not work without a reg
+ * property when #address-cells != 0
+ */
+ if (addrsize && !addr) {
+ uk_pr_info(" -> no reg passed in when needed !\n");
+ goto fail;
+ }
+
+ /* Now look for an interrupt-map */
+ imap = fdt_getprop(fdt_start, ipar, "interrupt-map", &imaplen);
+ /* No interrupt map, check for an interrupt parent */
+ if (imap == NULL) {
+ uk_pr_info(" -> no map, getting parent\n");
+ newpar = irq_find_parent(fdt_start, ipar);
+ goto skiplevel;
+ }
+ imaplen /= sizeof(__u32);
+
+ /* Look for a mask */
+ imask = fdt_getprop(fdt_start, ipar, "interrupt-map-mask",
NULL);
+ if (!imask)
+ imask = dummy_imask;
+
+ /* Parse interrupt-map */
+ match = 0;
+ while (imaplen > (addrsize + intsize + 1) && !match) {
+ /* Compare specifiers */
+ match = 1;
+ for (i = 0; i < (addrsize + intsize); i++, imaplen--)
+ match &= !((match_array[i] ^ *imap++) &
imask[i]);
+
+ uk_pr_info(" -> match=%d (imaplen=%d)\n", match,
imaplen);
+
+ /* Get the interrupt parent */
+ newpar = fdt_node_offset_by_phandle(fdt_start,
fdt32_to_cpu(*imap));
+ imap++;
+ --imaplen;
+
+ /* Check if not found */
+ if (newpar < 0) {
+ uk_pr_info(" -> imap parent not found !\n");
+ goto fail;
+ }
+
+ /* Get #interrupt-cells and #address-cells of new
+ * parent
+ */
+ prop = fdt_getprop(fdt_start, newpar,
"#interrupt-cells",
+ &plen);
+ if (prop == NULL) {
+ uk_pr_info(" -> parent lacks
#interrupt-cells!\n");
+ goto fail;
+ }
+ newintsize = fdt32_to_cpu(prop[0]);
+
+ prop = fdt_getprop(fdt_start, newpar, "#address-cells",
+ &plen);
+ if (prop == NULL) {
+ uk_pr_info(" -> parent lacks
#address-cells!\n");
+ goto fail;
+ }
+ newaddrsize = fdt32_to_cpu(prop[0]);
+
+ uk_pr_debug(" -> newintsize=%d, newaddrsize=%d\n",
+ newintsize, newaddrsize);
+
+ /* Check for malformed properties */
+ if ((newaddrsize + newintsize > 16)
+ || (imaplen < (newaddrsize + newintsize))) {
+ rc = -EFAULT;
+ goto fail;
+ }
+
+ imap += newaddrsize + newintsize;
+ imaplen -= newaddrsize + newintsize;
+
+ uk_pr_info(" -> imaplen=%d\n", imaplen);
+ }
+ if (!match)
+ goto fail;
+
+ /*
+ * Successfully parsed an interrrupt-map translation; copy new
+ * interrupt specifier into the out_irq structure
+ */
+ match_array = imap - newaddrsize - newintsize;
+ for (i = 0; i < newintsize; i++)
+ out_irq->args[i] = fdt32_to_cpu(*(imap - newintsize +
i));
+ out_irq->args_count = intsize = newintsize;
+ addrsize = newaddrsize;
+
+skiplevel:
+ /* Iterate again with new parent */
+ out_irq->np = newpar;
+ uk_pr_info(" -> new parent: %pOF\n", newpar);
+
+ ipar = newpar;
+ newpar = 0;
+ }
+
+ rc = -ENOENT; /* No interrupt-map found */
+
+fail:
+ return rc;
+}
+
+
+static int gen_pci_probe(struct pf_device *pfdev)
+{
+ const fdt32_t *prop;
+ int prop_len;
+ __u64 reg_base;
+ __u64 reg_size;
+ int err;
+ struct pci_range range;
+ struct pci_range_parser parser;
+ struct fdt_phandle_args out_irq;
+ fdt32_t laddr[3];
+
+ /* 1.Get the base and size of config space */
+ gen_pci_fdt = fdt_node_offset_by_compatible(fdt_start, -1,
+
gen_pci_match_table[0].compatible);
+ if (gen_pci_fdt < 0) {
+ uk_pr_info("Error in searching pci controller in fdt\n");
+ goto error_exit;
+ } else {
+ prop = fdt_getprop(fdt_start, gen_pci_fdt, "reg", &prop_len);
+ if (!prop) {
+ uk_pr_err("reg of device not found in fdt\n");
+ goto error_exit;
+ }
+
+ /* Get the base addr and the size */
+ fdt_get_address(fdt_start, gen_pci_fdt, 0,
+ ®_base, ®_size);
+ reg_base = fdt32_to_cpu(prop[0]);
+ reg_base = reg_base << 32 | fdt32_to_cpu(prop[1]);
+ reg_size = fdt32_to_cpu(prop[2]);
+ reg_size = reg_size << 32 | fdt32_to_cpu(prop[3]);
+ }
+
+ pcw.config_base = reg_base;
+ pcw.config_space_size = reg_size;
+ uk_pr_info("generic pci config base(0x%llx),size(0x%llx)\n",
+ reg_base, reg_size);
+
+ /* 2.Get the bus range of pci controller */
+ prop = fdt_getprop(fdt_start, gen_pci_fdt, "bus-range", &prop_len);
+ if (!prop) {
+ uk_pr_err("bus-range of device not found in fdt\n");
+ goto error_exit;
+ }
+
+ pcw.br.bus_start = fdt32_to_cpu(prop[0]);
+ pcw.br.bus_end = fdt32_to_cpu(prop[1]);
+ uk_pr_info("generic pci bus start(%d),end(%d)\n",
+ pcw.br.bus_start, pcw.br.bus_end);
+
+ if (pcw.br.bus_start > pcw.br.bus_end) {
+ uk_pr_err("bus-range detect error in fdt\n");
+ goto error_exit;
+ }
+
+ /* 3.Get the pci addr base and limit size for pci devices */
+ err = gen_pci_parser_range(&parser, gen_pci_fdt);
+ do {
+ pci_range_parser_one(&parser, &range, gen_pci_fdt);
+ if (range.flags == IORESOURCE_IO) {
+ pcw.pci_device_base = range.cpu_addr;
+ pcw.pci_device_limit = range.size;
+ break;
+ }
+ parser.range += parser.np;
+ } while (parser.range + parser.np <= parser.end);
+ uk_pr_info("generic pci range base(0x%llx),size(0x%llx)\n",
+ pcw.pci_device_base, pcw.pci_device_limit);
+
+ return 0;
+
+error_exit:
+ return -ENODEV;
+}
+
+static int gen_pci_drv_init(struct uk_alloc *drv_allocator)
+{
+ /* driver initialization */
+ if (!drv_allocator)
+ return -EINVAL;
+
+ a = drv_allocator;
+
+ return 0;
+}
+
+static int gen_pci_add_dev(struct pf_device *pfdev __unused)
+{
+ return 0;
+}
+
+static const struct device_match_table gen_pci_match_table[];
+
+static struct pf_device_id *gen_pci_id_match_compatible(const char *compatible)
+{
+ int i;
+
+ for (int i = 0; gen_pci_match_table[i].compatible != NULL; i++)
+ if (strcmp(gen_pci_match_table[i].compatible, compatible) == 0)
+ return gen_pci_match_table[i].id;
+
+ return NULL;
+}
+
+static const struct pf_device_id gen_pci_ids = {
+ .device_id = GEN_PCI_ID
+};
+
+struct pf_driver gen_pci_driver = {
+ .device_ids = &gen_pci_ids,
+ .init = gen_pci_drv_init,
+ .probe = gen_pci_probe,
+ .add_dev = gen_pci_add_dev,
+ .match = gen_pci_id_match_compatible
+};
+
+static const struct device_match_table gen_pci_match_table[] = {
+ { .compatible = "pci-host-ecam-generic",
+ .id = &gen_pci_ids },
+ {NULL}
+};
+
+PF_REGISTER_DRIVER(&gen_pci_driver);
--
2.17.1
_______________________________________________
Minios-devel mailing list
Minios-devel@xxxxxxxxxxxxxxxxxxxx
https://lists.xenproject.org/mailman/listinfo/minios-devel
|
![]() |
Lists.xenproject.org is hosted with RackSpace, monitoring our |