From 89ecfb4cb77a591ec53b0d9d8a3003716a385640 Mon Sep 17 00:00:00 2001 From: landonf Date: Tue, 21 Nov 2017 01:54:48 +0000 Subject: [PATCH] Preemptively map MIPS INTRNG interrupts on non-FDT MIPS targets This replaces a partial workaround introduced in r305527 that was incompatible with nested INTRNG interrupt controllers if not also using FDT. On non-FDT MIPS INTRNG targets, we now preemptively produce a set of fixed mappings for the MIPS IRQ range during nexus attach. On FDT targets, OFW_BUS_MAP_INTR() remains responsible for mapping the MIPS IRQs. Approved by: adrian (mentor) Sponsored by: The FreeBSD Foundation Differential Revision: https://reviews.freebsd.org/D12385 --- sys/mips/include/intr.h | 11 +- sys/mips/mips/mips_pic.c | 391 +++++++++++++++++++++++++++++++-------- sys/mips/mips/nexus.c | 34 ++-- 3 files changed, 335 insertions(+), 101 deletions(-) diff --git a/sys/mips/include/intr.h b/sys/mips/include/intr.h index ee8d09336fd8..ed32f4f96b04 100644 --- a/sys/mips/include/intr.h +++ b/sys/mips/include/intr.h @@ -55,16 +55,23 @@ #define NIRQ MIPS_NIRQ #endif +#ifndef FDT +#define MIPS_PIC_XREF 1 /**< unique xref */ +#endif + #define INTR_IRQ_NSPC_SWI 4 +/* MIPS32 PIC APIs */ +int mips_pic_map_fixed_intrs(void); +int mips_pic_activate_intr(device_t child, struct resource *r); +int mips_pic_deactivate_intr(device_t child, struct resource *r); + /* MIPS compatibility for legacy mips code */ void cpu_init_interrupts(void); void cpu_establish_hardintr(const char *, driver_filter_t *, driver_intr_t *, void *, int, int, void **); void cpu_establish_softintr(const char *, driver_filter_t *, void (*)(void*), void *, int, int, void **); -int cpu_create_intr_map(int); -struct resource *cpu_get_irq_resource(int); /* MIPS interrupt C entry point */ void cpu_intr(struct trapframe *); diff --git a/sys/mips/mips/mips_pic.c b/sys/mips/mips/mips_pic.c index 18ecb5c15da7..86c9eaac8b50 100644 --- a/sys/mips/mips/mips_pic.c +++ b/sys/mips/mips/mips_pic.c @@ -2,8 +2,12 @@ * Copyright (c) 2015 Alexander Kabaev * Copyright (c) 2006 Oleksandr Tymoshenko * Copyright (c) 2002-2004 Juli Mallett + * Copyright (c) 2017 The FreeBSD Foundation * All rights reserved. * + * Portions of this software were developed by Landon Fuller + * under sponsorship from the FreeBSD Foundation. + * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: @@ -44,6 +48,7 @@ __FBSDID("$FreeBSD$"); #include #include #include +#include #include #include #include @@ -69,24 +74,56 @@ __FBSDID("$FreeBSD$"); #define NSOFT_IRQS 2 #define NREAL_IRQS (NHARD_IRQS + NSOFT_IRQS) -static int mips_pic_intr(void *); +struct mips_pic_softc; + +static int mips_pic_intr(void *); +static struct mips_pic_intr *mips_pic_find_intr(struct resource *r); +static int mips_pic_map_fixed_intr(u_int irq, + struct mips_pic_intr **mapping); +static void cpu_establish_intr(struct mips_pic_softc *sc, + const char *name, driver_filter_t *filt, + void (*handler)(void*), void *arg, int irq, + int flags, void **cookiep); + +#define INTR_MAP_DATA_MIPS INTR_MAP_DATA_PLAT_1 struct intr_map_data_mips_pic { struct intr_map_data hdr; u_int irq; }; +/** + * MIPS interrupt state; available prior to MIPS PIC device attachment. + */ +static struct mips_pic_intr { + u_int mips_irq; /**< MIPS IRQ# 0-7 */ + u_int intr_irq; /**< INTRNG IRQ#, or INTR_IRQ_INVALID if unmapped */ + u_int consumers; /**< INTRNG activation refcount */ + struct resource *res; /**< resource shared by all interrupt handlers registered via + cpu_establish_hardintr() or cpu_establish_softintr(); NULL + if no interrupt handlers are yet registered. */ +} mips_pic_intrs[] = { + { 0, INTR_IRQ_INVALID, 0, NULL }, + { 1, INTR_IRQ_INVALID, 0, NULL }, + { 2, INTR_IRQ_INVALID, 0, NULL }, + { 3, INTR_IRQ_INVALID, 0, NULL }, + { 4, INTR_IRQ_INVALID, 0, NULL }, + { 5, INTR_IRQ_INVALID, 0, NULL }, + { 6, INTR_IRQ_INVALID, 0, NULL }, + { 7, INTR_IRQ_INVALID, 0, NULL }, +}; + +struct mtx mips_pic_mtx; +MTX_SYSINIT(mips_pic_mtx, &mips_pic_mtx, "mips intr controller mutex", MTX_DEF); + struct mips_pic_irqsrc { struct intr_irqsrc isrc; - struct resource *res; u_int irq; }; struct mips_pic_softc { device_t pic_dev; struct mips_pic_irqsrc pic_irqs[NREAL_IRQS]; - struct rman pic_irq_rman; - struct mtx mutex; uint32_t nirqs; }; @@ -145,7 +182,7 @@ pic_xref(device_t dev) #ifdef FDT return (OF_xref_from_node(ofw_bus_get_node(dev))); #else - return (0); + return (MIPS_PIC_XREF); #endif } @@ -159,14 +196,7 @@ mips_pic_register_isrcs(struct mips_pic_softc *sc) for (irq = 0; irq < sc->nirqs; irq++) { sc->pic_irqs[irq].irq = irq; - sc->pic_irqs[irq].res = rman_reserve_resource(&sc->pic_irq_rman, - irq, irq, 1, RF_ACTIVE, sc->pic_dev); - if (sc->pic_irqs[irq].res == NULL) { - device_printf(sc->pic_dev, - "%s failed to alloc resource for irq %u", - __func__, irq); - return (ENOMEM); - } + isrc = PIC_INTR_ISRC(sc, irq); if (irq < NSOFT_IRQS) { name = "sint"; @@ -203,21 +233,9 @@ mips_pic_attach(device_t dev) sc->pic_dev = dev; pic_sc = sc; - /* Initialize mutex */ - mtx_init(&sc->mutex, "PIC lock", "", MTX_SPIN); - /* Set the number of interrupts */ sc->nirqs = nitems(sc->pic_irqs); - /* Init the IRQ rman */ - sc->pic_irq_rman.rm_type = RMAN_ARRAY; - sc->pic_irq_rman.rm_descr = "MIPS PIC IRQs"; - if (rman_init(&sc->pic_irq_rman) != 0 || - rman_manage_region(&sc->pic_irq_rman, 0, sc->nirqs - 1) != 0) { - device_printf(dev, "failed to setup IRQ rman\n"); - goto cleanup; - } - /* Register the interrupts */ if (mips_pic_register_isrcs(sc) != 0) { device_printf(dev, "could not register PIC ISRCs\n"); @@ -326,7 +344,7 @@ mips_pic_map_intr(device_t dev, struct intr_map_data *data, *isrcp = PIC_INTR_ISRC(sc, daf->cells[0]); } else #endif - if (data->type == INTR_MAP_DATA_PLAT_1) { + if (data->type == INTR_MAP_DATA_MIPS) { struct intr_map_data_mips_pic *mpd; mpd = (struct intr_map_data_mips_pic *)data; @@ -396,73 +414,292 @@ EARLY_DRIVER_MODULE(cpupic, nexus, mips_pic_driver, mips_pic_devclass, 0, 0, BUS_PASS_INTERRUPT); #endif +/** + * Return the MIPS interrupt map entry for @p r, or NULL if no such entry has + * been created. + */ +static struct mips_pic_intr * +mips_pic_find_intr(struct resource *r) +{ + struct mips_pic_intr *intr; + rman_res_t irq; + + irq = rman_get_start(r); + if (irq != rman_get_end(r) || rman_get_size(r) != 1) + return (NULL); + + mtx_lock(&mips_pic_mtx); + for (size_t i = 0; i < nitems(mips_pic_intrs); i++) { + intr = &mips_pic_intrs[i]; + + if (intr->intr_irq != irq) + continue; + + mtx_unlock(&mips_pic_mtx); + return (intr); + } + mtx_unlock(&mips_pic_mtx); + + /* Not found */ + return (NULL); +} + +/** + * Allocate a fixed IRQ mapping for the given MIPS @p irq, or return the + * existing mapping if @p irq was previously mapped. + * + * @param irq The MIPS IRQ to be mapped. + * @param[out] mapping On success, will be populated with the interrupt + * mapping. + * + * @retval 0 success + * @retval EINVAL if @p irq is not a valid MIPS IRQ#. + * @retval non-zero If allocating the MIPS IRQ mapping otherwise fails, a + * regular unix error code will be returned. + */ +static int +mips_pic_map_fixed_intr(u_int irq, struct mips_pic_intr **mapping) +{ + struct mips_pic_intr *intr; + struct intr_map_data_mips_pic *data; + device_t pic_dev; + uintptr_t xref; + + if (irq < 0 || irq >= nitems(mips_pic_intrs)) + return (EINVAL); + + mtx_lock(&mips_pic_mtx); + + /* Fetch corresponding interrupt entry */ + intr = &mips_pic_intrs[irq]; + KASSERT(intr->mips_irq == irq, + ("intr %u found at index %u", intr->mips_irq, irq)); + + /* Already mapped? */ + if (intr->intr_irq != INTR_IRQ_INVALID) { + mtx_unlock(&mips_pic_mtx); + *mapping = intr; + return (0); + } + + /* Map the interrupt */ + data = (struct intr_map_data_mips_pic *)intr_alloc_map_data( + INTR_MAP_DATA_MIPS, sizeof(*data), M_WAITOK | M_ZERO); + data->irq = intr->mips_irq; + +#ifdef FDT + /* PIC must be attached on FDT devices */ + KASSERT(pic_sc != NULL, ("%s: no pic", __func__)); + + pic_dev = pic_sc->pic_dev; + xref = pic_xref(pic_dev); +#else /* !FDT */ + /* PIC has a fixed xref, and may not have been attached yet */ + pic_dev = NULL; + if (pic_sc != NULL) + pic_dev = pic_sc->pic_dev; + + xref = MIPS_PIC_XREF; +#endif /* FDT */ + + KASSERT(intr->intr_irq == INTR_IRQ_INVALID, ("duplicate map")); + intr->intr_irq = intr_map_irq(pic_dev, xref, &data->hdr); + *mapping = intr; + + mtx_unlock(&mips_pic_mtx); + return (0); +} + +/** + * + * Produce fixed IRQ mappings for all MIPS IRQs. + * + * Non-FDT/OFW MIPS targets do not provide an equivalent to OFW_BUS_MAP_INTR(); + * it is instead necessary to reserve INTRNG IRQ# 0-7 for use by MIPS device + * drivers that assume INTRNG IRQs 0-7 are directly mapped to MIPS IRQs 0-7. + * + * XXX: There is no support in INTRNG for reserving a fixed IRQ range. However, + * we should be called prior to any other interrupt mapping requests, and work + * around this by iteratively allocating the required 0-7 MIP IRQ# range. + * + * @retval 0 success + * @retval non-zero If allocating the MIPS IRQ mappings otherwise fails, a + * regular unix error code will be returned. + */ +int +mips_pic_map_fixed_intrs(void) +{ + int error; + + for (u_int i = 0; i < nitems(mips_pic_intrs); i++) { + struct mips_pic_intr *intr; + + if ((error = mips_pic_map_fixed_intr(i, &intr))) + return (error); + + /* INTRNG IRQs 0-7 must be directly mapped to MIPS IRQs 0-7 */ + if (intr->intr_irq != intr->mips_irq) { + panic("invalid IRQ mapping: %u->%u", intr->intr_irq, + intr->mips_irq); + } + } + + return (0); +} + +/** + * If @p r references a MIPS interrupt mapped by the MIPS32 interrupt + * controller, handle interrupt activation internally. + * + * Otherwise, delegate directly to intr_activate_irq(). + */ +int +mips_pic_activate_intr(device_t child, struct resource *r) +{ + struct mips_pic_intr *intr; + int error; + + /* Is this one of our shared MIPS interrupts? */ + if ((intr = mips_pic_find_intr(r)) == NULL) { + /* Delegate to standard INTRNG activation */ + return (intr_activate_irq(child, r)); + } + + /* Bump consumer count and request activation if required */ + mtx_lock(&mips_pic_mtx); + if (intr->consumers == UINT_MAX) { + mtx_unlock(&mips_pic_mtx); + return (ENOMEM); + } + + if (intr->consumers == 0) { + if ((error = intr_activate_irq(child, r))) { + mtx_unlock(&mips_pic_mtx); + return (error); + } + } + + intr->consumers++; + mtx_unlock(&mips_pic_mtx); + + return (0); +} + +/** + * If @p r references a MIPS interrupt mapped by the MIPS32 interrupt + * controller, handle interrupt deactivation internally. + * + * Otherwise, delegate directly to intr_deactivate_irq(). + */ +int +mips_pic_deactivate_intr(device_t child, struct resource *r) +{ + struct mips_pic_intr *intr; + int error; + + /* Is this one of our shared MIPS interrupts? */ + if ((intr = mips_pic_find_intr(r)) == NULL) { + /* Delegate to standard INTRNG deactivation */ + return (intr_deactivate_irq(child, r)); + } + + /* Decrement consumer count and request deactivation if required */ + mtx_lock(&mips_pic_mtx); + KASSERT(intr->consumers > 0, ("refcount overrelease")); + + if (intr->consumers == 1) { + if ((error = intr_deactivate_irq(child, r))) { + mtx_unlock(&mips_pic_mtx); + return (error); + } + } + intr->consumers--; + + mtx_unlock(&mips_pic_mtx); + return (0); +} + void cpu_init_interrupts(void) { } -int -cpu_create_intr_map(int irq) +/** + * Provide backwards-compatible support for registering a MIPS interrupt handler + * directly, without allocating a bus resource. + */ +static void +cpu_establish_intr(struct mips_pic_softc *sc, const char *name, + driver_filter_t *filt, void (*handler)(void*), void *arg, int irq, + int flags, void **cookiep) { - struct intr_map_data_mips_pic *mips_pic_data; - intptr_t iparent; - size_t len; - u_int new_irq; + struct mips_pic_intr *intr; + struct resource *res; + int rid; + int error; - len = sizeof(*mips_pic_data); - iparent = pic_xref(pic_sc->pic_dev); + rid = -1; - /* Allocate mips_pic data and fill it in */ - mips_pic_data = (struct intr_map_data_mips_pic *)intr_alloc_map_data( - INTR_MAP_DATA_PLAT_1, len, M_WAITOK | M_ZERO); - mips_pic_data->irq = irq; + /* Fetch (or create) a fixed mapping */ + if ((error = mips_pic_map_fixed_intr(irq, &intr))) + panic("Unable to map IRQ %d: %d", irq, error); - /* Get the new irq number */ - new_irq = intr_map_irq(pic_sc->pic_dev, iparent, - (struct intr_map_data *)mips_pic_data); + /* Fetch the backing resource, if any */ + mtx_lock(&mips_pic_mtx); + res = intr->res; + mtx_unlock(&mips_pic_mtx); - /* Adjust the resource accordingly */ - rman_set_start(pic_sc->pic_irqs[irq].res, new_irq); - rman_set_end(pic_sc->pic_irqs[irq].res, new_irq); + /* Allocate our IRQ resource */ + if (res == NULL) { + /* Optimistically perform resource allocation */ + rid = intr->intr_irq; + res = bus_alloc_resource(sc->pic_dev, SYS_RES_IRQ, &rid, + intr->intr_irq, intr->intr_irq, 1, RF_SHAREABLE|RF_ACTIVE); - /* Activate the new irq */ - return (intr_activate_irq(pic_sc->pic_dev, pic_sc->pic_irqs[irq].res)); -} + if (res != NULL) { + /* Try to update intr->res */ + mtx_lock(&mips_pic_mtx); + if (intr->res == NULL) { + intr->res = res; + } + mtx_unlock(&mips_pic_mtx); -struct resource * -cpu_get_irq_resource(int irq) -{ + /* If intr->res was updated concurrently, free our local + * resource allocation */ + if (intr->res != res) { + bus_release_resource(sc->pic_dev, SYS_RES_IRQ, + rid, res); + } + } else { + /* Maybe someone else allocated it? */ + mtx_lock(&mips_pic_mtx); + res = intr->res; + mtx_unlock(&mips_pic_mtx); + } + + if (res == NULL) { + panic("Unable to allocate IRQ %d->%u resource", irq, + intr->intr_irq); + } + } - KASSERT(pic_sc != NULL, ("%s: no pic", __func__)); - - if (irq < 0 || irq >= pic_sc->nirqs) - panic("%s called for unknown irq %d", __func__, irq); - - return pic_sc->pic_irqs[irq].res; + error = bus_setup_intr(sc->pic_dev, res, flags, filt, handler, arg, + cookiep); + if (error) + panic("Unable to add IRQ %d handler: %d", irq, error); } void cpu_establish_hardintr(const char *name, driver_filter_t *filt, void (*handler)(void*), void *arg, int irq, int flags, void **cookiep) { - int res; + KASSERT(pic_sc != NULL, ("%s: no pic", __func__)); - /* - * We have 6 levels, but thats 0 - 5 (not including 6) - */ if (irq < 0 || irq >= NHARD_IRQS) panic("%s called for unknown hard intr %d", __func__, irq); - KASSERT(pic_sc != NULL, ("%s: no pic", __func__)); - - irq += NSOFT_IRQS; - - res = cpu_create_intr_map(irq); - if (res != 0) panic("Unable to create map for hard IRQ %d", irq); - - res = intr_setup_irq(pic_sc->pic_dev, pic_sc->pic_irqs[irq].res, filt, - handler, arg, flags, cookiep); - if (res != 0) panic("Unable to add hard IRQ %d handler", irq); + cpu_establish_intr(pic_sc, name, filt, handler, arg, irq+NSOFT_IRQS, + flags, cookiep); } void @@ -470,18 +707,12 @@ cpu_establish_softintr(const char *name, driver_filter_t *filt, void (*handler)(void*), void *arg, int irq, int flags, void **cookiep) { - int res; - - if (irq < 0 || irq > NSOFT_IRQS) - panic("%s called for unknown soft intr %d", __func__, irq); - KASSERT(pic_sc != NULL, ("%s: no pic", __func__)); - res = cpu_create_intr_map(irq); - if (res != 0) panic("Unable to create map for soft IRQ %d", irq); + if (irq < 0 || irq >= NSOFT_IRQS) + panic("%s called for unknown soft intr %d", __func__, irq); - res = intr_setup_irq(pic_sc->pic_dev, pic_sc->pic_irqs[irq].res, filt, - handler, arg, flags, cookiep); - if (res != 0) panic("Unable to add soft IRQ %d handler", irq); + cpu_establish_intr(pic_sc, name, filt, handler, arg, irq, flags, + cookiep); } diff --git a/sys/mips/mips/nexus.c b/sys/mips/mips/nexus.c index a5bac7f1872a..3503e6d1a232 100644 --- a/sys/mips/mips/nexus.c +++ b/sys/mips/mips/nexus.c @@ -76,7 +76,11 @@ __FBSDID("$FreeBSD$"); #define dprintf(x, arg...) #endif /* NEXUS_DEBUG */ -#define NUM_MIPS_IRQS 6 +#ifdef INTRNG +#define NUM_MIPS_IRQS NIRQ /* Any INTRNG-mapped IRQ */ +#else +#define NUM_MIPS_IRQS 6 /* HW IRQs only */ +#endif static MALLOC_DEFINE(M_NEXUSDEV, "nexusdev", "Nexus device"); @@ -200,6 +204,12 @@ nexus_probe(device_t dev) static int nexus_attach(device_t dev) { +#if defined(INTRNG) && !defined(FDT) + int error; + + if ((error = mips_pic_map_fixed_intrs())) + return (error); +#endif bus_generic_probe(dev); bus_enumerate_hinted_children(dev); @@ -291,7 +301,7 @@ nexus_alloc_resource(device_t bus, device_t child, int type, int *rid, * and we know what the resources for this device are (ie. they aren't * maintained by a child bus), then work out the start/end values. */ - if (isdefault) { + if (!passthrough && isdefault) { rle = resource_list_find(&ndev->nx_resources, type, *rid); if (rle == NULL) return (NULL); @@ -432,20 +442,11 @@ nexus_activate_resource(device_t bus, device_t child, int type, int rid, rman_set_bushandle(r, (bus_space_handle_t)(uintptr_t)vaddr); } else if (type == SYS_RES_IRQ) { #ifdef INTRNG -#ifdef FDT - err = intr_activate_irq(child, r); + err = mips_pic_activate_intr(child, r); if (err != 0) { rman_deactivate_resource(r); return (err); } -#else - /* - * INTRNG without FDT needs to have the interrupt properly - * mapped first. cpu_create_intr_map() will do that and - * call intr_activate_irq() at the end. - */ - cpu_create_intr_map(rman_get_start(r)); -#endif #endif } @@ -468,7 +469,7 @@ nexus_deactivate_resource(device_t bus, device_t child, int type, int rid, rman_set_bushandle(r, 0); } else if (type == SYS_RES_IRQ) { #ifdef INTRNG - intr_deactivate_irq(child, r); + mips_pic_deactivate_intr(child, r); #endif } @@ -480,12 +481,7 @@ nexus_setup_intr(device_t dev, device_t child, struct resource *res, int flags, driver_filter_t *filt, driver_intr_t *intr, void *arg, void **cookiep) { #ifdef INTRNG - struct resource *r = res; - -#ifndef FDT - r = cpu_get_irq_resource(rman_get_start(r)); -#endif - return (intr_setup_irq(child, r, filt, intr, arg, flags, cookiep)); + return (intr_setup_irq(child, res, filt, intr, arg, flags, cookiep)); #else int irq; register_t s;