diff options
Diffstat (limited to 'sys/arm64/iommu/smmu.c')
-rw-r--r-- | sys/arm64/iommu/smmu.c | 248 |
1 files changed, 166 insertions, 82 deletions
diff --git a/sys/arm64/iommu/smmu.c b/sys/arm64/iommu/smmu.c index 1f2d7283be72..1d1996a69027 100644 --- a/sys/arm64/iommu/smmu.c +++ b/sys/arm64/iommu/smmu.c @@ -65,7 +65,7 @@ * Queues. * * Register interface and Memory-based circular buffer queues are used - * to inferface SMMU. + * to interface SMMU. * * These are a Command queue for commands to send to the SMMU and an Event * queue for event/fault reports from the SMMU. Optionally PRI queue is @@ -90,21 +90,20 @@ #include "opt_platform.h" #include "opt_acpi.h" -#include <sys/cdefs.h> -__FBSDID("$FreeBSD$"); - #include <sys/param.h> #include <sys/bitstring.h> #include <sys/bus.h> #include <sys/kernel.h> #include <sys/malloc.h> +#include <sys/mutex.h> #include <sys/rman.h> #include <sys/lock.h> +#include <sys/sysctl.h> #include <sys/tree.h> #include <sys/taskqueue.h> #include <vm/vm.h> #include <vm/vm_page.h> -#if DEV_ACPI +#ifdef DEV_ACPI #include <contrib/dev/acpica/include/acpi.h> #include <dev/acpica/acpivar.h> #endif @@ -113,6 +112,14 @@ __FBSDID("$FreeBSD$"); #include <dev/iommu/iommu.h> #include <arm64/iommu/iommu_pmap.h> +#include <machine/bus.h> + +#ifdef FDT +#include <dev/fdt/fdt_common.h> +#include <dev/ofw/ofw_bus.h> +#include <dev/ofw/ofw_bus_subr.h> +#endif + #include "iommu.h" #include "iommu_if.h" @@ -139,11 +146,15 @@ __FBSDID("$FreeBSD$"); #define SMMU_Q_ALIGN (64 * 1024) +#define MAXADDR_48BIT 0xFFFFFFFFFFFFUL +#define MAXADDR_52BIT 0xFFFFFFFFFFFFFUL + static struct resource_spec smmu_spec[] = { { SYS_RES_MEMORY, 0, RF_ACTIVE }, { SYS_RES_IRQ, 0, RF_ACTIVE }, - { SYS_RES_IRQ, 1, RF_ACTIVE }, + { SYS_RES_IRQ, 1, RF_ACTIVE | RF_OPTIONAL }, { SYS_RES_IRQ, 2, RF_ACTIVE }, + { SYS_RES_IRQ, 3, RF_ACTIVE }, RESOURCE_SPEC_END }; @@ -765,8 +776,8 @@ smmu_init_ste_s1(struct smmu_softc *sc, struct smmu_cd *cd, return (0); } -static int -smmu_init_ste(struct smmu_softc *sc, struct smmu_cd *cd, int sid, bool bypass) +static uint64_t * +smmu_get_ste_addr(struct smmu_softc *sc, int sid) { struct smmu_strtab *strtab; struct l1_desc *l1_desc; @@ -783,6 +794,16 @@ smmu_init_ste(struct smmu_softc *sc, struct smmu_cd *cd, int sid, bool bypass) STRTAB_STE_DWORDS * 8 * sid); }; + return (addr); +} + +static int +smmu_init_ste(struct smmu_softc *sc, struct smmu_cd *cd, int sid, bool bypass) +{ + uint64_t *addr; + + addr = smmu_get_ste_addr(sc, sid); + if (bypass) smmu_init_ste_bypass(sc, sid, addr); else @@ -793,6 +814,21 @@ smmu_init_ste(struct smmu_softc *sc, struct smmu_cd *cd, int sid, bool bypass) return (0); } +static void +smmu_deinit_ste(struct smmu_softc *sc, int sid) +{ + uint64_t *ste; + + ste = smmu_get_ste_addr(sc, sid); + ste[0] = 0; + + smmu_invalidate_sid(sc, sid); + smmu_sync_cd(sc, sid, 0, true); + smmu_invalidate_sid(sc, sid); + + smmu_sync(sc); +} + static int smmu_init_cd(struct smmu_softc *sc, struct smmu_domain *domain) { @@ -801,7 +837,7 @@ smmu_init_cd(struct smmu_softc *sc, struct smmu_domain *domain) uint64_t val; vm_size_t size; struct smmu_cd *cd; - pmap_t p; + struct smmu_pmap *p; size = 1 * (CD_DWORDS << 3); @@ -836,8 +872,8 @@ smmu_init_cd(struct smmu_softc *sc, struct smmu_domain *domain) val |= ((64 - sc->ias) << CD0_T0SZ_S); val |= CD0_IPS_48BITS; - paddr = p->pm_l0_paddr & CD1_TTB0_M; - KASSERT(paddr == p->pm_l0_paddr, ("bad allocation 1")); + paddr = p->sp_l0_paddr & CD1_TTB0_M; + KASSERT(paddr == p->sp_l0_paddr, ("bad allocation 1")); ptr[1] = paddr; ptr[2] = 0; @@ -979,6 +1015,10 @@ smmu_init_l1_entry(struct smmu_softc *sc, int sid) strtab = &sc->strtab; l1_desc = &strtab->l1[sid >> STRTAB_SPLIT]; + if (l1_desc->va) { + /* Already allocated. */ + return (0); + } size = 1 << (STRTAB_SPLIT + ilog2(STRTAB_STE_DWORDS) + 3); @@ -1010,7 +1050,7 @@ smmu_init_l1_entry(struct smmu_softc *sc, int sid) return (0); } -static void +static void __unused smmu_deinit_l1_entry(struct smmu_softc *sc, int sid) { struct smmu_strtab *strtab; @@ -1025,10 +1065,8 @@ smmu_deinit_l1_entry(struct smmu_softc *sc, int sid) STRTAB_L1_DESC_DWORDS * 8 * i); *addr = 0; - if (sc->features & SMMU_FEATURE_2_LVL_STREAM_TABLE) { - l1_desc = &strtab->l1[sid >> STRTAB_SPLIT]; - contigfree(l1_desc->va, l1_desc->size, M_SMMU); - } + l1_desc = &strtab->l1[sid >> STRTAB_SPLIT]; + contigfree(l1_desc->va, l1_desc->size, M_SMMU); } static int @@ -1129,7 +1167,7 @@ smmu_enable_interrupts(struct smmu_softc *sc) return (0); } -#if DEV_ACPI +#ifdef DEV_ACPI static void smmu_configure_intr(struct smmu_softc *sc, struct resource *res) { @@ -1155,14 +1193,15 @@ smmu_setup_interrupts(struct smmu_softc *sc) dev = sc->dev; -#if DEV_ACPI +#ifdef DEV_ACPI /* * Configure SMMU interrupts as EDGE triggered manually * as ACPI tables carries no information for that. */ smmu_configure_intr(sc, sc->res[1]); - smmu_configure_intr(sc, sc->res[2]); + /* PRIQ is not in use. */ smmu_configure_intr(sc, sc->res[3]); + smmu_configure_intr(sc, sc->res[4]); #endif error = bus_setup_intr(dev, sc->res[1], INTR_TYPE_MISC, @@ -1172,7 +1211,7 @@ smmu_setup_interrupts(struct smmu_softc *sc) return (ENXIO); } - error = bus_setup_intr(dev, sc->res[3], INTR_TYPE_MISC, + error = bus_setup_intr(dev, sc->res[4], INTR_TYPE_MISC, smmu_gerr_intr, NULL, sc, &sc->intr_cookie[2]); if (error) { device_printf(dev, "Couldn't setup Gerr interrupt handler\n"); @@ -1317,7 +1356,7 @@ smmu_check_features(struct smmu_softc *sc) switch (reg & IDR0_TTENDIAN_M) { case IDR0_TTENDIAN_MIXED: if (bootverbose) - device_printf(sc->dev, "Mixed endianess supported.\n"); + device_printf(sc->dev, "Mixed endianness supported.\n"); sc->features |= SMMU_FEATURE_TT_LE; sc->features |= SMMU_FEATURE_TT_BE; break; @@ -1535,12 +1574,6 @@ smmu_attach(device_t dev) mtx_init(&sc->sc_mtx, device_get_nameunit(sc->dev), "smmu", MTX_DEF); - error = bus_alloc_resources(dev, smmu_spec, sc->res); - if (error) { - device_printf(dev, "Couldn't allocate resources.\n"); - return (ENXIO); - } - error = smmu_setup_interrupts(sc); if (error) { bus_release_resources(dev, smmu_spec, sc->res); @@ -1619,7 +1652,7 @@ smmu_unmap(device_t dev, struct iommu_domain *iodom, dprintf("%s: %lx, %ld, domain %d\n", __func__, va, size, domain->asid); for (i = 0; i < size; i += PAGE_SIZE) { - if (pmap_smmu_remove(&domain->p, va) == 0) { + if (smmu_pmap_remove(&domain->p, va) == 0) { /* pmap entry removed, invalidate TLB. */ smmu_tlbi_va(sc, va, domain->asid); } else { @@ -1654,7 +1687,7 @@ smmu_map(device_t dev, struct iommu_domain *iodom, for (i = 0; size > 0; size -= PAGE_SIZE) { pa = VM_PAGE_TO_PHYS(ma[i++]); - error = pmap_smmu_enter(&domain->p, va, pa, prot, 0); + error = smmu_pmap_enter(&domain->p, va, pa, prot, 0); if (error) return (error); smmu_tlbi_va(sc, va, domain->asid); @@ -1669,6 +1702,7 @@ smmu_map(device_t dev, struct iommu_domain *iodom, static struct iommu_domain * smmu_domain_alloc(device_t dev, struct iommu_unit *iommu) { + struct iommu_domain *iodom; struct smmu_domain *domain; struct smmu_unit *unit; struct smmu_softc *sc; @@ -1691,8 +1725,7 @@ smmu_domain_alloc(device_t dev, struct iommu_unit *iommu) domain->asid = (uint16_t)new_asid; - iommu_pmap_pinit(&domain->p); - PMAP_LOCK_INIT(&domain->p); + smmu_pmap_pinit(&domain->p); error = smmu_init_cd(sc, domain); if (error) { @@ -1709,7 +1742,15 @@ smmu_domain_alloc(device_t dev, struct iommu_unit *iommu) LIST_INSERT_HEAD(&unit->domain_list, domain, next); IOMMU_UNLOCK(iommu); - return (&domain->iodom); + iodom = &domain->iodom; + + /* + * Use 48-bit address space regardless of VAX bit + * as we need 64k IOMMU_PAGE_SIZE for 52-bit space. + */ + iodom->end = MAXADDR_48BIT; + + return (iodom); } static void @@ -1727,8 +1768,8 @@ smmu_domain_free(device_t dev, struct iommu_domain *iodom) cd = domain->cd; - iommu_pmap_remove_pages(&domain->p); - iommu_pmap_release(&domain->p); + smmu_pmap_remove_pages(&domain->p); + smmu_pmap_release(&domain->p); smmu_tlbi_asid(sc, domain->asid); smmu_asid_free(sc, domain->asid); @@ -1754,41 +1795,80 @@ smmu_set_buswide(device_t dev, struct smmu_domain *domain, return (0); } +static int +smmu_pci_get_sid(device_t child, u_int *xref0, u_int *sid0) +{ + struct pci_id_ofw_iommu pi; + int err; + + err = pci_get_id(child, PCI_ID_OFW_IOMMU, (uintptr_t *)&pi); + if (err == 0) { + if (sid0) + *sid0 = pi.id; + if (xref0) + *xref0 = pi.xref; + } + + return (err); +} + static struct iommu_ctx * smmu_ctx_alloc(device_t dev, struct iommu_domain *iodom, device_t child, bool disabled) { struct smmu_domain *domain; + struct smmu_ctx *ctx; + + domain = (struct smmu_domain *)iodom; + + ctx = malloc(sizeof(struct smmu_ctx), M_SMMU, M_WAITOK | M_ZERO); + ctx->dev = child; + ctx->domain = domain; + if (disabled) + ctx->bypass = true; + + IOMMU_DOMAIN_LOCK(iodom); + LIST_INSERT_HEAD(&domain->ctx_list, ctx, next); + IOMMU_DOMAIN_UNLOCK(iodom); + + return (&ctx->ioctx); +} + +static int +smmu_ctx_init(device_t dev, struct iommu_ctx *ioctx) +{ + struct smmu_domain *domain; + struct iommu_domain *iodom; struct smmu_softc *sc; struct smmu_ctx *ctx; - uint16_t rid; - u_int xref, sid; - int seg; + devclass_t pci_class; + u_int sid; int err; + ctx = (struct smmu_ctx *)ioctx; + sc = device_get_softc(dev); - domain = (struct smmu_domain *)iodom; - seg = pci_get_domain(child); - rid = pci_get_rid(child); - err = acpi_iort_map_pci_smmuv3(seg, rid, &xref, &sid); - if (err) - return (NULL); + domain = ctx->domain; + iodom = (struct iommu_domain *)domain; - if (sc->features & SMMU_FEATURE_2_LVL_STREAM_TABLE) { - err = smmu_init_l1_entry(sc, sid); + pci_class = devclass_find("pci"); + if (device_get_devclass(device_get_parent(ctx->dev)) == pci_class) { + err = smmu_pci_get_sid(ctx->dev, NULL, &sid); if (err) - return (NULL); + return (err); + + ioctx->rid = pci_get_rid(dev); + ctx->sid = sid; + ctx->vendor = pci_get_vendor(ctx->dev); + ctx->device = pci_get_device(ctx->dev); } - ctx = malloc(sizeof(struct smmu_ctx), M_SMMU, M_WAITOK | M_ZERO); - ctx->vendor = pci_get_vendor(child); - ctx->device = pci_get_device(child); - ctx->dev = child; - ctx->sid = sid; - ctx->domain = domain; - if (disabled) - ctx->bypass = true; + if (sc->features & SMMU_FEATURE_2_LVL_STREAM_TABLE) { + err = smmu_init_l1_entry(sc, ctx->sid); + if (err) + return (err); + } /* * Neoverse N1 SDP: @@ -1799,14 +1879,11 @@ smmu_ctx_alloc(device_t dev, struct iommu_domain *iodom, device_t child, smmu_init_ste(sc, domain->cd, ctx->sid, ctx->bypass); - if (iommu_is_buswide_ctx(iodom->iommu, pci_get_bus(ctx->dev))) - smmu_set_buswide(dev, domain, ctx); + if (device_get_devclass(device_get_parent(ctx->dev)) == pci_class) + if (iommu_is_buswide_ctx(iodom->iommu, pci_get_bus(ctx->dev))) + smmu_set_buswide(dev, domain, ctx); - IOMMU_DOMAIN_LOCK(iodom); - LIST_INSERT_HEAD(&domain->ctx_list, ctx, next); - IOMMU_DOMAIN_UNLOCK(iodom); - - return (&ctx->ioctx); + return (0); } static void @@ -1820,7 +1897,7 @@ smmu_ctx_free(device_t dev, struct iommu_ctx *ioctx) sc = device_get_softc(dev); ctx = (struct smmu_ctx *)ioctx; - smmu_deinit_l1_entry(sc, ctx->sid); + smmu_deinit_ste(sc, ctx->sid); LIST_REMOVE(ctx, next); @@ -1852,7 +1929,7 @@ smmu_ctx_lookup_by_sid(device_t dev, u_int sid) static struct iommu_ctx * smmu_ctx_lookup(device_t dev, device_t child) { - struct iommu_unit *iommu; + struct iommu_unit *iommu __diagused; struct smmu_softc *sc; struct smmu_domain *domain; struct smmu_unit *unit; @@ -1883,29 +1960,14 @@ static int smmu_find(device_t dev, device_t child) { struct smmu_softc *sc; - u_int xref, sid; - uint16_t rid; - int error; - int seg; + u_int xref; + int err; sc = device_get_softc(dev); - rid = pci_get_rid(child); - seg = pci_get_domain(child); - - /* - * Find an xref of an IOMMU controller that serves traffic for dev. - */ -#ifdef DEV_ACPI - error = acpi_iort_map_pci_smmuv3(seg, rid, &xref, &sid); - if (error) { - /* Could not find reference to an SMMU device. */ + err = smmu_pci_get_sid(child, &xref, NULL); + if (err) return (ENOENT); - } -#else - /* TODO: add FDT support. */ - return (ENXIO); -#endif /* Check if xref is ours. */ if (xref != sc->xref) @@ -1914,6 +1976,24 @@ smmu_find(device_t dev, device_t child) return (0); } +#ifdef FDT +static int +smmu_ofw_md_data(device_t dev, struct iommu_ctx *ioctx, pcell_t *cells, + int ncells) +{ + struct smmu_ctx *ctx; + + ctx = (struct smmu_ctx *)ioctx; + + if (ncells != 1) + return (-1); + + ctx->sid = cells[0]; + + return (0); +} +#endif + static device_method_t smmu_methods[] = { /* Device interface */ DEVMETHOD(device_detach, smmu_detach), @@ -1925,8 +2005,12 @@ static device_method_t smmu_methods[] = { DEVMETHOD(iommu_domain_alloc, smmu_domain_alloc), DEVMETHOD(iommu_domain_free, smmu_domain_free), DEVMETHOD(iommu_ctx_alloc, smmu_ctx_alloc), + DEVMETHOD(iommu_ctx_init, smmu_ctx_init), DEVMETHOD(iommu_ctx_free, smmu_ctx_free), DEVMETHOD(iommu_ctx_lookup, smmu_ctx_lookup), +#ifdef FDT + DEVMETHOD(iommu_ofw_md_data, smmu_ofw_md_data), +#endif /* Bus interface */ DEVMETHOD(bus_read_ivar, smmu_read_ivar), |