Page Menu
Home
FreeBSD
Search
Configure Global Search
Log In
Files
F166610513
D54745.id.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Flag For Later
Award Token
Size
61 KB
Referenced Files
None
Subscribers
None
D54745.id.diff
View Options
diff --git a/sys/powerpc/powernv/opal.h b/sys/powerpc/powernv/opal.h
--- a/sys/powerpc/powernv/opal.h
+++ b/sys/powerpc/powernv/opal.h
@@ -203,6 +203,8 @@
#define OPAL_XIVE_EQ_ALWAYS_NOTIFY 0x00000002
#define OPAL_XIVE_EQ_ESCALATE 0x00000004
+#define OPAL_IODA_INVALID_PE 0xffffffff
+
struct opal_msg {
uint32_t msg_type;
uint32_t reserved;
diff --git a/sys/powerpc/powernv/opal_iommu.h b/sys/powerpc/powernv/opal_iommu.h
new file mode 100644
--- /dev/null
+++ b/sys/powerpc/powernv/opal_iommu.h
@@ -0,0 +1,42 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2025-2026 Raptor Engineering, LLC
+ * 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 unmodified, 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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.
+ */
+
+#ifndef _POWERNV_IODA_IOMMU_H_
+#define _POWERNV_IODA_IOMMU_H_
+
+#include <sys/types.h>
+#include <sys/bus_dma.h>
+
+int powernv_iommu_set_dma_tag(device_t dev, device_t child, bus_dma_tag_t tag);
+int powernv_iommu_map(device_t dev, bus_dma_segment_t *segs, int *nsegs,
+ bus_addr_t min, bus_addr_t max, bus_size_t alignment, bus_addr_t boundary,
+ void *cookie);
+int powernv_iommu_unmap(device_t dev, bus_dma_segment_t *segs, int nsegs,
+ void *cookie);
+
+#endif
diff --git a/sys/powerpc/powernv/opal_pci.h b/sys/powerpc/powernv/opal_pci.h
new file mode 100644
--- /dev/null
+++ b/sys/powerpc/powernv/opal_pci.h
@@ -0,0 +1,179 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2025-2026 Raptor Engineering, LLC
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 TOOLS GMBH 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.
+ */
+
+#ifndef _POWERNV_OPAL_PCI_H_
+#define _POWERNV_OPAL_PCI_H_
+
+#include <sys/rman.h>
+#include <sys/vmem.h>
+
+#include <dev/ofw/ofwpci.h>
+
+/*
+ * IODA firmware and hardware both expect a 64k page size, even if the kernel
+ * notionally uses a lower page size (e.g. 4k) for other operations.
+ */
+#define POWERNV_FW_PAGE_SHIFT 16
+#define POWERNV_INVALID_DEVFN 0xffffffff
+
+/*
+ * The content of the TVT itself is read by hardware (cached into the PHB);
+ * all other fields are used by the kernel. Note that the TVT contents
+ * are interpreted differently based on how the PHB is set up; this is why
+ * we need to keep a local struct for each PHB with the associated configuration
+ * values!
+ */
+struct powernv_ioda_tvt {
+ uint64_t tce_size;
+ uint64_t tce_tbl_size;
+ uint64_t tvt_entry_count;
+
+ int page_shift;
+
+ /* contiguous memory read directly by hardware after TCE invalidation */
+ uint64_t *tvt_base;
+};
+
+/* Delayed Resource Configuration (DRC) list */
+typedef struct opalpci_phb_drc_list {
+ device_t bus;
+ device_t child;
+ int type;
+ int rid;
+ rman_res_t start;
+ rman_res_t end;
+ rman_res_t count;
+ u_int flags;
+} opalpci_phb_drc_list_t;
+
+struct phb_drc_list_entry
+{
+ LIST_ENTRY(phb_drc_list_entry) next_entry;
+ opalpci_phb_drc_list_t list;
+};
+
+/*
+ * NOTE
+ * The "master" PE flag is a convention with no hardware meaning
+ * It indicates that basic device control (32-bit BARs, MSI, etc.) has been
+ * routed through that PE. "Slave" PEs are used only to enable additional
+ * 64-bit BARs on the device that won't fit inside the master PE fpr that
+ * specific device.
+ */
+typedef struct opalpci_phb_pe_map {
+ device_t pci_bus_device;
+ uint32_t pci_bus_start_devfn;
+ uint32_t pci_bus_end_devfn;
+ uint64_t phb_pe;
+ int is_master_pe;
+ uint64_t master_pe;
+} opalpci_phb_pe_map_t;
+
+struct phb_pe_map_entry {
+ LIST_ENTRY(phb_pe_map_entry) next_entry;
+ opalpci_phb_pe_map_t mapping;
+};
+
+typedef struct opalpci_phb_pe_data {
+ uint32_t pci_bus_start_devfn;
+ uint32_t pci_bus_end_devfn;
+ uint8_t opal_bus_filter;
+ uint8_t opal_dev_filter;
+ uint8_t opal_func_filter;
+ struct powernv_ioda_tvt tvt; /* TVT table for IOMMU */
+ int device_count;
+ int dma_setup_finished;
+ int msi_activated;
+ uint8_t *active_32_bit_mmio_segments;
+ uint64_t phb_pe;
+} opalpci_phb_pe_data_t;
+
+struct phb_pe_data_entry {
+ LIST_ENTRY(phb_pe_data_entry) next_entry;
+ opalpci_phb_pe_data_t mapping;
+};
+
+/* Define the shared soft context for all OPAL PHBs */
+struct opalpci_softc {
+ struct ofw_pci_softc ofw_sc;
+ uint64_t phb_id;
+ vmem_t *msi_vmem;
+ int msi_base; /* Base XIVE number */
+ int base_msi_irq; /* Base IRQ assigned by FreeBSD to this PIC */
+ int pe_setup_finalized; /* 0 if PE setup has not yet been run */
+ struct resource *r_reg;
+
+ uint64_t *tce; /* TCE table for 1:1 mapping */
+
+ uint8_t root_bus_id;
+ uint64_t root_bus_pe;
+ cell_t max_pe_count;
+ cell_t reserved_pe_id; /* Index of the reserved PE */
+ uint64_t m32_pci_base;
+ uint64_t m32_window_base;
+ uint64_t m32_window_size;
+ uint64_t m32_segment_size;
+ int m64_window_present;
+ int m64_window_bar_idx;
+ uint64_t m64_pci_base;
+ uint64_t m64_window_base;
+ uint64_t m64_window_size;
+ uint64_t m64_segment_size;
+
+ /* linked list storing PCI device to PE mappings */
+ LIST_HEAD(phb_drc_list, phb_drc_list_entry) pe_drc_list;
+ LIST_HEAD(phb_pe_map_list, phb_pe_map_entry) pe_map_list;
+ LIST_HEAD(phb_pe_data_list, phb_pe_data_entry) pe_data_list;
+};
+
+struct phb_pe_data_entry * opalpci_add_phb_pe_data_entry(device_t phb,
+ uint64_t pe);
+int opalpci_remove_phb_pe_data_entry(device_t phb, uint64_t pe);
+struct phb_pe_data_entry * opalpci_get_phb_pe_data_entry(device_t phb,
+ uint64_t pe);
+
+struct phb_pe_map_entry * opalpci_add_phb_pe_map_entry(device_t phb,
+ device_t dev, uint64_t pe, int is_master_pe);
+int opalpci_remove_phb_pe_map_entry(device_t phb, device_t dev, uint64_t pe);
+uint64_t opalpci_find_phb_pe_for_device(device_t phb, device_t dev);
+uint64_t opalpci_find_phb_pe_for_devfn(device_t phb, uint32_t devfn);
+uint64_t opalpci_find_unused_phb_pe(device_t phb);
+uint64_t opalpci_phb_get_slave_pe_count_for_device(device_t phb, device_t dev,
+ uint64_t pe);
+
+int opalpc_ioda2_configure_dma_bypass(device_t phb, uint64_t pe, int enable);
+
+int opalpci_phb_is_pci_memory_window_64_bit(device_t phb,
+ rman_res_t window_start);
+uint64_t opalpci_phb_get_m64_pe_for_addr(device_t phb, rman_res_t addr);
+
+int opalpci_ioda2_set_iommu_window(device_t phb, uint64_t pe,
+ int window_number, struct powernv_ioda_tvt * tvt);
+int opalpci_ioda2_clear_iommu_window(device_t phb, uint64_t pe,
+ int window_number, struct powernv_ioda_tvt * tvt);
+
+#endif /* _POWERNV_OPAL_PCI_H_ */
\ No newline at end of file
diff --git a/sys/powerpc/powernv/opal_pci.c b/sys/powerpc/powernv/opal_pci.c
--- a/sys/powerpc/powernv/opal_pci.c
+++ b/sys/powerpc/powernv/opal_pci.c
@@ -1,6 +1,7 @@
/*-
* Copyright (c) 2015-2016 Nathan Whitehorn
* Copyright (c) 2017-2018 Semihalf
+ * Copyright (c) 2025-2026 Raptor Engineering, LLC
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@@ -44,6 +45,7 @@
#include <dev/pci/pcivar.h>
#include <dev/pci/pcireg.h>
+#include <dev/pci/pcib_private.h>
#include <machine/bus.h>
#include <machine/intr_machdep.h>
@@ -56,12 +58,15 @@
#include "pic_if.h"
#include "iommu_if.h"
#include "opal.h"
+#include "opal_pci.h"
+#include "opal_iommu.h"
#define OPAL_PCI_TCE_MAX_ENTRIES (1024*1024UL)
#define OPAL_PCI_TCE_DEFAULT_SEG_SIZE (16*1024*1024UL)
#define OPAL_PCI_TCE_R (1UL << 0)
#define OPAL_PCI_TCE_W (1UL << 1)
#define PHB3_TCE_KILL_INVAL_ALL (1UL << 63)
+#define PHB3_TCE_KILL_INVAL_PE (1UL << 62)
/*
* Device interface.
@@ -86,6 +91,9 @@
int irq);
static int opalpci_map_msi(device_t dev, device_t child,
int irq, uint64_t *addr, uint32_t *data);
+static void opalpci_finalize_pe_setup(device_t dev);
+static int opalpci_release_resource(device_t bus, device_t child,
+ struct resource *res);
static int opalpci_route_interrupt(device_t bus, device_t dev, int pin);
/*
@@ -148,27 +156,28 @@
DEVMETHOD(pic_eoi, opalpic_pic_eoi),
/* Bus interface */
+ DEVMETHOD(bus_release_resource, opalpci_release_resource),
+ DEVMETHOD(bus_new_pass, opalpci_finalize_pe_setup),
DEVMETHOD(bus_get_dma_tag, opalpci_get_dma_tag),
DEVMETHOD(bus_get_cpus, ofw_pcibus_get_cpus),
DEVMETHOD(bus_get_domain, ofw_pcibus_get_domain),
- DEVMETHOD_END
-};
+#ifdef IOMMU
+ /* IOMMU functions */
+ DEVMETHOD(iommu_map, powernv_iommu_map),
+ DEVMETHOD(iommu_unmap, powernv_iommu_unmap),
+#endif
-struct opalpci_softc {
- struct ofw_pci_softc ofw_sc;
- uint64_t phb_id;
- vmem_t *msi_vmem;
- int msi_base; /* Base XIVE number */
- int base_msi_irq; /* Base IRQ assigned by FreeBSD to this PIC */
- uint64_t *tce; /* TCE table for 1:1 mapping */
- struct resource *r_reg;
+ DEVMETHOD_END
};
DEFINE_CLASS_1(pcib, opalpci_driver, opalpci_methods,
sizeof(struct opalpci_softc), ofw_pcib_driver);
EARLY_DRIVER_MODULE(opalpci, ofwbus, opalpci_driver, 0, 0, BUS_PASS_BUS);
+/* define a dedicated memory pool */
+MALLOC_DEFINE(M_OPALPCI_PHB, "opal-phb", "Internal data for PowerNV PHBs");
+
static int
opalpci_probe(device_t dev)
{
@@ -191,11 +200,12 @@
}
static void
-pci_phb3_tce_invalidate_entire(struct opalpci_softc *sc)
+pci_phb3_tce_invalidate_pe(struct opalpci_softc *sc, uint64_t pe)
{
+ uint64_t dword = PHB3_TCE_KILL_INVAL_PE | (pe & 0xff);
mb();
- bus_write_8(sc->r_reg, 0x210, PHB3_TCE_KILL_INVAL_ALL);
+ bus_write_8(sc->r_reg, 0x210, dword);
mb();
}
@@ -233,23 +243,556 @@
return (1ULL << sizes[count - 1]);
}
+struct phb_pe_data_entry *
+opalpci_add_phb_pe_data_entry(device_t phb, uint64_t pe)
+{
+ struct opalpci_softc *sc;
+ struct phb_pe_data_entry * pe_data_entry = NULL;
+
+ sc = device_get_softc(phb);
+
+ LIST_FOREACH(pe_data_entry, &sc->pe_data_list, next_entry) {
+ KASSERT(pe_data_entry->mapping.phb_pe != pe,
+ ("PHB[%jd] PE %02lx already allocated",
+ (uintmax_t)sc->phb_id, (uintmax_t)pe));
+ }
+
+ pe_data_entry = malloc(sizeof(struct phb_pe_data_entry), M_OPALPCI_PHB,
+ M_WAITOK | M_ZERO);
+ if (!pe_data_entry)
+ return NULL;
+
+ pe_data_entry->mapping.pci_bus_start_devfn = POWERNV_INVALID_DEVFN;
+ pe_data_entry->mapping.pci_bus_end_devfn = POWERNV_INVALID_DEVFN;
+ pe_data_entry->mapping.opal_bus_filter = OPAL_PCI_BUS_ANY;
+ pe_data_entry->mapping.opal_dev_filter = OPAL_IGNORE_RID_DEVICE_NUMBER;
+ pe_data_entry->mapping.opal_func_filter = OPAL_IGNORE_RID_FUNC_NUMBER;
+ pe_data_entry->mapping.tvt.tvt_base = NULL;
+ pe_data_entry->mapping.device_count = 0;
+ pe_data_entry->mapping.dma_setup_finished = 0;
+ pe_data_entry->mapping.msi_activated = 0;
+ pe_data_entry->mapping.phb_pe = pe;
+ pe_data_entry->mapping.active_32_bit_mmio_segments = malloc(
+ (sizeof(pe_data_entry->mapping.active_32_bit_mmio_segments[0]) *
+ sc->max_pe_count),
+ M_OPALPCI_PHB, M_WAITOK | M_ZERO);
+ if (!pe_data_entry->mapping.active_32_bit_mmio_segments) {
+ free(pe_data_entry, M_OPALPCI_PHB);
+ return NULL;
+ }
+
+ LIST_INSERT_HEAD(&sc->pe_data_list, pe_data_entry, next_entry);
+
+ return pe_data_entry;
+}
+
+int
+opalpci_remove_phb_pe_data_entry(device_t phb, uint64_t pe)
+{
+ struct opalpci_softc *sc;
+ struct phb_pe_data_entry * pe_data_entry = NULL;
+
+ sc = device_get_softc(phb);
+
+ LIST_FOREACH(pe_data_entry, &sc->pe_data_list, next_entry) {
+ if (pe_data_entry->mapping.phb_pe == pe)
+ break;
+ }
+
+ KASSERT(pe_data_entry != NULL,
+ ("PHB[%jd] PE %02lx not found",
+ (uintmax_t)sc->phb_id, (uintmax_t)pe));
+
+ free(pe_data_entry->mapping.tvt.tvt_base, M_DEVBUF);
+ free(pe_data_entry->mapping.active_32_bit_mmio_segments, M_OPALPCI_PHB);
+
+ LIST_REMOVE(pe_data_entry, next_entry);
+ free(pe_data_entry, M_OPALPCI_PHB);
+
+ return 0;
+}
+
+struct phb_pe_data_entry *
+opalpci_get_phb_pe_data_entry(device_t phb, uint64_t pe)
+{
+ struct opalpci_softc *sc;
+ struct phb_pe_data_entry * pe_data_entry;
+
+ sc = device_get_softc(phb);
+
+ LIST_FOREACH(pe_data_entry, &sc->pe_data_list, next_entry) {
+ if (pe_data_entry->mapping.phb_pe == pe)
+ return pe_data_entry;
+ }
+
+ return NULL;
+}
+
+struct phb_pe_map_entry *
+opalpci_add_phb_pe_map_entry(device_t phb, device_t dev, uint64_t pe,
+ int is_master_pe)
+{
+ struct opalpci_softc *sc;
+ struct phb_pe_map_entry * pe_map_entry;
+ struct phb_pe_data_entry * pe_data_entry;
+
+ sc = device_get_softc(phb);
+
+ pe_data_entry = opalpci_get_phb_pe_data_entry(phb, pe);
+ if (!pe_data_entry) {
+ pe_data_entry = opalpci_add_phb_pe_data_entry(phb, pe);
+
+ KASSERT(pe_data_entry != NULL,
+ ("PHB[%jd] Unable to allocate data structures for PE %02lx",
+ (uintmax_t)sc->phb_id, (uintmax_t)pe));
+ }
+
+ pe_data_entry->mapping.device_count++;
+
+ LIST_FOREACH(pe_map_entry, &sc->pe_map_list, next_entry) {
+ if ((pe_map_entry->mapping.pci_bus_device == dev) &&
+ (pe_map_entry->mapping.phb_pe == pe))
+ KASSERT(0,
+ ("PHB[%jd] PE %02lx already assigned to device",
+ (uintmax_t)sc->phb_id, (uintmax_t)pe));
+ }
+
+ pe_map_entry = malloc(sizeof(struct phb_pe_map_entry), M_OPALPCI_PHB,
+ M_WAITOK | M_ZERO);
+ if (!pe_map_entry)
+ return NULL;
+
+ pe_map_entry->mapping.pci_bus_device = dev;
+ pe_map_entry->mapping.pci_bus_start_devfn = POWERNV_INVALID_DEVFN;
+ pe_map_entry->mapping.pci_bus_end_devfn = POWERNV_INVALID_DEVFN;
+ pe_map_entry->mapping.phb_pe = pe;
+ pe_map_entry->mapping.is_master_pe = is_master_pe;
+ pe_map_entry->mapping.master_pe = OPAL_IODA_INVALID_PE;
+ LIST_INSERT_HEAD(&sc->pe_map_list, pe_map_entry, next_entry);
+
+ return pe_map_entry;
+}
+
+int
+opalpci_remove_phb_pe_map_entry(device_t phb, device_t dev, uint64_t pe)
+{
+ struct opalpci_softc *sc;
+ struct phb_pe_map_entry *pe_map_entry, *pe_map_entry_tmp;
+ struct phb_pe_data_entry *pe_data_entry;
+ int device_removed = 0;
+
+ sc = device_get_softc(phb);
+
+ pe_data_entry = opalpci_get_phb_pe_data_entry(phb, pe);
+ if (!pe_data_entry) {
+ KASSERT(pe_data_entry != NULL,
+ ("PHB[%jd] Missing data structures for PE %02lx",
+ (uintmax_t)sc->phb_id, (uintmax_t)pe));
+ }
+
+ LIST_FOREACH_SAFE(pe_map_entry, &sc->pe_map_list, next_entry,
+ pe_map_entry_tmp) {
+ if ((pe_map_entry->mapping.pci_bus_device == dev) &&
+ (pe_map_entry->mapping.phb_pe == pe)) {
+ KASSERT(pe_data_entry->mapping.device_count > 0,
+ ("PHB[%jd] Attempted to remove spurious device "
+ "map entry for PE %02lx", (uintmax_t)sc->phb_id,
+ (uintmax_t)pe));
+
+ if (pe_data_entry->mapping.device_count > 0)
+ pe_data_entry->mapping.device_count--;
+
+ LIST_REMOVE(pe_map_entry, next_entry);
+ free(pe_map_entry, M_OPALPCI_PHB);
+
+ device_removed = 1;
+ }
+ }
+
+ KASSERT(device_removed,
+ ("PHB[%jd] PE %02lx device mapping not found",
+ (uintmax_t)sc->phb_id, (uintmax_t)pe));
+
+ if (pe_data_entry->mapping.device_count == 0) {
+ opalpci_remove_phb_pe_data_entry(phb, pe);
+ }
+
+ return 0;
+}
+
+uint64_t
+opalpci_find_phb_pe_for_device(device_t phb, device_t dev)
+{
+ struct opalpci_softc *sc;
+ struct phb_pe_map_entry * pe_map_entry;
+
+ sc = device_get_softc(phb);
+
+ LIST_FOREACH(pe_map_entry, &sc->pe_map_list, next_entry) {
+ if (pe_map_entry->mapping.pci_bus_device == dev)
+ return pe_map_entry->mapping.phb_pe;
+ }
+
+ return OPAL_IODA_INVALID_PE;
+}
+
+uint64_t
+opalpci_find_phb_pe_for_devfn(device_t phb, uint32_t devfn)
+{
+ struct opalpci_softc *sc;
+ struct phb_pe_map_entry * pe_map_entry;
+
+ sc = device_get_softc(phb);
+
+ LIST_FOREACH(pe_map_entry, &sc->pe_map_list, next_entry) {
+ if ((pe_map_entry->mapping.pci_bus_start_devfn >= devfn)
+ && (devfn <= pe_map_entry->mapping.pci_bus_end_devfn))
+ return pe_map_entry->mapping.phb_pe;
+ }
+
+ return OPAL_IODA_INVALID_PE;
+}
+
+uint64_t
+opalpci_find_unused_phb_pe(device_t phb)
+{
+ struct opalpci_softc *sc;
+ struct phb_pe_map_entry * pe_map_entry = NULL;
+ int64_t pe;
+ uint64_t unused_pe = OPAL_IODA_INVALID_PE;
+
+ sc = device_get_softc(phb);
+
+ /* Scan backward as PEs are traditionally allocated from high to low */
+ for (pe = sc->max_pe_count-1; pe >= 0; pe--) {
+ LIST_FOREACH(pe_map_entry, &sc->pe_map_list, next_entry) {
+ if (pe_map_entry->mapping.phb_pe == pe)
+ break;
+ }
+
+ if (pe_map_entry == NULL) {
+ unused_pe = pe;
+ break;
+ }
+ }
+
+ return unused_pe;
+}
+
+uint64_t
+opalpci_phb_get_slave_pe_count_for_device(device_t phb, device_t dev, uint64_t pe)
+{
+ struct opalpci_softc *sc;
+ struct phb_pe_map_entry * pe_map_entry;
+ uint64_t slave_count = 0;
+
+ sc = device_get_softc(phb);
+
+ LIST_FOREACH(pe_map_entry, &sc->pe_map_list, next_entry) {
+ if ((pe_map_entry->mapping.pci_bus_device == dev) &&
+ (pe_map_entry->mapping.phb_pe == pe) &&
+ (!pe_map_entry->mapping.is_master_pe))
+ slave_count++;
+ }
+
+ return slave_count;
+}
+
+static int
+opalpci_initialize_ioda2_mmio(device_t dev)
+{
+ struct opalpci_softc *sc;
+ int err;
+
+ sc = device_get_softc(dev);
+
+ /* Set up the default 64-bit memory window */
+ err = opal_call(OPAL_PCI_SET_PHB_MEM_WINDOW, sc->phb_id,
+ OPAL_M64_WINDOW_TYPE, sc->m64_window_bar_idx,
+ sc->m64_window_base, 0,
+ sc->m64_window_size);
+ if (err != OPAL_SUCCESS) {
+ device_printf(dev,
+ "64-bit memory window setup failed: %d\n", err);
+ return (ENXIO);
+ }
+
+ /* Enable the default 64-bit memory window
+ *
+ * Note that we set this up in segmented mode, which basically
+ * splits the 64-bit MMIO window into <max_pe_count> segments,
+ * with each segment assigned in hardware to each PE#, in numerical
+ * order.
+ *
+ * This means that the the upper address bits of each 64-bit device
+ * BAR effectively select the associated PE# that we can use for
+ * that particular device and BAR combination...
+ */
+ err = opal_call(OPAL_PCI_PHB_MMIO_ENABLE, sc->phb_id,
+ OPAL_M64_WINDOW_TYPE, sc->m64_window_bar_idx,
+ OPAL_ENABLE_M64_SPLIT);
+ if (err != OPAL_SUCCESS) {
+ device_printf(dev,
+ "64-bit memory window enable failed: %d\n", err);
+ return (ENXIO);
+ }
+
+ if (bootverbose)
+ device_printf(dev,
+ "Enabled segmented 64-bit memory window #%d at 0x%lx "
+ "size 0x%lx segment size 0x%lx\n",
+ sc->m64_window_bar_idx, sc->m64_window_base,
+ sc->m64_window_size, sc->m64_segment_size);
+
+ return 0;
+}
+
+static int
+opalpc_ioda2_create_tvt(device_t phb, uint64_t pe, int tve_num,
+ uint64_t window_size, int page_shift, int levels)
+{
+ struct opalpci_softc *sc;
+ struct phb_pe_data_entry * pe_data_entry;
+
+ sc = device_get_softc(phb);
+ pe_data_entry = opalpci_get_phb_pe_data_entry(phb, pe);
+ if (pe_data_entry == NULL)
+ panic("Failed to locate data structures for PHB %jd PE %02lx\n",
+ (uintmax_t)sc->phb_id, pe);
+
+ pe_data_entry->mapping.tvt.tce_size = max_tce_size(phb);
+ pe_data_entry->mapping.tvt.tvt_entry_count =
+ round_pow2(window_size / pe_data_entry->mapping.tvt.tce_size);
+ pe_data_entry->mapping.tvt.tce_tbl_size =
+ MAX(pe_data_entry->mapping.tvt.tvt_entry_count * sizeof(uint64_t), 4096);
+ pe_data_entry->mapping.tvt.page_shift = page_shift;
+ if (pe_data_entry->mapping.tvt.tvt_entry_count > OPAL_PCI_TCE_MAX_ENTRIES)
+ panic("PowerNV supports only %jdGB of memory space\n",
+ (uintmax_t)((OPAL_PCI_TCE_MAX_ENTRIES *
+ pe_data_entry->mapping.tvt.tce_size) >> 30));
+
+ pe_data_entry->mapping.tvt.tvt_base = contigmalloc(
+ pe_data_entry->mapping.tvt.tce_tbl_size,
+ M_DEVBUF, M_NOWAIT | M_ZERO, 0,
+ BUS_SPACE_MAXADDR, pe_data_entry->mapping.tvt.tce_tbl_size, 0);
+
+ if (pe_data_entry->mapping.tvt.tvt_base == NULL)
+ panic("Failed to allocate TVT for PHB %jd PE %02lx\n",
+ (uintmax_t)sc->phb_id, pe);
+
+ return 0;
+}
+
+int
+opalpc_ioda2_configure_dma_bypass(device_t phb, uint64_t pe, int enable)
+{
+ struct opalpci_softc *sc;
+ struct phb_pe_data_entry * pe_data_entry;
+ int err;
+ uint64_t maxmem;
+
+ sc = device_get_softc(phb);
+ pe_data_entry = opalpci_get_phb_pe_data_entry(phb, pe);
+ if (pe_data_entry == NULL)
+ panic("Failed to locate data structures for PHB %jd PE %02lx\n",
+ (uintmax_t)sc->phb_id, pe);
+
+ /*
+ * The window number for the 64-bit bypass is always 1 by convention,
+ * as this selects TVE #1
+ */
+ if (enable) {
+ device_printf(phb, "Enabling 64-bit DMA bypass mode\n");
+ maxmem = roundup2(powerpc_ptob(Maxmem),
+ pe_data_entry->mapping.tvt.tce_size);
+ err = opal_call(OPAL_PCI_MAP_PE_DMA_WINDOW_REAL, sc->phb_id,
+ pe, (pe << 1) + 1,
+ (1UL << 59), maxmem);
+ } else {
+ device_printf(phb, "Disabling 64-bit DMA bypass mode\n");
+ err = opal_call(OPAL_PCI_MAP_PE_DMA_WINDOW_REAL, sc->phb_id,
+ pe, (pe << 1) + 1,
+ (1UL << 59), 0);
+ }
+ if (err != OPAL_SUCCESS) {
+ device_printf(phb, "64-bit DMA bypass mapping failed: %d\n", err);
+ return (ENXIO);
+ }
+
+ return 0;
+}
+
+int
+opalpci_ioda2_set_iommu_window(device_t phb, uint64_t pe, int window_number,
+ struct powernv_ioda_tvt * tvt)
+{
+ struct opalpci_softc *sc;
+ struct phb_pe_data_entry * pe_data_entry;
+ int err;
+
+ if (bootverbose)
+ device_printf(phb, "Mapping PE# %lx window #%d %#jx-%#jx "
+ "TCE segment size %lx\n",
+ pe, window_number,
+ (uintmax_t)(0),
+ (uintmax_t)(tvt->tce_tbl_size),
+ (uintmax_t)(tvt->tce_size));
+
+ sc = device_get_softc(phb);
+ pe_data_entry = opalpci_get_phb_pe_data_entry(phb, pe);
+ if (pe_data_entry == NULL)
+ panic("Failed to locate data structures for PHB %jd PE %02lx\n",
+ (uintmax_t)sc->phb_id, pe);
+
+ err = opal_call(OPAL_PCI_MAP_PE_DMA_WINDOW, sc->phb_id,
+ pe, (pe << 1) + window_number,
+ 1, pmap_kextract((uint64_t)&tvt->tvt_base[0]),
+ tvt->tce_tbl_size, tvt->tce_size);
+ if (err != OPAL_SUCCESS) {
+ device_printf(phb, "DMA IOMMU mapping failed: %d\n", err);
+ return (ENXIO);
+ }
+
+ /*
+ * Invalidate all previous TCE entries for this PE.
+ */
+ if (ofw_bus_is_compatible(phb, "ibm,power8-pciex")) {
+ pci_phb3_tce_invalidate_pe(sc, pe);
+ } else {
+ err = opal_call(OPAL_PCI_TCE_KILL, sc->phb_id, OPAL_PCI_TCE_KILL_PE,
+ pe, 0, 0, 0);
+ if (err != OPAL_SUCCESS) {
+ device_printf(phb, "PE# %lx TCE invalidation failed: %d\n",
+ pe, err);
+ return (ENXIO);
+ }
+ }
+
+ return 0;
+}
+
+int
+opalpci_ioda2_clear_iommu_window(device_t phb, uint64_t pe, int window_number,
+ struct powernv_ioda_tvt * tvt)
+{
+ struct opalpci_softc *sc;
+ int err;
+
+ sc = device_get_softc(phb);
+
+ /*
+ * Disable IOMMU for PE - deactivate all mappings
+ */
+ if (bootverbose)
+ device_printf(phb, "Unmapping PE# %lx window #%d for DMA\n",
+ pe, window_number);
+
+ err = opal_call(OPAL_PCI_MAP_PE_DMA_WINDOW, sc->phb_id,
+ pe, (pe << 1) + window_number, 0, 0, 0, 0);
+ if (err != OPAL_SUCCESS) {
+ device_printf(phb, "DMA IOMMU unmapping failed: %d\n", err);
+ return (ENXIO);
+ }
+
+ /*
+ * Invalidate all previous TCE entries for this PE.
+ */
+ if (ofw_bus_is_compatible(phb, "ibm,power8-pciex")) {
+ pci_phb3_tce_invalidate_pe(sc, pe);
+ } else {
+ err = opal_call(OPAL_PCI_TCE_KILL, sc->phb_id, OPAL_PCI_TCE_KILL_PE,
+ pe, 0, 0, 0);
+ if (err != OPAL_SUCCESS) {
+ device_printf(phb, "PE# %lx TCE invalidation failed: %d\n",
+ pe, err);
+ return (ENXIO);
+ }
+ }
+
+ /*
+ * Clear kernel mappings
+ */
+ memset(tvt->tvt_base, 0, tvt->tce_tbl_size);
+
+ return 0;
+}
+
+static int
+opalpci_ioda2_set_default_dma_window(device_t phb, uint64_t pe)
+{
+ struct opalpci_softc *sc;
+ struct phb_pe_data_entry * pe_data_entry;
+ int i, err;
+ rman_res_t start, end;
+ int index_start, index_end;
+
+ sc = device_get_softc(phb);
+ pe_data_entry = opalpci_get_phb_pe_data_entry(phb, pe);
+ if (pe_data_entry == NULL)
+ panic("Failed to locate data structures for PHB %jd PE %02lx\n",
+ (uintmax_t)sc->phb_id, pe);
+
+ /* Don't rerun setup if DMA has already been configured on this PE */
+ if (pe_data_entry->mapping.dma_setup_finished)
+ return 0;
+
+ /* Set up default IOMMU window using all available physical memory */
+ start = 0;
+ end = powerpc_ptob(Maxmem);
+
+ /*
+ * Map everything within the default IOMMU window 1:1, using the
+ * preconfigured TVT segment size
+ */
+ index_start = start / pe_data_entry->mapping.tvt.tce_size;
+ index_end = end / pe_data_entry->mapping.tvt.tce_size;
+ if (index_start > pe_data_entry->mapping.tvt.tvt_entry_count)
+ index_start = pe_data_entry->mapping.tvt.tvt_entry_count;
+ if (index_end > pe_data_entry->mapping.tvt.tvt_entry_count)
+ index_end = pe_data_entry->mapping.tvt.tvt_entry_count;
+ for (i = index_start; i < index_end; i++) {
+ pe_data_entry->mapping.tvt.tvt_base[i] =
+ htobe64((i * pe_data_entry->mapping.tvt.tce_size) |
+ OPAL_PCI_TCE_R | OPAL_PCI_TCE_W);
+ }
+
+ /* Activate the default IOMMU window */
+ err = opalpci_ioda2_set_iommu_window(phb, pe, 0,
+ &pe_data_entry->mapping.tvt);
+ if (err != 0)
+ return err;
+
+ /* Enable 64-bit DMA bypass */
+ err = opalpc_ioda2_configure_dma_bypass(phb, pe, 1);
+ if (err != 0)
+ return (ENXIO);
+
+ pe_data_entry->mapping.dma_setup_finished = 1;
+
+ return 0;
+}
+
static int
opalpci_attach(device_t dev)
{
struct opalpci_softc *sc;
- cell_t id[2], m64ranges[2], m64window[6], npe;
+ cell_t id[2], m64ranges[2], m64window[6];
phandle_t node;
int i, err;
- uint64_t maxmem;
- uint64_t entries;
- uint64_t tce_size;
- uint64_t tce_tbl_size;
- int m64bar;
int rid;
sc = device_get_softc(dev);
node = ofw_bus_get_node(dev);
+ /* allocate initial delayed resource allocation linked list */
+ LIST_INIT(&sc->pe_drc_list);
+
+ /* allocate initial PE data linked list */
+ LIST_INIT(&sc->pe_data_list);
+
+ /* allocate initial device to PE map linked list */
+ LIST_INIT(&sc->pe_map_list);
+
switch (OF_getproplen(node, "ibm,opal-phbid")) {
case 8:
OF_getencprop(node, "ibm,opal-phbid", id, 8);
@@ -265,8 +808,46 @@
return (ENXIO);
}
+ /*
+ * Retrieve reserved PE number from device tree
+ * Use PE #0 if reserved PE number not present in DT
+ */
+ if (OF_getencprop(node, "ibm,opal-reserved-pe",
+ &sc->reserved_pe_id, 4) != 4)
+ sc->reserved_pe_id = 0;
+
+ /* Retrieve maximum PE count from device tree
+ * Use a minimum of 1 if PE count not present in DT
+ */
+ if (OF_getencprop(node, "ibm,opal-num-pes", &sc->max_pe_count, 4) != 4)
+ sc->max_pe_count = 1;
+
if (bootverbose)
- device_printf(dev, "OPAL ID %#lx\n", sc->phb_id);
+ device_printf(dev, "OPAL ID %#lx reserved PE# %x\n",
+ sc->phb_id, sc->reserved_pe_id);
+
+ /*
+ * Retrieve 64-bit memory window configuration data from device tree,
+ * and use the last 64-bit BAR as the 64-bit memory window
+ */
+ if (OF_getencprop(node, "ibm,opal-available-m64-ranges",
+ m64ranges, sizeof(m64ranges)) == sizeof(m64ranges)) {
+ sc->m64_window_bar_idx = m64ranges[0] + m64ranges[1] - 1;
+ m64ranges[1]--;
+ } else {
+ sc->m64_window_bar_idx = 0;
+ }
+
+ sc->m64_window_present = 0;
+ if (OF_getencprop(node, "ibm,opal-m64-window",
+ m64window, sizeof(m64window)) == sizeof(m64window)) {
+ sc->m64_pci_base = ((uint64_t)m64window[0] << 32) | m64window[1];
+ sc->m64_window_base = ((uint64_t)m64window[2] << 32) | m64window[3];
+ sc->m64_window_size = ((uint64_t)m64window[4] << 32) | m64window[5];
+ sc->m64_segment_size = sc->m64_window_size / sc->max_pe_count;
+
+ sc->m64_window_present = 1;
+ }
rid = 0;
sc->r_reg = bus_alloc_resource_any(dev, SYS_RES_MEMORY,
@@ -277,141 +858,65 @@
return (ENXIO);
}
-#if 0
/*
* Reset PCI IODA table
*/
err = opal_call(OPAL_PCI_RESET, sc->phb_id, OPAL_RESET_PCI_IODA_TABLE,
1);
- if (err != 0) {
+ if (err != OPAL_SUCCESS) {
device_printf(dev, "IODA table reset failed: %d\n", err);
return (ENXIO);
}
- err = opal_call(OPAL_PCI_RESET, sc->phb_id, OPAL_RESET_PHB_COMPLETE,
- 1);
- if (err < 0) {
- device_printf(dev, "PHB reset failed: %d\n", err);
- return (ENXIO);
- }
- if (err > 0) {
- while ((err = opal_call(OPAL_PCI_POLL, sc->phb_id)) > 0) {
- DELAY(1000*(err + 1)); /* Returns expected delay in ms */
- }
- }
- if (err < 0) {
- device_printf(dev, "WARNING: PHB IODA reset poll failed: %d\n", err);
- }
- err = opal_call(OPAL_PCI_RESET, sc->phb_id, OPAL_RESET_PHB_COMPLETE,
- 0);
- if (err < 0) {
- device_printf(dev, "PHB reset failed: %d\n", err);
- return (ENXIO);
- }
- if (err > 0) {
- while ((err = opal_call(OPAL_PCI_POLL, sc->phb_id)) > 0) {
- DELAY(1000*(err + 1)); /* Returns expected delay in ms */
- }
- }
-#endif
/*
- * Map all devices on the bus to partitionable endpoint one until
- * such time as we start wanting to do things like bhyve.
+ * Root bus number is fixed at 0 on PHB3/PHB4 systems
+ * (and possibly others)
*/
- err = opal_call(OPAL_PCI_SET_PE, sc->phb_id, OPAL_PCI_DEFAULT_PE,
- 0, OPAL_PCI_BUS_ANY, OPAL_IGNORE_RID_DEVICE_NUMBER,
- OPAL_IGNORE_RID_FUNC_NUMBER, OPAL_MAP_PE);
- if (err != 0) {
- device_printf(dev, "PE mapping failed: %d\n", err);
+ sc->root_bus_id = 0;
+
+ /* Mark firmware-reserved PE as allocated */
+ if (opalpci_add_phb_pe_map_entry(dev, dev, sc->reserved_pe_id, 1) ==
+ NULL) {
+ device_printf(dev, "PE map update failed: %d\n", err);
return (ENXIO);
}
/*
- * Turn on MMIO, mapped to PE 1
+ * Select PE number for root bus
+ * The root bus is special in that it does not consume 64-bit memory
+ * resources, and by convention it should use a PE number adjacent to
+ * the reserved PE number.
*/
- if (OF_getencprop(node, "ibm,opal-num-pes", &npe, 4) != 4)
- npe = 1;
- for (i = 0; i < npe; i++) {
- err = opal_call(OPAL_PCI_MAP_PE_MMIO_WINDOW, sc->phb_id,
- OPAL_PCI_DEFAULT_PE, OPAL_M32_WINDOW_TYPE, 0, i);
- if (err != 0)
- device_printf(dev, "MMIO %d map failed: %d\n", i, err);
+ if (sc->reserved_pe_id == 0) {
+ sc->root_bus_pe = 1;
}
-
- if (OF_getencprop(node, "ibm,opal-available-m64-ranges",
- m64ranges, sizeof(m64ranges)) == sizeof(m64ranges))
- m64bar = m64ranges[0];
- else
- m64bar = 0;
-
- /* XXX: multiple M64 windows? */
- if (OF_getencprop(node, "ibm,opal-m64-window",
- m64window, sizeof(m64window)) == sizeof(m64window)) {
- opal_call(OPAL_PCI_PHB_MMIO_ENABLE, sc->phb_id,
- OPAL_M64_WINDOW_TYPE, m64bar, 0);
- opal_call(OPAL_PCI_SET_PHB_MEM_WINDOW, sc->phb_id,
- OPAL_M64_WINDOW_TYPE, m64bar /* index */,
- ((uint64_t)m64window[2] << 32) | m64window[3], 0,
- ((uint64_t)m64window[4] << 32) | m64window[5]);
- opal_call(OPAL_PCI_MAP_PE_MMIO_WINDOW, sc->phb_id,
- OPAL_PCI_DEFAULT_PE, OPAL_M64_WINDOW_TYPE,
- m64bar /* index */, 0);
- opal_call(OPAL_PCI_PHB_MMIO_ENABLE, sc->phb_id,
- OPAL_M64_WINDOW_TYPE, m64bar, OPAL_ENABLE_M64_NON_SPLIT);
- }
-
- /*
- * Enable IOMMU for PE1 - map everything 1:1 using
- * segments of max_tce_size size
- */
- tce_size = max_tce_size(dev);
- maxmem = roundup2(powerpc_ptob(Maxmem), tce_size);
- entries = round_pow2(maxmem / tce_size);
- tce_tbl_size = MAX(entries * sizeof(uint64_t), 4096);
- if (entries > OPAL_PCI_TCE_MAX_ENTRIES)
- panic("POWERNV supports only %jdGB of memory space\n",
- (uintmax_t)((OPAL_PCI_TCE_MAX_ENTRIES * tce_size) >> 30));
- if (bootverbose)
- device_printf(dev, "Mapping 0-%#jx for DMA\n", (uintmax_t)maxmem);
- sc->tce = contigmalloc(tce_tbl_size,
- M_DEVBUF, M_NOWAIT | M_ZERO, 0,
- BUS_SPACE_MAXADDR, tce_tbl_size, 0);
- if (sc->tce == NULL)
- panic("Failed to allocate TCE memory for PHB %jd\n",
- (uintmax_t)sc->phb_id);
-
- for (i = 0; i < entries; i++)
- sc->tce[i] = htobe64((i * tce_size) | OPAL_PCI_TCE_R | OPAL_PCI_TCE_W);
-
- /* Map TCE for every PE. It seems necessary for Power8 */
- for (i = 0; i < npe; i++) {
- err = opal_call(OPAL_PCI_MAP_PE_DMA_WINDOW, sc->phb_id,
- i, (i << 1),
- 1, pmap_kextract((uint64_t)&sc->tce[0]),
- tce_tbl_size, tce_size);
- if (err != 0) {
- device_printf(dev, "DMA IOMMU mapping failed: %d\n", err);
+ else if (sc->reserved_pe_id == (sc->max_pe_count - 1)) {
+ sc->root_bus_pe = sc->max_pe_count - 2;
+ } else {
+ /*
+ * If the reserved PE is not located in one of the expected
+ * locations, fall back to allocating a random new PE index for
+ * the root bus.
+ */
+ sc->root_bus_pe = opalpci_find_unused_phb_pe(dev);
+ if (sc->root_bus_pe == OPAL_IODA_INVALID_PE) {
+ device_printf(dev,
+ "Failed to allocate reserved PE for root bus\n");
return (ENXIO);
}
+ }
+ if (opalpci_add_phb_pe_map_entry(dev, dev, sc->root_bus_pe, 1) ==
+ NULL) {
+ device_printf(dev, "PE map update failed: %d\n", err);
+ return (ENXIO);
+ }
- err = opal_call(OPAL_PCI_MAP_PE_DMA_WINDOW_REAL, sc->phb_id,
- i, (i << 1) + 1,
- (1UL << 59), maxmem);
- if (err != 0) {
- device_printf(dev, "DMA 64b bypass mapping failed: %d\n", err);
- return (ENXIO);
+ if (sc->m64_window_present) {
+ if (opalpci_initialize_ioda2_mmio(dev) != 0) {
+ sc->m64_window_present = 0;
}
}
- /*
- * Invalidate all previous TCE entries.
- */
- if (ofw_bus_is_compatible(dev, "power8-pciex"))
- pci_phb3_tce_invalidate_entire(sc);
- else
- opal_call(OPAL_PCI_TCE_KILL, sc->phb_id, OPAL_PCI_TCE_KILL_ALL,
- OPAL_PCI_DEFAULT_PE, 0, 0, 0);
-
/*
* Get MSI properties
*/
@@ -434,29 +939,6 @@
msi_ranges[1], msi_ranges[0]);
}
- /* Create the parent DMA tag */
- /*
- * Constrain it to POWER8 PHB (ioda2) for now. It seems to mess up on
- * POWER9 systems.
- */
- if (ofw_bus_is_compatible(dev, "ibm,ioda2-phb")) {
- err = bus_dma_tag_create(bus_get_dma_tag(dev), /* parent */
- 1, 0, /* alignment, bounds */
- OPAL_PCI_BUS_SPACE_LOWADDR_32BIT, /* lowaddr */
- BUS_SPACE_MAXADDR_32BIT, /* highaddr */
- NULL, NULL, /* filter, filterarg */
- BUS_SPACE_MAXSIZE, /* maxsize */
- BUS_SPACE_UNRESTRICTED, /* nsegments */
- BUS_SPACE_MAXSIZE, /* maxsegsize */
- 0, /* flags */
- NULL, NULL, /* lockfunc, lockarg */
- &sc->ofw_sc.sc_dmat);
- if (err != 0) {
- device_printf(dev, "Failed to create DMA tag\n");
- return (err);
- }
- }
-
/*
* General OFW PCI attach
*/
@@ -464,18 +946,45 @@
if (err != 0)
return (err);
+ /* Find 32-bit non-prefetchable memory window */
+ for (i = 0; i < sc->ofw_sc.sc_nrange; i++) {
+ struct ofw_pci_range *rp;
+
+ rp = sc->ofw_sc.sc_range + i;
+
+ if ((sc->ofw_sc.sc_range_mask & ((uint64_t)1 << i)) != 0)
+ continue;
+
+ if ((rp->pci_hi & OFW_PCI_PHYS_HI_SPACEMASK) ==
+ OFW_PCI_PHYS_HI_SPACE_MEM32) {
+ sc->m32_pci_base = rp->pci;
+ sc->m32_window_base = rp->host;
+ sc->m32_window_size = rp->size;
+
+ /*
+ * OPAL drops the MSI region from the device tree
+ * advertised PCI resource size.
+ *
+ * Add it back in to the 32-bit window size to enable
+ * correct segment size and PE offset calculations.
+ */
+ sc->m32_window_size += 0x10000;
+
+ sc->m32_segment_size =
+ sc->m32_window_size / sc->max_pe_count;
+ }
+ }
+
/*
* Unfreeze non-config-space PCI operations. Let this fail silently
* if e.g. there is no current freeze.
*/
- opal_call(OPAL_PCI_EEH_FREEZE_CLEAR, sc->phb_id, OPAL_PCI_DEFAULT_PE,
+ opal_call(OPAL_PCI_EEH_FREEZE_CLEAR, sc->phb_id, sc->reserved_pe_id,
OPAL_EEH_ACTION_CLEAR_FREEZE_ALL);
- /*
- * OPAL stores 64-bit BARs in a special property rather than "ranges"
- */
- if (OF_getencprop(node, "ibm,opal-m64-window",
- m64window, sizeof(m64window)) == sizeof(m64window)) {
+
+ /* Inform the PCI core about the 64-bit memory window on the PHB */
+ if (sc->m64_window_present) {
struct ofw_pci_range *rp;
sc->ofw_sc.sc_nrange++;
@@ -485,13 +994,31 @@
rp = &sc->ofw_sc.sc_range[sc->ofw_sc.sc_nrange-1];
rp->pci_hi = OFW_PCI_PHYS_HI_SPACE_MEM64 |
OFW_PCI_PHYS_HI_PREFETCHABLE;
- rp->pci = ((uint64_t)m64window[0] << 32) | m64window[1];
- rp->host = ((uint64_t)m64window[2] << 32) | m64window[3];
- rp->size = ((uint64_t)m64window[4] << 32) | m64window[5];
- rman_manage_region(&sc->ofw_sc.sc_mem_rman, rp->pci,
+ rp->pci = sc->m64_pci_base;
+ rp->host = sc->m64_window_base;
+ rp->size = sc->m64_window_size;
+
+ /* Exclude the memory segments for the two reserved PEs */
+ if (sc->reserved_pe_id == 0) {
+ rp->host += (2 * sc->m64_segment_size);
+ rp->size -= (2 * sc->m64_segment_size);
+ } else if (sc->reserved_pe_id == (sc->max_pe_count - 1)) {
+ rp->size -= (2 * sc->m64_segment_size);
+ } else {
+ device_printf(dev,
+ "Unable to adjust 64-bit memory window to exclude "
+ "reserved PE# %x\n",
+ sc->reserved_pe_id);
+ }
+
+ /* All 64-bit regions are prefetchable */
+ sc->ofw_sc.sc_have_pmem = 1;
+ rman_manage_region(&sc->ofw_sc.sc_pmem_rman, rp->pci,
rp->pci + rp->size - 1);
}
+ sc->pe_setup_finalized = 0;
+
return (ofw_pcib_attach(dev));
}
@@ -542,12 +1069,12 @@
(word == ((1UL << (8 * width)) - 1))) {
if (error != OPAL_HARDWARE) {
opal_call(OPAL_PCI_EEH_FREEZE_STATUS, sc->phb_id,
- OPAL_PCI_DEFAULT_PE, vtophys(&eeh_state),
+ sc->reserved_pe_id, vtophys(&eeh_state),
vtophys(&err_type), NULL);
err_type = be16toh(err_type); /* XXX unused */
if (eeh_state != OPAL_EEH_STOPPED_NOT_FROZEN)
opal_call(OPAL_PCI_EEH_FREEZE_CLEAR,
- sc->phb_id, OPAL_PCI_DEFAULT_PE,
+ sc->phb_id, sc->reserved_pe_id,
OPAL_EEH_ACTION_CLEAR_FREEZE_ALL);
}
if (error != OPAL_SUCCESS)
@@ -591,16 +1118,706 @@
*/
if (error != OPAL_HARDWARE) {
opal_call(OPAL_PCI_EEH_FREEZE_CLEAR,
- sc->phb_id, OPAL_PCI_DEFAULT_PE,
+ sc->phb_id, sc->reserved_pe_id,
OPAL_EEH_ACTION_CLEAR_FREEZE_ALL);
}
}
}
+int
+opalpci_phb_is_pci_memory_window_64_bit(device_t phb, rman_res_t window_start)
+{
+ struct opalpci_softc *sc;
+
+ sc = device_get_softc(phb);
+
+ if (!sc->m64_window_present)
+ return 0;
+
+ if ((window_start >= sc->m64_window_base)
+ && (window_start < (sc->m64_window_base + sc->m64_window_size)))
+ return 1;
+
+ return 0;
+}
+
+/*
+ * Find the associated 64-bit memory window segment number (i.e PE#) for
+ * the given physical device address. This is intended to be used when
+ * the 64-bit memory window is configured in M64_SPLIT mode.
+ */
+
+uint64_t
+opalpci_phb_get_m64_pe_for_addr(device_t phb, rman_res_t addr)
+{
+ struct opalpci_softc *sc;
+
+ sc = device_get_softc(phb);
+
+ if (!sc->m64_window_present)
+ return OPAL_IODA_INVALID_PE;
+
+ /* Degenerate cases -- segment index is always zero here */
+ if ((sc->m64_segment_size == 0) ||
+ (sc->m64_segment_size == sc->m64_window_size))
+ return 0;
+
+ return (addr - sc->m64_window_base) / sc->m64_window_size;
+}
+
static int
-opalpci_route_interrupt(device_t bus, device_t dev, int pin)
+opalpci_add_slave_pe_to_master_pe(device_t phb, device_t child,
+ uint64_t master_pe, uint64_t desired_slave_pe_number)
+{
+ struct opalpci_softc *sc;
+ struct phb_pe_data_entry * master_pe_data_entry;
+ struct phb_pe_data_entry * slave_pe_data_entry;
+ struct phb_pe_map_entry * slave_pe_map_entry;
+ int err;
+
+ sc = device_get_softc(phb);
+
+ /* Retrieve pointer to PE data structure */
+ master_pe_data_entry = opalpci_get_phb_pe_data_entry(phb, master_pe);
+ if (!master_pe_data_entry) {
+ device_printf(phb,
+ "Unable to locate data structures for PE %02lx\n",
+ (uintmax_t)master_pe);
+ return (ENODEV);
+ }
+
+ slave_pe_data_entry = opalpci_get_phb_pe_data_entry(phb,
+ desired_slave_pe_number);
+ if (slave_pe_data_entry) {
+ device_printf(phb,
+ "PE %02lx already exists\n",
+ (uintmax_t)desired_slave_pe_number);
+ return (ENODEV);
+ }
+
+ /* Assign slave PE to device */
+ err = opal_call(OPAL_PCI_SET_PE, sc->phb_id, desired_slave_pe_number,
+ master_pe_data_entry->mapping.pci_bus_start_devfn,
+ master_pe_data_entry->mapping.opal_bus_filter,
+ master_pe_data_entry->mapping.opal_dev_filter,
+ master_pe_data_entry->mapping.opal_func_filter, OPAL_MAP_PE);
+ if (err == OPAL_SUCCESS) {
+ device_printf(phb, "Mapped PE# %lx to bus\n",
+ desired_slave_pe_number);
+ } else {
+ device_printf(phb, "PE mapping failed: %d\n", err);
+ return (ENXIO);
+ }
+
+ /* Mark slave PE as allocated */
+ slave_pe_map_entry = opalpci_add_phb_pe_map_entry(phb, child,
+ desired_slave_pe_number, 0);
+ if (slave_pe_map_entry == NULL) {
+ device_printf(phb, "PE map update failed: %d\n", err);
+ return (ENXIO);
+ }
+ slave_pe_map_entry->mapping.master_pe = master_pe;
+
+ /* Retrieve pointer to PE data structure */
+ slave_pe_data_entry = opalpci_get_phb_pe_data_entry(phb,
+ desired_slave_pe_number);
+ if (!slave_pe_data_entry) {
+ device_printf(phb,
+ "Unable to locate data structures for PE %02lx\n",
+ (uintmax_t)desired_slave_pe_number);
+ return (ENODEV);
+ }
+
+ /* Update the PE to device map with the allocated bus range */
+ slave_pe_data_entry->mapping.pci_bus_start_devfn =
+ master_pe_data_entry->mapping.pci_bus_start_devfn;
+ slave_pe_data_entry->mapping.pci_bus_end_devfn =
+ master_pe_data_entry->mapping.pci_bus_end_devfn;
+
+ /* Update the PE to device map with the OPAL filter settings */
+ slave_pe_data_entry->mapping.opal_bus_filter =
+ master_pe_data_entry->mapping.opal_bus_filter;
+ slave_pe_data_entry->mapping.opal_dev_filter =
+ master_pe_data_entry->mapping.opal_dev_filter;
+ slave_pe_data_entry->mapping.opal_func_filter =
+ master_pe_data_entry->mapping.opal_func_filter;
+
+ /* Initialize IOMMU structures */
+ opalpc_ioda2_create_tvt(phb, desired_slave_pe_number, 0,
+ roundup2(powerpc_ptob(Maxmem), max_tce_size(phb)),
+ POWERNV_FW_PAGE_SHIFT, 1);
+
+ /*
+ * Unfreeze non-config-space PCI operations. Let this fail silently
+ * if e.g. there is no current freeze.
+ */
+ opal_call(OPAL_PCI_EEH_FREEZE_CLEAR, sc->phb_id,
+ desired_slave_pe_number, OPAL_EEH_ACTION_CLEAR_FREEZE_ALL);
+
+ return 0;
+}
+
+static int
+opalpci_associate_pe_to_bus(device_t bus, device_t child, uint64_t pe,
+ rman_res_t start, rman_res_t end)
+{
+ struct ofw_pci_softc *sc;
+ struct opalpci_softc *phb_sc;
+ uint8_t opal_bus_filter;
+ uint32_t opal_resource_id;
+ int err;
+ struct phb_pe_map_entry * pe_map_entry;
+ struct phb_pe_data_entry * pe_data_entry;
+ rman_res_t count = (end - start) + 1;
+
+ sc = device_get_softc(bus);
+ phb_sc = device_get_softc(sc->sc_dev);
+
+ /* Assemble PCI resource ID for bus (devfn always == 0x0 for busses) */
+ opal_resource_id = start << 8;
+
+ /* Determine required bus filter mode */
+ switch (count) {
+ case 1:
+ opal_bus_filter = OPAL_PCI_BUS_ALL;
+ break;
+ case 2:
+ opal_bus_filter = OPAL_PCI_BUS_7BITS;
+ break;
+ case 4:
+ opal_bus_filter = OPAL_PCI_BUS_6BITS;
+ break;
+ case 8:
+ opal_bus_filter = OPAL_PCI_BUS_5BITS;
+ break;
+ case 16:
+ opal_bus_filter = OPAL_PCI_BUS_4BITS;
+ break;
+ case 32:
+ opal_bus_filter = OPAL_PCI_BUS_3BITS;
+ break;
+ default:
+ device_printf(sc->sc_dev,
+ "Invalid subordinate bus count %ld, defaulting "
+ "to exact bus match\n", count);
+ opal_bus_filter = OPAL_PCI_BUS_ALL;
+ }
+
+ if (bootverbose) {
+ if (count == 1)
+ device_printf(sc->sc_dev,
+ "Assigning subordinate bus 0x%lx\n",
+ start);
+ else
+ device_printf(sc->sc_dev,
+ "Assigning subordinate bus range [0x%lx-0x%lx]\n",
+ start, end);
+ }
+
+ err = opal_call(OPAL_PCI_SET_PE, phb_sc->phb_id, pe,
+ opal_resource_id, opal_bus_filter, OPAL_IGNORE_RID_DEVICE_NUMBER,
+ OPAL_IGNORE_RID_FUNC_NUMBER, OPAL_MAP_PE);
+ if (err == OPAL_SUCCESS) {
+ device_printf(sc->sc_dev, "Mapped PE# %lx to bus\n", pe);
+ } else {
+ device_printf(sc->sc_dev, "PE mapping failed: %d\n", err);
+ return (ENXIO);
+ }
+
+ /*
+ * The root bus PE is always allocated; only add a new entry for a
+ * subordinate bus or device
+ */
+ if (pe != phb_sc->root_bus_pe) {
+ pe_map_entry = opalpci_add_phb_pe_map_entry(sc->sc_dev, child,
+ pe, 1);
+ if (pe_map_entry == NULL) {
+ device_printf(sc->sc_dev, "PE map update failed: %d\n",
+ err);
+ return (ENXIO);
+ }
+
+ pe_data_entry = opalpci_get_phb_pe_data_entry(sc->sc_dev, pe);
+ if (pe_data_entry == NULL) {
+ device_printf(sc->sc_dev, "PE data update failed: %d\n",
+ err);
+ return (ENXIO);
+ }
+
+ /* Update the PE to device map with the allocated bus range */
+ pe_map_entry->mapping.pci_bus_start_devfn = start << 8;
+ pe_map_entry->mapping.pci_bus_end_devfn = end << 8;
+
+ /* Update the PE data structure with the OPAL filter settings */
+ pe_data_entry->mapping.pci_bus_start_devfn = start << 8;
+ pe_data_entry->mapping.pci_bus_end_devfn = end << 8;
+ pe_data_entry->mapping.opal_bus_filter = opal_bus_filter;
+ pe_data_entry->mapping.opal_dev_filter =
+ OPAL_IGNORE_RID_DEVICE_NUMBER;
+ pe_data_entry->mapping.opal_func_filter =
+ OPAL_IGNORE_RID_FUNC_NUMBER;
+ }
+
+ /* Initialize IOMMU structures */
+ opalpc_ioda2_create_tvt(sc->sc_dev, pe, 0, roundup2(powerpc_ptob(Maxmem),
+ max_tce_size(sc->sc_dev)), POWERNV_FW_PAGE_SHIFT, 1);
+
+ if (pe != phb_sc->root_bus_pe) {
+ /* Set up default 32-bit IOMMU mapping for PE */
+ err = opalpci_ioda2_set_default_dma_window(sc->sc_dev, pe);
+ if (err != 0)
+ return (err);
+ }
+
+ /*
+ * Unfreeze non-config-space PCI operations. Let this fail silently
+ * if e.g. there is no current freeze.
+ */
+ opal_call(OPAL_PCI_EEH_FREEZE_CLEAR, phb_sc->phb_id, pe,
+ OPAL_EEH_ACTION_CLEAR_FREEZE_ALL);
+
+ return 0;
+}
+
+static void
+opalpci_setup_pci_device_resources(device_t bus, device_t child, int type,
+ int rid, rman_res_t start, rman_res_t end, rman_res_t count)
+{
+ struct ofw_pci_softc *sc;
+ struct opalpci_softc *phb_sc;
+ struct phb_pe_data_entry *pe_data_entry;
+ struct phb_drc_list_entry *phb_drc_entry;
+ uint64_t pe;
+ uint64_t start_pe;
+ uint64_t base_pe;
+ uint8_t child_has_64_bit_bar;
+
+ sc = device_get_softc(bus);
+ phb_sc = device_get_softc(sc->sc_dev);
+
+ if (type == PCI_RES_BUS) {
+ if (start == phb_sc->root_bus_id) {
+ pe = phb_sc->root_bus_pe;
+ } else {
+ pe = opalpci_find_unused_phb_pe(sc->sc_dev);
+ if (pe == OPAL_IODA_INVALID_PE) {
+ device_printf(sc->sc_dev,
+ "No unused PE available\n");
+ return;
+ }
+
+ /* Select a correct base PE# if any 64-bit memory
+ * address(es) are in use
+ */
+ child_has_64_bit_bar = 0;
+ LIST_FOREACH(phb_drc_entry, &phb_sc->pe_drc_list,
+ next_entry) {
+ if ((phb_drc_entry->list.child == child) &&
+ (phb_drc_entry->list.type == SYS_RES_MEMORY)
+ && opalpci_phb_is_pci_memory_window_64_bit(
+ sc->sc_dev, phb_drc_entry->list.start)) {
+ start_pe = OPAL_IODA_INVALID_PE;
+ base_pe = OPAL_IODA_INVALID_PE;
+ child_has_64_bit_bar = 1;
+ start_pe =
+ opalpci_phb_get_m64_pe_for_addr(
+ sc->sc_dev,
+ phb_drc_entry->list.start);
+ if (opalpci_get_phb_pe_data_entry(
+ sc->sc_dev, start_pe)) {
+ /* PE is already reserved, skip... */
+ continue;
+ }
+ /*
+ * Find the lowest candidate PE# to use
+ * as the base PE# for the bus
+ */
+ if (start_pe < base_pe) {
+ base_pe = start_pe;
+ }
+ }
+
+ if (child_has_64_bit_bar &&
+ (base_pe != pe) &&
+ (base_pe != OPAL_IODA_INVALID_PE)) {
+ device_printf(sc->sc_dev,
+ "Overriding PE# %lx to PE# %lx due to "
+ "64-bit segmented memory constraint\n",
+ pe, base_pe);
+ pe = base_pe;
+ }
+ }
+ }
+ } else {
+ pe = opalpci_find_phb_pe_for_device(sc->sc_dev, child);
+ }
+
+ if (pe == OPAL_IODA_INVALID_PE) {
+ device_printf(sc->sc_dev,
+ "Unable to locate PE for device, aborting resource config\n");
+ return;
+ }
+
+ switch (type) {
+ case PCI_RES_BUS:
+ opalpci_associate_pe_to_bus(bus, child, pe, start, end);
+ break;
+ case SYS_RES_IOPORT:
+ device_printf(sc->sc_dev,
+ "No I/O port support, ignoring device I/O resource\n");
+ break;
+ case SYS_RES_MEMORY:
+ phb_sc = device_get_softc(sc->sc_dev);
+ if (phb_sc &&
+ opalpci_phb_is_pci_memory_window_64_bit(sc->sc_dev, start)) {
+ /* Retrieve pointer to PE data structures */
+ pe_data_entry =
+ opalpci_get_phb_pe_data_entry(sc->sc_dev, pe);
+ KASSERT(pe_data_entry != NULL,
+ ("PHB[%jd] Unable to locate data structures "
+ "for PE %02lx",
+ (uintmax_t)phb_sc->phb_id, (uintmax_t)pe));
+
+ /* Determine the start PE# for the 64-bit memory range */
+ /*
+ * TODO: Add slave PEs to create larger aggregate window
+ * if required range exceeds m64_window_size
+ */
+ KASSERT((end - start) <= phb_sc->m64_window_size,
+ ("PHB[%jd] 64-bit memory range exceeds 64-bit memory "
+ "window size for PE %02lx",
+ (uintmax_t)phb_sc->phb_id, (uintmax_t)pe));
+ start_pe = opalpci_phb_get_m64_pe_for_addr(sc->sc_dev,start);
+
+ /*
+ * Remap the master PE for this device if remap is
+ * allowed and the desired PE is not already allocated
+ * as a slave PE for this device
+ */
+ if ((start_pe != pe) &&
+ !opalpci_get_phb_pe_data_entry(sc->sc_dev, start_pe)) {
+ device_printf(sc->sc_dev,
+ "Adding slave PE# %lx to PE# %lx due to 64-bit "
+ "segmented memory constraint\n",
+ start_pe, pe);
+ if (opalpci_add_slave_pe_to_master_pe(
+ sc->sc_dev, child, pe, start_pe)) {
+ device_printf(sc->sc_dev,
+ "Unable to allocate slave PE# %lx, "
+ "64-bit memory accesses will fail\n",
+ start_pe);
+ }
+ }
+ }
+ break;
+ }
+}
+
+static void
+opalpci_activate_pci_device_resources(device_t bus, device_t child, int type,
+ int rid, rman_res_t start, rman_res_t end, rman_res_t count)
+{
+ struct ofw_pci_softc *sc;
+ struct opalpci_softc *phb_sc;
+ struct phb_pe_data_entry * pe_data_entry;
+ int i, err;
+ uint64_t pe;
+ uint64_t window_start;
+ uint64_t window_index;
+
+ sc = device_get_softc(bus);
+ phb_sc = device_get_softc(sc->sc_dev);
+
+ /* Busses are handled in setup above */
+ if (type == PCI_RES_BUS)
+ return;
+
+ pe = opalpci_find_phb_pe_for_device(sc->sc_dev, child);
+ if (pe == OPAL_IODA_INVALID_PE) {
+ device_printf(sc->sc_dev,
+ "Unable to locate PE for device, aborting resource "
+ "activation\n");
+ return;
+ }
+
+ switch (type) {
+ case SYS_RES_IOPORT:
+ device_printf(sc->sc_dev,
+ "No I/O port support, ignoring device I/O resource\n");
+ break;
+ case SYS_RES_MEMORY:
+ phb_sc = device_get_softc(sc->sc_dev);
+ if (phb_sc &&
+ !opalpci_phb_is_pci_memory_window_64_bit(sc->sc_dev, start)) {
+ /* Retrieve pointer to PE data structures */
+ pe_data_entry =
+ opalpci_get_phb_pe_data_entry(sc->sc_dev, pe);
+ KASSERT(pe_data_entry != NULL,
+ ("PHB[%jd] Unable to locate data structures "
+ "for PE %02lx",
+ (uintmax_t)phb_sc->phb_id, (uintmax_t)pe));
+
+ /*
+ * Turn on 32-bit MMIO, mapped to PE
+ */
+ window_start = start - phb_sc->m32_pci_base;
+ window_index = window_start / phb_sc->m32_segment_size;
+
+ for (i = window_index; i < phb_sc->max_pe_count; i++) {
+ err = opal_call(OPAL_PCI_MAP_PE_MMIO_WINDOW,
+ phb_sc->phb_id, pe, OPAL_M32_WINDOW_TYPE,
+ 0, i);
+ if (err == OPAL_SUCCESS)
+ pe_data_entry->mapping.
+ active_32_bit_mmio_segments[i] = 1;
+ else
+ device_printf(sc->sc_dev,
+ "PE# %lx MMIO %d map failed: %d\n",
+ pe, i, err);
+
+ /*
+ * Stop assigning segments once the end of the
+ * desired 32-bit window is reached
+ */
+ window_start += phb_sc->m32_segment_size;
+ if (window_start > (end - phb_sc->m32_pci_base)) {
+ break;
+ }
+ }
+
+ if (bootverbose)
+ device_printf(sc->sc_dev,
+ "Enabled 32-bit memory window on PE# %lx "
+ "at 0x%lx size 0x%lx\n",
+ pe, start,
+ end - start);
+ }
+ break;
+ }
+}
+
+static void
+scan_phb_pci_bus_structure(device_t phb, device_t dev)
+{
+ device_t *children;
+ device_t *subchildren;
+ int child_count;
+ int subchild_count;
+ device_t *child;
+ device_t parent;
+ int i;
+ struct resource_list *rl;
+ struct opalpci_softc *phb_sc;
+ struct phb_drc_list_entry *phb_drc_entry;
+
+ phb_sc = device_get_softc(phb);
+
+ if (bootverbose) {
+ device_printf(dev,
+ "Scanning bus (devclass '%s')\n",
+ device_get_name(dev));
+ }
+
+ if (device_get_children(dev, &children, &child_count))
+ return;
+
+ /* Assign all busses first */
+ for (i = 0, child = children; i < child_count; i++, child++) {
+ if (device_get_children(*child, &subchildren, &subchild_count))
+ continue;
+
+ rl = NULL;
+
+ /* Retrieve all resources present on PCI bus */
+ rl = BUS_GET_RESOURCE_LIST(dev, *child);
+
+ if (rl) {
+ if (bootverbose)
+ device_printf(phb, "Recording child resources\n");
+
+ struct resource_list_entry *rle;
+ STAILQ_FOREACH(rle, rl, link) {
+ if ((rle->type != PCI_RES_BUS) &&
+ (rle->type != SYS_RES_MEMORY) &&
+ (rle->type != SYS_RES_IOPORT))
+ continue;
+
+ if (bootverbose)
+ device_printf(phb, "Found child "
+ "resource type %d, start 0x%lx, "
+ "end 0x%lx\n",
+ rle->type, rle->start, rle->end);
+
+ phb_drc_entry = malloc(
+ sizeof(struct phb_drc_list_entry),
+ M_OPALPCI_PHB, M_WAITOK | M_ZERO);
+
+ if (!phb_drc_entry) {
+ device_printf(phb,
+ "Unable to allocate DRC memory\n");
+ continue;
+ }
+
+ /* Find parent device */
+ parent = dev;
+ while ((parent != NULL) &&
+ (strcmp(device_get_name(parent), "pcib")
+ != 0)) {
+ parent = device_get_parent(parent);
+ }
+
+ if (parent == NULL) {
+ device_printf(dev,
+ "Unable to locate parent PCI bus\n");
+ continue;
+ }
+
+ phb_drc_entry->list.bus = phb;
+ if (rle->type == PCI_RES_BUS)
+ phb_drc_entry->list.child = *child;
+ else
+ phb_drc_entry->list.child = parent;
+ phb_drc_entry->list.type = rle->type;
+ phb_drc_entry->list.rid = rle->rid;
+ phb_drc_entry->list.start = rle->start;
+ phb_drc_entry->list.end = rle->end;
+ phb_drc_entry->list.count = rle->count;
+ phb_drc_entry->list.flags = rle->flags;
+
+ LIST_INSERT_HEAD(
+ &phb_sc->pe_drc_list,
+ phb_drc_entry, next_entry);
+ }
+ }
+
+ if (subchild_count > 0) {
+ /* Scan bus for children */
+ scan_phb_pci_bus_structure(phb, *child);
+ }
+ free(subchildren, M_TEMP);
+ subchildren = NULL;
+ }
+ free(children, M_TEMP);
+ children = NULL;
+}
+
+static void
+opalpci_finalize_pe_setup(device_t dev)
{
+ struct opalpci_softc *sc;
+ struct phb_drc_list_entry *phb_drc_entry, *phb_drc_entry_tmp;
+
+ sc = device_get_softc(dev);
+ bus_generic_new_pass(dev);
+
+ if (sc->pe_setup_finalized)
+ return;
+
+ /* Scan all PCI resources underneath the PHB */
+ scan_phb_pci_bus_structure(dev, dev);
+
+ /* Assign all bus PEs first... */
+ LIST_FOREACH_SAFE(phb_drc_entry, &sc->pe_drc_list, next_entry,
+ phb_drc_entry_tmp) {
+ if (phb_drc_entry->list.type != PCI_RES_BUS)
+ continue;
+
+ opalpci_setup_pci_device_resources(phb_drc_entry->list.bus,
+ phb_drc_entry->list.child,
+ phb_drc_entry->list.type,
+ phb_drc_entry->list.rid,
+ phb_drc_entry->list.start,
+ phb_drc_entry->list.end,
+ phb_drc_entry->list.count);
+
+ opalpci_activate_pci_device_resources(phb_drc_entry->list.bus,
+ phb_drc_entry->list.child,
+ phb_drc_entry->list.type,
+ phb_drc_entry->list.rid,
+ phb_drc_entry->list.start,
+ phb_drc_entry->list.end,
+ phb_drc_entry->list.count);
+
+ /* Remove the processed entry from the list */
+ LIST_REMOVE(phb_drc_entry, next_entry);
+ free(phb_drc_entry, M_OPALPCI_PHB);
+ }
+ /* .. then configure device resource windows */
+ LIST_FOREACH_SAFE(phb_drc_entry, &sc->pe_drc_list, next_entry,
+ phb_drc_entry_tmp) {
+ if (phb_drc_entry->list.type == PCI_RES_BUS)
+ continue;
+
+ opalpci_setup_pci_device_resources(phb_drc_entry->list.bus,
+ phb_drc_entry->list.child,
+ phb_drc_entry->list.type,
+ phb_drc_entry->list.rid,
+ phb_drc_entry->list.start,
+ phb_drc_entry->list.end,
+ phb_drc_entry->list.count);
+
+ opalpci_activate_pci_device_resources(phb_drc_entry->list.bus,
+ phb_drc_entry->list.child,
+ phb_drc_entry->list.type,
+ phb_drc_entry->list.rid,
+ phb_drc_entry->list.start,
+ phb_drc_entry->list.end,
+ phb_drc_entry->list.count);
+
+ /* Remove the processed entry from the list */
+ LIST_REMOVE(phb_drc_entry, next_entry);
+ free(phb_drc_entry, M_OPALPCI_PHB);
+ }
+
+ sc->pe_setup_finalized = 1;
+}
+
+/* We need to hook the bus release function in order to release PHB PEs for
+ * each subordinate bus.
+ *
+ * Overall, this use the logic from OFW PCI bus driver, with some additions
+ * for PCI_RES_BUS types.
+ */
+static int
+opalpci_release_resource(device_t bus, device_t child, struct resource *res)
+{
+ struct ofw_pci_softc *sc;
+ struct opalpci_softc *phb_sc;
+ int type;
+
+ type = rman_get_type(res);
+ sc = device_get_softc(bus);
+
+ phb_sc = device_get_softc(sc->sc_dev);
+
+ if (phb_sc &&
+ ((type == PCI_RES_BUS) ||
+ (type == SYS_RES_MEMORY) ||
+ (type == SYS_RES_IOPORT))) {
+ if (!phb_sc->pe_setup_finalized) {
+ device_printf(sc->sc_dev,
+ "Unable to release resource, initial PE activation "
+ "still pending on PHB\n");
+ return (EBUSY);
+ }
+ }
+
+ sc = device_get_softc(bus);
+ switch (rman_get_type(res)) {
+ case PCI_RES_BUS:
+ return (pci_domain_release_bus(sc->sc_pci_domain, child, res));
+ case SYS_RES_MEMORY:
+ case SYS_RES_IOPORT:
+ return (bus_generic_rman_release_resource(bus, child, res));
+ default:
+ return (bus_generic_release_resource(bus, child, res));
+ }
+}
+
+static int
+opalpci_route_interrupt(device_t bus, device_t dev, int pin)
+{
return (pin);
}
@@ -661,31 +1878,49 @@
{
struct opalpci_softc *sc;
struct pci_devinfo *dinfo;
+ struct phb_pe_data_entry * pe_data_entry;
int err, xive;
+ uint32_t devfn;
+ uint64_t pe;
sc = device_get_softc(dev);
if (sc->msi_vmem == NULL)
return (ENODEV);
+ devfn = ((pci_get_slot(child) & 0x1f) << 3) |
+ (pci_get_function(child) & 0x7);
+ pe = opalpci_find_phb_pe_for_devfn(dev, devfn & 0xff << 8);
+
+ pe_data_entry = opalpci_get_phb_pe_data_entry(dev, pe);
+ KASSERT(pe_data_entry != NULL,
+ ("PHB[%jd] Unable to allocate data structures for PE %02lx",
+ (uintmax_t)sc->phb_id, (uintmax_t)pe));
+
+ pe_data_entry->mapping.msi_activated = 1;
+
xive = irq - sc->base_msi_irq - sc->msi_base;
- opal_call(OPAL_PCI_SET_XIVE_PE, sc->phb_id, OPAL_PCI_DEFAULT_PE, xive);
+ err = opal_call(OPAL_PCI_SET_XIVE_PE, sc->phb_id, pe, xive);
+ if (err != OPAL_SUCCESS) {
+ device_printf(child, "XIVE PE assignment error: %d\n", err);
+ return ENXIO;
+ }
dinfo = device_get_ivars(child);
if (dinfo->cfg.msi.msi_alloc > 0 &&
(dinfo->cfg.msi.msi_ctrl & PCIM_MSICTRL_64BIT) == 0) {
uint32_t msi32;
err = opal_call(OPAL_GET_MSI_32, sc->phb_id,
- OPAL_PCI_DEFAULT_PE, xive, 1, vtophys(&msi32),
+ pe, xive, 1, vtophys(&msi32),
vtophys(data));
*addr = be32toh(msi32);
} else {
err = opal_call(OPAL_GET_MSI_64, sc->phb_id,
- OPAL_PCI_DEFAULT_PE, xive, 1, vtophys(addr), vtophys(data));
+ pe, xive, 1, vtophys(addr), vtophys(data));
*addr = be64toh(*addr);
}
*data = be32toh(*data);
- if (bootverbose && err != 0)
+ if (bootverbose && err != OPAL_SUCCESS)
device_printf(child, "OPAL MSI mapping error: %d\n", err);
return ((err == 0) ? 0 : ENXIO);
@@ -714,7 +1949,40 @@
opalpci_get_dma_tag(device_t dev, device_t child)
{
struct opalpci_softc *sc;
+ int err;
sc = device_get_softc(dev);
+
+ /* DMA tag already exists, return existing tag */
+ if (sc->ofw_sc.sc_dmat != NULL)
+ return (sc->ofw_sc.sc_dmat);
+
+ /* Create the parent DMA tag */
+
+ /*
+ * Constrain it to POWER8 PHB3 (ioda2) and POWER9 PHB4 (ioda3),
+ * treating both the same way
+ */
+ if (ofw_bus_is_compatible(dev, "ibm,ioda2-phb") ||
+ ofw_bus_is_compatible(dev, "ibm,ioda3-phb")) {
+
+ err = bus_dma_tag_create(bus_get_dma_tag(dev), /* parent */
+ 1, 0, /* alignment, bounds */
+ sc->m32_pci_base - 1, /* lowaddr */
+ BUS_SPACE_MAXADDR_32BIT, /* highaddr */
+ NULL, NULL, /* filter, filterarg */
+ BUS_SPACE_MAXSIZE, /* maxsize */
+ BUS_SPACE_UNRESTRICTED, /* nsegments */
+ BUS_SPACE_MAXSIZE, /* maxsegsize */
+ 0, /* flags */
+ NULL, NULL, /* lockfunc, lockarg */
+ &sc->ofw_sc.sc_dmat);
+
+ if (err != 0) {
+ device_printf(dev, "Failed to create DMA tag\n");
+ return (NULL);
+ }
+ }
+
return (sc->ofw_sc.sc_dmat);
-}
+}
\ No newline at end of file
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Sat, Aug 15, 8:54 PM (9 h, 34 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
36800838
Default Alt Text
D54745.id.diff (61 KB)
Attached To
Mode
D54745: powerpc64/powernv: Enable Partitionable Endpoint (PE) support
Attached
Detach File
Event Timeline
Log In to Comment