Index: head/sys/dev/bhnd/bcma/bcma.c =================================================================== --- head/sys/dev/bhnd/bcma/bcma.c +++ head/sys/dev/bhnd/bcma/bcma.c @@ -1,7 +1,11 @@ /*- - * Copyright (c) 2015 Landon Fuller + * Copyright (c) 2015-2016 Landon Fuller + * 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: @@ -124,7 +128,7 @@ /* Free bcma device info */ if ((dinfo = device_get_ivars(child)) != NULL) - bcma_free_dinfo(dev, dinfo); + bcma_free_dinfo(dev, child, dinfo); device_set_ivars(child, NULL); } @@ -613,66 +617,46 @@ /** * Default bcma(4) bus driver implementation of BHND_BUS_GET_INTR_COUNT(). - * - * This implementation consults @p child's agent register block, - * returning the number of interrupt output lines routed to @p child. */ -int +u_int bcma_get_intr_count(device_t dev, device_t child) { - struct bcma_devinfo *dinfo; - uint32_t dmpcfg, oobw; + struct bcma_devinfo *dinfo; - dinfo = device_get_ivars(child); + /* delegate non-bus-attached devices to our parent */ + if (device_get_parent(child) != dev) + return (BHND_BUS_GET_INTR_COUNT(device_get_parent(dev), child)); - /* Agent block must be mapped */ - if (dinfo->res_agent == NULL) - return (0); - - /* Agent must support OOB */ - dmpcfg = bhnd_bus_read_4(dinfo->res_agent, BCMA_DMP_CONFIG); - if (!BCMA_DMP_GET_FLAG(dmpcfg, BCMA_DMP_CFG_OOB)) - return (0); - - /* Return OOB width as interrupt count */ - oobw = bhnd_bus_read_4(dinfo->res_agent, - BCMA_DMP_OOB_OUTWIDTH(BCMA_OOB_BANK_INTR)); - if (oobw > BCMA_OOB_NUM_SEL) { - device_printf(dev, "ignoring invalid OOBOUTWIDTH for core %u: " - "%#x\n", BCMA_DINFO_COREIDX(dinfo), oobw); - return (0); - } - - return (oobw); + dinfo = device_get_ivars(child); + return (dinfo->num_intrs); } /** - * Default bcma(4) bus driver implementation of BHND_BUS_GET_CORE_IVEC(). - * - * This implementation consults @p child's agent register block, - * returning the interrupt output line routed to @p child, at OOB selector - * @p intr. + * Default bcma(4) bus driver implementation of BHND_BUS_GET_INTR_IVEC(). */ int -bcma_get_core_ivec(device_t dev, device_t child, u_int intr, uint32_t *ivec) +bcma_get_intr_ivec(device_t dev, device_t child, u_int intr, u_int *ivec) { struct bcma_devinfo *dinfo; - uint32_t oobsel; + struct bcma_intr *desc; + /* delegate non-bus-attached devices to our parent */ + if (device_get_parent(child) != dev) { + return (BHND_BUS_GET_INTR_IVEC(device_get_parent(dev), child, + intr, ivec)); + } + dinfo = device_get_ivars(child); - /* Interrupt ID must be valid. */ - if (intr >= bcma_get_intr_count(dev, child)) - return (ENXIO); + STAILQ_FOREACH(desc, &dinfo->intrs, i_link) { + if (desc->i_sel == intr) { + *ivec = desc->i_busline; + return (0); + } + } - /* Fetch OOBSEL busline value */ - KASSERT(dinfo->res_agent != NULL, ("missing agent registers")); - oobsel = bhnd_bus_read_4(dinfo->res_agent, BCMA_DMP_OOBSELOUT( - BCMA_OOB_BANK_INTR, intr)); - *ivec = (oobsel >> BCMA_DMP_OOBSEL_SHIFT(intr)) & - BCMA_DMP_OOBSEL_BUSLINE_MASK; - - return (0); + /* Not found */ + return (ENXIO); } /** @@ -707,8 +691,6 @@ /* Add all cores. */ bcma_erom = (struct bcma_erom *)erom; while ((error = bcma_erom_next_corecfg(bcma_erom, &corecfg)) == 0) { - int nintr; - /* Add the child device */ child = BUS_ADD_CHILD(bus, 0, NULL, -1); if (child == NULL) { @@ -718,27 +700,12 @@ /* Initialize device ivars */ dinfo = device_get_ivars(child); - if ((error = bcma_init_dinfo(bus, dinfo, corecfg))) + if ((error = bcma_init_dinfo(bus, child, dinfo, corecfg))) goto cleanup; /* The dinfo instance now owns the corecfg value */ corecfg = NULL; - /* Allocate device's agent registers, if any */ - if ((error = bcma_dinfo_alloc_agent(bus, child, dinfo))) - goto cleanup; - - /* Assign interrupts */ - nintr = bhnd_get_intr_count(child); - for (int rid = 0; rid < nintr; rid++) { - error = BHND_BUS_ASSIGN_INTR(bus, child, rid); - if (error) { - device_printf(bus, "failed to assign interrupt " - "%d to core %u: %d\n", rid, - BCMA_DINFO_COREIDX(dinfo), error); - } - } - /* If pins are floating or the hardware is otherwise * unpopulated, the device shouldn't be used. */ if (bhnd_is_hw_disabled(child)) @@ -794,7 +761,7 @@ DEVMETHOD(bhnd_bus_decode_port_rid, bcma_decode_port_rid), DEVMETHOD(bhnd_bus_get_region_addr, bcma_get_region_addr), DEVMETHOD(bhnd_bus_get_intr_count, bcma_get_intr_count), - DEVMETHOD(bhnd_bus_get_core_ivec, bcma_get_core_ivec), + DEVMETHOD(bhnd_bus_get_intr_ivec, bcma_get_intr_ivec), DEVMETHOD_END }; Index: head/sys/dev/bhnd/bcma/bcma_subr.c =================================================================== --- head/sys/dev/bhnd/bcma/bcma_subr.c +++ head/sys/dev/bhnd/bcma/bcma_subr.c @@ -1,7 +1,11 @@ /*- - * Copyright (c) 2015 Landon Fuller + * Copyright (c) 2015-2016 Landon Fuller + * 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: @@ -154,7 +158,7 @@ * @param ports The set of ports to be enumerated */ static void -bcma_dinfo_init_resource_info(device_t bus, struct bcma_devinfo *dinfo, +bcma_dinfo_init_port_resource_info(device_t bus, struct bcma_devinfo *dinfo, struct bcma_sport_list *ports) { struct bcma_map *map; @@ -193,7 +197,127 @@ } + /** + * Allocate the per-core agent register block for a device info structure. + * + * If an agent0.0 region is not defined on @p dinfo, the device info + * agent resource is set to NULL and 0 is returned. + * + * @param bus The requesting bus device. + * @param child The bcma child device. + * @param dinfo The device info associated with @p child + * + * @retval 0 success + * @retval non-zero resource allocation failed. + */ +static int +bcma_dinfo_init_agent(device_t bus, device_t child, struct bcma_devinfo *dinfo) +{ + bhnd_addr_t addr; + bhnd_size_t size; + rman_res_t r_start, r_count, r_end; + int error; + + KASSERT(dinfo->res_agent == NULL, ("double allocation of agent")); + + /* Verify that the agent register block exists and is + * mappable */ + if (bhnd_get_port_rid(child, BHND_PORT_AGENT, 0, 0) == -1) + return (0); /* nothing to do */ + + /* Fetch the address of the agent register block */ + error = bhnd_get_region_addr(child, BHND_PORT_AGENT, 0, 0, + &addr, &size); + if (error) { + device_printf(bus, "failed fetching agent register block " + "address for core %u\n", BCMA_DINFO_COREIDX(dinfo)); + return (error); + } + + /* Allocate the resource */ + r_start = addr; + r_count = size; + r_end = r_start + r_count - 1; + + dinfo->rid_agent = BCMA_AGENT_RID(dinfo); + dinfo->res_agent = BHND_BUS_ALLOC_RESOURCE(bus, bus, SYS_RES_MEMORY, + &dinfo->rid_agent, r_start, r_end, r_count, RF_ACTIVE|RF_SHAREABLE); + if (dinfo->res_agent == NULL) { + device_printf(bus, "failed allocating agent register block for " + "core %u\n", BCMA_DINFO_COREIDX(dinfo)); + return (ENXIO); + } + + return (0); +} + +/** + * Populate the list of interrupts for a device info structure + * previously initialized via bcma_dinfo_alloc_agent(). + * + * If an agent0.0 region is not mapped on @p dinfo, the OOB interrupt bank is + * assumed to be unavailable and 0 is returned. + * + * @param bus The requesting bus device. + * @param dinfo The device info instance to be initialized. + */ +static int +bcma_dinfo_init_intrs(device_t bus, device_t child, + struct bcma_devinfo *dinfo) +{ + uint32_t dmpcfg, oobw; + + /* Agent block must be mapped */ + if (dinfo->res_agent == NULL) + return (0); + + /* Agent must support OOB */ + dmpcfg = bhnd_bus_read_4(dinfo->res_agent, BCMA_DMP_CONFIG); + if (!BCMA_DMP_GET_FLAG(dmpcfg, BCMA_DMP_CFG_OOB)) + return (0); + + /* Fetch width of the OOB interrupt bank */ + oobw = bhnd_bus_read_4(dinfo->res_agent, + BCMA_DMP_OOB_OUTWIDTH(BCMA_OOB_BANK_INTR)); + if (oobw > BCMA_OOB_NUM_SEL) { + device_printf(bus, "ignoring invalid OOBOUTWIDTH for core %u: " + "%#x\n", BCMA_DINFO_COREIDX(dinfo), oobw); + return (0); + } + + /* Fetch OOBSEL busline values and populate list of interrupt + * descriptors */ + for (uint32_t sel = 0; sel < oobw; sel++) { + struct bcma_intr *intr; + uint32_t selout; + uint8_t line; + + if (dinfo->num_intrs == UINT_MAX) + return (ENOMEM); + + selout = bhnd_bus_read_4(dinfo->res_agent, BCMA_DMP_OOBSELOUT( + BCMA_OOB_BANK_INTR, sel)); + + line = (selout >> BCMA_DMP_OOBSEL_SHIFT(sel)) & + BCMA_DMP_OOBSEL_BUSLINE_MASK; + + intr = bcma_alloc_intr(BCMA_OOB_BANK_INTR, sel, line); + if (intr == NULL) { + device_printf(bus, "failed allocating interrupt " + "descriptor %#x for core %u\n", sel, + BCMA_DINFO_COREIDX(dinfo)); + return (ENOMEM); + } + + STAILQ_INSERT_HEAD(&dinfo->intrs, intr, i_link); + dinfo->num_intrs++; + } + + return (0); +} + +/** * Allocate and return a new empty device info structure. * * @param bus The requesting bus device. @@ -213,6 +337,9 @@ dinfo->res_agent = NULL; dinfo->rid_agent = -1; + STAILQ_INIT(&dinfo->intrs); + dinfo->num_intrs = 0; + resource_list_init(&dinfo->resources); return (dinfo); @@ -224,7 +351,8 @@ * configuration. * * @param bus The requesting bus device. - * @param dinfo The device info instance. + * @param child The bcma child device. + * @param dinfo The device info associated with @p child * @param corecfg Device core configuration; ownership of this value * will be assumed by @p dinfo. * @@ -232,9 +360,12 @@ * @retval non-zero initialization failed. */ int -bcma_init_dinfo(device_t bus, struct bcma_devinfo *dinfo, +bcma_init_dinfo(device_t bus, device_t child, struct bcma_devinfo *dinfo, struct bcma_corecfg *corecfg) { + struct bcma_intr *intr; + int error; + KASSERT(dinfo->corecfg == NULL, ("dinfo previously initialized")); /* Save core configuration value */ @@ -242,71 +373,52 @@ /* The device ports must always be initialized first to ensure that * rid 0 maps to the first device port */ - bcma_dinfo_init_resource_info(bus, dinfo, &corecfg->dev_ports); + bcma_dinfo_init_port_resource_info(bus, dinfo, &corecfg->dev_ports); + bcma_dinfo_init_port_resource_info(bus, dinfo, &corecfg->bridge_ports); + bcma_dinfo_init_port_resource_info(bus, dinfo, &corecfg->wrapper_ports); - bcma_dinfo_init_resource_info(bus, dinfo, &corecfg->bridge_ports); - bcma_dinfo_init_resource_info(bus, dinfo, &corecfg->wrapper_ports); + /* Now that we've defined the port resources, we can map the device's + * agent registers (if any) */ + if ((error = bcma_dinfo_init_agent(bus, child, dinfo))) + goto failed; - return (0); -} + /* With agent registers mapped, we can populate the device's interrupt + * descriptors */ + if ((error = bcma_dinfo_init_intrs(bus, child, dinfo))) + goto failed; + /* Finally, map the interrupt descriptors */ + STAILQ_FOREACH(intr, &dinfo->intrs, i_link) { + /* Already mapped? */ + if (intr->i_mapped) + continue; -/** - * Allocate the per-core agent register block for a device info structure - * previous initialized via bcma_init_dinfo(). - * - * If an agent0.0 region is not defined on @p dinfo, the device info - * agent resource is set to NULL and 0 is returned. - * - * @param bus The requesting bus device. - * @param child The bcma child device. - * @param dinfo The device info associated with @p child - * - * @retval 0 success - * @retval non-zero resource allocation failed. - */ -int -bcma_dinfo_alloc_agent(device_t bus, device_t child, struct bcma_devinfo *dinfo) -{ - bhnd_addr_t addr; - bhnd_size_t size; - rman_res_t r_start, r_count, r_end; - int error; + /* Map the interrupt */ + error = BHND_BUS_MAP_INTR(bus, child, intr->i_sel, + &intr->i_irq); + if (error) { + device_printf(bus, "failed mapping interrupt line %u " + "for core %u: %d\n", intr->i_sel, + BCMA_DINFO_COREIDX(dinfo), error); + goto failed; + } - KASSERT(dinfo->res_agent == NULL, ("double allocation of agent")); + intr->i_mapped = true; - /* Verify that the agent register block exists and is - * mappable */ - if (bhnd_get_port_rid(child, BHND_PORT_AGENT, 0, 0) == -1) - return (0); /* nothing to do */ - - /* Fetch the address of the agent register block */ - error = bhnd_get_region_addr(child, BHND_PORT_AGENT, 0, 0, - &addr, &size); - if (error) { - device_printf(bus, "failed fetching agent register block " - "address for core %u\n", BCMA_DINFO_COREIDX(dinfo)); - return (error); + /* Add to resource list */ + intr->i_rid = resource_list_add_next(&dinfo->resources, + SYS_RES_IRQ, intr->i_irq, intr->i_irq, 1); } - /* Allocate the resource */ - r_start = addr; - r_count = size; - r_end = r_start + r_count - 1; + return (0); - dinfo->rid_agent = BCMA_AGENT_RID(dinfo); - dinfo->res_agent = BHND_BUS_ALLOC_RESOURCE(bus, bus, SYS_RES_MEMORY, - &dinfo->rid_agent, r_start, r_end, r_count, RF_ACTIVE); - if (dinfo->res_agent == NULL) { - device_printf(bus, "failed allocating agent register block for " - "core %u\n", BCMA_DINFO_COREIDX(dinfo)); - return (ENXIO); - } +failed: + /* Owned by the caller on failure */ + dinfo->corecfg = NULL; - return (0); + return (error); } - /** * Deallocate the given device info structure and any associated resources. * @@ -314,8 +426,10 @@ * @param dinfo Device info to be deallocated. */ void -bcma_free_dinfo(device_t bus, struct bcma_devinfo *dinfo) +bcma_free_dinfo(device_t bus, device_t child, struct bcma_devinfo *dinfo) { + struct bcma_intr *intr, *inext; + resource_list_free(&dinfo->resources); if (dinfo->corecfg != NULL) @@ -327,9 +441,69 @@ dinfo->res_agent); } + /* Clean up interrupt descriptors */ + STAILQ_FOREACH_SAFE(intr, &dinfo->intrs, i_link, inext) { + STAILQ_REMOVE(&dinfo->intrs, intr, bcma_intr, i_link); + + /* Release our IRQ mapping */ + if (intr->i_mapped) { + BHND_BUS_UNMAP_INTR(bus, child, intr->i_irq); + intr->i_mapped = false; + } + + bcma_free_intr(intr); + } + free(dinfo, M_BHND); } + +/** + * Allocate and initialize a new interrupt descriptor. + * + * @param bank OOB bank. + * @param sel OOB selector. + * @param line OOB bus line. + */ +struct bcma_intr * +bcma_alloc_intr(uint8_t bank, uint8_t sel, uint8_t line) +{ + struct bcma_intr *intr; + + if (bank >= BCMA_OOB_NUM_BANKS) + return (NULL); + + if (sel >= BCMA_OOB_NUM_SEL) + return (NULL); + + if (line >= BCMA_OOB_NUM_BUSLINES) + return (NULL); + + intr = malloc(sizeof(*intr), M_BHND, M_NOWAIT); + if (intr == NULL) + return (NULL); + + intr->i_bank = bank; + intr->i_sel = sel; + intr->i_busline = line; + intr->i_mapped = false; + intr->i_irq = 0; + + return (intr); +} + +/** + * Deallocate all resources associated with the given interrupt descriptor. + * + * @param intr Interrupt descriptor to be deallocated. + */ +void +bcma_free_intr(struct bcma_intr *intr) +{ + KASSERT(!intr->i_mapped, ("interrupt %u still mapped", intr->i_sel)); + + free(intr, M_BHND); +} /** * Allocate and initialize new slave port descriptor. Index: head/sys/dev/bhnd/bcma/bcmavar.h =================================================================== --- head/sys/dev/bhnd/bcma/bcmavar.h +++ head/sys/dev/bhnd/bcma/bcmavar.h @@ -1,7 +1,11 @@ /*- - * Copyright (c) 2015 Landon Fuller + * Copyright (c) 2015-2016 Landon Fuller + * 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: @@ -67,6 +71,7 @@ struct bcma_devinfo; struct bcma_corecfg; +struct bcma_intr; struct bcma_map; struct bcma_mport; struct bcma_sport; @@ -74,8 +79,8 @@ int bcma_probe(device_t dev); int bcma_attach(device_t dev); int bcma_detach(device_t dev); -int bcma_get_intr_count(device_t dev, device_t child); -int bcma_get_core_ivec(device_t dev, device_t child, +u_int bcma_get_intr_count(device_t dev, device_t child); +int bcma_get_intr_ivec(device_t dev, device_t child, u_int intr, uint32_t *ivec); int bcma_add_children(device_t bus); @@ -84,18 +89,20 @@ bhnd_port_type type); struct bcma_devinfo *bcma_alloc_dinfo(device_t bus); -int bcma_init_dinfo(device_t bus, +int bcma_init_dinfo(device_t bus, device_t child, struct bcma_devinfo *dinfo, struct bcma_corecfg *corecfg); -int bcma_dinfo_alloc_agent(device_t bus, device_t child, +void bcma_free_dinfo(device_t bus, device_t child, struct bcma_devinfo *dinfo); -void bcma_free_dinfo(device_t bus, - struct bcma_devinfo *dinfo); struct bcma_corecfg *bcma_alloc_corecfg(u_int core_index, int core_unit, uint16_t vendor, uint16_t device, uint8_t hwrev); void bcma_free_corecfg(struct bcma_corecfg *corecfg); +struct bcma_intr *bcma_alloc_intr(uint8_t bank, uint8_t sel, + uint8_t line); +void bcma_free_intr(struct bcma_intr *intr); + struct bcma_sport *bcma_alloc_sport(bcma_pid_t port_num, bhnd_port_type port_type); void bcma_free_sport(struct bcma_sport *sport); @@ -121,6 +128,18 @@ STAILQ_ENTRY(bcma_map) m_link; }; +/** BCMA interrupt descriptor */ +struct bcma_intr { + uint8_t i_bank; /**< OOB bank (see BCMA_OOB_BANK[A-D]) */ + uint8_t i_sel; /**< OOB selector (0-7) */ + uint8_t i_busline; /**< OOB bus line assigned to this selector */ + bool i_mapped; /**< if an irq has been mapped for this selector */ + int i_rid; /**< bus resource id, or -1 */ + rman_res_t i_irq; /**< the mapped bus irq, if any */ + + STAILQ_ENTRY(bcma_intr) i_link; +}; + /** BCMA slave port descriptor */ struct bcma_sport { bcma_pid_t sp_num; /**< slave port number (core-unique) */ @@ -131,8 +150,9 @@ STAILQ_ENTRY(bcma_sport) sp_link; }; -STAILQ_HEAD(bcma_mport_list, bcma_mport); -STAILQ_HEAD(bcma_sport_list, bcma_sport); +STAILQ_HEAD(bcma_mport_list, bcma_mport); +STAILQ_HEAD(bcma_intr_list, bcma_intr); +STAILQ_HEAD(bcma_sport_list, bcma_sport); /** BCMA IP core/block configuration */ struct bcma_corecfg { @@ -161,6 +181,9 @@ struct bhnd_resource *res_agent; /**< Agent (wrapper) resource, or NULL. Not * all bcma(4) cores have or require an agent. */ int rid_agent; /**< Agent resource ID, or -1 */ + + u_int num_intrs; /**< number of interrupt descriptors. */ + struct bcma_intr_list intrs; /**< interrupt descriptors */ struct bhnd_core_pmu_info *pmu_info; /**< Bus-managed PMU state, or NULL */ }; Index: head/sys/dev/bhnd/bhnd.h =================================================================== --- head/sys/dev/bhnd/bhnd.h +++ head/sys/dev/bhnd/bhnd.h @@ -250,10 +250,10 @@ {{ BHND_MATCH_CORE_REV(_rev) }, (_flags) } #define BHND_CHIP_QUIRK(_chip, _rev, _flags) \ - {{ BHND_CHIP_IR(BCM ## _chip, _rev) }, (_flags) } + {{ BHND_MATCH_CHIP_IR(BCM ## _chip, _rev) }, (_flags) } #define BHND_PKG_QUIRK(_chip, _pkg, _flags) \ - {{ BHND_CHIP_IP(BCM ## _chip, BCM ## _chip ## _pkg) }, (_flags) } + {{ BHND_MATCH_CHIP_IP(BCM ## _chip, BCM ## _chip ## _pkg) }, (_flags) } #define BHND_BOARD_QUIRK(_board, _flags) \ {{ BHND_MATCH_BOARD_TYPE(_board) }, \ @@ -528,8 +528,8 @@ int bhnd_bus_generic_deactivate_resource (device_t dev, device_t child, int type, int rid, struct bhnd_resource *r); -bhnd_attach_type bhnd_bus_generic_get_attach_type(device_t dev, - device_t child); +uintptr_t bhnd_bus_generic_get_intr_domain(device_t dev, + device_t child, bool self); /** * Return the bhnd(4) bus driver's device enumeration parser class @@ -865,25 +865,22 @@ } /** - * Return the number of interrupts to be assigned to @p child via - * BHND_BUS_ASSIGN_INTR(). + * Return the number of interrupt lines assigned to @p dev. * * @param dev A bhnd bus child device. */ -static inline int +static inline u_int bhnd_get_intr_count(device_t dev) { return (BHND_BUS_GET_INTR_COUNT(device_get_parent(dev), dev)); } /** - * Return the backplane interrupt vector corresponding to @p dev's given - * @p intr number. + * Get the backplane interrupt vector of the @p intr line attached to @p dev. * * @param dev A bhnd bus child device. - * @param intr The interrupt number being queried. This is equivalent to the - * bus resource ID for the interrupt. - * @param[out] ivec On success, the assigned hardware interrupt vector be + * @param intr The index of the interrupt line being queried. + * @param[out] ivec On success, the assigned hardware interrupt vector will be * written to this pointer. * * On bcma(4) devices, this returns the OOB bus line assigned to the @@ -893,14 +890,48 @@ * to the interrupt. * * @retval 0 success - * @retval ENXIO If @p intr exceeds the number of interrupts available - * to @p child. + * @retval ENXIO If @p intr exceeds the number of interrupt lines + * assigned to @p child. */ static inline int -bhnd_get_core_ivec(device_t dev, u_int intr, uint32_t *ivec) +bhnd_get_intr_ivec(device_t dev, u_int intr, u_int *ivec) { - return (BHND_BUS_GET_CORE_IVEC(device_get_parent(dev), dev, intr, + return (BHND_BUS_GET_INTR_IVEC(device_get_parent(dev), dev, intr, ivec)); +} + +/** + * Map the given @p intr to an IRQ number; until unmapped, this IRQ may be used + * to allocate a resource of type SYS_RES_IRQ. + * + * On success, the caller assumes ownership of the interrupt mapping, and + * is responsible for releasing the mapping via bhnd_unmap_intr(). + * + * @param dev The requesting device. + * @param intr The interrupt being mapped. + * @param[out] irq On success, the bus interrupt value mapped for @p intr. + * + * @retval 0 If an interrupt was assigned. + * @retval non-zero If mapping an interrupt otherwise fails, a regular + * unix error code will be returned. + */ +static inline int +bhnd_map_intr(device_t dev, u_int intr, rman_res_t *irq) +{ + return (BHND_BUS_MAP_INTR(device_get_parent(dev), dev, intr, irq)); +} + +/** + * Unmap an bus interrupt previously mapped via bhnd_map_intr(). + * + * @param dev The requesting device. + * @param intr The interrupt number being unmapped. This is equivalent to the + * bus resource ID for the interrupt. + */ +static inline void +bhnd_unmap_intr(device_t dev, rman_res_t irq) +{ + return (BHND_BUS_UNMAP_INTR(device_get_parent(dev), dev, irq)); } /** Index: head/sys/dev/bhnd/bhnd.c =================================================================== --- head/sys/dev/bhnd/bhnd.c +++ head/sys/dev/bhnd/bhnd.c @@ -814,6 +814,22 @@ return bus_generic_resume_child(dev, child); } + +/** + * Default bhnd(4) bus driver implementation of BUS_SETUP_INTR(). + * + * This implementation of BUS_SETUP_INTR() will delegate interrupt setup + * to the parent of @p dev, if any. + */ +int +bhnd_generic_setup_intr(device_t dev, device_t child, struct resource *irq, + int flags, driver_filter_t *filter, driver_intr_t *intr, void *arg, + void **cookiep) +{ + return (bus_generic_setup_intr(dev, child, irq, flags, filter, intr, + arg, cookiep)); +} + /* * Delegate all indirect I/O to the parent device. When inherited by * non-bridged bus implementations, resources will never be marked as @@ -917,7 +933,7 @@ DEVMETHOD(bus_activate_resource, bus_generic_activate_resource), DEVMETHOD(bus_deactivate_resource, bus_generic_deactivate_resource), - DEVMETHOD(bus_setup_intr, bus_generic_setup_intr), + DEVMETHOD(bus_setup_intr, bhnd_generic_setup_intr), DEVMETHOD(bus_teardown_intr, bus_generic_teardown_intr), DEVMETHOD(bus_config_intr, bus_generic_config_intr), DEVMETHOD(bus_bind_intr, bus_generic_bind_intr), Index: head/sys/dev/bhnd/bhnd_bus_if.m =================================================================== --- head/sys/dev/bhnd/bhnd_bus_if.m +++ head/sys/dev/bhnd/bhnd_bus_if.m @@ -141,25 +141,6 @@ panic("bhnd_bus_read_boardinfo unimplemented"); } - static int - bhnd_bus_null_get_intr_count(device_t dev, device_t child) - { - panic("bhnd_bus_get_intr_count unimplemented"); - } - - static int - bhnd_bus_null_assign_intr(device_t dev, device_t child, int rid) - { - panic("bhnd_bus_assign_intr unimplemented"); - } - - static int - bhnd_bus_null_get_core_ivec(device_t dev, device_t child, u_int intr, - uint32_t *ivec) - { - panic("bhnd_bus_get_core_ivec unimplemented"); - } - static void bhnd_bus_null_child_added(device_t dev, device_t child) { @@ -243,7 +224,40 @@ panic("bhnd_bus_get_probe_order unimplemented"); } + static uintptr_t + bhnd_bus_null_get_intr_domain(device_t dev, device_t child, bool self) + { + /* Unsupported */ + return (0); + } + + static u_int + bhnd_bus_null_get_intr_count(device_t dev, device_t child) + { + return (0); + } + static int + bhnd_bus_null_get_intr_ivec(device_t dev, device_t child, u_int intr, + u_int *ivec) + { + panic("bhnd_bus_get_intr_ivec unimplemented"); + } + + static int + bhnd_bus_null_map_intr(device_t dev, device_t child, u_int intr, + rman_res_t *irq) + { + panic("bhnd_bus_map_intr unimplemented"); + } + + static int + bhnd_bus_null_unmap_intr(device_t dev, device_t child, rman_res_t irq) + { + panic("bhnd_bus_unmap_intr unimplemented"); + } + + static int bhnd_bus_null_get_port_rid(device_t dev, device_t child, bhnd_port_type port_type, u_int port, u_int region) { @@ -488,77 +502,6 @@ } DEFAULT bhnd_bus_null_read_board_info; /** - * Return the number of interrupts to be assigned to @p child via - * BHND_BUS_ASSIGN_INTR(). - * - * @param dev The bhnd bus parent of @p child. - * @param child The bhnd device for which a count should be returned. - * - * @retval 0 If no interrupts should be assigned. - * @retval non-zero The count of interrupt resource IDs to be - * assigned, starting at rid 0. - */ -METHOD int get_intr_count { - device_t dev; - device_t child; -} DEFAULT bhnd_bus_null_get_intr_count; - -/** - * Assign an interrupt to @p child via bus_set_resource(). - * - * The default bus implementation of this method should assign backplane - * interrupt values to @p child. - * - * Bridge-attached bus implementations may instead override standard - * interconnect IRQ assignment, providing IRQs inherited from the parent bus. - * - * TODO: Once we can depend on INTRNG, investigate replacing this with a - * bridge-level interrupt controller. - * - * @param dev The bhnd bus parent of @p child. - * @param child The bhnd device to which an interrupt should be assigned. - * @param rid The interrupt resource ID to be assigned. - * - * @retval 0 If an interrupt was assigned. - * @retval non-zero If assigning an interrupt otherwise fails, a regular - * unix error code will be returned. - */ -METHOD int assign_intr { - device_t dev; - device_t child; - int rid; -} DEFAULT bhnd_bus_null_assign_intr; - -/** - * Return the backplane interrupt vector corresponding to @p child's given - * @p intr number. - * - * @param dev The bhnd bus parent of @p child. - * @param child The bhnd device for which the assigned interrupt vector should - * be queried. - * @param intr The interrupt number being queried. This is equivalent to the - * bus resource ID for the interrupt. - * @param[out] ivec On success, the assigned hardware interrupt vector be - * written to this pointer. - * - * On bcma(4) devices, this returns the OOB bus line assigned to the - * interrupt. - * - * On siba(4) devices, this returns the target OCP slave flag number assigned - * to the interrupt. - * - * @retval 0 success - * @retval ENXIO If @p intr exceeds the number of interrupts available - * to @p child. - */ -METHOD int get_core_ivec { - device_t dev; - device_t child; - u_int intr; - uint32_t *ivec; -} DEFAULT bhnd_bus_null_get_core_ivec; - -/** * Notify a bhnd bus that a child was added. * * This method must be called by concrete bhnd(4) driver impementations @@ -996,6 +939,106 @@ int rid; struct bhnd_resource *r; } DEFAULT bhnd_bus_generic_deactivate_resource; + +/** + * Return the interrupt domain. + * + * This globally unique value may be used as the interrupt controller 'xref' + * on targets that support INTRNG. + * + * @param dev The device whose child is being examined. + * @param child The child device. + * @parem self If true, return @p child's interrupt domain, rather than the + * domain in which @p child resides. + * + * On Non-OFW targets, this should either return: + * - The pointer address of a device that can uniquely identify @p child's + * interrupt domain (e.g., the bhnd bus' device_t address), or + * - 0 if unsupported by the bus. + * + * On OFW (including FDT) targets, this should return the @p child's iparent + * property's xref if @p self is false, the child's own node xref value if + * @p self is true, or 0 if no interrupt parent is found. + */ +METHOD uintptr_t get_intr_domain { + device_t dev; + device_t child; + bool self; +} DEFAULT bhnd_bus_null_get_intr_domain; + +/** + * Return the number of interrupt lines assigned to @p child. + * + * @param dev The bhnd device whose child is being examined. + * @param child The child device. + */ +METHOD u_int get_intr_count { + device_t dev; + device_t child; +} DEFAULT bhnd_bus_null_get_intr_count; + +/** + * Get the backplane interrupt vector of the @p intr line attached to @p child. + * + * @param dev The device whose child is being examined. + * @param child The child device. + * @param intr The index of the interrupt line being queried. + * @param[out] ivec On success, the assigned hardware interrupt vector will be + * written to this pointer. + * + * On bcma(4) devices, this returns the OOB bus line assigned to the + * interrupt. + * + * On siba(4) devices, this returns the target OCP slave flag number assigned + * to the interrupt. + * + * @retval 0 success + * @retval ENXIO If @p intr exceeds the number of interrupt lines + * assigned to @p child. + */ +METHOD int get_intr_ivec { + device_t dev; + device_t child; + u_int intr; + u_int *ivec; +} DEFAULT bhnd_bus_null_get_intr_ivec; + +/** + * Map the given @p intr to an IRQ number; until unmapped, this IRQ may be used + * to allocate a resource of type SYS_RES_IRQ. + * + * On success, the caller assumes ownership of the interrupt mapping, and + * is responsible for releasing the mapping via BHND_BUS_UNMAP_INTR(). + * + * @param dev The bhnd bus device. + * @param child The requesting child device. + * @param intr The interrupt being mapped. + * @param[out] irq On success, the bus interrupt value mapped for @p intr. + * + * @retval 0 If an interrupt was assigned. + * @retval non-zero If mapping an interrupt otherwise fails, a regular + * unix error code will be returned. + */ +METHOD int map_intr { + device_t dev; + device_t child; + u_int intr; + rman_res_t *irq; +} DEFAULT bhnd_bus_null_map_intr; + +/** + * Unmap an bus interrupt previously mapped via BHND_BUS_MAP_INTR(). + * + * @param dev The bhnd bus device. + * @param child The requesting child device. + * @param intr The interrupt number being unmapped. This is equivalent to the + * bus resource ID for the interrupt. + */ +METHOD void unmap_intr { + device_t dev; + device_t child; + rman_res_t irq; +} DEFAULT bhnd_bus_null_unmap_intr; /** * Return true if @p region_num is a valid region on @p port_num of Index: head/sys/dev/bhnd/bhnd_ids.h =================================================================== --- head/sys/dev/bhnd/bhnd_ids.h +++ head/sys/dev/bhnd/bhnd_ids.h @@ -535,11 +535,15 @@ #define BHND_CHIPTYPE_UBUS 2 /**< ubus interconnect found in bcm63xx devices */ #define BHND_CHIPTYPE_BCMA_ALT 3 /**< bcma(4) interconnect */ -/** Evaluates to true if @p _type uses a BCMA EROM table */ -#define BHND_CHIPTYPE_HAS_EROM(_type) \ +/** Evaluates to true if @p _type is a BCMA or BCMA-compatible interconenct */ +#define BHND_CHIPTYPE_IS_BCMA_COMPATIBLE(_type) \ ((_type) == BHND_CHIPTYPE_BCMA || \ (_type) == BHND_CHIPTYPE_BCMA_ALT || \ (_type) == BHND_CHIPTYPE_UBUS) + +/** Evaluates to true if @p _type uses a BCMA EROM table */ +#define BHND_CHIPTYPE_HAS_EROM(_type) \ + BHND_CHIPTYPE_IS_BCMA_COMPATIBLE(_type) /* Boardflags */ #define BHND_BFL_BTC2WIRE 0x00000001 /* old 2wire Bluetooth coexistence, OBSOLETE */ Index: head/sys/dev/bhnd/bhnd_match.h =================================================================== --- head/sys/dev/bhnd/bhnd_match.h +++ head/sys/dev/bhnd/bhnd_match.h @@ -154,7 +154,8 @@ chip_id:1, chip_rev:1, chip_pkg:1, - flags_unused:5; + chip_type:1, + flags_unused:4; } match; } m; @@ -162,38 +163,46 @@ uint16_t chip_id; /**< required chip id */ struct bhnd_hwrev_match chip_rev; /**< matching chip revisions */ uint8_t chip_pkg; /**< required package */ + uint8_t chip_type; /**< required chip type (BHND_CHIPTYPE_*) */ }; #define _BHND_CHIP_MATCH_COPY(_src) \ _BHND_COPY_MATCH_FIELD(_src, chip_id), \ _BHND_COPY_MATCH_FIELD(_src, chip_rev), \ - _BHND_COPY_MATCH_FIELD(_src, chip_pkg) \ + _BHND_COPY_MATCH_FIELD(_src, chip_pkg), \ + _BHND_COPY_MATCH_FIELD(_src, chip_type),\ /** Set the required chip ID within a bhnd match descriptor */ -#define BHND_CHIP_ID(_cid) _BHND_SET_MATCH_FIELD(chip_id, \ +#define BHND_MATCH_CHIP_ID(_cid) _BHND_SET_MATCH_FIELD(chip_id, \ BHND_CHIPID_ ## _cid) /** Set the required chip revision range within a bhnd match descriptor */ -#define BHND_CHIP_REV(_rev) _BHND_SET_MATCH_FIELD(chip_rev, \ +#define BHND_MATCH_CHIP_REV(_rev) _BHND_SET_MATCH_FIELD(chip_rev, \ BHND_ ## _rev) /** Set the required package ID within a bhnd match descriptor */ -#define BHND_CHIP_PKG(_pkg) _BHND_SET_MATCH_FIELD(chip_pkg, \ +#define BHND_MATCH_CHIP_PKG(_pkg) _BHND_SET_MATCH_FIELD(chip_pkg, \ BHND_PKGID_ ## _pkg) +/** Set the required chip type within a bhnd match descriptor */ +#define BHND_MATCH_CHIP_TYPE(_type) _BHND_SET_MATCH_FIELD(chip_type, \ + BHND_CHIPTYPE_ ## _type) + /** Set the required chip and package ID within a bhnd match descriptor */ -#define BHND_CHIP_IP(_cid, _pkg) \ - BHND_CHIP_ID(_cid), BHND_CHIP_PKG(_pkg) +#define BHND_MATCH_CHIP_IP(_cid, _pkg) \ + BHND_MATCH_CHIP_ID(_cid), BHND_MATCH_CHIP_PKG(_pkg) /** Set the required chip ID, package ID, and revision within a bhnd_device_match * instance */ -#define BHND_CHIP_IPR(_cid, _pkg, _rev) \ - BHND_CHIP_ID(_cid), BHND_CHIP_PKG(_pkg), BHND_CHIP_REV(_rev) +#define BHND_MATCH_CHIP_IPR(_cid, _pkg, _rev) \ + BHND_MATCH_CHIP_ID(_cid), \ + BHND_MATCH_CHIP_PKG(_pkg), \ + BHND_MATCH_CHIP_REV(_rev) /** Set the required chip ID and revision within a bhnd_device_match * instance */ -#define BHND_CHIP_IR(_cid, _rev) \ - BHND_CHIP_ID(_cid), BHND_CHIP_REV(_rev) +#define BHND_MATCH_CHIP_IR(_cid, _rev) \ + BHND_MATCH_CHIP_ID(_cid), BHND_MATCH_CHIP_REV(_rev) /** * A bhnd(4) board match descriptor. @@ -252,9 +261,9 @@ struct bhnd_device_match { /** Select fields to be matched */ union { - uint16_t match_flags; + uint32_t match_flags; struct { - uint16_t + uint32_t core_vendor:1, core_id:1, core_rev:1, @@ -264,11 +273,12 @@ chip_id:1, chip_rev:1, chip_pkg:1, + chip_type:1, board_vendor:1, board_type:1, board_rev:1, board_srom_rev:1, - flags_unused:1; + flags_unused:16; } match; } m; @@ -282,6 +292,7 @@ uint16_t chip_id; /**< required chip id */ struct bhnd_hwrev_match chip_rev; /**< matching chip revisions */ uint8_t chip_pkg; /**< required package */ + uint8_t chip_type; /**< required chip type (BHND_CHIPTYPE_*) */ uint16_t board_vendor; /**< required board vendor */ uint16_t board_type; /**< required board type */ Index: head/sys/dev/bhnd/bhnd_subr.c =================================================================== --- head/sys/dev/bhnd/bhnd_subr.c +++ head/sys/dev/bhnd/bhnd_subr.c @@ -738,6 +738,9 @@ !bhnd_hwrev_matches(chip->chip_rev, &desc->chip_rev)) return (false); + if (desc->m.match.chip_type && chip->chip_type != desc->chip_type) + return (false); + return (true); } @@ -2317,3 +2320,14 @@ return (EINVAL); } +/** + * Helper function for implementing BHND_BUS_GET_INTR_DOMAIN(). + * + * This implementation simply returns the address of nearest bhnd(4) bus, + * which may be @p dev; this behavior may be incompatible with FDT/OFW targets. + */ +uintptr_t +bhnd_bus_generic_get_intr_domain(device_t dev, device_t child, bool self) +{ + return ((uintptr_t)dev); +} \ No newline at end of file Index: head/sys/dev/bhnd/bhndb/bhnd_bhndb.c =================================================================== --- head/sys/dev/bhnd/bhndb/bhnd_bhndb.c +++ head/sys/dev/bhnd/bhndb/bhnd_bhndb.c @@ -98,12 +98,19 @@ } static int -bhnd_bhndb_assign_intr(device_t dev, device_t child, int rid) +bhnd_bhndb_map_intr(device_t dev, device_t child, u_int intr, rman_res_t *irq) { /* Delegate to parent bridge */ - return (BHND_BUS_ASSIGN_INTR(device_get_parent(dev), child, rid)); + return (BHND_BUS_MAP_INTR(device_get_parent(dev), child, intr, irq)); } +static void +bhnd_bhndb_unmap_intr(device_t dev, device_t child, rman_res_t irq) +{ + /* Delegate to parent bridge */ + return (BHND_BUS_UNMAP_INTR(device_get_parent(dev), child, irq)); +} + static bhnd_clksrc bhnd_bhndb_pwrctl_get_clksrc(device_t dev, device_t child, bhnd_clock clock) @@ -131,13 +138,48 @@ clock)); } +static int +bhnd_bhndb_setup_intr(device_t dev, device_t child, struct resource *irq, + int flags, driver_filter_t *filter, driver_intr_t *intr, void *arg, + void **cookiep) +{ + device_t core, bus; + int error; + + /* Find the actual bus-attached child core */ + core = child; + while ((bus = device_get_parent(core)) != NULL) { + if (bus == dev) + break; + + core = bus; + } + + KASSERT(core != NULL, ("%s is not a child of %s", + device_get_nameunit(child), device_get_nameunit(dev))); + + /* Ask our bridge to enable interrupt routing for the child core */ + error = BHNDB_ROUTE_INTERRUPTS(device_get_parent(dev), core); + if (error) + return (error); + + /* Delegate actual interrupt setup to the default bhnd bus + * implementation */ + return (bhnd_generic_setup_intr(dev, child, irq, flags, filter, intr, + arg, cookiep)); +} + static device_method_t bhnd_bhndb_methods[] = { + /* Bus interface */ + DEVMETHOD(bus_setup_intr, bhnd_bhndb_setup_intr), + /* BHND interface */ DEVMETHOD(bhnd_bus_get_attach_type, bhnd_bhndb_get_attach_type), DEVMETHOD(bhnd_bus_is_hw_disabled, bhnd_bhndb_is_hw_disabled), DEVMETHOD(bhnd_bus_find_hostb_device, bhnd_bhndb_find_hostb_device), DEVMETHOD(bhnd_bus_read_board_info, bhnd_bhndb_read_board_info), - DEVMETHOD(bhnd_bus_assign_intr, bhnd_bhndb_assign_intr), + DEVMETHOD(bhnd_bus_map_intr, bhnd_bhndb_map_intr), + DEVMETHOD(bhnd_bus_unmap_intr, bhnd_bhndb_unmap_intr), DEVMETHOD(bhnd_bus_pwrctl_get_clksrc, bhnd_bhndb_pwrctl_get_clksrc), DEVMETHOD(bhnd_bus_pwrctl_gate_clock, bhnd_bhndb_pwrctl_gate_clock), Index: head/sys/dev/bhnd/bhndb/bhndb.c =================================================================== --- head/sys/dev/bhnd/bhndb/bhndb.c +++ head/sys/dev/bhnd/bhndb/bhndb.c @@ -804,7 +804,7 @@ case SYS_RES_MEMORY: return (&sc->bus_res->br_mem_rman); case SYS_RES_IRQ: - return (NULL); + return (&sc->bus_res->br_irq_rman); default: return (NULL); } @@ -1088,9 +1088,9 @@ if (!(rman_get_flags(r) & RF_ACTIVE)) goto done; - /* Otherwise, the range is limited to the existing register window - * mapping */ - error = bhndb_find_resource_limits(sc->bus_res, r, &mstart, &mend); + /* Otherwise, the range is limited by the bridged resource mapping */ + error = bhndb_find_resource_limits(sc->bus_res, type, r, &mstart, + &mend); if (error) goto done; @@ -1285,13 +1285,26 @@ BHNDB_LOCK_ASSERT(sc, MA_NOTOWNED); - /* Only MMIO resources can be mapped via register windows */ - if (type != SYS_RES_MEMORY) + if (indirect != NULL) + *indirect = false; + + switch (type) { + case SYS_RES_IRQ: + /* IRQ resources are always directly mapped */ + return (rman_activate_resource(r)); + + case SYS_RES_MEMORY: + /* Handled below */ + break; + + default: + device_printf(sc->dev, "unsupported resource type %d\n", type); return (ENXIO); + } + + /* Only MMIO resources can be mapped via register windows */ + KASSERT(type == SYS_RES_MEMORY, ("invalid type: %d", type)); - if (indirect) - *indirect = false; - r_start = rman_get_start(r); r_size = rman_get_size(r); @@ -1386,9 +1399,6 @@ /** * Default bhndb(4) implementation of BUS_ACTIVATE_RESOURCE(). - * - * Maps resource activation requests to a viable static or dynamic - * register window, if any. */ static int bhndb_activate_resource(device_t dev, device_t child, int type, int rid, @@ -1432,16 +1442,27 @@ if ((error = rman_deactivate_resource(r))) return (error); - /* Free any dynamic window allocation. */ - if (bhndb_get_addrspace(sc, child) == BHNDB_ADDRSPACE_BRIDGED) { - BHNDB_LOCK(sc); - dwa = bhndb_dw_find_resource(sc->bus_res, r); - if (dwa != NULL) - bhndb_dw_release(sc->bus_res, dwa, r); - BHNDB_UNLOCK(sc); - } + switch (type) { + case SYS_RES_IRQ: + /* No bridge-level state to be freed */ + return (0); - return (0); + case SYS_RES_MEMORY: + /* Free any dynamic window allocation. */ + if (bhndb_get_addrspace(sc, child) == BHNDB_ADDRSPACE_BRIDGED) { + BHNDB_LOCK(sc); + dwa = bhndb_dw_find_resource(sc->bus_res, r); + if (dwa != NULL) + bhndb_dw_release(sc->bus_res, dwa, r); + BHNDB_UNLOCK(sc); + } + + return (0); + + default: + device_printf(dev, "unsupported resource type %d\n", type); + return (ENXIO); + } } /** @@ -1457,12 +1478,15 @@ /** * Default bhndb(4) implementation of BHND_BUS_ACTIVATE_RESOURCE(). * - * For BHNDB_ADDRSPACE_NATIVE children, all resources may be assumed to - * be activated by the bridge. + * For BHNDB_ADDRSPACE_NATIVE children, all resources are activated as direct + * resources via BUS_ACTIVATE_RESOURCE(). * - * For BHNDB_ADDRSPACE_BRIDGED children, attempts to activate a static register - * window, a dynamic register window, or configures @p r as an indirect - * resource -- in that order. + * For BHNDB_ADDRSPACE_BRIDGED children, the resource priority is determined, + * and if possible, the resource is activated as a direct resource. For example, + * depending on resource priority and bridge resource availability, this + * function will attempt to activate SYS_RES_MEMORY resources using either a + * static register window, a dynamic register window, or it will configure @p r + * as an indirect resource -- in that order. */ static int bhndb_activate_bhnd_resource(device_t dev, device_t child, @@ -1470,6 +1494,7 @@ { struct bhndb_softc *sc; struct bhndb_region *region; + bhndb_priority_t r_prio; rman_res_t r_start, r_size; int error; bool indirect; @@ -1494,22 +1519,34 @@ r_start = rman_get_start(r->res); r_size = rman_get_size(r->res); - /* Verify bridged address range's resource priority, and skip direct + /* Determine the resource priority of bridged resources, and skip direct * allocation if the priority is too low. */ if (bhndb_get_addrspace(sc, child) == BHNDB_ADDRSPACE_BRIDGED) { - bhndb_priority_t r_prio; + switch (type) { + case SYS_RES_IRQ: + /* IRQ resources are always direct */ + break; - region = bhndb_find_resource_region(sc->bus_res, r_start, - r_size); - if (region != NULL) - r_prio = region->priority; - else - r_prio = BHNDB_PRIORITY_NONE; + case SYS_RES_MEMORY: + region = bhndb_find_resource_region(sc->bus_res, + r_start, r_size); + if (region != NULL) + r_prio = region->priority; + else + r_prio = BHNDB_PRIORITY_NONE; - /* If less than the minimum dynamic window priority, this - * resource should always be indirect. */ - if (r_prio < sc->bus_res->min_prio) - return (0); + /* If less than the minimum dynamic window priority, + * this resource should always be indirect. */ + if (r_prio < sc->bus_res->min_prio) + return (0); + + break; + + default: + device_printf(dev, "unsupported resource type %d\n", + type); + return (ENXIO); + } } /* Attempt direct activation */ @@ -1792,6 +1829,217 @@ } /** + * Default bhndb(4) implementation of BHND_MAP_INTR(). + */ +static int +bhndb_bhnd_map_intr(device_t dev, device_t child, u_int intr, rman_res_t *irq) +{ + struct bhndb_softc *sc; + u_int ivec; + int error; + + sc = device_get_softc(dev); + + /* Is the intr valid? */ + if (intr >= bhnd_get_intr_count(child)) + return (EINVAL); + + /* Fetch the interrupt vector */ + if ((error = bhnd_get_intr_ivec(child, intr, &ivec))) + return (error); + + /* Map directly to the actual backplane interrupt vector */ + *irq = ivec; + + return (0); +} + +/** + * Default bhndb(4) implementation of BHND_UNMAP_INTR(). + */ +static void +bhndb_bhnd_unmap_intr(device_t dev, device_t child, rman_res_t irq) +{ + /* No state to clean up */ +} + +/** + * Default bhndb(4) implementation of BUS_SETUP_INTR(). + */ +static int +bhndb_setup_intr(device_t dev, device_t child, struct resource *r, + int flags, driver_filter_t filter, driver_intr_t handler, void *arg, + void **cookiep) +{ + struct bhndb_softc *sc; + struct bhndb_intr_isrc *isrc; + struct bhndb_intr_handler *ih; + int error; + + sc = device_get_softc(dev); + + /* Fetch the isrc */ + if ((error = BHNDB_MAP_INTR_ISRC(dev, r, &isrc))) { + device_printf(dev, "failed to fetch isrc: %d\n", error); + return (error); + } + + /* Allocate new ihandler entry */ + ih = bhndb_alloc_intr_handler(child, r, isrc); + if (ih == NULL) + return (ENOMEM); + + /* Perform actual interrupt setup via the host isrc */ + error = bus_setup_intr(isrc->is_owner, isrc->is_res, flags, filter, + handler, arg, &ih->ih_cookiep); + if (error) { + bhndb_free_intr_handler(ih); + return (error); + } + + /* Add to our interrupt handler list */ + BHNDB_LOCK(sc); + bhndb_register_intr_handler(sc->bus_res, ih); + BHNDB_UNLOCK(sc); + + /* Provide the interrupt handler entry as our cookiep value */ + *cookiep = ih; + return (0); +} + +/** + * Default bhndb(4) implementation of BUS_TEARDOWN_INTR(). + */ +static int +bhndb_teardown_intr(device_t dev, device_t child, struct resource *r, + void *cookiep) +{ + struct bhndb_softc *sc; + struct bhndb_intr_handler *ih; + struct bhndb_intr_isrc *isrc; + int error; + + sc = device_get_softc(dev); + + /* Locate and claim ownership of the interrupt handler entry */ + BHNDB_LOCK(sc); + + ih = bhndb_find_intr_handler(sc->bus_res, cookiep); + if (ih == NULL) { + panic("%s requested teardown of invalid cookiep %p", + device_get_nameunit(child), cookiep); + } + + bhndb_deregister_intr_handler(sc->bus_res, ih); + + BHNDB_UNLOCK(sc); + + /* Perform actual interrupt teardown via the host isrc */ + isrc = ih->ih_isrc; + error = bus_teardown_intr(isrc->is_owner, isrc->is_res, ih->ih_cookiep); + if (error) { + /* If teardown fails, we need to reinsert the handler entry + * to allow later teardown */ + BHNDB_LOCK(sc); + bhndb_register_intr_handler(sc->bus_res, ih); + BHNDB_UNLOCK(sc); + + return (error); + } + + /* Free the entry */ + bhndb_free_intr_handler(ih); + return (0); +} + +/** + * Default bhndb(4) implementation of BUS_BIND_INTR(). + */ +static int +bhndb_bind_intr(device_t dev, device_t child, struct resource *irq, int cpu) +{ + struct bhndb_softc *sc; + struct bhndb_intr_handler *ih; + struct bhndb_intr_isrc *isrc; + + sc = device_get_softc(dev); + isrc = NULL; + + /* Fetch the isrc corresponding to the child IRQ resource */ + BHNDB_LOCK(sc); + STAILQ_FOREACH(ih, &sc->bus_res->bus_intrs, ih_link) { + if (ih->ih_res == irq) { + isrc = ih->ih_isrc; + break; + } + } + BHNDB_UNLOCK(sc); + + if (isrc == NULL) { + panic("%s requested bind of invalid irq %#jx-%#jx", + device_get_nameunit(child), rman_get_start(irq), + rman_get_end(irq)); + } + + /* Perform actual bind via the host isrc */ + return (bus_bind_intr(isrc->is_owner, isrc->is_res, cpu)); +} + +/** + * Default bhndb(4) implementation of BUS_DESCRIBE_INTR(). + */ +static int +bhndb_describe_intr(device_t dev, device_t child, struct resource *irq, + void *cookie, const char *descr) +{ + struct bhndb_softc *sc; + struct bhndb_intr_handler *ih; + struct bhndb_intr_isrc *isrc; + + sc = device_get_softc(dev); + + /* Locate the interrupt handler entry; the caller owns the handler + * reference, and thus our entry is guaranteed to remain valid after + * we drop out lock below. */ + BHNDB_LOCK(sc); + + ih = bhndb_find_intr_handler(sc->bus_res, cookie); + if (ih == NULL) { + panic("%s requested invalid cookiep %p", + device_get_nameunit(child), cookie); + } + + isrc = ih->ih_isrc; + + BHNDB_UNLOCK(sc); + + /* Perform the actual request via the host isrc */ + return (BUS_DESCRIBE_INTR(device_get_parent(isrc->is_owner), + isrc->is_owner, isrc->is_res, ih->ih_cookiep, descr)); +} + +/** + * Default bhndb(4) implementation of BUS_CONFIG_INTR(). + */ +static int +bhndb_config_intr(device_t dev, int irq, enum intr_trigger trig, + enum intr_polarity pol) +{ + /* Unsupported */ + return (ENXIO); +} + +/** + * Default bhndb(4) implementation of BUS_REMAP_INTR(). + */ +static int +bhndb_remap_intr(device_t dev, device_t child, u_int irq) +{ + /* Unsupported */ + return (ENXIO); +} + +/** * Default bhndb(4) implementation of BUS_GET_DMA_TAG(). */ static bus_dma_tag_t @@ -1822,11 +2070,12 @@ DEVMETHOD(bus_activate_resource, bhndb_activate_resource), DEVMETHOD(bus_deactivate_resource, bhndb_deactivate_resource), - DEVMETHOD(bus_setup_intr, bus_generic_setup_intr), - DEVMETHOD(bus_teardown_intr, bus_generic_teardown_intr), - DEVMETHOD(bus_config_intr, bus_generic_config_intr), - DEVMETHOD(bus_bind_intr, bus_generic_bind_intr), - DEVMETHOD(bus_describe_intr, bus_generic_describe_intr), + DEVMETHOD(bus_setup_intr, bhndb_setup_intr), + DEVMETHOD(bus_teardown_intr, bhndb_teardown_intr), + DEVMETHOD(bus_config_intr, bhndb_config_intr), + DEVMETHOD(bus_bind_intr, bhndb_bind_intr), + DEVMETHOD(bus_describe_intr, bhndb_describe_intr), + DEVMETHOD(bus_remap_intr, bhndb_remap_intr), DEVMETHOD(bus_get_dma_tag, bhndb_get_dma_tag), @@ -1851,6 +2100,8 @@ DEVMETHOD(bhnd_bus_activate_resource, bhndb_activate_bhnd_resource), DEVMETHOD(bhnd_bus_deactivate_resource, bhndb_deactivate_bhnd_resource), DEVMETHOD(bhnd_bus_get_nvram_var, bhnd_bus_generic_get_nvram_var), + DEVMETHOD(bhnd_bus_map_intr, bhndb_bhnd_map_intr), + DEVMETHOD(bhnd_bus_unmap_intr, bhndb_bhnd_unmap_intr), DEVMETHOD(bhnd_bus_get_service_registry,bhndb_get_service_registry), DEVMETHOD(bhnd_bus_register_provider, bhnd_bus_generic_sr_register_provider), Index: head/sys/dev/bhnd/bhndb/bhndb_if.m =================================================================== --- head/sys/dev/bhnd/bhndb/bhndb_if.m +++ head/sys/dev/bhnd/bhndb/bhndb_if.m @@ -1,7 +1,11 @@ #- -# Copyright (c) 2015 Landon Fuller +# Copyright (c) 2015-2016 Landon Fuller +# 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: @@ -40,6 +44,7 @@ INTERFACE bhndb; HEADER { + struct bhndb_intr_isrc; struct bhndb_regwin; struct bhndb_hw; struct bhndb_hw_priority; @@ -91,11 +96,24 @@ } static int + bhndb_null_route_interrupts(device_t dev, device_t child) + { + panic("bhndb_route_interrupts unimplemented"); + } + + static int bhndb_null_set_window_addr(device_t dev, const struct bhndb_regwin *rw, bhnd_addr_t addr) { panic("bhndb_set_window_addr unimplemented"); } + + static int + bhndb_null_map_intr_isrc(device_t dev, struct resource *irq, + struct bhndb_intr_isrc **isrc) + { + panic("bhndb_map_intr_isrc unimplemented"); + } } /** @@ -208,6 +226,17 @@ } DEFAULT bhndb_null_resume_resource; /** + * Enable bridge-level interrupt routing for @p child. + * + * @param dev The bridge device. + * @param child The bhnd child device for which interrupts should be routed. + */ +METHOD int route_interrupts { + device_t dev; + device_t child; +} DEFAULT bhndb_null_route_interrupts; + +/** * Set a given register window's base address. * * @param dev The bridge device. @@ -224,3 +253,22 @@ const struct bhndb_regwin *win; bhnd_addr_t addr; } DEFAULT bhndb_null_set_window_addr; + +/** + * Map a bridged interrupt resource to its corresponding host interrupt source, + * if any. + * + * @param dev The bridge device. + * @param irq The bridged interrupt resource. + * @param[out] isrc The host interrupt source to which the bridged interrupt + * is routed. + * + * @retval 0 success + * @retval non-zero if mapping @p irq otherwise fails, a regular unix error code + * will be returned. + */ +METHOD int map_intr_isrc { + device_t dev; + struct resource *irq; + struct bhndb_intr_isrc **isrc; +} DEFAULT bhndb_null_map_intr_isrc; Index: head/sys/dev/bhnd/bhndb/bhndb_pci.c =================================================================== --- head/sys/dev/bhnd/bhndb/bhndb_pci.c +++ head/sys/dev/bhnd/bhndb/bhndb_pci.c @@ -64,6 +64,8 @@ #include #include +#include + #include #include "bhndb_pcireg.h" @@ -72,13 +74,15 @@ struct bhndb_pci_eio; -static int bhndb_pci_init_msi(struct bhndb_pci_softc *sc); +static int bhndb_pci_alloc_msi(struct bhndb_pci_softc *sc, + int *msi_count); static int bhndb_pci_read_core_table(device_t dev, struct bhnd_chipid *chipid, struct bhnd_core_info **cores, u_int *ncores, bhnd_erom_class_t **eromcls); static int bhndb_pci_add_children(struct bhndb_pci_softc *sc); +static bhnd_devclass_t bhndb_expected_pci_devclass(device_t dev); static bool bhndb_is_pcie_attached(device_t dev); static int bhndb_enable_pci_clocks(device_t dev); @@ -90,6 +94,11 @@ static int bhndb_pci_fast_setregwin(device_t dev, device_t pci_dev, const struct bhndb_regwin *, bhnd_addr_t); +static void bhndb_pci_write_core(struct bhndb_pci_softc *sc, + bus_size_t offset, uint32_t value, u_int width); +static uint32_t bhndb_pci_read_core(struct bhndb_pci_softc *sc, + bus_size_t offset, u_int width); + static void bhndb_init_sromless_pci_config( struct bhndb_pci_softc *sc); @@ -106,7 +115,18 @@ #define BHNDB_PCI_MSI_COUNT 1 -/* bhndb_pci erom I/O implementation */ +static struct bhndb_pci_quirk bhndb_pci_quirks[]; +static struct bhndb_pci_quirk bhndb_pcie_quirks[]; +static struct bhndb_pci_quirk bhndb_pcie2_quirks[]; + +static struct bhndb_pci_core bhndb_pci_cores[] = { + BHNDB_PCI_CORE(PCI, BHND_PCI_SRSH_PI_OFFSET, bhndb_pci_quirks), + BHNDB_PCI_CORE(PCIE, BHND_PCIE_SRSH_PI_OFFSET, bhndb_pcie_quirks), + BHNDB_PCI_CORE(PCIE2, BHND_PCIE_SRSH_PI_OFFSET, bhndb_pcie2_quirks), + BHNDB_PCI_CORE_END +}; + +/* bhndb_pci erom I/O instance state */ struct bhndb_pci_eio { struct bhnd_erom_io eio; device_t dev; /**< bridge device */ @@ -120,6 +140,83 @@ bhnd_size_t size; /**< mapped size */ }; +static struct bhndb_pci_quirk bhndb_pci_quirks[] = { + /* Backplane interrupt flags must be routed via siba-specific + * SIBA_CFG0_INTVEC configuration register; the BHNDB_PCI_INT_MASK + * PCI configuration register is unsupported. */ + {{ BHND_MATCH_CHIP_TYPE (SIBA) }, + { BHND_MATCH_CORE_REV (HWREV_LTE(5)) }, + BHNDB_PCI_QUIRK_SIBA_INTVEC }, + + /* All PCI core revisions require the SRSH work-around */ + BHNDB_PCI_QUIRK(HWREV_ANY, BHNDB_PCI_QUIRK_SRSH_WAR), + BHNDB_PCI_QUIRK_END +}; + +static struct bhndb_pci_quirk bhndb_pcie_quirks[] = { + /* All PCIe-G1 core revisions require the SRSH work-around */ + BHNDB_PCI_QUIRK(HWREV_ANY, BHNDB_PCI_QUIRK_SRSH_WAR), + BHNDB_PCI_QUIRK_END +}; + +static struct bhndb_pci_quirk bhndb_pcie2_quirks[] = { + /* All PCIe-G2 core revisions require the SRSH work-around */ + BHNDB_PCI_QUIRK(HWREV_ANY, BHNDB_PCI_QUIRK_SRSH_WAR), + BHNDB_PCI_QUIRK_END +}; + + +/** + * Return the device table entry for @p ci, or NULL if none. + */ +static struct bhndb_pci_core * +bhndb_pci_find_core(struct bhnd_core_info *ci) +{ + for (size_t i = 0; !BHNDB_PCI_IS_CORE_END(&bhndb_pci_cores[i]); i++) { + struct bhndb_pci_core *entry = &bhndb_pci_cores[i]; + + if (bhnd_core_matches(ci, &entry->match)) + return (entry); + } + + return (NULL); +} + +/** + * Return all quirk flags for the given @p cid and @p ci. + */ +static uint32_t +bhndb_pci_get_core_quirks(struct bhnd_chipid *cid, struct bhnd_core_info *ci) +{ + struct bhndb_pci_core *entry; + struct bhndb_pci_quirk *qtable; + uint32_t quirks; + + quirks = 0; + + /* No core entry? */ + if ((entry = bhndb_pci_find_core(ci)) == NULL) + return (quirks); + + /* No quirks? */ + if ((qtable = entry->quirks) == NULL) + return (quirks); + + for (size_t i = 0; !BHNDB_PCI_IS_QUIRK_END(&qtable[i]); i++) { + struct bhndb_pci_quirk *q = &qtable[i]; + + if (!bhnd_chip_matches(cid, &q->chip_desc)) + continue; + + if (!bhnd_core_matches(ci, &q->core_desc)) + continue; + + quirks |= q->quirks; + } + + return (quirks); +} + /** * Default bhndb_pci implementation of device_probe(). * @@ -128,10 +225,17 @@ static int bhndb_pci_probe(device_t dev) { - device_t parent; - devclass_t parent_bus; - devclass_t pci; + struct bhnd_chipid cid; + struct bhnd_core_info *cores, hostb_core; + struct bhndb_pci_core *entry; + bhnd_devclass_t hostb_devclass; + u_int ncores; + device_t parent; + devclass_t parent_bus, pci; + int error; + cores = NULL; + /* Our parent must be a PCI/PCIe device. */ pci = devclass_find("pci"); parent = device_get_parent(dev); @@ -140,35 +244,67 @@ if (parent_bus != pci) return (ENXIO); + /* Enable clocks */ + if ((error = bhndb_enable_pci_clocks(dev))) + return (error); + + /* Identify the chip and enumerate the bridged cores */ + error = bhndb_pci_read_core_table(dev, &cid, &cores, &ncores, NULL); + if (error) + goto cleanup; + + /* Search our core table for the host bridge core */ + hostb_devclass = bhndb_expected_pci_devclass(dev); + error = bhndb_find_hostb_core(cores, ncores, hostb_devclass, + &hostb_core); + if (error) + goto cleanup; + + /* Look for a matching core table entry */ + if ((entry = bhndb_pci_find_core(&hostb_core)) == NULL) { + error = ENXIO; + goto cleanup; + } + device_set_desc(dev, "PCI-BHND bridge"); - return (BUS_PROBE_DEFAULT); + /* fall-through */ + error = BUS_PROBE_DEFAULT; + +cleanup: + bhndb_disable_pci_clocks(dev); + if (cores != NULL) + free(cores, M_BHND); + + return (error); } -/* Configure MSI interrupts */ +/** + * Attempt to allocate MSI interrupts, returning the count in @p msi_count + * on success. + */ static int -bhndb_pci_init_msi(struct bhndb_pci_softc *sc) +bhndb_pci_alloc_msi(struct bhndb_pci_softc *sc, int *msi_count) { - int error; + int error, count; /* Is MSI available? */ if (pci_msi_count(sc->parent) < BHNDB_PCI_MSI_COUNT) return (ENXIO); /* Allocate expected message count */ - sc->intr.msi_count = BHNDB_PCI_MSI_COUNT; - if ((error = pci_alloc_msi(sc->parent, &sc->intr.msi_count))) { + count = BHNDB_PCI_MSI_COUNT; + if ((error = pci_alloc_msi(sc->parent, &count))) { device_printf(sc->dev, "failed to allocate MSI interrupts: " "%d\n", error); + return (error); } - if (sc->intr.msi_count < BHNDB_PCI_MSI_COUNT) + if (count < BHNDB_PCI_MSI_COUNT) return (ENXIO); - /* MSI uses resource IDs starting at 1 */ - sc->intr.intr_rid = 1; - + *msi_count = count; return (0); } @@ -180,34 +316,46 @@ struct bhnd_core_info *cores, hostb_core; bhnd_erom_class_t *erom_class; u_int ncores; - int error, reg; + int irq_rid; + int error; sc = device_get_softc(dev); sc->dev = dev; sc->parent = device_get_parent(dev); + sc->pci_devclass = bhndb_expected_pci_devclass(dev); + sc->pci_quirks = 0; sc->set_regwin = NULL; + BHNDB_PCI_LOCK_INIT(sc); + cores = NULL; /* Enable PCI bus mastering */ pci_enable_busmaster(sc->parent); /* Set up PCI interrupt handling */ - if (bhndb_pci_init_msi(sc) == 0) { + if (bhndb_pci_alloc_msi(sc, &sc->msi_count) == 0) { + /* MSI uses resource IDs starting at 1 */ + irq_rid = 1; + device_printf(dev, "Using MSI interrupts on %s\n", device_get_nameunit(sc->parent)); } else { + sc->msi_count = 0; + irq_rid = 0; + device_printf(dev, "Using INTx interrupts on %s\n", device_get_nameunit(sc->parent)); - sc->intr.intr_rid = 0; } - /* Determine our bridge device class */ - sc->pci_devclass = BHND_DEVCLASS_PCI; - if (pci_find_cap(sc->parent, PCIY_EXPRESS, ®) == 0) - sc->pci_devclass = BHND_DEVCLASS_PCIE; - else - sc->pci_devclass = BHND_DEVCLASS_PCI; + sc->isrc = bhndb_alloc_intr_isrc(sc->parent, irq_rid, 0, RM_MAX_END, 1, + RF_SHAREABLE | RF_ACTIVE); + if (sc->isrc == NULL) { + device_printf(sc->dev, "failed to allocate interrupt " + "resource\n"); + error = ENXIO; + goto cleanup; + } /* Enable clocks (if required by this hardware) */ if ((error = bhndb_enable_pci_clocks(sc->dev))) @@ -226,12 +374,14 @@ sc->set_regwin = bhndb_pci_fast_setregwin; } - /* Determine our host bridge core */ + /* Determine our host bridge core and populate our quirk flags */ error = bhndb_find_hostb_core(cores, ncores, sc->pci_devclass, &hostb_core); if (error) goto cleanup; + sc->pci_quirks = bhndb_pci_get_core_quirks(&cid, &hostb_core); + /* Perform bridge attach */ error = bhndb_attach(dev, &cid, cores, ncores, &hostb_core, erom_class); if (error) @@ -256,7 +406,10 @@ device_delete_children(dev); bhndb_disable_pci_clocks(sc->dev); - if (sc->intr.msi_count > 0) + if (sc->isrc != NULL) + bhndb_free_intr_isrc(sc->isrc); + + if (sc->msi_count > 0) pci_release_msi(dev); if (cores != NULL) @@ -264,6 +417,8 @@ pci_disable_busmaster(sc->parent); + BHNDB_PCI_LOCK_DESTROY(sc); + return (error); } @@ -287,13 +442,18 @@ if ((error = bhndb_disable_pci_clocks(sc->dev))) return (error); + /* Free our interrupt resources */ + bhndb_free_intr_isrc(sc->isrc); + /* Release MSI interrupts */ - if (sc->intr.msi_count > 0) + if (sc->msi_count > 0) pci_release_msi(dev); /* Disable PCI bus mastering */ pci_disable_busmaster(sc->parent); + BHNDB_PCI_LOCK_DESTROY(sc); + return (0); } @@ -541,6 +701,112 @@ return (sprom_sz); } +/** + * Return the host resource providing a static mapping of the PCI core's + * registers. + * + * @param sc bhndb PCI driver state. + * @param[out] res On success, the host resource containing our PCI + * core's register window. + * @param[out] offset On success, the offset of the PCI core registers within + * @p res. + * + * @retval 0 success + * @retval ENXIO if a valid static register window mapping the PCI core + * registers is not available. + */ +static int +bhndb_pci_get_core_regs(struct bhndb_pci_softc *sc, struct resource **res, + bus_size_t *offset) +{ + const struct bhndb_regwin *win; + struct resource *r; + + /* Locate the static register window mapping the PCI core */ + win = bhndb_regwin_find_core(sc->bhndb.bus_res->cfg->register_windows, + sc->pci_devclass, 0, BHND_PORT_DEVICE, 0, 0); + if (win == NULL) { + device_printf(sc->dev, "missing PCI core register window\n"); + return (ENXIO); + } + + /* Fetch the resource containing the register window */ + r = bhndb_host_resource_for_regwin(sc->bhndb.bus_res->res, win); + if (r == NULL) { + device_printf(sc->dev, "missing PCI core register resource\n"); + return (ENXIO); + } + + *res = r; + *offset = win->win_offset; + + return (0); +} + +/** + * Write a 1, 2, or 4 byte data item to the PCI core's registers at @p offset. + * + * @param sc bhndb PCI driver state. + * @param offset register write offset. + * @param value value to be written. + * @param width item width (1, 2, or 4 bytes). + */ +static void +bhndb_pci_write_core(struct bhndb_pci_softc *sc, bus_size_t offset, + uint32_t value, u_int width) +{ + struct resource *r; + bus_size_t r_offset; + int error; + + if ((error = bhndb_pci_get_core_regs(sc, &r, &r_offset))) + panic("no PCI core registers: %d", error); + + switch (width) { + case 1: + bus_write_1(r, r_offset + offset, value); + break; + case 2: + bus_write_2(r, r_offset + offset, value); + break; + case 4: + bus_write_4(r, r_offset + offset, value); + break; + default: + panic("invalid width: %u", width); + } +} + +/** + * Read a 1, 2, or 4 byte data item from the PCI core's registers + * at @p offset. + * + * @param sc bhndb PCI driver state. + * @param offset register read offset. + * @param width item width (1, 2, or 4 bytes). + */ +static uint32_t +bhndb_pci_read_core(struct bhndb_pci_softc *sc, bus_size_t offset, u_int width) +{ + struct resource *r; + bus_size_t r_offset; + int error; + + if ((error = bhndb_pci_get_core_regs(sc, &r, &r_offset))) + panic("no PCI core registers: %d", error); + + switch (width) { + case 1: + return (bus_read_1(r, r_offset + offset)); + case 2: + return (bus_read_2(r, r_offset + offset)); + case 4: + return (bus_read_4(r, r_offset + offset)); + default: + panic("invalid width: %u", width); + } +} + /* * On devices without a SROM, the PCI(e) cores will be initialized with * their Power-on-Reset defaults; this can leave two of the BAR0 PCI windows @@ -554,68 +820,31 @@ static void bhndb_init_sromless_pci_config(struct bhndb_pci_softc *sc) { - struct bhndb_resources *bres; - const struct bhndb_hwcfg *cfg; - const struct bhndb_regwin *win; - struct bhnd_core_info hostb_core; - struct resource *core_regs; - bus_size_t srom_offset; + const struct bhndb_pci_core *pci_core; + bus_size_t srsh_offset; u_int pci_cidx, sprom_cidx; uint16_t val; - int error; - bres = sc->bhndb.bus_res; - cfg = bres->cfg; - - /* Find our hostb core */ - error = BHNDB_GET_HOSTB_CORE(sc->dev, sc->bhndb.bus_dev, &hostb_core); - if (error) { - device_printf(sc->dev, "no host bridge device found\n"); + if ((sc->pci_quirks & BHNDB_PCI_QUIRK_SRSH_WAR) == 0) return; - } - if (hostb_core.vendor != BHND_MFGID_BCM) - return; + /* Determine the correct register offset for our PCI core */ + pci_core = bhndb_pci_find_core(&sc->bhndb.bridge_core); + KASSERT(pci_core != NULL, ("missing core table entry")); - switch (hostb_core.device) { - case BHND_COREID_PCI: - srom_offset = BHND_PCI_SRSH_PI_OFFSET; - break; - case BHND_COREID_PCIE: - srom_offset = BHND_PCIE_SRSH_PI_OFFSET; - break; - default: - device_printf(sc->dev, "unsupported PCI host bridge device\n"); - return; - } + srsh_offset = pci_core->srsh_offset; - /* Locate the static register window mapping the PCI core */ - win = bhndb_regwin_find_core(cfg->register_windows, sc->pci_devclass, - 0, BHND_PORT_DEVICE, 0, 0); - if (win == NULL) { - device_printf(sc->dev, "missing PCI core register window\n"); - return; - } - - /* Fetch the resource containing the register window */ - core_regs = bhndb_host_resource_for_regwin(bres->res, win); - if (core_regs == NULL) { - device_printf(sc->dev, "missing PCI core register resource\n"); - return; - } - /* Fetch the SPROM's configured core index */ - val = bus_read_2(core_regs, win->win_offset + srom_offset); + val = bhndb_pci_read_core(sc, srsh_offset, sizeof(val)); sprom_cidx = (val & BHND_PCI_SRSH_PI_MASK) >> BHND_PCI_SRSH_PI_SHIFT; /* If it doesn't match host bridge's core index, update the index * value */ - pci_cidx = hostb_core.core_idx; + pci_cidx = sc->bhndb.bridge_core.core_idx; if (sprom_cidx != pci_cidx) { val &= ~BHND_PCI_SRSH_PI_MASK; val |= (pci_cidx << BHND_PCI_SRSH_PI_SHIFT); - bus_write_2(core_regs, - win->win_offset + srom_offset, val); + bhndb_pci_write_core(sc, srsh_offset, val, sizeof(val)); } } @@ -770,7 +999,22 @@ } /** - * Return true if the bridge device @p bhndb is attached via PCIe, + * Examine the bridge device @p dev and return the expected host bridge + * device class. + * + * @param dev The bhndb bridge device + */ +static bhnd_devclass_t +bhndb_expected_pci_devclass(device_t dev) +{ + if (bhndb_is_pcie_attached(dev)) + return (BHND_DEVCLASS_PCIE); + else + return (BHND_DEVCLASS_PCI); +} + +/** + * Return true if the bridge device @p dev is attached via PCIe, * false otherwise. * * @param dev The bhndb bridge device @@ -939,27 +1183,85 @@ return (bhndb_enable_pci_clocks(sc->dev)); } +/** + * BHNDB_MAP_INTR_ISRC() + */ static int -bhndb_pci_assign_intr(device_t dev, device_t child, int rid) +bhndb_pci_map_intr_isrc(device_t dev, struct resource *irq, + struct bhndb_intr_isrc **isrc) { + struct bhndb_pci_softc *sc = device_get_softc(dev); + + /* There's only one bridged interrupt to choose from */ + *isrc = sc->isrc; + return (0); +} + +/* siba-specific implementation of BHNDB_ROUTE_INTERRUPTS() */ +static int +bhndb_pci_route_siba_interrupts(struct bhndb_pci_softc *sc, device_t child) +{ + uint32_t sbintvec; + u_int ivec; + int error; + + KASSERT(sc->pci_quirks & BHNDB_PCI_QUIRK_SIBA_INTVEC, + ("route_siba_interrupts not supported by this hardware")); + + /* Fetch the sbflag# for the child */ + if ((error = bhnd_get_intr_ivec(child, 0, &ivec))) + return (error); + + if (ivec > (sizeof(sbintvec)*8) - 1 /* aka '31' */) { + /* This should never be an issue in practice */ + device_printf(sc->dev, "cannot route interrupts to high " + "sbflag# %u\n", ivec); + return (ENXIO); + } + + BHNDB_PCI_LOCK(sc); + + sbintvec = bhndb_pci_read_core(sc, SB0_REG_ABS(SIBA_CFG0_INTVEC), 4); + sbintvec |= (1 << ivec); + bhndb_pci_write_core(sc, SB0_REG_ABS(SIBA_CFG0_INTVEC), sbintvec, 4); + + BHNDB_PCI_UNLOCK(sc); + + return (0); +} + +/* BHNDB_ROUTE_INTERRUPTS() */ +static int +bhndb_pci_route_interrupts(device_t dev, device_t child) +{ struct bhndb_pci_softc *sc; - rman_res_t start, count; - int error; + struct bhnd_core_info core; + uint32_t core_bit; + uint32_t intmask; sc = device_get_softc(dev); - /* Is the rid valid? */ - if (rid >= bhnd_get_intr_count(child)) - return (EINVAL); - - /* Fetch our common PCI interrupt's start/count. */ - error = bus_get_resource(sc->parent, SYS_RES_IRQ, sc->intr.intr_rid, - &start, &count); - if (error) - return (error); + if (sc->pci_quirks & BHNDB_PCI_QUIRK_SIBA_INTVEC) + return (bhndb_pci_route_siba_interrupts(sc, child)); - /* Add to child's resource list */ - return (bus_set_resource(child, SYS_RES_IRQ, rid, start, count)); + core = bhnd_get_core_info(child); + if (core.core_idx > BHNDB_PCI_SBIM_COREIDX_MAX) { + /* This should never be an issue in practice */ + device_printf(dev, "cannot route interrupts to high core " + "index %u\n", core.core_idx); + return (ENXIO); + } + + BHNDB_PCI_LOCK(sc); + + core_bit = (1<parent, BHNDB_PCI_INT_MASK, 4); + intmask |= core_bit; + pci_write_config(sc->parent, BHNDB_PCI_INT_MASK, intmask, 4); + + BHNDB_PCI_UNLOCK(sc); + + return (0); } /** @@ -1156,8 +1458,6 @@ DEVMETHOD(device_detach, bhndb_pci_detach), /* BHND interface */ - DEVMETHOD(bhnd_bus_assign_intr, bhndb_pci_assign_intr), - DEVMETHOD(bhnd_bus_pwrctl_get_clksrc, bhndb_pci_pwrctl_get_clksrc), DEVMETHOD(bhnd_bus_pwrctl_gate_clock, bhndb_pci_pwrctl_gate_clock), DEVMETHOD(bhnd_bus_pwrctl_ungate_clock, bhndb_pci_pwrctl_ungate_clock), @@ -1165,6 +1465,8 @@ /* BHNDB interface */ DEVMETHOD(bhndb_set_window_addr, bhndb_pci_set_window_addr), DEVMETHOD(bhndb_populate_board_info, bhndb_pci_populate_board_info), + DEVMETHOD(bhndb_map_intr_isrc, bhndb_pci_map_intr_isrc), + DEVMETHOD(bhndb_route_interrupts, bhndb_pci_route_interrupts), DEVMETHOD_END }; Index: head/sys/dev/bhnd/bhndb/bhndb_pcireg.h =================================================================== --- head/sys/dev/bhnd/bhndb/bhndb_pcireg.h +++ head/sys/dev/bhnd/bhndb/bhndb_pcireg.h @@ -184,6 +184,7 @@ /* BHNDB_PCI_INT_MASK */ #define BHNDB_PCI_SBIM_SHIFT 8 /* backplane core interrupt mask bits offset */ +#define BHNDB_PCI_SBIM_COREIDX_MAX 15 /**< maximum representible core index (in 16 bit field) */ #define BHNDB_PCI_SBIM_MASK 0xff00 /* backplane core interrupt mask */ #define BHNDB_PCI_SBIM_MASK_SERR 0x4 /* backplane SBErr interrupt mask */ Index: head/sys/dev/bhnd/bhndb/bhndb_pcivar.h =================================================================== --- head/sys/dev/bhnd/bhndb/bhndb_pcivar.h +++ head/sys/dev/bhnd/bhndb/bhndb_pcivar.h @@ -1,7 +1,11 @@ /*- - * Copyright (c) 2015 Landon Fuller + * Copyright (c) 2015-2016 Landon Fuller + * 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: @@ -48,20 +52,82 @@ typedef int (*bhndb_pci_set_regwin_t)(device_t dev, device_t pci_dev, const struct bhndb_regwin *rw, bhnd_addr_t addr); -/* bhndb_pci interrupt state */ -struct bhndb_pci_intr { - int msi_count; /**< MSI count, or 0 */ - int intr_rid; /**< interrupt resource ID.*/ +/** + * PCI/PCIe bridge-level device quirks + */ +enum { + /** No quirks */ + BHNDB_PCI_QUIRK_NONE = 0, + + /** + * The core requires fixup of the BAR0 SROM shadow to point at the + * current PCI core. + */ + BHNDB_PCI_QUIRK_SRSH_WAR = (1<<0), + + /** + * The PCI (rev <= 5) core does not provide interrupt status/mask + * registers; these siba-only devices require routing backplane + * interrupt flags via the SIBA_CFG0_INTVEC register. + */ + BHNDB_PCI_QUIRK_SIBA_INTVEC = (1<<1), }; +/** bhndb_pci quirk table entry */ +struct bhndb_pci_quirk { + struct bhnd_chip_match chip_desc; /**< chip match descriptor */ + struct bhnd_core_match core_desc; /**< core match descriptor */ + uint32_t quirks; /**< quirk flags */ +}; + +#define BHNDB_PCI_QUIRK(_rev, _flags) { \ + { BHND_MATCH_ANY }, \ + { BHND_MATCH_CORE_REV(_rev) }, \ + _flags, \ +} + +#define BHNDB_PCI_QUIRK_END \ + { { BHND_MATCH_ANY }, { BHND_MATCH_ANY }, 0 } + +#define BHNDB_PCI_IS_QUIRK_END(_q) \ + (BHND_MATCH_IS_ANY(&(_q)->core_desc) && \ + BHND_MATCH_IS_ANY(&(_q)->chip_desc) && \ + (_q)->quirks == 0) + +/** bhndb_pci core table entry */ +struct bhndb_pci_core { + struct bhnd_core_match match; /**< core match descriptor */ + bus_size_t srsh_offset; /**< offset to SRSH_PI register, if any */ + struct bhndb_pci_quirk *quirks; /**< quirk table */ +}; + +#define BHNDB_PCI_CORE(_device, _srsh, _quirks) { \ + { BHND_MATCH_CORE(BHND_MFGID_BCM, BHND_COREID_ ## _device) }, \ + _srsh, \ + _quirks \ +} +#define BHNDB_PCI_CORE_END { { BHND_MATCH_ANY }, 0, NULL } +#define BHNDB_PCI_IS_CORE_END(_c) BHND_MATCH_IS_ANY(&(_c)->match) + struct bhndb_pci_softc { - struct bhndb_softc bhndb; /**< parent softc */ - device_t dev; /**< bridge device */ - device_t parent; /**< parent PCI device */ - bhnd_devclass_t pci_devclass; /**< PCI core's devclass */ - struct bhndb_pci_intr intr; /**< PCI interrupt config */ + struct bhndb_softc bhndb; /**< parent softc */ + device_t dev; /**< bridge device */ + device_t parent; /**< parent PCI device */ + bhnd_devclass_t pci_devclass; /**< PCI core's devclass */ + uint32_t pci_quirks; /**< PCI bridge-level quirks */ + int msi_count; /**< MSI count, or 0 */ + struct bhndb_intr_isrc *isrc; /**< host interrupt source */ - bhndb_pci_set_regwin_t set_regwin; /**< regwin handler */ + struct mtx mtx; + bhndb_pci_set_regwin_t set_regwin; /**< regwin handler */ }; + +#define BHNDB_PCI_LOCK_INIT(sc) \ + mtx_init(&(sc)->mtx, device_get_nameunit((sc)->dev), \ + "bhndb_pc state", MTX_DEF) +#define BHNDB_PCI_LOCK(sc) mtx_lock(&(sc)->mtx) +#define BHNDB_PCI_UNLOCK(sc) mtx_unlock(&(sc)->mtx) +#define BHNDB_PCI_LOCK_ASSERT(sc, what) mtx_assert(&(sc)->mtx, what) +#define BHNDB_PCI_LOCK_DESTROY(sc) mtx_destroy(&(sc)->mtx) #endif /* _BHND_BHNDB_PCIVAR_H_ */ Index: head/sys/dev/bhnd/bhndb/bhndb_private.h =================================================================== --- head/sys/dev/bhnd/bhndb/bhndb_private.h +++ head/sys/dev/bhnd/bhndb/bhndb_private.h @@ -1,10 +1,14 @@ /*- * Copyright (c) 2015-2016 Landon Fuller + * Copyright (c) 2017 The FreeBSD Foundation * All rights reserved. * * Portions of this software were developed by Landon Fuller * under sponsorship from the FreeBSD Foundation. * + * 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: @@ -51,6 +55,7 @@ */ struct bhndb_dw_alloc; +struct bhndb_intr_handler; struct bhndb_region; struct bhndb_resources; @@ -68,10 +73,26 @@ const struct bhndb_regwin *static_regwin); int bhndb_find_resource_limits( - struct bhndb_resources *br, + struct bhndb_resources *br, int type, struct resource *r, rman_res_t *start, rman_res_t *end); +struct bhndb_intr_handler *bhndb_alloc_intr_handler(device_t owner, + struct resource *r, + struct bhndb_intr_isrc *isrc); +void bhndb_free_intr_handler( + struct bhndb_intr_handler *ih); + +void bhndb_register_intr_handler( + struct bhndb_resources *br, + struct bhndb_intr_handler *ih); +void bhndb_deregister_intr_handler( + struct bhndb_resources *br, + struct bhndb_intr_handler *ih); +struct bhndb_intr_handler *bhndb_find_intr_handler( + struct bhndb_resources *br, + void *cookiep); + struct bhndb_region *bhndb_find_resource_region( struct bhndb_resources *br, bhnd_addr_t addr, bhnd_size_t size); @@ -137,6 +158,19 @@ }; /** + * Attached interrupt handler state + */ +struct bhndb_intr_handler { + device_t ih_owner; /**< child device */ + struct resource *ih_res; /**< child resource */ + void *ih_cookiep; /**< hostb-assigned cookiep, or NULL if bus_setup_intr() incomplete. */ + struct bhndb_intr_isrc *ih_isrc; /**< host interrupt source routing the child's interrupt */ + bool ih_active; /**< handler has been registered via bhndb_register_intr_handler */ + + STAILQ_ENTRY(bhndb_intr_handler) ih_link; +}; + +/** * BHNDB resource allocation state. */ struct bhndb_resources { @@ -147,6 +181,7 @@ struct rman ht_mem_rman; /**< host memory manager */ struct rman br_mem_rman; /**< bridged memory manager */ + struct rman br_irq_rman; /**< bridged irq manager */ STAILQ_HEAD(, bhndb_region) bus_regions; /**< bus region descriptors */ @@ -155,6 +190,8 @@ bitstr_t *dwa_freelist; /**< dynamic window free list */ bhndb_priority_t min_prio; /**< minimum resource priority required to allocate a dynamic window */ + + STAILQ_HEAD(,bhndb_intr_handler) bus_intrs; /**< attached child interrupt handlers */ }; /** Index: head/sys/dev/bhnd/bhndb/bhndb_subr.c =================================================================== --- head/sys/dev/bhnd/bhndb/bhndb_subr.c +++ head/sys/dev/bhnd/bhndb/bhndb_subr.c @@ -270,10 +270,11 @@ bus_size_t last_window_size; int rnid; int error; - bool free_ht_mem, free_br_mem; + bool free_ht_mem, free_br_mem, free_br_irq; free_ht_mem = false; free_br_mem = false; + free_br_irq = false; r = malloc(sizeof(*r), M_BHND, M_NOWAIT|M_ZERO); if (r == NULL) @@ -285,6 +286,7 @@ r->res = NULL; r->min_prio = BHNDB_PRIORITY_NONE; STAILQ_INIT(&r->bus_regions); + STAILQ_INIT(&r->bus_intrs); /* Initialize host address space resource manager. */ r->ht_mem_rman.rm_start = 0; @@ -316,6 +318,25 @@ goto failed; } + + /* Initialize resource manager for the bridged interrupt controller. */ + r->br_irq_rman.rm_start = 0; + r->br_irq_rman.rm_end = RM_MAX_END; + r->br_irq_rman.rm_type = RMAN_ARRAY; + r->br_irq_rman.rm_descr = "BHNDB bridged interrupts"; + + if ((error = rman_init(&r->br_irq_rman))) { + device_printf(r->dev, "could not initialize br_irq_rman\n"); + goto failed; + } + free_br_irq = true; + + error = rman_manage_region(&r->br_irq_rman, 0, RM_MAX_END); + if (error) { + device_printf(r->dev, "could not configure br_irq_rman\n"); + goto failed; + } + /* Fetch the dynamic regwin count and verify that it does not exceed * what is representable via our freelist bitstring. */ r->dwa_count = bhndb_regwin_count(cfg->register_windows, @@ -455,6 +476,9 @@ if (free_br_mem) rman_fini(&r->br_mem_rman); + if (free_br_irq) + rman_fini(&r->br_irq_rman); + if (r->dw_alloc != NULL) free(r->dw_alloc, M_BHND); @@ -477,10 +501,15 @@ void bhndb_free_resources(struct bhndb_resources *br) { - struct bhndb_region *region, *r_next; - struct bhndb_dw_alloc *dwa; - struct bhndb_dw_rentry *dwr, *dwr_next; + struct bhndb_region *region, *r_next; + struct bhndb_dw_alloc *dwa; + struct bhndb_dw_rentry *dwr, *dwr_next; + struct bhndb_intr_handler *ih; + bool leaked_regions, leaked_intrs; + leaked_regions = false; + leaked_intrs = false; + /* No window regions may still be held */ if (!bhndb_dw_all_free(br)) { for (int i = 0; i < br->dwa_count; i++) { @@ -492,9 +521,21 @@ device_printf(br->dev, "leaked dynamic register window %d\n", dwa->rnid); + leaked_regions = true; } } + /* There should be no interrupt handlers still registered */ + STAILQ_FOREACH(ih, &br->bus_intrs, ih_link) { + device_printf(br->dev, "interrupt handler leaked %p\n", + ih->ih_cookiep); + } + + if (leaked_intrs || leaked_regions) { + panic("leaked%s%s", leaked_intrs ? " active interrupts" : "", + leaked_regions ? " active register windows" : ""); + } + /* Release host resources allocated through our parent. */ if (br->res != NULL) bhndb_release_host_resources(br->res); @@ -518,6 +559,7 @@ /* Release our resource managers */ rman_fini(&br->ht_mem_rman); rman_fini(&br->br_mem_rman); + rman_fini(&br->br_irq_rman); free(br->dw_alloc, M_BHND); free(br->dwa_freelist, M_BHND); @@ -667,6 +709,222 @@ } /** + * Allocate a host interrupt source and its backing SYS_RES_IRQ host resource. + * + * @param owner The device to be used to allocate a SYS_RES_IRQ + * resource with @p rid. + * @param rid The resource ID of the IRQ to be allocated. + * @param start The start value to be passed to bus_alloc_resource(). + * @param end The end value to be passed to bus_alloc_resource(). + * @param count The count to be passed to bus_alloc_resource(). + * @param flags The flags to be passed to bus_alloc_resource(). + * + * @retval non-NULL success + * @retval NULL if allocation fails. + */ +struct bhndb_intr_isrc * +bhndb_alloc_intr_isrc(device_t owner, int rid, rman_res_t start, rman_res_t end, + rman_res_t count, u_int flags) +{ + struct bhndb_intr_isrc *isrc; + + isrc = malloc(sizeof(*isrc), M_BHND, M_NOWAIT); + if (isrc == NULL) + return (NULL); + + isrc->is_owner = owner; + isrc->is_rid = rid; + isrc->is_res = bus_alloc_resource(owner, SYS_RES_IRQ, &isrc->is_rid, + start, end, count, flags); + if (isrc->is_res == NULL) { + free(isrc, M_BHND); + return (NULL); + } + + return (isrc); +} + +/** + * Free a host interrupt source and its backing host resource. + * + * @param isrc The interrupt source to be freed. + */ +void +bhndb_free_intr_isrc(struct bhndb_intr_isrc *isrc) +{ + bus_release_resource(isrc->is_owner, SYS_RES_IRQ, isrc->is_rid, + isrc->is_res); + free(isrc, M_BHND); +} + +/** + * Allocate and initialize a new interrupt handler entry. + * + * @param owner The child device that owns this entry. + * @param r The child's interrupt resource. + * @param isrc The isrc mapped for this entry. + * + * @retval non-NULL success + * @retval NULL if allocation fails. + */ +struct bhndb_intr_handler * +bhndb_alloc_intr_handler(device_t owner, struct resource *r, + struct bhndb_intr_isrc *isrc) +{ + struct bhndb_intr_handler *ih; + + ih = malloc(sizeof(*ih), M_BHND, M_NOWAIT | M_ZERO); + ih->ih_owner = owner; + ih->ih_res = r; + ih->ih_isrc = isrc; + ih->ih_cookiep = NULL; + ih->ih_active = false; + + return (ih); +} + +/** + * Free an interrupt handler entry. + * + * @param br The resource state owning @p ih. + * @param ih The interrupt handler entry to be removed. + */ +void +bhndb_free_intr_handler(struct bhndb_intr_handler *ih) +{ + KASSERT(!ih->ih_active, ("free of active interrupt handler %p", + ih->ih_cookiep)); + + free(ih, M_BHND); +} + +/** + * Add an active interrupt handler to the given resource state. + * + * @param br The resource state to be modified. + * @param ih The interrupt handler entry to be added. + */ +void +bhndb_register_intr_handler(struct bhndb_resources *br, + struct bhndb_intr_handler *ih) +{ + KASSERT(!ih->ih_active, ("duplicate registration of interrupt " + "handler %p", ih->ih_cookiep)); + KASSERT(ih->ih_cookiep != NULL, ("missing cookiep")); + + ih->ih_active = true; + STAILQ_INSERT_HEAD(&br->bus_intrs, ih, ih_link); +} + +/** + * Remove an interrupt handler from the given resource state. + * + * @param br The resource state containing @p ih. + * @param ih The interrupt handler entry to be removed. + */ +void +bhndb_deregister_intr_handler(struct bhndb_resources *br, + struct bhndb_intr_handler *ih) +{ + KASSERT(!ih->ih_active, ("duplicate deregistration of interrupt " + "handler %p", ih->ih_cookiep)); + + KASSERT(bhndb_find_intr_handler(br, ih) == ih, + ("unknown interrupt handler %p", ih)); + + STAILQ_REMOVE(&br->bus_intrs, ih, bhndb_intr_handler, ih_link); + ih->ih_active = false; +} + +/** + * Return the interrupt handler entry corresponding to @p cookiep, or NULL + * if no entry is found. + * + * @param br The resource state to search for the given @p cookiep. + * @param cookiep The interrupt handler's bus-assigned cookiep value. + */ +struct bhndb_intr_handler * +bhndb_find_intr_handler(struct bhndb_resources *br, void *cookiep) +{ + struct bhndb_intr_handler *ih; + + STAILQ_FOREACH(ih, &br->bus_intrs, ih_link) { + if (ih == cookiep) + return (ih); + } + + /* Not found */ + return (NULL); +} + +/** + * Find the maximum start and end limits of the bridged resource @p r. + * + * If the resource is not currently mapped by the bridge, ENOENT will be + * returned. + * + * @param br The resource state to search. + * @param type The resource type (see SYS_RES_*). + * @param r The resource to search for in @p br. + * @param[out] start On success, the minimum supported start address. + * @param[out] end On success, the maximum supported end address. + * + * @retval 0 success + * @retval ENOENT no active mapping found for @p r of @p type + */ +int +bhndb_find_resource_limits(struct bhndb_resources *br, int type, + struct resource *r, rman_res_t *start, rman_res_t *end) +{ + struct bhndb_dw_alloc *dynamic; + struct bhndb_region *sregion; + struct bhndb_intr_handler *ih; + + switch (type) { + case SYS_RES_IRQ: + /* Is this one of ours? */ + STAILQ_FOREACH(ih, &br->bus_intrs, ih_link) { + if (ih->ih_res == r) + continue; + + /* We don't support adjusting IRQ resource limits */ + *start = rman_get_start(r); + *end = rman_get_end(r); + return (0); + } + + /* Not found */ + return (ENOENT); + + case SYS_RES_MEMORY: { + /* Check for an enclosing dynamic register window */ + if ((dynamic = bhndb_dw_find_resource(br, r))) { + *start = dynamic->target; + *end = dynamic->target + dynamic->win->win_size - 1; + return (0); + } + + /* Check for a static region */ + sregion = bhndb_find_resource_region(br, rman_get_start(r), + rman_get_size(r)); + if (sregion != NULL && sregion->static_regwin != NULL) { + *start = sregion->addr; + *end = sregion->addr + sregion->size - 1; + + return (0); + } + + /* Not found */ + return (ENOENT); + } + + default: + device_printf(br->dev, "unknown resource type: %d\n", type); + return (ENOENT); + } +} + +/** * Add a bus region entry to @p r for the given base @p addr and @p size. * * @param br The resource state to which the bus region entry will be added. @@ -704,49 +962,6 @@ return (0); } - -/** - * Find the maximum start and end limits of the register window mapping - * resource @p r. - * - * If the memory range is not mapped by an existing dynamic or static register - * window, ENOENT will be returned. - * - * @param br The resource state to search. - * @param r The resource to search for in @p br. - * @param addr The requested starting address. - * @param size The requested size. - * - * @retval bhndb_region A region that fully contains the requested range. - * @retval NULL If no mapping region can be found. - */ -int -bhndb_find_resource_limits(struct bhndb_resources *br, struct resource *r, - rman_res_t *start, rman_res_t *end) -{ - struct bhndb_dw_alloc *dynamic; - struct bhndb_region *sregion; - - /* Check for an enclosing dynamic register window */ - if ((dynamic = bhndb_dw_find_resource(br, r))) { - *start = dynamic->target; - *end = dynamic->target + dynamic->win->win_size - 1; - return (0); - } - - /* Check for a static region */ - sregion = bhndb_find_resource_region(br, rman_get_start(r), - rman_get_size(r)); - if (sregion != NULL && sregion->static_regwin != NULL) { - *start = sregion->addr; - *end = sregion->addr + sregion->size - 1; - - return (0); - } - - /* Not found */ - return (ENOENT); -} /** * Find the bus region that maps @p size bytes at @p addr. Index: head/sys/dev/bhnd/bhndb/bhndbvar.h =================================================================== --- head/sys/dev/bhnd/bhndb/bhndbvar.h +++ head/sys/dev/bhnd/bhndb/bhndbvar.h @@ -55,6 +55,8 @@ DECLARE_CLASS(bhndb_driver); +/* forward declarations */ +struct bhndb_intr_isrc; struct bhndb_resources; struct bhndb_host_resources; @@ -82,6 +84,12 @@ bhnd_devclass_t bridge_devclass, struct bhnd_core_info *core); +struct bhndb_intr_isrc *bhndb_alloc_intr_isrc(device_t owner, int rid, + rman_res_t start, rman_res_t end, + rman_res_t count, u_int flags); +void bhndb_free_intr_isrc( + struct bhndb_intr_isrc *isrc); + int bhndb_alloc_host_resources(device_t dev, const struct bhndb_hwcfg *hwcfg, struct bhndb_host_resources **resources); @@ -137,6 +145,15 @@ }; /** + * Host interrupt source to which bridged interrupts may be routed. + */ +struct bhndb_intr_isrc { + device_t is_owner; /**< host device (e.g. the pci device). */ + struct resource *is_res; /**< irq resource */ + int is_rid; /**< irq resource ID */ +}; + +/** * Host resources allocated for a bridge hardware configuration. */ struct bhndb_host_resources { @@ -162,6 +179,7 @@ struct mtx sc_mtx; /**< resource lock. */ struct bhndb_resources *bus_res; /**< bus resource state */ + STAILQ_HEAD(,bhndb_intr_handler) bus_intrs; /**< attached child interrupt handlers */ }; #endif /* _BHND_BHNDBVAR_H_ */ Index: head/sys/dev/bhnd/bhndvar.h =================================================================== --- head/sys/dev/bhnd/bhndvar.h +++ head/sys/dev/bhnd/bhndvar.h @@ -86,6 +86,12 @@ int bhnd_generic_resume_child(device_t dev, device_t child); +int bhnd_generic_setup_intr(device_t dev, + device_t child, struct resource *irq, + int flags, driver_filter_t *filter, + driver_intr_t *intr, void *arg, + void **cookiep); + int bhnd_generic_get_nvram_var(device_t dev, device_t child, const char *name, void *buf, size_t *size, Index: head/sys/dev/bhnd/cores/chipc/chipc.c =================================================================== --- head/sys/dev/bhnd/cores/chipc/chipc.c +++ head/sys/dev/bhnd/cores/chipc/chipc.c @@ -112,9 +112,6 @@ BHND_DEVICE_QUIRK_END }; -// FIXME: IRQ shouldn't be hard-coded -#define CHIPC_MIPS_IRQ 2 - static int chipc_add_children(struct chipc_softc *sc); static bhnd_nvram_src chipc_find_nvram_src(struct chipc_softc *sc, @@ -274,10 +271,13 @@ } /* Both OTP and external SPROM are mapped at CHIPC_SPROM_OTP */ - error = chipc_set_resource(sc, child, SYS_RES_MEMORY, 0, - CHIPC_SPROM_OTP, CHIPC_SPROM_OTP_SIZE, 0, 0); - if (error) + error = chipc_set_mem_resource(sc, child, 0, CHIPC_SPROM_OTP, + CHIPC_SPROM_OTP_SIZE, 0, 0); + if (error) { + device_printf(sc->dev, "failed to set OTP memory " + "resource: %d\n", error); return (error); + } } /* @@ -300,6 +300,11 @@ /* UARTs */ for (u_int i = 0; i < min(sc->caps.num_uarts, CHIPC_UART_MAX); i++) { + int irq_rid, mem_rid; + + irq_rid = 0; + mem_rid = 0; + child = BUS_ADD_CHILD(sc->dev, 0, "uart", -1); if (child == NULL) { device_printf(sc->dev, "failed to add uart%u\n", i); @@ -307,24 +312,28 @@ } /* Shared IRQ */ - error = bus_set_resource(child, SYS_RES_IRQ, 0, CHIPC_MIPS_IRQ, - 1); + error = chipc_set_irq_resource(sc, child, irq_rid, 0); if (error) { device_printf(sc->dev, "failed to set uart%u irq %u\n", - i, CHIPC_MIPS_IRQ); + i, 0); return (error); } /* UART registers are mapped sequentially */ - error = chipc_set_resource(sc, child, SYS_RES_MEMORY, 0, + error = chipc_set_mem_resource(sc, child, mem_rid, CHIPC_UART(i), CHIPC_UART_SIZE, 0, 0); - if (error) + if (error) { + device_printf(sc->dev, "failed to set uart%u memory " + "resource: %d\n", i, error); return (error); + } } /* Flash */ flash_bus = chipc_flash_bus_name(sc->caps.flash_type); if (flash_bus != NULL) { + int rid; + child = BUS_ADD_CHILD(sc->dev, 0, flash_bus, -1); if (child == NULL) { device_printf(sc->dev, "failed to add %s device\n", @@ -333,16 +342,24 @@ } /* flash memory mapping */ - error = chipc_set_resource(sc, child, SYS_RES_MEMORY, 0, - 0, RM_MAX_END, 1, 1); - if (error) + rid = 0; + error = chipc_set_mem_resource(sc, child, rid, 0, RM_MAX_END, 1, + 1); + if (error) { + device_printf(sc->dev, "failed to set flash memory " + "resource %d: %d\n", rid, error); return (error); + } /* flashctrl registers */ - error = chipc_set_resource(sc, child, SYS_RES_MEMORY, 1, + rid++; + error = chipc_set_mem_resource(sc, child, rid, CHIPC_SFLASH_BASE, CHIPC_SFLASH_SIZE, 0, 0); - if (error) + if (error) { + device_printf(sc->dev, "failed to set flash memory " + "resource %d: %d\n", rid, error); return (error); + } } return (0); @@ -592,6 +609,7 @@ } resource_list_init(&dinfo->resources); + dinfo->irq_mapped = false; device_set_ivars(child, dinfo); return (child); @@ -603,7 +621,15 @@ struct chipc_devinfo *dinfo = device_get_ivars(child); if (dinfo != NULL) { + /* Free the child's resource list */ resource_list_free(&dinfo->resources); + + /* Unmap the child's IRQ */ + if (dinfo->irq_mapped) { + bhnd_unmap_intr(dev, dinfo->irq); + dinfo->irq_mapped = false; + } + free(dinfo, M_BHND); } @@ -731,8 +757,7 @@ return (&sc->mem_rman); case SYS_RES_IRQ: - /* IRQs can be used with RF_SHAREABLE, so we don't perform - * any local proxying of resource requests. */ + /* We delegate IRQ resource management to the parent bus */ return (NULL); default: Index: head/sys/dev/bhnd/cores/chipc/chipc_private.h =================================================================== --- head/sys/dev/bhnd/cores/chipc/chipc_private.h +++ head/sys/dev/bhnd/cores/chipc/chipc_private.h @@ -1,7 +1,11 @@ /*- * Copyright (c) 2015-2016 Landon Fuller + * 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: @@ -55,10 +59,11 @@ struct resource *parent, bhnd_size_t offset, bhnd_size_t size); -int chipc_set_resource(struct chipc_softc *sc, - device_t child, int type, int rid, - rman_res_t start, rman_res_t count, u_int port, - u_int region); +int chipc_set_irq_resource(struct chipc_softc *sc, + device_t child, int rid, u_int intr); +int chipc_set_mem_resource(struct chipc_softc *sc, + device_t child, int rid, rman_res_t start, + rman_res_t count, u_int port, u_int region); struct chipc_region *chipc_alloc_region(struct chipc_softc *sc, bhnd_port_type type, u_int port, Index: head/sys/dev/bhnd/cores/chipc/chipc_subr.c =================================================================== --- head/sys/dev/bhnd/cores/chipc/chipc_subr.c +++ head/sys/dev/bhnd/cores/chipc/chipc_subr.c @@ -162,22 +162,69 @@ } /** - * Associate a resource with a given resource ID, relative to the given - * port and region. + * Map an interrupt line to an IRQ, and then register a corresponding SYS_RES_IRQ + * with @p child's resource list. + * + * @param sc chipc driver state. + * @param child The device to set the resource on. + * @param rid The resource ID. + * @param intr The interrupt line to be mapped. + * @param count The length of the resource. + * @param port The mapping port number (ignored if not SYS_RES_MEMORY). + * @param region The mapping region number (ignored if not SYS_RES_MEMORY). + */ +int +chipc_set_irq_resource(struct chipc_softc *sc, device_t child, int rid, + u_int intr) +{ + struct chipc_devinfo *dinfo; + int error; + + KASSERT(device_get_parent(child) == sc->dev, ("not a direct child")); + dinfo = device_get_ivars(child); + + /* We currently only support a single IRQ mapping */ + if (dinfo->irq_mapped) { + device_printf(sc->dev, "irq already mapped for child\n"); + return (ENOMEM); + } + + /* Map the IRQ */ + if ((error = bhnd_map_intr(sc->dev, intr, &dinfo->irq))) { + device_printf(sc->dev, "failed to map intr %u: %d\n", intr, + error); + return (error); + } + + dinfo->irq_mapped = true; + + /* Add to child's resource list */ + error = bus_set_resource(child, SYS_RES_IRQ, rid, dinfo->irq, 1); + if (error) { + device_printf(sc->dev, "failed to set child irq resource %d to " + "%ju: %d\n", rid, dinfo->irq, error); + + bhnd_unmap_intr(sc->dev, dinfo->irq); + return (error); + } + + return (0); +} + + +/** + * Add a SYS_RES_MEMORY resource with a given resource ID, relative to the + * given port and region, to @p child's resource list. * - * This function behaves identically to bus_set_resource() for all resource - * types other than SYS_RES_MEMORY. + * The specified @p region's address and size will be fetched from the bhnd(4) + * bus, and bus_set_resource() will be called with @p start added the region's + * actual base address. * - * For SYS_RES_MEMORY resources, the specified @p region's address and size - * will be fetched from the bhnd(4) bus, and bus_set_resource() will be called - * with @p start added the region's actual base address. - * * To use the default region values for @p start and @p count, specify * a @p start value of 0ul, and an end value of RMAN_MAX_END * * @param sc chipc driver state. * @param child The device to set the resource on. - * @param type The resource type. * @param rid The resource ID. * @param start The resource start address (if SYS_RES_MEMORY, this is * relative to @p region's base address). @@ -186,7 +233,7 @@ * @param region The mapping region number (ignored if not SYS_RES_MEMORY). */ int -chipc_set_resource(struct chipc_softc *sc, device_t child, int type, int rid, +chipc_set_mem_resource(struct chipc_softc *sc, device_t child, int rid, rman_res_t start, rman_res_t count, u_int port, u_int region) { bhnd_addr_t region_addr; @@ -194,9 +241,7 @@ bool isdefault; int error; - if (type != SYS_RES_MEMORY) - return (bus_set_resource(child, type, rid, start, count)); - + KASSERT(device_get_parent(child) == sc->dev, ("not a direct child")); isdefault = RMAN_IS_DEFAULT_RANGE(start, count); /* Fetch region address and size */ @@ -224,7 +269,8 @@ return (ERANGE); } - return (bus_set_resource(child, type, rid, region_addr + start, count)); + return (bus_set_resource(child, SYS_RES_MEMORY, rid, + region_addr + start, count)); } Index: head/sys/dev/bhnd/cores/chipc/chipcvar.h =================================================================== --- head/sys/dev/bhnd/cores/chipc/chipcvar.h +++ head/sys/dev/bhnd/cores/chipc/chipcvar.h @@ -1,7 +1,11 @@ /*- - * Copyright (c) 2015 Landon Fuller + * Copyright (c) 2015-2016 Landon Fuller + * 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: @@ -138,6 +142,8 @@ */ struct chipc_devinfo { struct resource_list resources; /**< child resources */ + rman_res_t irq; /**< child IRQ, if mapped */ + bool irq_mapped; /**< true if IRQ mapped, false otherwise */ }; /** Index: head/sys/dev/bhnd/cores/pci/bhnd_pci_hostb.c =================================================================== --- head/sys/dev/bhnd/cores/pci/bhnd_pci_hostb.c +++ head/sys/dev/bhnd/cores/pci/bhnd_pci_hostb.c @@ -139,13 +139,13 @@ BHND_PCIE_QUIRK_BFL2_PCIEWAR_EN }, /* Apple BCM4322 boards that require 700mV SerDes TX drive strength. */ - {{ BHND_CHIP_ID(BCM4322), + {{ BHND_MATCH_CHIP_ID(BCM4322), BHND_MATCH_BOARD(PCI_VENDOR_APPLE, BCM94322X9), }, BHND_PCIE_QUIRK_SERDES_TXDRV_700MV }, /* Apple BCM4331 board-specific quirks */ #define BHND_A4331_QUIRK(_board, ...) \ - {{ BHND_CHIP_ID(BCM4331), \ + {{ BHND_MATCH_CHIP_ID(BCM4331), \ BHND_MATCH_BOARD(PCI_VENDOR_APPLE, _board) }, __VA_ARGS__ } BHND_A4331_QUIRK(BCM94331X19, BHND_PCIE_QUIRK_SERDES_TXDRV_MAX | Index: head/sys/dev/bhnd/cores/usb/bhnd_usb.c =================================================================== --- head/sys/dev/bhnd/cores/usb/bhnd_usb.c +++ head/sys/dev/bhnd/cores/usb/bhnd_usb.c @@ -137,18 +137,6 @@ panic("%s: sc->mem_rman", __func__); } - sc->irq_rman.rm_start = sc->sc_irqn; - sc->irq_rman.rm_end = sc->sc_irqn; - sc->irq_rman.rm_type = RMAN_ARRAY; - sc->irq_rman.rm_descr = "BHND USB core IRQ"; - /* - * BHND USB share same IRQ between OHCI and EHCI - */ - if (rman_init(&sc->irq_rman) != 0 || - rman_manage_region(&sc->irq_rman, sc->irq_rman.rm_start, - sc->irq_rman.rm_end) != 0) - panic("%s: failed to set up IRQ rman", __func__); - /* TODO: macros for registers */ bus_write_4(sc->sc_mem, 0x200, 0x7ff); DELAY(100); @@ -254,17 +242,19 @@ struct resource *rv; struct resource_list *rl; struct resource_list_entry *rle; - int isdefault, needactivate; + int passthrough, isdefault, needactivate; struct bhnd_usb_softc *sc = device_get_softc(bus); isdefault = RMAN_IS_DEFAULT_RANGE(start,end); + passthrough = (device_get_parent(child) != bus); needactivate = flags & RF_ACTIVE; - rl = BUS_GET_RESOURCE_LIST(bus, child); rle = NULL; - if (isdefault) { + if (!passthrough && isdefault) { BHND_INFO_DEV(bus, "trying allocate def %d - %d for %s", type, *rid, device_get_nameunit(child) ); + + rl = BUS_GET_RESOURCE_LIST(bus, child); rle = resource_list_find(rl, type, *rid); if (rle == NULL) return (NULL); @@ -303,32 +293,11 @@ return (rv); } - if (type == SYS_RES_IRQ) { - - rv = rman_reserve_resource(&sc->irq_rman, start, end, count, - flags, child); - if (rv == NULL) { - BHND_ERROR_DEV(bus, "could not reserve resource"); - return (0); - } - - rman_set_rid(rv, *rid); - - if (needactivate && - bus_activate_resource(child, type, *rid, rv)) { - BHND_ERROR_DEV(bus, "could not activate resource"); - rman_release_resource(rv); - return (0); - } - - return (rv); - } - /* * Pass the request to the parent. */ - return (resource_list_alloc(rl, bus, child, type, rid, - start, end, count, flags)); + return (bus_generic_rl_alloc_resource(bus, child, type, rid, start, end, + count, flags)); } static struct resource_list * @@ -345,18 +314,39 @@ bhnd_usb_release_resource(device_t dev, device_t child, int type, int rid, struct resource *r) { - struct resource_list *rl; + struct bhnd_usb_softc *sc; struct resource_list_entry *rle; + bool passthrough; + int error; - rl = bhnd_usb_get_reslist(dev, child); - if (rl == NULL) - return (EINVAL); - rle = resource_list_find(rl, type, rid); - if (rle == NULL) - return (EINVAL); - rman_release_resource(r); - rle->res = NULL; + sc = device_get_softc(dev); + passthrough = (device_get_parent(child) != dev); + /* Delegate to our parent device's bus if the requested resource type + * isn't handled locally. */ + if (type != SYS_RES_MEMORY) { + return (bus_generic_rl_release_resource(dev, child, type, rid, + r)); + } + + /* Deactivate resources */ + if (rman_get_flags(r) & RF_ACTIVE) { + error = BUS_DEACTIVATE_RESOURCE(dev, child, type, rid, r); + if (error) + return (error); + } + + if ((error = rman_release_resource(r))) + return (error); + + if (!passthrough) { + /* Clean resource list entry */ + rle = resource_list_find(BUS_GET_RESOURCE_LIST(dev, child), + type, rid); + if (rle != NULL) + rle->res = NULL; + } + return (0); } @@ -400,58 +390,86 @@ struct bhnd_usb_softc *sc; struct bhnd_usb_devinfo *sdi; device_t child; + int error; sc = device_get_softc(dev); - child = device_add_child_ordered(dev, order, name, unit); - if (child == NULL) - return (NULL); - sdi = malloc(sizeof(struct bhnd_usb_devinfo), M_DEVBUF, M_NOWAIT|M_ZERO); if (sdi == NULL) return (NULL); + resource_list_init(&sdi->sdi_rl); + sdi->sdi_irq_mapped = false; + if (strncmp(name, "ohci", 4) == 0) { sdi->sdi_maddr = sc->sc_maddr + 0x000; sdi->sdi_msize = 0x200; - sdi->sdi_irq = sc->sc_irqn; - BHND_INFO_DEV(dev, "ohci: irq=%d maddr=0x%jx", sdi->sdi_irq, - sdi->sdi_maddr); } else if (strncmp(name, "ehci", 4) == 0) { sdi->sdi_maddr = sc->sc_maddr + 0x000; sdi->sdi_msize = 0x1000; - sdi->sdi_irq = sc->sc_irqn; - BHND_INFO_DEV(dev, "ehci: irq=%d maddr=0x%jx", sdi->sdi_irq, - sdi->sdi_maddr); } else { panic("Unknown subdevice"); - /* Unknown subdevice */ - sdi->sdi_maddr = 1; - sdi->sdi_msize = 1; - sdi->sdi_irq = 1; } - resource_list_init(&sdi->sdi_rl); + /* Map the child's IRQ */ + if ((error = bhnd_map_intr(dev, 0, &sdi->sdi_irq))) { + BHND_ERROR_DEV(dev, "could not map %s interrupt: %d", name, + error); + goto failed; + } + sdi->sdi_irq_mapped = true; + BHND_INFO_DEV(dev, "%s: irq=%ju maddr=0x%jx", name, sdi->sdi_irq, + sdi->sdi_maddr); + /* - * Determine memory window on bus and irq if one is needed. + * Add memory window and irq to child's resource list. */ - resource_list_add(&sdi->sdi_rl, SYS_RES_MEMORY, 0, - sdi->sdi_maddr, sdi->sdi_maddr + sdi->sdi_msize - 1, sdi->sdi_msize); + resource_list_add(&sdi->sdi_rl, SYS_RES_MEMORY, 0, sdi->sdi_maddr, + sdi->sdi_maddr + sdi->sdi_msize - 1, sdi->sdi_msize); - resource_list_add(&sdi->sdi_rl, SYS_RES_IRQ, 0, - sdi->sdi_irq, sdi->sdi_irq, 1); + resource_list_add(&sdi->sdi_rl, SYS_RES_IRQ, 0, sdi->sdi_irq, + sdi->sdi_irq, 1); + child = device_add_child_ordered(dev, order, name, unit); + if (child == NULL) { + BHND_ERROR_DEV(dev, "could not add %s", name); + goto failed; + } + device_set_ivars(child, sdi); return (child); +failed: + if (sdi->sdi_irq_mapped) + bhnd_unmap_intr(dev, sdi->sdi_irq); + + resource_list_free(&sdi->sdi_rl); + + free(sdi, M_DEVBUF); + return (NULL); } +static void +bhnd_usb_child_deleted(device_t dev, device_t child) +{ + struct bhnd_usb_devinfo *dinfo; + + if ((dinfo = device_get_ivars(child)) == NULL) + return; + + if (dinfo->sdi_irq_mapped) + bhnd_unmap_intr(dev, dinfo->sdi_irq); + + resource_list_free(&dinfo->sdi_rl); + free(dinfo, M_DEVBUF); +} + static device_method_t bhnd_usb_methods[] = { /* Device interface */ DEVMETHOD(device_attach, bhnd_usb_attach), @@ -459,6 +477,7 @@ /* Bus interface */ DEVMETHOD(bus_add_child, bhnd_usb_add_child), + DEVMETHOD(bus_child_deleted, bhnd_usb_child_deleted), DEVMETHOD(bus_alloc_resource, bhnd_usb_alloc_resource), DEVMETHOD(bus_get_resource_list, bhnd_usb_get_reslist), DEVMETHOD(bus_print_child, bhnd_usb_print_child), Index: head/sys/dev/bhnd/cores/usb/bhnd_usbvar.h =================================================================== --- head/sys/dev/bhnd/cores/usb/bhnd_usbvar.h +++ head/sys/dev/bhnd/cores/usb/bhnd_usbvar.h @@ -50,7 +50,8 @@ struct bhnd_usb_devinfo { struct resource_list sdi_rl; uint8_t sdi_unit; /* core index on bus */ - uint8_t sdi_irq; + rman_res_t sdi_irq; /**< child IRQ, if mapped */ + bool sdi_irq_mapped; /**< true if IRQ mapped, false otherwise */ char sdi_name[8]; rman_res_t sdi_maddr; rman_res_t sdi_msize; Index: head/sys/dev/bhnd/siba/siba.c =================================================================== --- head/sys/dev/bhnd/siba/siba.c +++ head/sys/dev/bhnd/siba/siba.c @@ -1,7 +1,11 @@ /*- - * Copyright (c) 2015 Landon Fuller + * Copyright (c) 2015-2016 Landon Fuller + * 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: @@ -214,7 +218,7 @@ /* Fetch CFG0 mapping */ dinfo = device_get_ivars(child); - if ((r = dinfo->cfg[0]) == NULL) + if ((r = dinfo->cfg_res[0]) == NULL) return (ENODEV); /* Mask and set TMSTATELOW core flag bits */ @@ -266,7 +270,7 @@ dinfo = device_get_ivars(child); /* Can't suspend the core without access to the CFG0 registers */ - if ((r = dinfo->cfg[0]) == NULL) + if ((r = dinfo->cfg_res[0]) == NULL) return (ENODEV); /* We require exclusive control over BHND_IOCTL_CLK_EN and @@ -338,7 +342,7 @@ pm = dinfo->pmu_info; /* Can't suspend the core without access to the CFG0 registers */ - if ((r = dinfo->cfg[0]) == NULL) + if ((r = dinfo->cfg_res[0]) == NULL) return (ENODEV); /* Already in RESET? */ @@ -433,23 +437,26 @@ /* CFG0 registers must be available */ dinfo = device_get_ivars(child); - if (dinfo->cfg[0] == NULL) + if (dinfo->cfg_res[0] == NULL) return (ENODEV); /* Offset must fall within CFG0 */ - r_size = rman_get_size(dinfo->cfg[0]->res); + r_size = rman_get_size(dinfo->cfg_res[0]->res); if (r_size < offset || r_size - offset < width) return (EFAULT); switch (width) { case 1: - *((uint8_t *)value) = bhnd_bus_read_1(dinfo->cfg[0], offset); + *((uint8_t *)value) = bhnd_bus_read_1(dinfo->cfg_res[0], + offset); return (0); case 2: - *((uint16_t *)value) = bhnd_bus_read_2(dinfo->cfg[0], offset); + *((uint16_t *)value) = bhnd_bus_read_2(dinfo->cfg_res[0], + offset); return (0); case 4: - *((uint32_t *)value) = bhnd_bus_read_4(dinfo->cfg[0], offset); + *((uint32_t *)value) = bhnd_bus_read_4(dinfo->cfg_res[0], + offset); return (0); default: return (EINVAL); @@ -470,7 +477,7 @@ /* CFG0 registers must be available */ dinfo = device_get_ivars(child); - if ((r = dinfo->cfg[0]) == NULL) + if ((r = dinfo->cfg_res[0]) == NULL) return (ENODEV); /* Offset must fall within CFG0 */ @@ -504,7 +511,7 @@ type)); dinfo = device_get_ivars(child); - return (siba_addrspace_port_count(dinfo->core_id.num_addrspace)); + return (siba_port_count(&dinfo->core_id, type)); } static u_int @@ -519,11 +526,7 @@ type, port)); dinfo = device_get_ivars(child); - if (!siba_is_port_valid(dinfo->core_id.num_addrspace, type, port)) - return (0); - - return (siba_addrspace_region_count(dinfo->core_id.num_addrspace, - port)); + return (siba_port_region_count(&dinfo->core_id, type, port)); } static int @@ -532,6 +535,7 @@ { struct siba_devinfo *dinfo; struct siba_addrspace *addrspace; + struct siba_cfg_block *cfg; /* delegate non-bus-attached devices to our parent */ if (device_get_parent(child) != dev) @@ -539,11 +543,19 @@ port_type, port_num, region_num)); dinfo = device_get_ivars(child); + + /* Look for a matching addrspace entry */ addrspace = siba_find_addrspace(dinfo, port_type, port_num, region_num); - if (addrspace == NULL) - return (-1); + if (addrspace != NULL) + return (addrspace->sa_rid); - return (addrspace->sa_rid); + /* Try the config blocks */ + cfg = siba_find_cfg_block(dinfo, port_type, port_num, region_num); + if (cfg != NULL) + return (cfg->cb_rid); + + /* Not found */ + return (-1); } static int @@ -563,16 +575,28 @@ if (type != SYS_RES_MEMORY) return (EINVAL); - for (int i = 0; i < dinfo->core_id.num_addrspace; i++) { + /* Look for a matching addrspace entry */ + for (u_int i = 0; i < dinfo->core_id.num_addrspace; i++) { if (dinfo->addrspace[i].sa_rid != rid) continue; *port_type = BHND_PORT_DEVICE; - *port_num = siba_addrspace_port(i); - *region_num = siba_addrspace_region(i); + *port_num = siba_addrspace_device_port(i); + *region_num = siba_addrspace_device_region(i); return (0); } + /* Try the config blocks */ + for (u_int i = 0; i < dinfo->core_id.num_cfg_blocks; i++) { + if (dinfo->cfg[i].cb_rid != rid) + continue; + + *port_type = BHND_PORT_AGENT; + *port_num = siba_cfg_agent_port(i); + *region_num = siba_cfg_agent_region(i); + return (0); + } + /* Not found */ return (ENOENT); } @@ -583,6 +607,7 @@ { struct siba_devinfo *dinfo; struct siba_addrspace *addrspace; + struct siba_cfg_block *cfg; /* delegate non-bus-attached devices to our parent */ if (device_get_parent(child) != dev) { @@ -591,67 +616,72 @@ } dinfo = device_get_ivars(child); + + /* Look for a matching addrspace */ addrspace = siba_find_addrspace(dinfo, port_type, port_num, region_num); - if (addrspace == NULL) - return (ENOENT); + if (addrspace != NULL) { + *addr = addrspace->sa_base; + *size = addrspace->sa_size - addrspace->sa_bus_reserved; + return (0); + } - *addr = addrspace->sa_base; - *size = addrspace->sa_size - addrspace->sa_bus_reserved; - return (0); + /* Look for a matching cfg block */ + cfg = siba_find_cfg_block(dinfo, port_type, port_num, region_num); + if (cfg != NULL) { + *addr = cfg->cb_base; + *size = cfg->cb_size; + return (0); + } + + /* Not found */ + return (ENOENT); } /** * Default siba(4) bus driver implementation of BHND_BUS_GET_INTR_COUNT(). - * - * This implementation consults @p child's configuration block mapping, - * returning SIBA_CORE_NUM_INTR if a valid CFG0 block is mapped. */ -int +u_int siba_get_intr_count(device_t dev, device_t child) { - struct siba_devinfo *dinfo; + struct siba_devinfo *dinfo; /* delegate non-bus-attached devices to our parent */ if (device_get_parent(child) != dev) return (BHND_BUS_GET_INTR_COUNT(device_get_parent(dev), child)); dinfo = device_get_ivars(child); - - /* We can get/set interrupt sbflags on any core with a valid cfg0 - * block; whether the core actually makes use of it is another matter - * entirely */ - if (dinfo->cfg[0] == NULL) + if (!dinfo->intr_en) { + /* No interrupts */ return (0); - - return (SIBA_CORE_NUM_INTR); + } else { + /* One assigned interrupt */ + return (1); + } } /** - * Default siba(4) bus driver implementation of BHND_BUS_GET_CORE_IVEC(). - * - * This implementation consults @p child's CFG0 register block, - * returning the interrupt flag assigned to @p child. + * Default siba(4) bus driver implementation of BHND_BUS_GET_INTR_IVEC(). */ int -siba_get_core_ivec(device_t dev, device_t child, u_int intr, uint32_t *ivec) +siba_get_intr_ivec(device_t dev, device_t child, u_int intr, u_int *ivec) { struct siba_devinfo *dinfo; - uint32_t tpsflag; /* delegate non-bus-attached devices to our parent */ if (device_get_parent(child) != dev) - return (BHND_BUS_GET_CORE_IVEC(device_get_parent(dev), child, + return (BHND_BUS_GET_INTR_IVEC(device_get_parent(dev), child, intr, ivec)); /* Must be a valid interrupt ID */ if (intr >= siba_get_intr_count(dev, child)) return (ENXIO); - /* Fetch sbflag number */ + KASSERT(intr == 0, ("invalid ivec %u", intr)); + dinfo = device_get_ivars(child); - tpsflag = bhnd_bus_read_4(dinfo->cfg[0], SIBA_CFG0_TPSFLAG); - *ivec = SIBA_REG_GET(tpsflag, TPS_NUM0); + KASSERT(dinfo->intr_en, ("core does not have an interrupt assigned")); + *ivec = dinfo->intr.flag; return (0); } @@ -715,7 +745,56 @@ return (0); } + /** + * Register all interrupt descriptors for @p dinfo. Must be called after + * configuration blocks have been mapped. + * + * @param dev The siba bus device. + * @param child The siba child device. + * @param dinfo The device info instance on which to register all interrupt + * descriptor entries. + * @param r A resource mapping the enumeration table block for @p di. + */ +static int +siba_register_interrupts(device_t dev, device_t child, + struct siba_devinfo *dinfo, struct bhnd_resource *r) +{ + uint32_t tpsflag; + int error; + + /* Is backplane interrupt distribution enabled for this core? */ + tpsflag = bhnd_bus_read_4(r, SB0_REG_ABS(SIBA_CFG0_TPSFLAG)); + if ((tpsflag & SIBA_TPS_F0EN0) == 0) { + dinfo->intr_en = false; + return (0); + } + + /* Have one interrupt */ + dinfo->intr_en = true; + dinfo->intr.flag = SIBA_REG_GET(tpsflag, TPS_NUM0); + dinfo->intr.mapped = false; + dinfo->intr.irq = 0; + dinfo->intr.rid = -1; + + /* Map the interrupt */ + error = BHND_BUS_MAP_INTR(dev, child, 0 /* single intr is always 0 */, + &dinfo->intr.irq); + if (error) { + device_printf(dev, "failed mapping interrupt line for core %u: " + "%d\n", dinfo->core_id.core_info.core_idx, error); + return (error); + } + dinfo->intr.mapped = true; + + /* Update the resource list */ + dinfo->intr.rid = resource_list_add_next(&dinfo->resources, SYS_RES_IRQ, + dinfo->intr.irq, dinfo->intr.irq, 1); + + return (0); +} + +/** * Map per-core configuration blocks for @p dinfo. * * @param dev The siba bus device. @@ -728,6 +807,7 @@ struct siba_addrspace *addrspace; rman_res_t r_start, r_count, r_end; uint8_t num_cfg; + int rid; num_cfg = dinfo->core_id.num_cfg_blocks; if (num_cfg > SIBA_MAX_CFG) { @@ -747,22 +827,28 @@ * Map the per-core configuration blocks */ for (uint8_t i = 0; i < num_cfg; i++) { - /* Determine the config block's address range; configuration - * blocks are allocated starting at SIBA_CFG0_OFFSET, - * growing downwards. */ - r_start = addrspace->sa_base + SIBA_CFG0_OFFSET; - r_start -= i * SIBA_CFG_SIZE; - + /* Add to child's resource list */ + r_start = addrspace->sa_base + SIBA_CFG_OFFSET(i); r_count = SIBA_CFG_SIZE; r_end = r_start + r_count - 1; - /* Allocate the config resource */ + rid = resource_list_add_next(&dinfo->resources, SYS_RES_MEMORY, + r_start, r_end, r_count); + + /* Initialize config block descriptor */ + dinfo->cfg[i] = ((struct siba_cfg_block) { + .cb_base = r_start, + .cb_size = SIBA_CFG_SIZE, + .cb_rid = rid + }); + + /* Map the config resource for bus-level access */ dinfo->cfg_rid[i] = SIBA_CFG_RID(dinfo, i); - dinfo->cfg[i] = BHND_BUS_ALLOC_RESOURCE(dev, dev, + dinfo->cfg_res[i] = BHND_BUS_ALLOC_RESOURCE(dev, dev, SYS_RES_MEMORY, &dinfo->cfg_rid[i], r_start, r_end, - r_count, RF_ACTIVE); + r_count, RF_ACTIVE|RF_SHAREABLE); - if (dinfo->cfg[i] == NULL) { + if (dinfo->cfg_res[i] == NULL) { device_printf(dev, "failed to allocate SIBA_CFG%hhu\n", i); return (ENXIO); @@ -805,7 +891,7 @@ /* Free siba device info */ if ((dinfo = device_get_ivars(child)) != NULL) - siba_free_dinfo(dev, dinfo); + siba_free_dinfo(dev, child, dinfo); device_set_ivars(child, NULL); } @@ -846,6 +932,7 @@ */ for (u_int i = 0; i < chipid->ncores; i++) { struct siba_devinfo *dinfo; + device_t child; uint32_t idhigh, idlow; rman_res_t r_count, r_end, r_start; @@ -878,14 +965,16 @@ } /* Add the child device */ - children[i] = BUS_ADD_CHILD(dev, 0, NULL, -1); - if (children[i] == NULL) { + child = BUS_ADD_CHILD(dev, 0, NULL, -1); + if (child == NULL) { error = ENXIO; goto failed; } + children[i] = child; + /* Initialize per-device bus info */ - if ((dinfo = device_get_ivars(children[i])) == NULL) { + if ((dinfo = device_get_ivars(child)) == NULL) { error = ENXIO; goto failed; } @@ -897,14 +986,18 @@ if ((error = siba_register_addrspaces(dev, dinfo, r))) goto failed; + /* Register the core's interrupts */ + if ((error = siba_register_interrupts(dev, child, dinfo, r))) + goto failed; + /* Unmap the core's register block */ bhnd_release_resource(dev, SYS_RES_MEMORY, rid, r); r = NULL; /* If pins are floating or the hardware is otherwise * unpopulated, the device shouldn't be used. */ - if (bhnd_is_hw_disabled(children[i])) - device_disable(children[i]); + if (bhnd_is_hw_disabled(child)) + device_disable(child); } /* Map all valid core's config register blocks and perform interrupt @@ -912,7 +1005,6 @@ for (u_int i = 0; i < chipid->ncores; i++) { struct siba_devinfo *dinfo; device_t child; - int nintr; child = children[i]; @@ -926,16 +1018,6 @@ if ((error = siba_map_cfg_resources(dev, dinfo))) goto failed; - /* Assign interrupts */ - nintr = bhnd_get_intr_count(child); - for (int rid = 0; rid < nintr; rid++) { - error = BHND_BUS_ASSIGN_INTR(dev, child, rid); - if (error) { - device_printf(dev, "failed to assign interrupt " - "%d to core %u: %d\n", rid, i, error); - } - } - /* Issue bus callback for fully initialized child. */ BHND_BUS_CHILD_ADDED(dev, child); } @@ -993,7 +1075,7 @@ DEVMETHOD(bhnd_bus_decode_port_rid, siba_decode_port_rid), DEVMETHOD(bhnd_bus_get_region_addr, siba_get_region_addr), DEVMETHOD(bhnd_bus_get_intr_count, siba_get_intr_count), - DEVMETHOD(bhnd_bus_get_core_ivec, siba_get_core_ivec), + DEVMETHOD(bhnd_bus_get_intr_ivec, siba_get_intr_ivec), DEVMETHOD_END }; Index: head/sys/dev/bhnd/siba/siba_bhndb.c =================================================================== --- head/sys/dev/bhnd/siba/siba_bhndb.c +++ head/sys/dev/bhnd/siba/siba_bhndb.c @@ -51,20 +51,17 @@ * Supports attachment of siba(4) bus devices via a bhndb bridge. */ -// -// TODO: PCI rev < 6 interrupt handling -// -// On early PCI cores (rev < 6) interrupt masking is handled via interconnect -// configuration registers (SBINTVEC), rather than the PCI_INT_MASK -// config register. -// -// On those devices, we should handle interrupts locally using SBINTVEC, rather -// than delegating to our parent bhndb device. -// +struct siba_bhndb_softc; -static int siba_bhndb_wars_hwup(struct siba_softc *sc); +static int siba_bhndb_wars_hwup(struct siba_bhndb_softc *sc); -/* Bridge-specific core device quirks */ +/* siba_bhndb per-instance state */ +struct siba_bhndb_softc { + struct siba_softc siba; /**< common siba per-instance state */ + uint32_t quirks; /**< bus-level quirks */ +}; + +/* siba_bhndb quirks */ enum { /** When PCIe-bridged, the D11 core's initiator request * timeout must be disabled to prevent D11 from entering a @@ -72,14 +69,22 @@ SIBA_QUIRK_PCIE_D11_SB_TIMEOUT = (1<<0) }; -static struct bhnd_device_quirk bridge_quirks[] = { - BHND_CHIP_QUIRK(4311, HWREV_EQ(2), SIBA_QUIRK_PCIE_D11_SB_TIMEOUT), - BHND_CHIP_QUIRK(4312, HWREV_EQ(0), SIBA_QUIRK_PCIE_D11_SB_TIMEOUT), +/* Bus-level quirks when bridged via a PCI host bridge core */ +static struct bhnd_device_quirk pci_bridge_quirks[] = { BHND_DEVICE_QUIRK_END }; +/* Bus-level quirks when bridged via a PCIe host bridge core */ +static struct bhnd_device_quirk pcie_bridge_quirks[] = { + BHND_CHIP_QUIRK (4311, HWREV_EQ(2), SIBA_QUIRK_PCIE_D11_SB_TIMEOUT), + BHND_CHIP_QUIRK (4312, HWREV_EQ(0), SIBA_QUIRK_PCIE_D11_SB_TIMEOUT), + BHND_DEVICE_QUIRK_END +}; + +/* Bus-level quirks specific to a particular host bridge core */ static struct bhnd_device bridge_devs[] = { - BHND_DEVICE(BCM, PCI, NULL, bridge_quirks), + BHND_DEVICE(BCM, PCI, NULL, pci_bridge_quirks), + BHND_DEVICE(BCM, PCIE, NULL, pcie_bridge_quirks), BHND_DEVICE_END }; @@ -107,15 +112,23 @@ static int siba_bhndb_attach(device_t dev) { - struct siba_softc *sc; + struct siba_bhndb_softc *sc; + device_t hostb; int error; sc = device_get_softc(dev); + sc->quirks = 0; /* Perform initial attach and enumerate our children. */ if ((error = siba_attach(dev))) goto failed; + /* Fetch bus-level quirks required by the host bridge core */ + if ((hostb = bhnd_bus_find_hostb_device(dev)) != NULL) { + sc->quirks |= bhnd_device_quirks(hostb, bridge_devs, + sizeof(bridge_devs[0])); + } + /* Apply attach/resume workarounds before any child drivers attach */ if ((error = siba_bhndb_wars_hwup(sc))) goto failed; @@ -134,7 +147,7 @@ static int siba_bhndb_resume(device_t dev) { - struct siba_softc *sc; + struct siba_bhndb_softc *sc; int error; sc = device_get_softc(dev); @@ -151,11 +164,11 @@ static void siba_bhndb_suspend_cfgblocks(device_t dev, struct siba_devinfo *dinfo) { for (u_int i = 0; i < dinfo->core_id.num_cfg_blocks; i++) { - if (dinfo->cfg[i] == NULL) + if (dinfo->cfg_res[i] == NULL) continue; BHNDB_SUSPEND_RESOURCE(device_get_parent(dev), dev, - SYS_RES_MEMORY, dinfo->cfg[i]->res); + SYS_RES_MEMORY, dinfo->cfg_res[i]->res); } } @@ -196,11 +209,11 @@ /* Resume all resource references to the child's config registers */ for (u_int i = 0; i < dinfo->core_id.num_cfg_blocks; i++) { - if (dinfo->cfg[i] == NULL) + if (dinfo->cfg_res[i] == NULL) continue; error = BHNDB_RESUME_RESOURCE(device_get_parent(dev), dev, - SYS_RES_MEMORY, dinfo->cfg[i]->res); + SYS_RES_MEMORY, dinfo->cfg_res[i]->res); if (error) { siba_bhndb_suspend_cfgblocks(dev, dinfo); return (error); @@ -218,22 +231,14 @@ /* Work-around implementation for SIBA_QUIRK_PCIE_D11_SB_TIMEOUT */ static int -siba_bhndb_wars_pcie_clear_d11_timeout(struct siba_softc *sc) +siba_bhndb_wars_pcie_clear_d11_timeout(struct siba_bhndb_softc *sc) { struct siba_devinfo *dinfo; - device_t hostb_dev; device_t d11; uint32_t imcfg; - /* Only applies when bridged by PCIe */ - if ((hostb_dev = bhnd_bus_find_hostb_device(sc->dev)) == NULL) - return (ENXIO); - - if (bhnd_get_class(hostb_dev) != BHND_DEVCLASS_PCIE) - return (0); - - /* Only applies if there's a D11 core */ - d11 = bhnd_bus_match_child(sc->dev, &(struct bhnd_core_match) { + /* Only applicable if there's a D11 core */ + d11 = bhnd_bus_match_child(sc->siba.dev, &(struct bhnd_core_match) { BHND_MATCH_CORE(BHND_MFGID_BCM, BHND_COREID_D11), BHND_MATCH_CORE_UNIT(0) }); @@ -242,12 +247,12 @@ /* Clear initiator timeout in D11's CFG0 block */ dinfo = device_get_ivars(d11); - KASSERT(dinfo->cfg[0] != NULL, ("missing core config mapping")); + KASSERT(dinfo->cfg_res[0] != NULL, ("missing core config mapping")); - imcfg = bhnd_bus_read_4(dinfo->cfg[0], SIBA_CFG0_IMCONFIGLOW); + imcfg = bhnd_bus_read_4(dinfo->cfg_res[0], SIBA_CFG0_IMCONFIGLOW); imcfg &= ~SIBA_IMCL_RTO_MASK; - bhnd_bus_write_4(dinfo->cfg[0], SIBA_CFG0_IMCONFIGLOW, imcfg); + bhnd_bus_write_4(dinfo->cfg_res[0], SIBA_CFG0_IMCONFIGLOW, imcfg); return (0); } @@ -257,19 +262,11 @@ * of the bus. */ static int -siba_bhndb_wars_hwup(struct siba_softc *sc) +siba_bhndb_wars_hwup(struct siba_bhndb_softc *sc) { - device_t hostb_dev; - uint32_t quirks; - int error; + int error; - if ((hostb_dev = bhnd_bus_find_hostb_device(sc->dev)) == NULL) - return (ENXIO); - - quirks = bhnd_device_quirks(hostb_dev, bridge_devs, - sizeof(bridge_devs[0])); - - if (quirks & SIBA_QUIRK_PCIE_D11_SB_TIMEOUT) { + if (sc->quirks & SIBA_QUIRK_PCIE_D11_SB_TIMEOUT) { if ((error = siba_bhndb_wars_pcie_clear_d11_timeout(sc))) return (error); } Index: head/sys/dev/bhnd/siba/siba_erom.c =================================================================== --- head/sys/dev/bhnd/siba/siba_erom.c +++ head/sys/dev/bhnd/siba/siba_erom.c @@ -334,7 +334,8 @@ struct siba_core_id sid; uint32_t am, am_addr, am_size; u_int am_offset; - u_int addrspace; + u_int addrspace, cfg; + int error; sc = (struct siba_erom *)erom; @@ -347,16 +348,64 @@ sid = siba_eio_read_core_id(&sc->io, core.core_idx, core.unit); /* Is port valid? */ - if (!siba_is_port_valid(sid.num_addrspace, type, port)) + if (!siba_is_port_valid(&sid, type, port)) return (ENOENT); /* Is region valid? */ - if (region >= siba_addrspace_region_count(sid.num_addrspace, port)) + if (region >= siba_port_region_count(&sid, type, port)) return (ENOENT); - /* Map the bhnd port values to a siba addrspace index */ - error = siba_addrspace_index(sid.num_addrspace, type, port, region, - &addrspace); + /* Is this a siba configuration region? If so, this is mapped to an + * offset within the device0.0 port */ + error = siba_cfg_index(&sid, type, port, region, &cfg); + if (!error) { + bhnd_addr_t region_addr; + bhnd_addr_t region_size; + bhnd_size_t cfg_offset, cfg_size; + + cfg_offset = SIBA_CFG_OFFSET(cfg); + cfg_size = SIBA_CFG_SIZE; + + /* Fetch the device0.0 addr/size */ + error = siba_erom_lookup_core_addr(erom, desc, BHND_PORT_DEVICE, + 0, 0, NULL, ®ion_addr, ®ion_size); + if (error) + return (error); + + /* Verify that our offset fits within the region */ + if (region_size < cfg_size) { + printf("%s%u.%u offset %ju exceeds %s0.0 size %ju\n", + bhnd_port_type_name(type), port, region, cfg_offset, + bhnd_port_type_name(BHND_PORT_DEVICE), region_size); + + return (ENXIO); + } + + if (BHND_ADDR_MAX - region_addr < cfg_offset) { + printf("%s%u.%u offset %ju would overflow %s0.0 addr " + "%ju\n", bhnd_port_type_name(type), port, region, + cfg_offset, bhnd_port_type_name(BHND_PORT_DEVICE), + region_addr); + + return (ENXIO); + } + + if (info != NULL) + *info = core; + + *addr = region_addr + cfg_offset; + *size = cfg_size; + return (0); + } + + /* + * Otherwise, must be a device port. + * + * Map the bhnd device port to a siba addrspace index. Unlike siba(4) + * bus drivers, we do not exclude the siba(4) configuration blocks from + * the first device port. + */ + error = siba_addrspace_index(&sid, type, port, region, &addrspace); if (error) return (error); Index: head/sys/dev/bhnd/siba/siba_subr.c =================================================================== --- head/sys/dev/bhnd/siba/siba_subr.c +++ head/sys/dev/bhnd/siba/siba_subr.c @@ -1,7 +1,11 @@ /*- - * Copyright (c) 2015 Landon Fuller + * Copyright (c) 2015-2016 Landon Fuller + * 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: @@ -122,12 +126,19 @@ return NULL; for (u_int i = 0; i < nitems(dinfo->cfg); i++) { - dinfo->cfg[i] = NULL; + dinfo->cfg[i] = ((struct siba_cfg_block){ + .cb_base = 0, + .cb_size = 0, + .cb_rid = -1, + }); + dinfo->cfg_res[i] = NULL; dinfo->cfg_rid[i] = -1; } resource_list_init(&dinfo->resources); + dinfo->intr_en = false; + return dinfo; } @@ -151,12 +162,13 @@ } /** - * Map an addrspace index to its corresponding bhnd(4) port number. + * Map an addrspace index to its corresponding bhnd(4) BHND_PORT_DEVICE port + * number. * * @param addrspace Address space index. */ u_int -siba_addrspace_port(u_int addrspace) +siba_addrspace_device_port(u_int addrspace) { /* The first addrspace is always mapped to device0; the remainder * are mapped to device1 */ @@ -167,12 +179,13 @@ } /** - * Map an addrspace index to its corresponding bhnd(4) region number. + * Map an addrspace index to its corresponding bhnd(4) BHND_PORT_DEVICE port + * region number. * * @param addrspace Address space index. */ u_int -siba_addrspace_region(u_int addrspace) +siba_addrspace_device_region(u_int addrspace) { /* The first addrspace is always mapped to device0.0; the remainder * are mapped to device1.0 + (n - 1) */ @@ -183,63 +196,194 @@ } /** + * Map an config block index to its corresponding bhnd(4) BHND_PORT_AGENT port + * number. + * + * @param cfg Config block index. + */ +u_int +siba_cfg_agent_port(u_int cfg) +{ + /* Always agent0 */ + return (0); +} + +/** + * Map an config block index to its corresponding bhnd(4) BHND_PORT_AGENT port + * region number. + * + * @param cfg Config block index. + */ +u_int +siba_cfg_agent_region(u_int cfg) +{ + /* Always agent0. */ + return (cfg); +} + +/** * Return the number of bhnd(4) ports to advertise for the given - * @p num_addrspace. + * @p core_id and @p port_type. * - * @param num_addrspace The number of siba address spaces. + * Refer to the siba_addrspace_index() and siba_cfg_index() functions for + * information on siba's mapping of bhnd(4) port and region identifiers. + * + * @param core_id The siba core info. + * @param port_type The bhnd(4) port type. */ u_int -siba_addrspace_port_count(u_int num_addrspace) +siba_port_count(struct siba_core_id *core_id, bhnd_port_type port_type) { - /* 0, 1, or 2 ports */ - return min(num_addrspace, 2); + switch (port_type) { + case BHND_PORT_DEVICE: + /* 0, 1, or 2 ports */ + return (min(core_id->num_addrspace, 2)); + + case BHND_PORT_AGENT: + /* One agent port maps all configuration blocks */ + if (core_id->num_cfg_blocks > 0) + return (1); + + /* Do not advertise an agent port if there are no configuration + * register blocks */ + return (0); + + default: + return (0); + } } /** - * Return the number of bhnd(4) regions to advertise on @p port - * given the provided @p num_addrspace address space count. + * Return true if @p port of @p port_type is defined by @p core_id, false + * otherwise. * - * @param num_addrspace The number of core-mapped siba(4) Sonics/OCP address - * spaces. + * @param core_id The siba core info. + * @param port_type The bhnd(4) port type. + * @param port The bhnd(4) port number. */ +bool +siba_is_port_valid(struct siba_core_id *core_id, bhnd_port_type port_type, + u_int port) +{ + /* Verify the index against the port count */ + if (siba_port_count(core_id, port_type) <= port) + return (false); + + return (true); +} + +/** + * Return the number of bhnd(4) regions to advertise for @p core_id on the + * @p port of @p port_type. + * + * @param core_id The siba core info. + * @param port_type The bhnd(4) port type. + */ u_int -siba_addrspace_region_count(u_int num_addrspace, u_int port) +siba_port_region_count(struct siba_core_id *core_id, bhnd_port_type port_type, + u_int port) { - /* The first address space, if any, is mapped to device0.0 */ - if (port == 0) - return (min(num_addrspace, 1)); + /* The port must exist */ + if (!siba_is_port_valid(core_id, port_type, port)) + return (0); - /* All remaining address spaces are mapped to device0.(n - 1) */ - if (port == 1 && num_addrspace >= 2) - return (num_addrspace - 1); + switch (port_type) { + case BHND_PORT_DEVICE: + /* The first address space, if any, is mapped to device0.0 */ + if (port == 0) + return (min(core_id->num_addrspace, 1)); - /* No region mapping */ + /* All remaining address spaces are mapped to device0.(n - 1) */ + if (port == 1 && core_id->num_addrspace >= 2) + return (core_id->num_addrspace - 1); + + break; + + case BHND_PORT_AGENT: + /* All config blocks are mapped to a single port */ + if (port == 0) + return (core_id->num_cfg_blocks); + + break; + + default: + break; + } + + /* Validated above */ + panic("siba_is_port_valid() returned true for unknown %s.%u port", + bhnd_port_type_name(port_type), port); + +} + +/** + * Map a bhnd(4) type/port/region triplet to its associated config block index, + * if any. + * + * We map config registers to port/region identifiers as follows: + * + * [port].[region] [cfg register block] + * agent0.0 0 + * agent0.1 1 + * + * @param num_addrspace The number of available siba address spaces. + * @param port_type The bhnd(4) port type. + * @param port The bhnd(4) port number. + * @param region The bhnd(4) port region. + * @param addridx On success, the corresponding addrspace index. + * + * @retval 0 success + * @retval ENOENT if the given type/port/region cannot be mapped to a + * siba config register block. + */ +int +siba_cfg_index(struct siba_core_id *core_id, bhnd_port_type port_type, + u_int port, u_int region, u_int *cfgidx) +{ + /* Config blocks are mapped to agent ports */ + if (port_type != BHND_PORT_AGENT) + return (ENOENT); + + /* Port must be valid */ + if (!siba_is_port_valid(core_id, port_type, port)) + return (ENOENT); + + if (region >= core_id->num_cfg_blocks) + return (ENOENT); + + if (region >= SIBA_MAX_CFG) + return (ENOENT); + + /* Found */ + *cfgidx = region; return (0); } /** - * Return true if @p port is defined given an address space count - * of @p num_addrspace, false otherwise. + * Map an bhnd(4) type/port/region triplet to its associated config block + * entry, if any. * - * Refer to the siba_find_addrspace() function for information on siba's - * mapping of bhnd(4) port and region identifiers. + * The only supported port type is BHND_PORT_DEVICE. * - * @param num_addrspace The number of address spaces to verify the port against. + * @param dinfo The device info to search for a matching address space. * @param type The bhnd(4) port type. * @param port The bhnd(4) port number. + * @param region The bhnd(4) port region. */ -bool -siba_is_port_valid(u_int num_addrspace, bhnd_port_type type, u_int port) +struct siba_cfg_block * +siba_find_cfg_block(struct siba_devinfo *dinfo, bhnd_port_type type, u_int port, + u_int region) { - /* Only device ports are supported */ - if (type != BHND_PORT_DEVICE) - return (false); + u_int cfgidx; + int error; - /* Verify the index against the port count */ - if (siba_addrspace_port_count(num_addrspace) <= port) - return (false); + /* Map to addrspace index */ + error = siba_cfg_index(&dinfo->core_id, type, port, region, &cfgidx); + if (error) + return (NULL); - return (true); + /* Found */ + return (&dinfo->cfg[cfgidx]); } /** @@ -255,10 +399,8 @@ * device1.1 2 * device1.2 3 * - * The only supported port type is BHND_PORT_DEVICE. - * - * @param num_addrspace The number of available siba address spaces. - * @param type The bhnd(4) port type. + * @param core_id The siba core info. + * @param port_type The bhnd(4) port type. * @param port The bhnd(4) port number. * @param region The bhnd(4) port region. * @param addridx On success, the corresponding addrspace index. @@ -268,13 +410,18 @@ * siba address space. */ int -siba_addrspace_index(u_int num_addrspace, bhnd_port_type type, u_int port, - u_int region, u_int *addridx) +siba_addrspace_index(struct siba_core_id *core_id, bhnd_port_type port_type, + u_int port, u_int region, u_int *addridx) { u_int idx; - if (!siba_is_port_valid(num_addrspace, type, port)) + /* Address spaces are always device ports */ + if (port_type != BHND_PORT_DEVICE) return (ENOENT); + + /* Port must be valid */ + if (!siba_is_port_valid(core_id, port_type, port)) + return (ENOENT); if (port == 0) idx = region; @@ -283,7 +430,7 @@ else return (ENOENT); - if (idx >= num_addrspace) + if (idx >= core_id->num_addrspace) return (ENOENT); /* Found */ @@ -310,8 +457,8 @@ int error; /* Map to addrspace index */ - error = siba_addrspace_index(dinfo->core_id.num_addrspace, type, port, - region, &addridx); + error = siba_addrspace_index(&dinfo->core_id, type, port, region, + &addridx); if (error) return (NULL); @@ -377,25 +524,32 @@ * Deallocate the given device info structure and any associated resources. * * @param dev The requesting bus device. - * @param dinfo Device info to be deallocated. + * @param child The siba child device. + * @param dinfo Device info associated with @p child to be deallocated. */ void -siba_free_dinfo(device_t dev, struct siba_devinfo *dinfo) +siba_free_dinfo(device_t dev, device_t child, struct siba_devinfo *dinfo) { resource_list_free(&dinfo->resources); /* Free all mapped configuration blocks */ for (u_int i = 0; i < nitems(dinfo->cfg); i++) { - if (dinfo->cfg[i] == NULL) + if (dinfo->cfg_res[i] == NULL) continue; bhnd_release_resource(dev, SYS_RES_MEMORY, dinfo->cfg_rid[i], - dinfo->cfg[i]); + dinfo->cfg_res[i]); - dinfo->cfg[i] = NULL; + dinfo->cfg_res[i] = NULL; dinfo->cfg_rid[i] = -1; } + /* Unmap the core's interrupt */ + if (dinfo->intr_en && dinfo->intr.mapped) { + BHND_BUS_UNMAP_INTR(dev, child, dinfo->intr.irq); + dinfo->intr.mapped = false; + } + free(dinfo, M_BHND); } @@ -490,7 +644,7 @@ uint32_t rval; /* Must have a CFG0 block */ - if ((r = dinfo->cfg[0]) == NULL) + if ((r = dinfo->cfg_res[0]) == NULL) return (ENODEV); /* Verify the register offset falls within CFG register block */ @@ -535,7 +689,7 @@ struct bhnd_resource *r; uint32_t ts_high; - if ((r = dinfo->cfg[0]) == NULL) + if ((r = dinfo->cfg_res[0]) == NULL) return (ENODEV); for (int i = 0; i < usec; i += 10) { Index: head/sys/dev/bhnd/siba/sibareg.h =================================================================== --- head/sys/dev/bhnd/siba/sibareg.h +++ head/sys/dev/bhnd/siba/sibareg.h @@ -48,7 +48,7 @@ #define SIBA_ENUM_ADDR BHND_DEFAULT_CHIPC_ADDR /**< enumeration space */ #define SIBA_ENUM_SIZE 0x00100000 /**< size of the enumeration space */ #define SIBA_CORE_SIZE BHND_DEFAULT_CORE_SIZE /**< per-core register block size */ -#define SIBA_CORE_NUM_INTR 1 /**< number of per-core interrupt lines */ +#define SIBA_MAX_INTR 32 /**< maximum number of backplane interrupt vectors */ #define SIBA_MAX_CORES \ (SIBA_ENUM_SIZE/SIBA_CORE_SIZE) /**< Maximum number of cores */ @@ -70,9 +70,13 @@ #define SIBA_CFG0_OFFSET 0xf00 /**< first configuration block */ #define SIBA_CFG1_OFFSET 0xe00 /**< second configuration block (sonics >= 2.3) */ - #define SIBA_CFG_SIZE 0x100 /**< cfg register block size */ +/* Return the SIBA_CORE_ADDR-relative offset for the given siba configuration + * register block; configuration blocks are allocated starting at + * SIBA_CFG0_OFFSET, growing downwards. */ +#define SIBA_CFG_OFFSET(_n) (SIBA_CFG0_OFFSET - ((_n) * SIBA_CFG_SIZE)) + /* Return the SIBA_CORE_ADDR-relative offset for a SIBA_CFG* register. */ #define SB0_REG_ABS(off) ((off) + SIBA_CFG0_OFFSET) #define SB1_REG_ABS(off) ((off) + SIBA_CFG1_OFFSET) @@ -117,6 +121,9 @@ #define SIBA_IPS_INT3_SHIFT 16 #define SIBA_IPS_INT4_MASK 0x3f000000 /* which sbflags get routed to mips interrupt 4 */ #define SIBA_IPS_INT4_SHIFT 24 + +#define SIBA_IPS_INT_SHIFT(_i) ((_i - 1) * 8) +#define SIBA_IPS_INT_MASK(_i) (SIBA_IPS_INT1_MASK << SIBA_IPS_INT_SHIFT(_i)) /* sbtpsflag */ #define SIBA_TPS_NUM0_MASK 0x3f /* interrupt sbFlag # generated by this core */ Index: head/sys/dev/bhnd/siba/sibavar.h =================================================================== --- head/sys/dev/bhnd/siba/sibavar.h +++ head/sys/dev/bhnd/siba/sibavar.h @@ -1,7 +1,11 @@ /*- - * Copyright (c) 2015 Landon Fuller + * Copyright (c) 2015-2016 Landon Fuller + * 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: @@ -46,6 +50,7 @@ */ struct siba_addrspace; +struct siba_cfg_block; struct siba_devinfo; struct siba_core_id; @@ -54,9 +59,9 @@ int siba_detach(device_t dev); int siba_resume(device_t dev); int siba_suspend(device_t dev); -int siba_get_intr_count(device_t dev, device_t child); -int siba_get_core_ivec(device_t dev, device_t child, - u_int intr, uint32_t *ivec); +u_int siba_get_intr_count(device_t dev, device_t child); +int siba_get_intr_ivec(device_t dev, device_t child, + u_int intr, u_int *ivec); uint16_t siba_get_bhnd_mfgid(uint16_t ocp_vendor); @@ -69,24 +74,38 @@ int siba_init_dinfo(device_t dev, struct siba_devinfo *dinfo, const struct siba_core_id *core_id); -void siba_free_dinfo(device_t dev, +void siba_free_dinfo(device_t dev, device_t child, struct siba_devinfo *dinfo); -u_int siba_addrspace_port_count(u_int num_addrspace); -u_int siba_addrspace_region_count(u_int num_addrspace, - u_int port); +u_int siba_port_count(struct siba_core_id *core_id, + bhnd_port_type port_type); +bool siba_is_port_valid(struct siba_core_id *core_id, + bhnd_port_type port_type, u_int port); -u_int siba_addrspace_port(u_int addrspace); -u_int siba_addrspace_region(u_int addrspace); -int siba_addrspace_index(u_int num_addrspace, +u_int siba_port_region_count( + struct siba_core_id *core_id, + bhnd_port_type port_type, u_int port); + +int siba_cfg_index(struct siba_core_id *core_id, bhnd_port_type type, u_int port, u_int region, + u_int *cfgidx); + +int siba_addrspace_index(struct siba_core_id *core_id, + bhnd_port_type type, u_int port, u_int region, u_int *addridx); -bool siba_is_port_valid(u_int num_addrspace, - bhnd_port_type type, u_int port); +u_int siba_addrspace_device_port(u_int addrspace); +u_int siba_addrspace_device_region(u_int addrspace); + +u_int siba_cfg_agent_port(u_int cfg); +u_int siba_cfg_agent_region(u_int cfg); + struct siba_addrspace *siba_find_addrspace(struct siba_devinfo *dinfo, bhnd_port_type type, u_int port, u_int region); +struct siba_cfg_block *siba_find_cfg_block(struct siba_devinfo *dinfo, + bhnd_port_type type, u_int port, u_int region); + int siba_append_dinfo_region(struct siba_devinfo *dinfo, uint8_t sid, uint32_t base, uint32_t size, uint32_t bus_reserved); @@ -134,6 +153,21 @@ * address space reserved for the bus */ }; +/** siba(4) config block descriptor */ +struct siba_cfg_block { + uint32_t cb_base; /**< base address */ + uint32_t cb_size; /**< size */ + int cb_rid; /**< bus resource id */ +}; + +/** siba(4) backplane interrupt flag descriptor */ +struct siba_intr { + u_int flag; /**< backplane flag # */ + bool mapped; /**< if an irq has been mapped */ + int rid; /**< bus resource id, or -1 if unassigned */ + rman_res_t irq; /**< the mapped bus irq, if any */ +}; + /** * siba(4) per-core identification info. */ @@ -156,9 +190,12 @@ struct resource_list resources; /**< per-core memory regions. */ struct siba_core_id core_id; /**< core identification info */ struct siba_addrspace addrspace[SIBA_MAX_ADDRSPACE]; /**< memory map descriptors */ + struct siba_cfg_block cfg[SIBA_MAX_CFG]; /**< config block descriptors */ + struct siba_intr intr; /**< interrupt flag descriptor, if any */ + bool intr_en; /**< if true, core has an assigned interrupt flag */ - struct bhnd_resource *cfg[SIBA_MAX_CFG]; /**< SIBA_CFG_* registers */ - int cfg_rid[SIBA_MAX_CFG]; /**< SIBA_CFG_* resource IDs */ + struct bhnd_resource *cfg_res[SIBA_MAX_CFG]; /**< bus-mapped config block registers */ + int cfg_rid[SIBA_MAX_CFG]; /**< bus-mapped config block resource IDs */ struct bhnd_core_pmu_info *pmu_info; /**< Bus-managed PMU state, or NULL */ }; Index: head/sys/mips/broadcom/bcm_bmips.c =================================================================== --- head/sys/mips/broadcom/bcm_bmips.c +++ head/sys/mips/broadcom/bcm_bmips.c @@ -1,7 +1,11 @@ /*- * Copyright (c) 2016 Landon Fuller + * 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: @@ -34,13 +38,20 @@ #include #include #include +#include #include #include + +#include #include #include +#include +#include "pic_if.h" + +#include "bcm_mipsvar.h" #include "bcm_bmipsreg.h" /* @@ -50,74 +61,360 @@ * us to assume the availability of siba interrupt registers. */ +struct bcm_bmips_softc; + +static int bcm_bmips_pic_intr(void *arg); +static void bcm_bmips_mask_irq(struct bcm_bmips_softc *sc, u_int mips_irq, + u_int ivec); +static void bcm_bmips_unmask_irq(struct bcm_bmips_softc *sc, u_int mips_irq, + u_int ivec); + static const struct bhnd_device bcm_bmips_devs[] = { BHND_DEVICE(BCM, MIPS33, NULL, NULL, BHND_DF_SOC), BHND_DEVICE_END }; struct bcm_bmips_softc { + struct bcm_mips_softc bcm_mips; /**< parent softc */ device_t dev; - struct resource *mem_res; + struct resource *mem; /**< cpu core registers */ int mem_rid; + struct resource *cfg; /**< cpu core's cfg0 register block */ + int cfg_rid; }; +#define BCM_BMIPS_NCPU_IRQS 5 /**< MIPS HW IRQs 0-4 are assignable */ +#define BCM_BMIPS_TIMER_IRQ 5 /**< MIPS HW IRQ5 is always assigned to the timer */ + static int bcm_bmips_probe(device_t dev) { - const struct bhnd_device *id; + const struct bhnd_device *id; - id = bhnd_device_lookup(dev, bcm_bmips_devs, - sizeof(bcm_bmips_devs[0])); + id = bhnd_device_lookup(dev, bcm_bmips_devs, sizeof(bcm_bmips_devs[0])); if (id == NULL) return (ENXIO); + /* Check the chip type; should only be found on siba(4) chipsets */ + if (bhnd_get_chipid(dev)->chip_type != BHND_CHIPTYPE_SIBA) + return (ENXIO); + bhnd_set_default_core_desc(dev); return (BUS_PROBE_DEFAULT); } + static int bcm_bmips_attach(device_t dev) { - struct bcm_bmips_softc *sc; + struct bcm_bmips_softc *sc; + int error; sc = device_get_softc(dev); sc->dev = dev; - /* Allocate bus resources */ + /* Allocate our core's register block */ sc->mem_rid = 0; - sc->mem_res = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &sc->mem_rid, + sc->mem = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &sc->mem_rid, RF_ACTIVE); - if (sc->mem_res == NULL) - return (ENXIO); + if (sc->mem == NULL) { + device_printf(dev, "failed to allocate cpu register block\n"); + error = ENXIO; + goto failed; + } + /* Determine the resource ID for our siba CFG0 registers */ + sc->cfg_rid = bhnd_get_port_rid(dev, BHND_PORT_AGENT, 0, 0); + if (sc->cfg_rid == -1) { + device_printf(dev, "missing required cfg0 register block\n"); + error = ENXIO; + goto failed; + } + + /* Allocate our CFG0 register block */ + sc->cfg = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &sc->cfg_rid, + RF_ACTIVE|RF_SHAREABLE); + if (sc->cfg == NULL) { + device_printf(dev, "failed to allocate cfg0 register block\n"); + error = ENXIO; + goto failed; + } + + /* Clear interrupt map */ + bus_write_4(sc->cfg, SIBA_CFG0_INTVEC, 0x0); /* MIPS IRQ0 */ + bus_write_4(sc->cfg, SIBA_CFG0_IPSFLAG, 0x0); /* MIPS IRQ1-4 */ + + /* Initialize the generic BHND MIPS driver state */ + error = bcm_mips_attach(dev, BCM_BMIPS_NCPU_IRQS, BCM_BMIPS_TIMER_IRQ, + bcm_bmips_pic_intr); + if (error) + goto failed; + return (0); + +failed: + if (sc->mem != NULL) + bus_release_resource(dev, SYS_RES_MEMORY, sc->mem_rid, sc->mem); + + if (sc->cfg != NULL) + bus_release_resource(dev, SYS_RES_MEMORY, sc->cfg_rid, sc->cfg); + + return (error); } static int bcm_bmips_detach(device_t dev) { - struct bcm_bmips_softc *sc; + struct bcm_bmips_softc *sc; + int error; sc = device_get_softc(dev); - bus_release_resource(dev, SYS_RES_MEMORY, sc->mem_rid, sc->mem_res); + if ((error = bcm_mips_detach(dev))) + return (error); + bus_release_resource(dev, SYS_RES_MEMORY, sc->mem_rid, sc->mem); + bus_release_resource(dev, SYS_RES_MEMORY, sc->cfg_rid, sc->cfg); + return (0); } +/* PIC_DISABLE_INTR() */ +static void +bcm_bmips_pic_disable_intr(device_t dev, struct intr_irqsrc *irqsrc) +{ + struct bcm_bmips_softc *sc; + struct bcm_mips_irqsrc *isrc; + + sc = device_get_softc(dev); + isrc = (struct bcm_mips_irqsrc *)irqsrc; + + KASSERT(isrc->cpuirq != NULL, ("no assigned MIPS IRQ")); + + bcm_bmips_mask_irq(sc, isrc->cpuirq->mips_irq, isrc->ivec); +} + +/* PIC_ENABLE_INTR() */ +static void +bcm_bmips_pic_enable_intr(device_t dev, struct intr_irqsrc *irqsrc) +{ + struct bcm_bmips_softc *sc; + struct bcm_mips_irqsrc *isrc; + + sc = device_get_softc(dev); + isrc = (struct bcm_mips_irqsrc *)irqsrc; + + KASSERT(isrc->cpuirq != NULL, ("no assigned MIPS IRQ")); + + bcm_bmips_unmask_irq(sc, isrc->cpuirq->mips_irq, isrc->ivec); +} + +/* PIC_PRE_ITHREAD() */ +static void +bcm_bmips_pic_pre_ithread(device_t dev, struct intr_irqsrc *isrc) +{ + bcm_bmips_pic_disable_intr(dev, isrc); +} + +/* PIC_POST_ITHREAD() */ +static void +bcm_bmips_pic_post_ithread(device_t dev, struct intr_irqsrc *isrc) +{ + bcm_bmips_pic_enable_intr(dev, isrc); +} + +/* PIC_POST_FILTER() */ +static void +bcm_bmips_pic_post_filter(device_t dev, struct intr_irqsrc *isrc) +{ +} + +/** + * Disable routing of backplane interrupt vector @p ivec to MIPS IRQ + * @p mips_irq. + */ +static void +bcm_bmips_mask_irq(struct bcm_bmips_softc *sc, u_int mips_irq, u_int ivec) +{ + KASSERT(ivec < SIBA_MAX_INTR, ("invalid sbflag# ivec")); + KASSERT(mips_irq < sc->bcm_mips.num_cpuirqs, ("invalid MIPS IRQ %u", + mips_irq)); + + if (mips_irq == 0) { + uint32_t sbintvec; + + sbintvec = bus_read_4(sc->cfg, SIBA_CFG0_INTVEC); + sbintvec &= ~(1 << ivec); + bus_write_4(sc->cfg, SIBA_CFG0_INTVEC, sbintvec); + } else { + uint32_t ipsflag; + + /* Can we route this via ipsflag? */ + KASSERT(((1 << ivec) & SIBA_IPS_INT1_MASK) != 0, + ("cannot route high sbflag# ivec %u", ivec)); + + ipsflag = bus_read_4(sc->cfg, SIBA_CFG0_IPSFLAG); + ipsflag &= ~( + ((1 << ivec) << SIBA_IPS_INT_SHIFT(mips_irq)) & + SIBA_IPS_INT_MASK(mips_irq)); + bus_write_4(sc->cfg, SIBA_CFG0_IPSFLAG, ipsflag); + } + +} + +/** + * Enable routing of an interrupt. + */ +static void +bcm_bmips_unmask_irq(struct bcm_bmips_softc *sc, u_int mips_irq, u_int ivec) +{ + KASSERT(ivec < SIBA_MAX_INTR, ("invalid sbflag# ivec")); + KASSERT(mips_irq < sc->bcm_mips.num_cpuirqs, ("invalid MIPS IRQ %u", + mips_irq)); + + if (mips_irq == 0) { + uint32_t sbintvec; + + sbintvec = bus_read_4(sc->cfg, SIBA_CFG0_INTVEC); + sbintvec |= (1 << ivec); + bus_write_4(sc->cfg, SIBA_CFG0_INTVEC, sbintvec); + } else { + uint32_t ipsflag; + + /* Can we route this via ipsflag? */ + KASSERT(((1 << ivec) & SIBA_IPS_INT1_MASK) != 0, + ("cannot route high sbflag# ivec %u", ivec)); + + ipsflag = bus_read_4(sc->cfg, SIBA_CFG0_IPSFLAG); + ipsflag |= (ivec << SIBA_IPS_INT_SHIFT(mips_irq)) & + SIBA_IPS_INT_MASK(mips_irq); + bus_write_4(sc->cfg, SIBA_CFG0_IPSFLAG, ipsflag); + } +} + +/* our MIPS CPU interrupt filter */ +static int +bcm_bmips_pic_intr(void *arg) +{ + struct bcm_bmips_softc *sc; + struct bcm_mips_cpuirq *cpuirq; + struct bcm_mips_irqsrc *isrc_solo; + uint32_t sbintvec, sbstatus; + u_int mips_irq, i; + int error; + + cpuirq = arg; + sc = (struct bcm_bmips_softc*)cpuirq->sc; + + /* Fetch current interrupt state */ + sbstatus = bus_read_4(sc->cfg, SIBA_CFG0_FLAGST); + + /* Fetch mask of interrupt vectors routed to this MIPS IRQ */ + mips_irq = cpuirq->mips_irq; + if (mips_irq == 0) { + sbintvec = bus_read_4(sc->cfg, SIBA_CFG0_INTVEC); + } else { + uint32_t ipsflag; + + ipsflag = bus_read_4(sc->cfg, SIBA_CFG0_IPSFLAG); + + /* Map to an intvec-compatible representation */ + switch (mips_irq) { + case 1: + sbintvec = (ipsflag & SIBA_IPS_INT1_MASK) >> + SIBA_IPS_INT1_SHIFT; + break; + case 2: + sbintvec = (ipsflag & SIBA_IPS_INT2_MASK) >> + SIBA_IPS_INT2_SHIFT; + break; + case 3: + sbintvec = (ipsflag & SIBA_IPS_INT3_MASK) >> + SIBA_IPS_INT3_SHIFT; + break; + case 4: + sbintvec = (ipsflag & SIBA_IPS_INT4_MASK) >> + SIBA_IPS_INT4_SHIFT; + break; + default: + panic("invalid irq %u", mips_irq); + } + } + + /* Ignore interrupts not routed to this MIPS IRQ */ + sbstatus &= sbintvec; + + /* Handle isrc_solo direct dispatch path */ + isrc_solo = cpuirq->isrc_solo; + if (isrc_solo != NULL) { + if (sbstatus & BCM_MIPS_IVEC_MASK(isrc_solo)) { + error = intr_isrc_dispatch(&isrc_solo->isrc, + curthread->td_intr_frame); + if (error) { + device_printf(sc->dev, "Stray interrupt %u " + "detected\n", isrc_solo->ivec); + bcm_bmips_pic_disable_intr(sc->dev, + &isrc_solo->isrc); + } + } + + sbstatus &= ~(BCM_MIPS_IVEC_MASK(isrc_solo)); + if (sbstatus == 0) + return (FILTER_HANDLED); + + /* Report and mask additional stray interrupts */ + while ((i = fls(sbstatus)) != 0) { + i--; /* Get a 0-offset interrupt. */ + sbstatus &= ~(1 << i); + + device_printf(sc->dev, "Stray interrupt %u " + "detected\n", i); + bcm_bmips_mask_irq(sc, mips_irq, i); + } + + return (FILTER_HANDLED); + } + + /* Standard dispatch path */ + while ((i = fls(sbstatus)) != 0) { + i--; /* Get a 0-offset interrupt. */ + sbstatus &= ~(1 << i); + + KASSERT(i < nitems(sc->bcm_mips.isrcs), ("invalid ivec %u", i)); + + error = intr_isrc_dispatch(&sc->bcm_mips.isrcs[i].isrc, + curthread->td_intr_frame); + if (error) { + device_printf(sc->dev, "Stray interrupt %u detected\n", + i); + bcm_bmips_mask_irq(sc, mips_irq, i); + continue; + } + } + + return (FILTER_HANDLED); +} + static device_method_t bcm_bmips_methods[] = { /* Device interface */ - DEVMETHOD(device_probe, bcm_bmips_probe), - DEVMETHOD(device_attach, bcm_bmips_attach), - DEVMETHOD(device_detach, bcm_bmips_detach), - + DEVMETHOD(device_probe, bcm_bmips_probe), + DEVMETHOD(device_attach, bcm_bmips_attach), + DEVMETHOD(device_detach, bcm_bmips_detach), + + /* Interrupt controller interface */ + DEVMETHOD(pic_disable_intr, bcm_bmips_pic_disable_intr), + DEVMETHOD(pic_enable_intr, bcm_bmips_pic_enable_intr), + DEVMETHOD(pic_pre_ithread, bcm_bmips_pic_pre_ithread), + DEVMETHOD(pic_post_ithread, bcm_bmips_pic_post_ithread), + DEVMETHOD(pic_post_filter, bcm_bmips_pic_post_filter), + DEVMETHOD_END }; static devclass_t bcm_mips_devclass; -DEFINE_CLASS_0(bcm_mips, bcm_bmips_driver, bcm_bmips_methods, sizeof(struct bcm_bmips_softc)); -EARLY_DRIVER_MODULE(bcm_bmips, bhnd, bcm_bmips_driver, bcm_mips_devclass, 0, 0, BUS_PASS_CPU + BUS_PASS_ORDER_EARLY); +DEFINE_CLASS_1(bcm_mips, bcm_bmips_driver, bcm_bmips_methods, sizeof(struct bcm_bmips_softc), bcm_mips_driver); +EARLY_DRIVER_MODULE(bcm_bmips, bhnd, bcm_bmips_driver, bcm_mips_devclass, 0, 0, BUS_PASS_INTERRUPT + BUS_PASS_ORDER_MIDDLE); MODULE_VERSION(bcm_bmips, 1); MODULE_DEPEND(bcm_bmips, bhnd, 1, 1, 1); Index: head/sys/mips/broadcom/bcm_machdep.h =================================================================== --- head/sys/mips/broadcom/bcm_machdep.h +++ head/sys/mips/broadcom/bcm_machdep.h @@ -55,12 +55,15 @@ uint32_t cc_caps; /**< chipc capabilities */ uint32_t cc_caps_ext; /**< chipc extended capabilies */ + struct bhnd_core_info cpu_id; /**< cpu core info */ + uintptr_t cpu_addr; /**< cpu core phys address */ + /* On non-AOB devices, the PMU register block is mapped to chipc; * the pmu_id and pmu_addr values will be copied from cc_id * and cc_addr. */ - struct bhnd_core_info pmu_id; /**< PMU core info */ + struct bhnd_core_info pmu_id; /**< PMU core info */ uintptr_t pmu_addr; /**< PMU core phys address, or - 0x0 if no PMU */ + 0x0 if no PMU */ struct bhnd_pmu_query pmu; /**< PMU query instance */ @@ -121,6 +124,11 @@ BCM_CORE_READ_4(_bp, cc_addr, (_reg)) #define BCM_CHIPC_WRITE_4(_bp, _reg, _val) \ BCM_CORE_WRITE_4(_bp, cc_addr, (_reg), (_val)) + +#define BCM_CPU_READ_4(_bp, _reg) \ + BCM_CORE_READ_4(_bp, cpu_addr, (_reg)) +#define BCM_CPU_WRITE_4(_bp, _reg, _val) \ + BCM_CORE_WRITE_4(_bp, cpu_addr, (_reg), (_val)) #define BCM_PMU_READ_4(_bp, _reg) \ BCM_CORE_READ_4(_bp, pmu_addr, (_reg)) Index: head/sys/mips/broadcom/bcm_machdep.c =================================================================== --- head/sys/mips/broadcom/bcm_machdep.c +++ head/sys/mips/broadcom/bcm_machdep.c @@ -126,6 +126,13 @@ { BHND_MATCH_CORE(BHND_MFGID_BCM, BHND_COREID_4706_CC) }, }; +static const struct bhnd_core_match bcm_cpu0_cores[] = { + { + BHND_MATCH_CORE_CLASS(BHND_DEVCLASS_CPU), + BHND_MATCH_CORE_UNIT(0) + } +}; + static const struct bhnd_core_match bcm_pmu_cores[] = { { BHND_MATCH_CORE(BHND_MFGID_BCM, BHND_COREID_PMU) }, }; @@ -427,6 +434,14 @@ BCM_ERR("bhnd_pmu_query_init() failed: %d\n", error); return (error); } + } + + /* Find CPU core info */ + error = bcm_find_core(bp, bcm_cpu0_cores, nitems(bcm_cpu0_cores), + &bp->cpu_id, &bp->cpu_addr); + if (error) { + BCM_ERR("error locating CPU core: %d\n", error); + return (error); } /* Initialize our platform service registry */ Index: head/sys/mips/broadcom/bcm_mips.c =================================================================== --- head/sys/mips/broadcom/bcm_mips.c +++ head/sys/mips/broadcom/bcm_mips.c @@ -0,0 +1,698 @@ +/*- + * Copyright (c) 2017 The FreeBSD Foundation + * All rights reserved. + * + * This software was 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: + * 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 AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include + +#include "pic_if.h" + +#include "bcm_mipsvar.h" + +/* + * Broadcom MIPS core driver. + * + * Abstract driver for Broadcom MIPS CPU/PIC cores. + */ + +static uintptr_t bcm_mips_pic_xref(struct bcm_mips_softc *sc); +static device_t bcm_mips_find_bhnd_parent(device_t dev); +static int bcm_mips_retain_cpu_intr(struct bcm_mips_softc *sc, + struct bcm_mips_irqsrc *isrc, struct resource *res); +static int bcm_mips_release_cpu_intr(struct bcm_mips_softc *sc, + struct bcm_mips_irqsrc *isrc, struct resource *res); + +static const int bcm_mips_debug = 0; + +#define DPRINTF(fmt, ...) do { \ + if (bcm_mips_debug) \ + printf("%s: " fmt, __FUNCTION__, ##__VA_ARGS__); \ +} while (0) + +#define DENTRY(dev, fmt, ...) do { \ + if (bcm_mips_debug) \ + printf("%s(%s, " fmt ")\n", __FUNCTION__, \ + device_get_nameunit(dev), ##__VA_ARGS__); \ +} while (0) + +/** + * Register all interrupt source definitions. + */ +static int +bcm_mips_register_isrcs(struct bcm_mips_softc *sc) +{ + const char *name; + uintptr_t xref; + int error; + + xref = bcm_mips_pic_xref(sc); + + name = device_get_nameunit(sc->dev); + for (size_t ivec = 0; ivec < nitems(sc->isrcs); ivec++) { + sc->isrcs[ivec].ivec = ivec; + sc->isrcs[ivec].cpuirq = NULL; + sc->isrcs[ivec].refs = 0; + + error = intr_isrc_register(&sc->isrcs[ivec].isrc, sc->dev, + xref, "%s,%u", name, ivec); + if (error) { + for (size_t i = 0; i < ivec; i++) + intr_isrc_deregister(&sc->isrcs[i].isrc); + + device_printf(sc->dev, "error registering IRQ %zu: " + "%d\n", ivec, error); + return (error); + } + } + + return (0); +} + +/** + * Initialize the given @p cpuirq state as unavailable. + * + * @param sc BHND MIPS driver instance state. + * @param cpuirq The CPU IRQ state to be initialized. + * + * @retval 0 success + * @retval non-zero if initializing @p cpuirq otherwise fails, a regular + * unix error code will be returned. + */ +static int +bcm_mips_init_cpuirq_unavail(struct bcm_mips_softc *sc, + struct bcm_mips_cpuirq *cpuirq) +{ + BCM_MIPS_LOCK(sc); + + KASSERT(cpuirq->sc == NULL, ("cpuirq already initialized")); + cpuirq->sc = sc; + cpuirq->mips_irq = 0; + cpuirq->irq_rid = -1; + cpuirq->irq_res = NULL; + cpuirq->irq_cookie = NULL; + cpuirq->isrc_solo = NULL; + cpuirq->refs = 0; + + BCM_MIPS_UNLOCK(sc); + + return (0); +} + +/** + * Allocate required resources and initialize the given @p cpuirq state. + * + * @param sc BHND MIPS driver instance state. + * @param cpuirq The CPU IRQ state to be initialized. + * @param rid The resource ID to be assigned for the CPU IRQ resource, + * or -1 if no resource should be assigned. + * @param irq The MIPS HW IRQ# to be allocated. + * @param filter The interrupt filter to be setup. + * + * @retval 0 success + * @retval non-zero if initializing @p cpuirq otherwise fails, a regular + * unix error code will be returned. + */ +static int +bcm_mips_init_cpuirq(struct bcm_mips_softc *sc, struct bcm_mips_cpuirq *cpuirq, + int rid, u_int irq, driver_filter_t filter) +{ + struct resource *res; + void *cookie; + u_int irq_real; + int error; + + /* Must fall within MIPS HW IRQ range */ + if (irq >= NHARD_IRQS) + return (EINVAL); + + /* HW IRQs are numbered relative to SW IRQs */ + irq_real = irq + NSOFT_IRQS; + + /* Try to assign and allocate the resource */ + BCM_MIPS_LOCK(sc); + + KASSERT(cpuirq->sc == NULL, ("cpuirq already initialized")); + + error = bus_set_resource(sc->dev, SYS_RES_IRQ, rid, irq_real, 1); + if (error) { + BCM_MIPS_UNLOCK(sc); + device_printf(sc->dev, "failed to assign interrupt %u: " + "%d\n", irq, error); + return (error); + } + + res = bus_alloc_resource_any(sc->dev, SYS_RES_IRQ, &rid, RF_ACTIVE); + if (res == NULL) { + BCM_MIPS_UNLOCK(sc); + device_printf(sc->dev, "failed to allocate interrupt " + "%u resource\n", irq); + bus_delete_resource(sc->dev, SYS_RES_IRQ, rid); + return (ENXIO); + } + + error = bus_setup_intr(sc->dev, res, + INTR_TYPE_MISC | INTR_MPSAFE | INTR_EXCL, filter, NULL, cpuirq, + &cookie); + if (error) { + BCM_MIPS_UNLOCK(sc); + + printf("failed to setup internal interrupt handler: %d\n", + error); + + bus_release_resource(sc->dev, SYS_RES_IRQ, rid, res); + bus_delete_resource(sc->dev, SYS_RES_IRQ, rid); + + return (error); + } + + /* Initialize CPU IRQ state */ + cpuirq->sc = sc; + cpuirq->mips_irq = irq; + cpuirq->irq_rid = rid; + cpuirq->irq_res = res; + cpuirq->irq_cookie = cookie; + cpuirq->isrc_solo = NULL; + cpuirq->refs = 0; + + BCM_MIPS_UNLOCK(sc); + return (0); +} + +/** + * Free any resources associated with the given @p cpuirq state. + * + * @param sc BHND MIPS driver instance state. + * @param cpuirq A CPU IRQ instance previously successfully initialized + * via bcm_mips_init_cpuirq(). + * + * @retval 0 success + * @retval non-zero if finalizing @p cpuirq otherwise fails, a regular + * unix error code will be returned. + */ +static int +bcm_mips_fini_cpuirq(struct bcm_mips_softc *sc, struct bcm_mips_cpuirq *cpuirq) +{ + int error; + + BCM_MIPS_LOCK(sc); + + if (cpuirq->sc == NULL) { + KASSERT(cpuirq->irq_res == NULL, ("leaking cpuirq resource")); + + BCM_MIPS_UNLOCK(sc); + return (0); /* not initialized */ + } + + if (cpuirq->refs != 0) { + BCM_MIPS_UNLOCK(sc); + return (EBUSY); + } + + if (cpuirq->irq_cookie != NULL) { + KASSERT(cpuirq->irq_res != NULL, ("resource missing")); + + error = bus_teardown_intr(sc->dev, cpuirq->irq_res, + cpuirq->irq_cookie); + if (error) { + BCM_MIPS_UNLOCK(sc); + return (error); + } + + cpuirq->irq_cookie = NULL; + } + + if (cpuirq->irq_res != NULL) { + bus_release_resource(sc->dev, SYS_RES_IRQ, cpuirq->irq_rid, + cpuirq->irq_res); + cpuirq->irq_res = NULL; + } + + if (cpuirq->irq_rid != -1) { + bus_delete_resource(sc->dev, SYS_RES_IRQ, cpuirq->irq_rid); + cpuirq->irq_rid = -1; + } + + BCM_MIPS_UNLOCK(sc); + + return (0); +} + +static int +bcm_mips_attach_default(device_t dev) +{ + /* subclassing drivers must provide an implementation of + * DEVICE_ATTACH() */ + panic("device_attach() unimplemented"); +} + +/** + * BHND MIPS device attach. + * + * This must be called from subclass drivers' DEVICE_ATTACH(). + * + * @param dev BHND MIPS device. + * @param num_cpuirqs The number of usable MIPS HW IRQs. + * @param timer_irq The MIPS HW IRQ assigned to the MIPS CPU timer. + * @param filter The subclass's core-specific IRQ dispatch filter. Will be + * passed the associated bcm_mips_cpuirq instance as its argument. + */ +int +bcm_mips_attach(device_t dev, u_int num_cpuirqs, u_int timer_irq, + driver_filter_t filter) +{ + struct bcm_mips_softc *sc; + struct intr_pic *pic; + uintptr_t xref; + u_int irq_rid; + rman_res_t irq; + int error; + + sc = device_get_softc(dev); + sc->dev = dev; + sc->num_cpuirqs = num_cpuirqs; + sc->timer_irq = timer_irq; + + /* Must not exceed the actual size of our fixed IRQ array */ + if (sc->num_cpuirqs > nitems(sc->cpuirqs)) { + device_printf(dev, "%u nirqs exceeds maximum supported %zu", + sc->num_cpuirqs, nitems(sc->cpuirqs)); + return (ENXIO); + } + + pic = NULL; + xref = bcm_mips_pic_xref(sc); + + BCM_MIPS_LOCK_INIT(sc); + + /* Register our interrupt sources */ + if ((error = bcm_mips_register_isrcs(sc))) { + BCM_MIPS_LOCK_DESTROY(sc); + return (error); + } + + /* Initialize our CPU interrupt state */ + irq_rid = bhnd_get_intr_count(dev); /* last bhnd-assigned RID + 1 */ + irq = 0; + for (u_int i = 0; i < sc->num_cpuirqs; i++) { + /* Must not overflow signed resource ID representation */ + if (irq_rid >= INT_MAX) { + device_printf(dev, "exhausted IRQ resource IDs\n"); + error = ENOMEM; + goto failed; + } + + if (irq == sc->timer_irq) { + /* Mark the CPU timer's IRQ as unavailable */ + error = bcm_mips_init_cpuirq_unavail(sc, + &sc->cpuirqs[i]); + } else { + /* Initialize state */ + error = bcm_mips_init_cpuirq(sc, &sc->cpuirqs[i], + irq_rid, irq, filter); + } + + if (error) + goto failed; + + /* Increment IRQ and resource ID for next allocation */ + irq_rid++; + irq++; + } + + /* Sanity check; our shared IRQ must be available */ + if (sc->num_cpuirqs <= BCM_MIPS_IRQ_SHARED) + panic("missing shared interrupt %d\n", BCM_MIPS_IRQ_SHARED); + + if (sc->cpuirqs[BCM_MIPS_IRQ_SHARED].irq_rid == -1) + panic("shared interrupt %d unavailable", BCM_MIPS_IRQ_SHARED); + + /* Register PIC */ + if ((pic = intr_pic_register(dev, xref)) == NULL) { + device_printf(dev, "error registering PIC\n"); + error = ENXIO; + goto failed; + } + + return (0); + +failed: + /* Deregister PIC before performing any other cleanup */ + if (pic != NULL) + intr_pic_deregister(dev, 0); + + /* Deregister all interrupt sources */ + for (size_t i = 0; i < nitems(sc->isrcs); i++) + intr_isrc_deregister(&sc->isrcs[i].isrc); + + /* Free our MIPS CPU interrupt handler state */ + for (u_int i = 0; i < sc->num_cpuirqs; i++) + bcm_mips_fini_cpuirq(sc, &sc->cpuirqs[i]); + + BCM_MIPS_LOCK_DESTROY(sc); + return (error); +} + +int +bcm_mips_detach(device_t dev) +{ + struct bcm_mips_softc *sc; + + sc = device_get_softc(dev); + + /* Deregister PIC before performing any other cleanup */ + intr_pic_deregister(dev, 0); + + /* Deregister all interrupt sources */ + for (size_t i = 0; i < nitems(sc->isrcs); i++) + intr_isrc_deregister(&sc->isrcs[i].isrc); + + /* Free our MIPS CPU interrupt handler state */ + for (u_int i = 0; i < sc->num_cpuirqs; i++) + bcm_mips_fini_cpuirq(sc, &sc->cpuirqs[i]); + + return (0); +} + +/* PIC_MAP_INTR() */ +static int +bcm_mips_pic_map_intr(device_t dev, struct intr_map_data *d, + struct intr_irqsrc **isrcp) +{ + struct bcm_mips_softc *sc; + struct bcm_mips_intr_map_data *data; + + sc = device_get_softc(dev); + + if (d->type != INTR_MAP_DATA_BCM_MIPS) { + DENTRY(dev, "type=%d", d->type); + return (ENOTSUP); + } + + data = (struct bcm_mips_intr_map_data *)d; + DENTRY(dev, "type=%d, ivec=%u", d->type, data->ivec); + if (data->ivec < 0 || data->ivec >= nitems(sc->isrcs)) + return (EINVAL); + + *isrcp = &sc->isrcs[data->ivec].isrc; + return (0); +} + +/* PIC_SETUP_INTR() */ +static int +bcm_mips_pic_setup_intr(device_t dev, struct intr_irqsrc *irqsrc, + struct resource *res, struct intr_map_data *data) +{ + struct bcm_mips_softc *sc; + struct bcm_mips_irqsrc *isrc; + int error; + + sc = device_get_softc(dev); + isrc = (struct bcm_mips_irqsrc *)irqsrc; + + /* Assign a CPU interrupt */ + BCM_MIPS_LOCK(sc); + error = bcm_mips_retain_cpu_intr(sc, isrc, res); + BCM_MIPS_UNLOCK(sc); + + return (error); +} + +/* PIC_TEARDOWN_INTR() */ +static int +bcm_mips_pic_teardown_intr(device_t dev, struct intr_irqsrc *irqsrc, + struct resource *res, struct intr_map_data *data) +{ + struct bcm_mips_softc *sc; + struct bcm_mips_irqsrc *isrc; + int error; + + sc = device_get_softc(dev); + isrc = (struct bcm_mips_irqsrc *)irqsrc; + + /* Release the CPU interrupt */ + BCM_MIPS_LOCK(sc); + error = bcm_mips_release_cpu_intr(sc, isrc, res); + BCM_MIPS_UNLOCK(sc); + + return (error); +} + + +/** return our PIC's xref */ +static uintptr_t +bcm_mips_pic_xref(struct bcm_mips_softc *sc) +{ + uintptr_t xref; + + /* Determine our interrupt domain */ + xref = BHND_BUS_GET_INTR_DOMAIN(device_get_parent(sc->dev), sc->dev, + true); + KASSERT(xref != 0, ("missing interrupt domain")); + + return (xref); +} + +/** + * Walk up the device tree from @p dev until we find a bhnd-attached core, + * returning either the core, or NULL if @p dev is not attached under a bhnd + * bus. + */ +static device_t +bcm_mips_find_bhnd_parent(device_t dev) +{ + device_t core, bus; + devclass_t bhnd_class; + + bhnd_class = devclass_find("bhnd"); + core = dev; + while ((bus = device_get_parent(core)) != NULL) { + if (device_get_devclass(bus) == bhnd_class) + return (core); + + core = bus; + } + + /* Not found */ + return (NULL); +} + +/** + * Retain @p isrc and assign a MIPS CPU interrupt on behalf of @p res; if + * the @p isrc already has a MIPS CPU interrupt assigned, the existing + * reference will be left unmodified. + * + * @param sc BHND MIPS driver state. + * @param isrc The interrupt source corresponding to @p res. + * @param res The interrupt resource for which a MIPS CPU IRQ will be + * assigned. + */ +static int +bcm_mips_retain_cpu_intr(struct bcm_mips_softc *sc, + struct bcm_mips_irqsrc *isrc, struct resource *res) +{ + struct bcm_mips_cpuirq *cpuirq; + bhnd_devclass_t devclass; + device_t core; + + BCM_MIPS_LOCK_ASSERT(sc, MA_OWNED); + + /* Prefer existing assignment */ + if (isrc->cpuirq != NULL) { + KASSERT(isrc->cpuirq->refs > 0, ("assigned IRQ has no " + "references")); + + /* Increment our reference count */ + if (isrc->refs == UINT_MAX) + return (ENOMEM); /* would overflow */ + + isrc->refs++; + return (0); + } + + /* Use the device class of the bhnd core to which the interrupt + * vector is routed to determine whether a shared interrupt should + * be preferred. */ + devclass = BHND_DEVCLASS_OTHER; + core = bcm_mips_find_bhnd_parent(rman_get_device(res)); + if (core != NULL) + devclass = bhnd_get_class(core); + + switch (devclass) { + case BHND_DEVCLASS_CC: + case BHND_DEVCLASS_CC_B: + case BHND_DEVCLASS_PMU: + case BHND_DEVCLASS_RAM: + case BHND_DEVCLASS_MEMC: + case BHND_DEVCLASS_CPU: + case BHND_DEVCLASS_SOC_ROUTER: + case BHND_DEVCLASS_SOC_BRIDGE: + case BHND_DEVCLASS_EROM: + case BHND_DEVCLASS_NVRAM: + /* Always use a shared interrupt for these devices */ + cpuirq = &sc->cpuirqs[BCM_MIPS_IRQ_SHARED]; + break; + + case BHND_DEVCLASS_PCI: + case BHND_DEVCLASS_PCIE: + case BHND_DEVCLASS_PCCARD: + case BHND_DEVCLASS_ENET: + case BHND_DEVCLASS_ENET_MAC: + case BHND_DEVCLASS_ENET_PHY: + case BHND_DEVCLASS_WLAN: + case BHND_DEVCLASS_WLAN_MAC: + case BHND_DEVCLASS_WLAN_PHY: + case BHND_DEVCLASS_USB_HOST: + case BHND_DEVCLASS_USB_DEV: + case BHND_DEVCLASS_USB_DUAL: + case BHND_DEVCLASS_OTHER: + case BHND_DEVCLASS_INVALID: + default: + /* Fall back on a shared interrupt */ + cpuirq = &sc->cpuirqs[BCM_MIPS_IRQ_SHARED]; + + /* Try to assign a dedicated MIPS HW interrupt */ + for (u_int i = 0; i < sc->num_cpuirqs; i++) { + if (i == BCM_MIPS_IRQ_SHARED) + continue; + + if (sc->cpuirqs[i].irq_rid == -1) + continue; /* unavailable */ + + if (sc->cpuirqs[i].refs != 0) + continue; /* already assigned */ + + /* Found an unused CPU IRQ */ + cpuirq = &sc->cpuirqs[i]; + break; + } + + break; + } + + DPRINTF("routing backplane interrupt vector %u to MIPS IRQ %u\n", + isrc->ivec, cpuirq->mips_irq); + + KASSERT(isrc->cpuirq == NULL, ("CPU IRQ already assigned")); + KASSERT(isrc->refs == 0, ("isrc has active references with no " + "assigned CPU IRQ")); + KASSERT(cpuirq->refs == 1 || cpuirq->isrc_solo == NULL, + ("single isrc dispatch enabled on cpuirq with multiple refs")); + + /* Verify that bumping the cpuirq refcount below will not overflow */ + if (cpuirq->refs == UINT_MAX) + return (ENOMEM); + + /* Increment cpuirq refcount on behalf of the isrc */ + cpuirq->refs++; + + /* Increment isrc refcount on behalf of the caller */ + isrc->refs++; + + /* Assign the IRQ to the isrc */ + isrc->cpuirq = cpuirq; + + /* Can we enable the single isrc dispatch path? */ + if (cpuirq->refs == 1 && cpuirq->mips_irq != BCM_MIPS_IRQ_SHARED) + cpuirq->isrc_solo = isrc; + + return (0); +} + +/** + * Release the MIPS CPU interrupt assigned to @p isrc on behalf of @p res. + * + * @param sc BHND MIPS driver state. + * @param isrc The interrupt source corresponding to @p res. + * @param res The interrupt resource being activated. + */ +static int +bcm_mips_release_cpu_intr(struct bcm_mips_softc *sc, + struct bcm_mips_irqsrc *isrc, struct resource *res) +{ + struct bcm_mips_cpuirq *cpuirq; + + BCM_MIPS_LOCK_ASSERT(sc, MA_OWNED); + + /* Decrement the refcount */ + KASSERT(isrc->refs > 0, ("isrc over-release")); + isrc->refs--; + + /* Nothing else to do if the isrc is still actively referenced */ + if (isrc->refs > 0) + return (0); + + /* Otherwise, we need to release our CPU IRQ reference */ + cpuirq = isrc->cpuirq; + isrc->cpuirq = NULL; + + KASSERT(cpuirq != NULL, ("no assigned IRQ")); + KASSERT(cpuirq->refs > 0, ("cpuirq over-release")); + + /* Disable single isrc dispatch path */ + if (cpuirq->refs == 1 && cpuirq->isrc_solo != NULL) { + KASSERT(cpuirq->isrc_solo == isrc, ("invalid solo isrc")); + cpuirq->isrc_solo = NULL; + } + + cpuirq->refs--; + + return (0); +} + +static device_method_t bcm_mips_methods[] = { + /* Device interface */ + DEVMETHOD(device_attach, bcm_mips_attach_default), + DEVMETHOD(device_detach, bcm_mips_detach), + + /* Interrupt controller interface */ + DEVMETHOD(pic_map_intr, bcm_mips_pic_map_intr), + DEVMETHOD(pic_setup_intr, bcm_mips_pic_setup_intr), + DEVMETHOD(pic_teardown_intr, bcm_mips_pic_teardown_intr), + + DEVMETHOD_END +}; + +DEFINE_CLASS_0(bcm_mips, bcm_mips_driver, bcm_mips_methods, sizeof(struct bcm_mips_softc)); + +MODULE_VERSION(bcm_mips, 1); +MODULE_DEPEND(bcm_mips, bhnd, 1, 1, 1); Index: head/sys/mips/broadcom/bcm_mips74k.c =================================================================== --- head/sys/mips/broadcom/bcm_mips74k.c +++ head/sys/mips/broadcom/bcm_mips74k.c @@ -1,8 +1,12 @@ /*- * Copyright (c) 2016 Michael Zhilin * Copyright (c) 2016 Landon Fuller + * 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: @@ -35,43 +39,106 @@ #include #include #include +#include #include #include + +#include +#include #include #include +#include +#include "pic_if.h" + +#include "bcm_machdep.h" + +#include "bcm_mipsvar.h" #include "bcm_mips74kreg.h" /* * Broadcom MIPS74K Core * - * These cores are only found on bcma(4) chipsets, allowing - * us to assume the availability of bcma interrupt registers. + * These cores are only found on bcma(4) chipsets. */ +struct bcm_mips74k_softc; + +static int bcm_mips74k_pic_intr(void *arg); +static void bcm_mips74k_mask_irq(struct bcm_mips74k_softc *sc, + u_int mips_irq, u_int ivec); +static void bcm_mips74k_unmask_irq(struct bcm_mips74k_softc *sc, + u_int mips_irq, u_int ivec); + static const struct bhnd_device bcm_mips74k_devs[] = { BHND_DEVICE(MIPS, MIPS74K, NULL, NULL, BHND_DF_SOC), BHND_DEVICE_END }; struct bcm_mips74k_softc { + struct bcm_mips_softc bcm_mips; /**< parent softc */ device_t dev; - struct resource *mem_res; + struct resource *mem; /**< cpu core registers */ int mem_rid; }; +/* Early routing of the CPU timer interrupt is required */ +static void +bcm_mips74k_timer_init(void *unused) +{ + struct bcm_platform *bp; + u_int irq; + uint32_t mask; + + bp = bcm_get_platform(); + + /* Must be a MIPS74K core attached to a BCMA interconnect */ + if (!bhnd_core_matches(&bp->cpu_id, &(struct bhnd_core_match) { + BHND_MATCH_CORE(BHND_MFGID_MIPS, BHND_COREID_MIPS74K) + })) { + if (bootverbose) { + BCM_ERR("not a MIPS74K core: %s %s\n", + bhnd_vendor_name(bp->cpu_id.vendor), + bhnd_core_name(&bp->cpu_id)); + } + + return; + } + + if (!BHND_CHIPTYPE_IS_BCMA_COMPATIBLE(bp->cid.chip_type)) { + if (bootverbose) + BCM_ERR("not a BCMA device\n"); + return; + } + + /* Route the timer bus ivec to the CPU's timer IRQ, and disable any + * other vectors assigned to the IRQ. */ + irq = BCM_MIPS74K_GET_TIMER_IRQ(); + mask = BCM_MIPS74K_INTR_SEL_FLAG(BCM_MIPS74K_TIMER_IVEC); + + BCM_CPU_WRITE_4(bp, BCM_MIPS74K_INTR_SEL(irq), mask); +} + static int bcm_mips74k_probe(device_t dev) { const struct bhnd_device *id; + const struct bhnd_chipid *cid; id = bhnd_device_lookup(dev, bcm_mips74k_devs, sizeof(bcm_mips74k_devs[0])); if (id == NULL) return (ENXIO); + /* Check the chip type; the MIPS74K core should only be found + * on bcma(4) chipsets (and we rely on bcma OOB interrupt + * routing). */ + cid = bhnd_get_chipid(dev); + if (!BHND_CHIPTYPE_IS_BCMA_COMPATIBLE(cid->chip_type)) + return (ENXIO); + bhnd_set_default_core_desc(dev); return (BUS_PROBE_DEFAULT); } @@ -79,22 +146,41 @@ static int bcm_mips74k_attach(device_t dev) { - struct bcm_mips74k_softc *sc; + struct bcm_mips74k_softc *sc; + u_int timer_irq; + int error; sc = device_get_softc(dev); sc->dev = dev; - /* Allocate bus resources */ + /* Allocate our core's register block */ sc->mem_rid = 0; - sc->mem_res = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &sc->mem_rid, + sc->mem = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &sc->mem_rid, RF_ACTIVE); - if (sc->mem_res == NULL) + if (sc->mem == NULL) { + device_printf(dev, "failed to allocate cpu register block\n"); return (ENXIO); + } - /* Route MIPS timer to IRQ5 */ - bus_write_4(sc->mem_res, BCM_MIPS74K_INTR5_SEL, - (1<mem, BCM_MIPS74K_INTR_SEL(i), 0); + } + + /* Initialize the generic BHND MIPS driver state */ + error = bcm_mips_attach(dev, BCM_MIPS74K_NUM_INTR, timer_irq, + bcm_mips74k_pic_intr); + if (error) { + bus_release_resource(dev, SYS_RES_MEMORY, sc->mem_rid, sc->mem); + return (error); + } + return (0); } @@ -102,26 +188,205 @@ bcm_mips74k_detach(device_t dev) { struct bcm_mips74k_softc *sc; + int error; sc = device_get_softc(dev); - bus_release_resource(dev, SYS_RES_MEMORY, sc->mem_rid, sc->mem_res); + if ((error = bcm_mips_detach(dev))) + return (error); + bus_release_resource(dev, SYS_RES_MEMORY, sc->mem_rid, sc->mem); + return (0); } + +/* PIC_DISABLE_INTR() */ +static void +bcm_mips74k_pic_disable_intr(device_t dev, struct intr_irqsrc *irqsrc) +{ + struct bcm_mips74k_softc *sc; + struct bcm_mips_irqsrc *isrc; + + sc = device_get_softc(dev); + isrc = (struct bcm_mips_irqsrc *)irqsrc; + + KASSERT(isrc->cpuirq != NULL, ("no assigned MIPS IRQ")); + + bcm_mips74k_mask_irq(sc, isrc->cpuirq->mips_irq, isrc->ivec); +} + +/* PIC_ENABLE_INTR() */ +static void +bcm_mips74k_pic_enable_intr(device_t dev, struct intr_irqsrc *irqsrc) +{ + struct bcm_mips74k_softc *sc; + struct bcm_mips_irqsrc *isrc; + + sc = device_get_softc(dev); + isrc = (struct bcm_mips_irqsrc *)irqsrc; + + KASSERT(isrc->cpuirq != NULL, ("no assigned MIPS IRQ")); + + bcm_mips74k_unmask_irq(sc, isrc->cpuirq->mips_irq, isrc->ivec); +} + +/* PIC_PRE_ITHREAD() */ +static void +bcm_mips74k_pic_pre_ithread(device_t dev, struct intr_irqsrc *isrc) +{ + bcm_mips74k_pic_disable_intr(dev, isrc); +} + +/* PIC_POST_ITHREAD() */ +static void +bcm_mips74k_pic_post_ithread(device_t dev, struct intr_irqsrc *isrc) +{ + bcm_mips74k_pic_enable_intr(dev, isrc); +} + +/* PIC_POST_FILTER() */ +static void +bcm_mips74k_pic_post_filter(device_t dev, struct intr_irqsrc *isrc) +{ +} + +/** + * Disable routing of backplane interrupt vector @p ivec to MIPS IRQ + * @p mips_irq. + */ +static void +bcm_mips74k_mask_irq(struct bcm_mips74k_softc *sc, u_int mips_irq, u_int ivec) +{ + uint32_t oobsel; + + KASSERT(mips_irq < sc->bcm_mips.num_cpuirqs, ("invalid MIPS IRQ %u", + mips_irq)); + KASSERT(mips_irq < BCM_MIPS74K_NUM_INTR, ("unsupported MIPS IRQ %u", + mips_irq)); + KASSERT(ivec < BCMA_OOB_NUM_BUSLINES, ("invalid backplane ivec")); + + oobsel = bus_read_4(sc->mem, BCM_MIPS74K_INTR_SEL(mips_irq)); + oobsel &= ~(BCM_MIPS74K_INTR_SEL_FLAG(ivec)); + bus_write_4(sc->mem, BCM_MIPS74K_INTR_SEL(mips_irq), oobsel); +} + +/** + * Enable routing of an interrupt. + */ +static void +bcm_mips74k_unmask_irq(struct bcm_mips74k_softc *sc, u_int mips_irq, u_int ivec) +{ + uint32_t oobsel; + + KASSERT(mips_irq < sc->bcm_mips.num_cpuirqs, ("invalid MIPS IRQ %u", + mips_irq)); + KASSERT(mips_irq < BCM_MIPS74K_NUM_INTR, ("unsupported MIPS IRQ %u", + mips_irq)); + KASSERT(ivec < BCMA_OOB_NUM_BUSLINES, ("invalid backplane ivec")); + + oobsel = bus_read_4(sc->mem, BCM_MIPS74K_INTR_SEL(mips_irq)); + oobsel |= BCM_MIPS74K_INTR_SEL_FLAG(ivec); + bus_write_4(sc->mem, BCM_MIPS74K_INTR_SEL(mips_irq), oobsel); +} + +/* our MIPS CPU interrupt filter */ +static int +bcm_mips74k_pic_intr(void *arg) +{ + struct bcm_mips74k_softc *sc; + struct bcm_mips_cpuirq *cpuirq; + struct bcm_mips_irqsrc *isrc_solo; + uint32_t oobsel, intr; + u_int i; + int error; + + cpuirq = arg; + sc = (struct bcm_mips74k_softc*)cpuirq->sc; + + /* Fetch current interrupt state */ + intr = bus_read_4(sc->mem, BCM_MIPS74K_INTR_STATUS); + + /* Fetch mask of interrupt vectors routed to this MIPS IRQ */ + KASSERT(cpuirq->mips_irq < BCM_MIPS74K_NUM_INTR, + ("invalid irq %u", cpuirq->mips_irq)); + + oobsel = bus_read_4(sc->mem, BCM_MIPS74K_INTR_SEL(cpuirq->mips_irq)); + + /* Ignore interrupts not routed to this MIPS IRQ */ + intr &= oobsel; + + /* Handle isrc_solo direct dispatch path */ + isrc_solo = cpuirq->isrc_solo; + if (isrc_solo != NULL) { + if (intr & BCM_MIPS_IVEC_MASK(isrc_solo)) { + error = intr_isrc_dispatch(&isrc_solo->isrc, + curthread->td_intr_frame); + if (error) { + device_printf(sc->dev, "Stray interrupt %u " + "detected\n", isrc_solo->ivec); + bcm_mips74k_pic_disable_intr(sc->dev, + &isrc_solo->isrc); + } + } + + intr &= ~(BCM_MIPS_IVEC_MASK(isrc_solo)); + if (intr == 0) + return (FILTER_HANDLED); + + /* Report and mask additional stray interrupts */ + while ((i = fls(intr)) != 0) { + i--; /* Get a 0-offset interrupt. */ + intr &= ~(1 << i); + + device_printf(sc->dev, "Stray interrupt %u " + "detected\n", i); + bcm_mips74k_mask_irq(sc, cpuirq->mips_irq, i); + } + + return (FILTER_HANDLED); + } + + /* Standard dispatch path */ + while ((i = fls(intr)) != 0) { + i--; /* Get a 0-offset interrupt. */ + intr &= ~(1 << i); + + KASSERT(i < nitems(sc->bcm_mips.isrcs), ("invalid ivec %u", i)); + + error = intr_isrc_dispatch(&sc->bcm_mips.isrcs[i].isrc, + curthread->td_intr_frame); + if (error) { + device_printf(sc->dev, "Stray interrupt %u detected\n", + i); + bcm_mips74k_mask_irq(sc, cpuirq->mips_irq, i); + continue; + } + } + + return (FILTER_HANDLED); +} + static device_method_t bcm_mips74k_methods[] = { /* Device interface */ - DEVMETHOD(device_probe, bcm_mips74k_probe), - DEVMETHOD(device_attach, bcm_mips74k_attach), - DEVMETHOD(device_detach, bcm_mips74k_detach), - + DEVMETHOD(device_probe, bcm_mips74k_probe), + DEVMETHOD(device_attach, bcm_mips74k_attach), + DEVMETHOD(device_detach, bcm_mips74k_detach), + + /* Interrupt controller interface */ + DEVMETHOD(pic_disable_intr, bcm_mips74k_pic_disable_intr), + DEVMETHOD(pic_enable_intr, bcm_mips74k_pic_enable_intr), + DEVMETHOD(pic_pre_ithread, bcm_mips74k_pic_pre_ithread), + DEVMETHOD(pic_post_ithread, bcm_mips74k_pic_post_ithread), + DEVMETHOD(pic_post_filter, bcm_mips74k_pic_post_filter), + DEVMETHOD_END }; static devclass_t bcm_mips_devclass; -DEFINE_CLASS_0(bcm_mips, bcm_mips74k_driver, bcm_mips74k_methods, sizeof(struct bcm_mips74k_softc)); -EARLY_DRIVER_MODULE(bcm_mips74k, bhnd, bcm_mips74k_driver, bcm_mips_devclass, 0, 0, BUS_PASS_CPU + BUS_PASS_ORDER_EARLY); +DEFINE_CLASS_1(bcm_mips, bcm_mips74k_driver, bcm_mips74k_methods, sizeof(struct bcm_mips_softc), bcm_mips_driver); +EARLY_DRIVER_MODULE(bcm_mips74k, bhnd, bcm_mips74k_driver, bcm_mips_devclass, 0, 0, BUS_PASS_INTERRUPT + BUS_PASS_ORDER_MIDDLE); +SYSINIT(cpu_init, SI_SUB_CPU, SI_ORDER_FIRST, bcm_mips74k_timer_init, NULL); MODULE_VERSION(bcm_mips74k, 1); MODULE_DEPEND(bcm_mips74k, bhnd, 1, 1, 1); Index: head/sys/mips/broadcom/bcm_mips74kreg.h =================================================================== --- head/sys/mips/broadcom/bcm_mips74kreg.h +++ head/sys/mips/broadcom/bcm_mips74kreg.h @@ -46,17 +46,23 @@ #define BCM_MIPS74K_INTR3_SEL 0x20 /**< IRQ3 OOBSEL mask */ #define BCM_MIPS74K_INTR4_SEL 0x24 /**< IRQ4 OOBSEL mask */ #define BCM_MIPS74K_INTR5_SEL 0x28 /**< IRQ5 OOBSEL mask */ +#define BCM_MIPS74K_NUM_INTR 6 /**< routable CPU interrupt count */ #define BCM_MIPS74K_INTR_SEL(_intr) \ (BCM_MIPS74K_INTR0_SEL + ((_intr) * 4)) +#define BCM_MIPS74K_INTR_SEL_FLAG(_i) (1<<_i) +#define BCM_MIPS74K_TIMER_IVEC 31 /**< MIPS timer's bus interrupt vector */ + #define BCM_MIPS74K_NMI_MASK 0x2C /**< nmi mask */ #define BCM_MIPS74K_GPIO_SEL 0x40 /**< gpio select */ #define BCM_MIPS74K_GPIO_OUT 0x44 /**< gpio output enable */ #define BCM_MIPS74K_GPIO_EN 0x48 /**< gpio enable */ +/** The MIPS timer interrupt IRQ assignment */ +#define BCM_MIPS74K_GET_TIMER_IRQ() \ + ((mips_rd_intctl() & MIPS_INTCTL_IPTI_MASK) >> MIPS_INTCTL_IPTI_SHIFT) -#define BCM_MIPS74K_TIMER_IVEC 31 /**< MIPS timer OOBSEL value */ #endif /* _MIPS_BROADCOM_MIPS74KREG_H_ */ Index: head/sys/mips/broadcom/bcm_mipsvar.h =================================================================== --- head/sys/mips/broadcom/bcm_mipsvar.h +++ head/sys/mips/broadcom/bcm_mipsvar.h @@ -0,0 +1,114 @@ +/*- + * Copyright (c) 2017 The FreeBSD Foundation + * All rights reserved. + * + * This software was 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: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer, + * without modification. + * 2. Redistributions in binary form must reproduce at minimum a disclaimer + * similar to the "NO WARRANTY" disclaimer below ("Disclaimer") and any + * redistribution must be conditioned upon including a substantially + * similar Disclaimer requirement for further binary redistribution. + * + * NO WARRANTY + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF NONINFRINGEMENT, MERCHANTIBILITY + * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR 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 DAMAGES. + * + * $FreeBSD$ + */ + +#ifndef _MIPS_BROADCOM_BCM_MIPSVAR_H_ +#define _MIPS_BROADCOM_BCM_MIPSVAR_H_ + +#include +#include +#include +#include + +#include + +DECLARE_CLASS(bcm_mips_driver); + +struct bcm_mips_irqsrc; +struct bcm_mips_softc; + +#define BCM_MIPS_NINTR 32 /**< maximum number of addressable backplane interrupt vectors */ +#define BCM_MIPS_IRQ_SHARED 0 /**< MIPS CPU IRQ reserved for shared interrupt handling */ +#define INTR_MAP_DATA_BCM_MIPS INTR_MAP_DATA_PLAT_2 /**< Broadcom MIPS PIC interrupt map data type */ + + +int bcm_mips_attach(device_t dev, u_int num_cpuirqs, u_int timer_irq, + driver_filter_t filter); +int bcm_mips_detach(device_t dev); + +/** + * Broadcom MIPS PIC interrupt map data. + */ +struct bcm_mips_intr_map_data { + struct intr_map_data mdata; + u_int ivec; /**< bus interrupt vector */ +}; + +/** + * Nested MIPS CPU interrupt handler state. + */ +struct bcm_mips_cpuirq { + struct bcm_mips_softc *sc; /**< driver instance state, or NULL if uninitialized. */ + u_int mips_irq; /**< mips hardware interrupt number (relative to NSOFT_IRQ) */ + int irq_rid; /**< mips IRQ resource id, or -1 if this entry is unavailable */ + struct resource *irq_res; /**< mips interrupt resource */ + void *irq_cookie; /**< mips interrupt handler cookie, or NULL */ + struct bcm_mips_irqsrc *isrc_solo; /**< solo isrc assigned to this interrupt, or NULL */ + u_int refs; /**< isrc consumer refcount */ +}; + +/** + * Broadcom MIPS PIC interrupt source definition. + */ +struct bcm_mips_irqsrc { + struct intr_irqsrc isrc; + u_int ivec; /**< bus interrupt vector */ + u_int refs; /**< active reference count */ + struct bcm_mips_cpuirq *cpuirq; /**< assigned MIPS HW IRQ, or NULL if no assignment */ +}; + +/** + * bcm_mips driver instance state. Must be first member of all subclass + * softc structures. + */ +struct bcm_mips_softc { + device_t dev; + struct bcm_mips_cpuirq cpuirqs[NREAL_IRQS]; /**< nested CPU IRQ handlers */ + u_int num_cpuirqs; /**< number of nested CPU IRQ handlers */ + u_int timer_irq; /**< CPU timer IRQ */ + struct bcm_mips_irqsrc isrcs[BCM_MIPS_NINTR]; + struct mtx mtx; +}; + + +#define BCM_MIPS_IVEC_MASK(_isrc) (1 << ((_isrc)->ivec)) + +#define BCM_MIPS_LOCK_INIT(sc) \ + mtx_init(&(sc)->mtx, device_get_nameunit((sc)->dev), \ + "bhnd mips driver lock", MTX_DEF) +#define BCM_MIPS_LOCK(sc) mtx_lock(&(sc)->mtx) +#define BCM_MIPS_UNLOCK(sc) mtx_unlock(&(sc)->mtx) +#define BCM_MIPS_LOCK_ASSERT(sc, what) mtx_assert(&(sc)->mtx, what) +#define BCM_MIPS_LOCK_DESTROY(sc) mtx_destroy(&(sc)->mtx) + +#endif /* _MIPS_BROADCOM_BCM_MIPSVAR_H_ */ Index: head/sys/mips/broadcom/bcma_nexus.c =================================================================== --- head/sys/mips/broadcom/bcma_nexus.c +++ head/sys/mips/broadcom/bcma_nexus.c @@ -45,7 +45,9 @@ #include #include +#include +#include "bcm_mipsvar.h" #include "bcm_machdep.h" #include "bhnd_nexusvar.h" @@ -56,6 +58,9 @@ static int bcma_nexus_attach(device_t); static int bcma_nexus_probe(device_t); + +_Static_assert(BCMA_OOB_NUM_BUSLINES == BCM_MIPS_NINTR, "BCMA incompatible " + "with generic NINTR"); static int bcma_nexus_probe(device_t dev) Index: head/sys/mips/broadcom/bhnd_nexus.c =================================================================== --- head/sys/mips/broadcom/bhnd_nexus.c +++ head/sys/mips/broadcom/bhnd_nexus.c @@ -44,6 +44,7 @@ #include #include #include +#include #include #include #include @@ -55,6 +56,7 @@ #include #include "bcm_machdep.h" +#include "bcm_mipsvar.h" #include "bhnd_nexusvar.h" @@ -149,28 +151,47 @@ } /** - * Default bhnd_nexus implementation of BHND_BUS_GET_INTR_COUNT(). + * Default bhnd_nexus implementation of BHND_BUS_MAP_INTR(). */ static int -bhnd_nexus_get_intr_count(device_t dev, device_t child) +bhnd_nexus_map_intr(device_t dev, device_t child, u_int intr, rman_res_t *irq) { - // TODO: arch-specific interrupt handling. + struct bcm_mips_intr_map_data *imd; + u_int ivec; + uintptr_t xref; + int error; + + /* Fetch the backplane interrupt vector */ + if ((error = bhnd_get_intr_ivec(child, intr, &ivec))) { + device_printf(dev, "error fetching ivec for intr %u: %d\n", + intr, error); + return (error); + } + + /* Determine our interrupt domain */ + xref = BHND_BUS_GET_INTR_DOMAIN(dev, child, false); + KASSERT(xref != 0, ("missing interrupt domain")); + + /* Allocate our map data */ + imd = (struct bcm_mips_intr_map_data *)intr_alloc_map_data( + INTR_MAP_DATA_BCM_MIPS, sizeof(*imd), M_WAITOK | M_ZERO); + imd->ivec = ivec; + + /* Map the IRQ */ + *irq = intr_map_irq(NULL, xref, &imd->mdata); return (0); } /** - * Default bhnd_nexus implementation of BHND_BUS_ASSIGN_INTR(). + * Default bhnd_nexus implementation of BHND_BUS_UNMAP_INTR(). */ -static int -bhnd_nexus_assign_intr(device_t dev, device_t child, int rid) +static void +bhnd_nexus_unmap_intr(device_t dev, device_t child, rman_res_t irq) { - uint32_t ivec; - int error; + if (irq > UINT_MAX) + panic("invalid irq: %ju", (uintmax_t)irq); - if ((error = bhnd_get_core_ivec(child, rid, &ivec))) - return (error); - - return (bus_set_resource(child, SYS_RES_IRQ, rid, ivec, 1)); + intr_unmap_irq(irq); } static device_method_t bhnd_nexus_methods[] = { @@ -185,8 +206,9 @@ DEVMETHOD(bhnd_bus_is_hw_disabled, bhnd_nexus_is_hw_disabled), DEVMETHOD(bhnd_bus_get_attach_type, bhnd_nexus_get_attach_type), DEVMETHOD(bhnd_bus_get_chipid, bhnd_nexus_get_chipid), - DEVMETHOD(bhnd_bus_get_intr_count, bhnd_nexus_get_intr_count), - DEVMETHOD(bhnd_bus_assign_intr, bhnd_nexus_assign_intr), + DEVMETHOD(bhnd_bus_get_intr_domain, bhnd_bus_generic_get_intr_domain), + DEVMETHOD(bhnd_bus_map_intr, bhnd_nexus_map_intr), + DEVMETHOD(bhnd_bus_unmap_intr, bhnd_nexus_unmap_intr), DEVMETHOD_END }; Index: head/sys/mips/broadcom/files.broadcom =================================================================== --- head/sys/mips/broadcom/files.broadcom +++ head/sys/mips/broadcom/files.broadcom @@ -7,6 +7,8 @@ mips/broadcom/bcm_machdep.c standard mips/broadcom/bcm_bmips.c optional siba_nexus siba mips/broadcom/bcm_mips74k.c optional bcma_nexus bcma +mips/broadcom/bcm_mips.c optional siba_nexus siba | \ + bcma_nexus bcma mips/broadcom/bcm_nvram_cfe.c optional bhnd siba_nexus cfe | \ bhnd bcma_nexus cfe mips/broadcom/bcm_pmu.c standard Index: head/sys/mips/broadcom/siba_nexus.c =================================================================== --- head/sys/mips/broadcom/siba_nexus.c +++ head/sys/mips/broadcom/siba_nexus.c @@ -39,9 +39,11 @@ #include +#include #include #include "bcm_machdep.h" +#include "bcm_mipsvar.h" #include "bhnd_nexusvar.h" @@ -50,6 +52,9 @@ * * Derived from Bruce M. Simpson' original siba(4) driver. */ + +_Static_assert(SIBA_MAX_INTR == BCM_MIPS_NINTR, "SIBA incompatible with " + "generic NINTR"); static int siba_nexus_probe(device_t dev) Index: head/sys/mips/include/intr.h =================================================================== --- head/sys/mips/include/intr.h +++ head/sys/mips/include/intr.h @@ -59,6 +59,10 @@ #define MIPS_PIC_XREF 1 /**< unique xref */ #endif +#define NHARD_IRQS 6 +#define NSOFT_IRQS 2 +#define NREAL_IRQS (NHARD_IRQS + NSOFT_IRQS) + #define INTR_IRQ_NSPC_SWI 4 /* MIPS32 PIC APIs */ Index: head/sys/mips/mips/mips_pic.c =================================================================== --- head/sys/mips/mips/mips_pic.c +++ head/sys/mips/mips/mips_pic.c @@ -70,10 +70,6 @@ #include "pic_if.h" -#define NHARD_IRQS 6 -#define NSOFT_IRQS 2 -#define NREAL_IRQS (NHARD_IRQS + NSOFT_IRQS) - struct mips_pic_softc; static int mips_pic_intr(void *);