[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index] [UNIKRAFT PATCH RFCv2 09/27] 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 0000000..b97a580 --- /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
|
Lists.xenproject.org is hosted with RackSpace, monitoring our |