From patchwork Fri Jul 29 11:59:10 2016 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Alexander Gordeev X-Patchwork-Id: 9252457 Return-Path: Received: from mail.wl.linuxfoundation.org (pdx-wl-mail.web.codeaurora.org [172.30.200.125]) by pdx-korg-patchwork.web.codeaurora.org (Postfix) with ESMTP id ECEAA60757 for ; Fri, 29 Jul 2016 11:59:25 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id CFB522621B for ; Fri, 29 Jul 2016 11:59:25 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id C13FF27F9A; Fri, 29 Jul 2016 11:59:25 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on pdx-wl-mail.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-6.9 required=2.0 tests=BAYES_00,RCVD_IN_DNSWL_HI autolearn=ham version=3.3.1 Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id CAB222621B for ; Fri, 29 Jul 2016 11:59:24 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1751261AbcG2L7Q (ORCPT ); Fri, 29 Jul 2016 07:59:16 -0400 Received: from mx1.redhat.com ([209.132.183.28]:38276 "EHLO mx1.redhat.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1750948AbcG2L7O (ORCPT ); Fri, 29 Jul 2016 07:59:14 -0400 Received: from int-mx13.intmail.prod.int.phx2.redhat.com (int-mx13.intmail.prod.int.phx2.redhat.com [10.5.11.26]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by mx1.redhat.com (Postfix) with ESMTPS id C15AFC057FA6 for ; Fri, 29 Jul 2016 11:59:13 +0000 (UTC) Received: from dhcp-27-118.brq.redhat.com (dhcp-27-122.brq.redhat.com [10.34.27.122]) by int-mx13.intmail.prod.int.phx2.redhat.com (8.14.4/8.14.4) with ESMTP id u6TBxAWD020681 (version=TLSv1/SSLv3 cipher=DHE-RSA-AES256-GCM-SHA384 bits=256 verify=NO); Fri, 29 Jul 2016 07:59:12 -0400 Date: Fri, 29 Jul 2016 13:59:10 +0200 From: Alexander Gordeev To: Andrew Jones Cc: kvm@vger.kernel.org, Thomas Huth Subject: [kvm-unit-tests PATCH v6 09/12] pci: Add generic ECAM host support Message-ID: <20160729115909.GA31064@dhcp-27-118.brq.redhat.com> References: <20160721093250.ntamtdousvfdgp6t@kamzik.localdomain> MIME-Version: 1.0 Content-Disposition: inline In-Reply-To: <20160721093250.ntamtdousvfdgp6t@kamzik.localdomain> User-Agent: Mutt/1.5.21 (2010-09-15) X-Scanned-By: MIMEDefang 2.68 on 10.5.11.26 X-Greylist: Sender IP whitelisted, not delayed by milter-greylist-4.5.16 (mx1.redhat.com [10.5.110.32]); Fri, 29 Jul 2016 11:59:13 +0000 (UTC) Sender: kvm-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: kvm@vger.kernel.org X-Virus-Scanned: ClamAV using ClamSMTP Unlike x86, other architectures using generic ECAM PCI host do not have a luxury of PCI bus initialized by a BIOS and ready to use at start. Thus, we need allocate and assign resources to all devices, much like an architecture's firmware would do. There is no any sort of resource management for memory and io spaces, since only ones-per-BAR allocations are expected and no deallocations at all. Cc: Thomas Huth Cc: Andrew Jones Reviewed-by: Andrew Jones Signed-off-by: Alexander Gordeev --- lib/asm-generic/pci-host-bridge.h | 26 ++++ lib/pci-host-generic.c | 295 ++++++++++++++++++++++++++++++++++++++ lib/pci-host-generic.h | 46 ++++++ lib/pci.c | 4 +- lib/pci.h | 3 + 5 files changed, 372 insertions(+), 2 deletions(-) create mode 100644 lib/asm-generic/pci-host-bridge.h create mode 100644 lib/pci-host-generic.c create mode 100644 lib/pci-host-generic.h diff --git a/lib/asm-generic/pci-host-bridge.h b/lib/asm-generic/pci-host-bridge.h new file mode 100644 index 000000000000..ac32b85198e9 --- /dev/null +++ b/lib/asm-generic/pci-host-bridge.h @@ -0,0 +1,26 @@ +#ifndef _ASM_PCI_HOST_BRIDGE_H_ +#define _ASM_PCI_HOST_BRIDGE_H_ +/* + * Copyright (C) 2016, Red Hat Inc, Alexander Gordeev + * + * This work is licensed under the terms of the GNU LGPL, version 2. + */ +#include "libcflat.h" + +phys_addr_t pci_host_bridge_get_paddr(uint64_t addr); + +static inline +phys_addr_t pci_translate_addr(pcidevaddr_t dev __unused, uint64_t addr) +{ + /* + * Assume we only have single PCI host bridge in a system. + */ + return pci_host_bridge_get_paddr(addr); +} + +uint8_t pci_config_readb(pcidevaddr_t dev, uint8_t reg); +uint16_t pci_config_readw(pcidevaddr_t dev, uint8_t reg); +uint32_t pci_config_readl(pcidevaddr_t dev, uint8_t reg); +void pci_config_writel(pcidevaddr_t dev, uint8_t reg, uint32_t val); + +#endif diff --git a/lib/pci-host-generic.c b/lib/pci-host-generic.c new file mode 100644 index 000000000000..4a2716a33150 --- /dev/null +++ b/lib/pci-host-generic.c @@ -0,0 +1,295 @@ +/* + * Generic PCI host controller as described in PCI Bus Binding to Open Firmware + * + * Copyright (C) 2016, Red Hat Inc, Alexander Gordeev + * + * This work is licensed under the terms of the GNU LGPL, version 2. + */ +#include "libcflat.h" +#include "devicetree.h" +#include "alloc.h" +#include "pci.h" +#include "asm/pci.h" +#include "asm/io.h" +#include "pci-host-generic.h" +#include + +static struct pci_host_bridge *pci_host_bridge; + +static int of_flags_to_pci_type(u32 of_flags) +{ + static int type_map[] = { + [1] = PCI_BASE_ADDRESS_SPACE_IO, + [2] = PCI_BASE_ADDRESS_MEM_TYPE_32, + [3] = PCI_BASE_ADDRESS_MEM_TYPE_64 + }; + int idx = (of_flags >> 24) & 0x03; + int res; + + assert(idx > 0); + res = type_map[idx]; + + if (of_flags & 0x40000000) + res |= PCI_BASE_ADDRESS_MEM_PREFETCH; + + return res; +} + +static int pci_bar_type(u32 bar) +{ + if (bar & PCI_BASE_ADDRESS_SPACE) + return PCI_BASE_ADDRESS_SPACE_IO; + else + return bar & (PCI_BASE_ADDRESS_MEM_TYPE_MASK | + PCI_BASE_ADDRESS_MEM_PREFETCH); +} + +/* + * Probe DT for a generic PCI host controller + * See kernel Documentation/devicetree/bindings/pci/host-generic-pci.txt + * and function gen_pci_probe() in drivers/pci/host/pci-host-generic.c + */ +static struct pci_host_bridge *pci_dt_probe(void) +{ + struct pci_host_bridge *host; + const void *fdt = dt_fdt(); + const struct fdt_property *prop; + struct dt_pbus_reg base; + struct dt_device dt_dev; + struct dt_bus dt_bus; + struct pci_addr_space *as; + fdt32_t *data; + u32 bus, bus_max; + u32 nac, nsc, nac_root, nsc_root; + int nr_range_cells, nr_addr_spaces; + int ret, node, len, i; + + if (!dt_available()) { + printf("No device tree found\n"); + return NULL; + } + + dt_bus_init_defaults(&dt_bus); + dt_device_init(&dt_dev, &dt_bus, NULL); + + node = fdt_path_offset(fdt, "/"); + assert(node >= 0); + + ret = dt_get_nr_cells(node, &nac_root, &nsc_root); + assert(ret == 0); + assert(nac_root == 1 || nac_root == 2); + + node = fdt_node_offset_by_compatible(fdt, node, + "pci-host-ecam-generic"); + if (node == -FDT_ERR_NOTFOUND) { + printf("No PCIe ECAM compatible controller found\n"); + return NULL; + } + assert(node >= 0); + + prop = fdt_get_property(fdt, node, "device_type", &len); + assert(prop && len == 4 && !strcmp((char *)prop->data, "pci")); + + dt_device_bind_node(&dt_dev, node); + ret = dt_pbus_get_base(&dt_dev, &base); + assert(ret == 0); + + prop = fdt_get_property(fdt, node, "bus-range", &len); + if (prop == NULL) { + assert(len == -FDT_ERR_NOTFOUND); + bus = 0x00; + bus_max = 0xff; + } else { + data = (fdt32_t *)prop->data; + bus = fdt32_to_cpu(data[0]); + bus_max = fdt32_to_cpu(data[1]); + assert(bus <= bus_max); + } + assert(bus_max < base.size / (1 << PCI_ECAM_BUS_SHIFT)); + + ret = dt_get_nr_cells(node, &nac, &nsc); + assert(ret == 0); + assert(nac == 3 && nsc == 2); + + prop = fdt_get_property(fdt, node, "ranges", &len); + assert(prop != NULL); + + nr_range_cells = nac + nsc + nac_root; + nr_addr_spaces = (len / 4) / nr_range_cells; + assert(nr_addr_spaces); + + host = malloc(sizeof(*host) + + sizeof(host->addr_space[0]) * nr_addr_spaces); + assert(host != NULL); + + host->start = base.addr; + host->size = base.size; + host->bus = bus; + host->bus_max = bus_max; + host->nr_addr_spaces = nr_addr_spaces; + + data = (fdt32_t *)prop->data; + as = &host->addr_space[0]; + + for (i = 0; i < nr_addr_spaces; i++) { + /* + * The PCI binding encodes the PCI address with three + * cells as follows: + * + * phys.hi cell: npt000ss bbbbbbbb dddddfff rrrrrrrr + * phys.mid cell: hhhhhhhh hhhhhhhh hhhhhhhh hhhhhhhh + * phys.lo cell: llllllll llllllll llllllll llllllll + * + * PCI device bus address and flags are encoded into phys.high + * PCI 64 bit address is encoded into phys.mid and phys.low + */ + as->type = of_flags_to_pci_type(fdt32_to_cpu(data[0])); + as->pci_start = ((u64)fdt32_to_cpu(data[1]) << 32) | + fdt32_to_cpu(data[2]); + + if (nr_range_cells == 6) { + as->start = fdt32_to_cpu(data[3]); + as->size = ((u64)fdt32_to_cpu(data[4]) << 32) | + fdt32_to_cpu(data[5]); + } else { + as->start = ((u64)fdt32_to_cpu(data[3]) << 32) | + fdt32_to_cpu(data[4]); + as->size = ((u64)fdt32_to_cpu(data[5]) << 32) | + fdt32_to_cpu(data[6]); + } + + data += nr_range_cells; + as++; + } + + return host; +} + +static u64 pci_alloc_resource(u32 bar, u64 size) +{ + struct pci_host_bridge *host = pci_host_bridge; + struct pci_addr_space *as = &host->addr_space[0]; + phys_addr_t addr; + u32 mask; + int i; + + for (i = 0; i < host->nr_addr_spaces; i++) { + if (as->type == pci_bar_type(bar)) + break; + as++; + } + if (i >= host->nr_addr_spaces) { + printf("No PCI resource found for a device\n"); + assert(0); + } + + mask = pci_bar_mask(bar); + size = ALIGN(size, ~mask + 1); + assert(as->allocated + size <= as->size); + + addr = as->pci_start + as->allocated; + as->allocated += size; + + return addr; +} + +bool pci_probe(void) +{ + pcidevaddr_t dev; + u8 header; + u32 cmd; + int i; + + assert(!pci_host_bridge); + pci_host_bridge = pci_dt_probe(); + if (!pci_host_bridge) + return false; + + for (dev = 0; dev < 256; dev++) { + if (!pci_dev_exists(dev)) + continue; + + /* We are only interested in normal PCI devices */ + header = pci_config_readb(dev, PCI_HEADER_TYPE); + if ((header & PCI_HEADER_TYPE_MASK) != PCI_HEADER_TYPE_NORMAL) + continue; + + cmd = PCI_COMMAND_SERR; + + for (i = 0; i < 6; i++) { + u64 addr, size; + u32 bar; + + size = pci_bar_size(dev, i); + if (!size) + continue; + + bar = pci_bar_get(dev, i); + addr = pci_alloc_resource(bar, size); + pci_bar_set_addr(dev, i, addr); + + if (pci_bar_is_memory(dev, i)) + cmd |= PCI_COMMAND_MEMORY; + else + cmd |= PCI_COMMAND_IO; + + if (pci_bar_is64(dev, i)) + i++; + } + + pci_config_writel(dev, PCI_COMMAND, cmd); + } + + return true; +} + +/* + * This function is to be called from pci_translate_addr() to provide + * mapping between this host bridge's PCI busses address and CPU physical + * address. + */ +phys_addr_t pci_host_bridge_get_paddr(u64 pci_addr) +{ + struct pci_host_bridge *host = pci_host_bridge; + struct pci_addr_space *as = &host->addr_space[0]; + int i; + + for (i = 0; i < host->nr_addr_spaces; i++) { + if (pci_addr >= as->pci_start && + pci_addr < as->pci_start + as->size) + return as->start + (pci_addr - as->pci_start); + as++; + } + + return 0; +} + +static void __iomem *pci_get_dev_conf(struct pci_host_bridge *host, int devfn) +{ + return (void __iomem *)(unsigned long) + host->start + (devfn << PCI_ECAM_DEVFN_SHIFT); +} + +u8 pci_config_readb(pcidevaddr_t dev, u8 off) +{ + void __iomem *conf = pci_get_dev_conf(pci_host_bridge, dev); + return readb(conf + off); +} + +u16 pci_config_readw(pcidevaddr_t dev, u8 off) +{ + void __iomem *conf = pci_get_dev_conf(pci_host_bridge, dev); + return readw(conf + off); +} + +u32 pci_config_readl(pcidevaddr_t dev, u8 off) +{ + void __iomem *conf = pci_get_dev_conf(pci_host_bridge, dev); + return readl(conf + off); +} + +void pci_config_writel(pcidevaddr_t dev, u8 off, u32 val) +{ + void __iomem *conf = pci_get_dev_conf(pci_host_bridge, dev); + writel(val, conf + off); +} diff --git a/lib/pci-host-generic.h b/lib/pci-host-generic.h new file mode 100644 index 000000000000..fd30e7c74ed8 --- /dev/null +++ b/lib/pci-host-generic.h @@ -0,0 +1,46 @@ +#ifndef PCI_HOST_GENERIC_H +#define PCI_HOST_GENERIC_H +/* + * PCI host bridge supporting structures and constants + * + * Copyright (C) 2016, Red Hat Inc, Alexander Gordeev + * + * This work is licensed under the terms of the GNU LGPL, version 2. + */ +#include "libcflat.h" + +struct pci_addr_space { + phys_addr_t pci_start; + phys_addr_t start; + phys_addr_t size; + phys_addr_t allocated; + int type; +}; + +struct pci_host_bridge { + phys_addr_t start; + phys_addr_t size; + int bus; + int bus_max; + int nr_addr_spaces; + struct pci_addr_space addr_space[]; +}; + +/* + * The following constants are derived from Linux, see this source: + * + * drivers/pci/host/pci-host-generic.c + * struct gen_pci_cfg_bus_ops::bus_shift + * int gen_pci_parse_map_cfg_windows(struct gen_pci *pci) + * + * Documentation/devicetree/bindings/pci/host-generic-pci.txt describes + * ECAM Configuration Space is be memory-mapped by concatenating the various + * components to form an offset: + * + * cfg_offset(bus, device, function, register) = + * bus << 20 | device << 15 | function << 12 | register + */ +#define PCI_ECAM_BUS_SHIFT 20 +#define PCI_ECAM_DEVFN_SHIFT 12 + +#endif diff --git a/lib/pci.c b/lib/pci.c index 4271ae5b528e..4bdfd803fd80 100644 --- a/lib/pci.c +++ b/lib/pci.c @@ -27,13 +27,13 @@ pcidevaddr_t pci_find_dev(uint16_t vendor_id, uint16_t device_id) return PCIDEVADDR_INVALID; } -static uint32_t pci_bar_mask(uint32_t bar) +uint32_t pci_bar_mask(uint32_t bar) { return (bar & PCI_BASE_ADDRESS_SPACE_IO) ? PCI_BASE_ADDRESS_IO_MASK : PCI_BASE_ADDRESS_MEM_MASK; } -static uint32_t pci_bar_get(pcidevaddr_t dev, int bar_num) +uint32_t pci_bar_get(pcidevaddr_t dev, int bar_num) { return pci_config_readl(dev, PCI_BASE_ADDRESS_0 + bar_num * 4); } diff --git a/lib/pci.h b/lib/pci.h index 5038a0c8cbb4..d0585a0451c2 100644 --- a/lib/pci.h +++ b/lib/pci.h @@ -15,6 +15,7 @@ enum { PCIDEVADDR_INVALID = 0xffff, }; +bool pci_probe(void); void pci_print(void); bool pci_dev_exists(pcidevaddr_t dev); pcidevaddr_t pci_find_dev(uint16_t vendor_id, uint16_t device_id); @@ -34,6 +35,8 @@ pcidevaddr_t pci_find_dev(uint16_t vendor_id, uint16_t device_id); phys_addr_t pci_bar_get_addr(pcidevaddr_t dev, int bar_num); void pci_bar_set_addr(pcidevaddr_t dev, int bar_num, phys_addr_t addr); phys_addr_t pci_bar_size(pcidevaddr_t dev, int bar_num); +uint32_t pci_bar_get(pcidevaddr_t dev, int bar_num); +uint32_t pci_bar_mask(uint32_t bar); bool pci_bar_is64(pcidevaddr_t dev, int bar_num); bool pci_bar_is_memory(pcidevaddr_t dev, int bar_num); bool pci_bar_is_valid(pcidevaddr_t dev, int bar_num);