Index: head/sys/dev/bhnd/bcma/bcma.c =================================================================== --- head/sys/dev/bhnd/bcma/bcma.c (revision 326078) +++ head/sys/dev/bhnd/bcma/bcma.c (revision 326079) @@ -1,804 +1,771 @@ /*- - * 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: * 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. */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include "bcma_dmp.h" #include "bcma_eromreg.h" #include "bcma_eromvar.h" #include "bcmavar.h" /* RID used when allocating EROM table */ #define BCMA_EROM_RID 0 static bhnd_erom_class_t * bcma_get_erom_class(driver_t *driver) { return (&bcma_erom_parser); } int bcma_probe(device_t dev) { device_set_desc(dev, "BCMA BHND bus"); return (BUS_PROBE_DEFAULT); } /** * Default bcma(4) bus driver implementation of DEVICE_ATTACH(). * * This implementation initializes internal bcma(4) state and performs * bus enumeration, and must be called by subclassing drivers in * DEVICE_ATTACH() before any other bus methods. */ int bcma_attach(device_t dev) { int error; /* Enumerate children */ if ((error = bcma_add_children(dev))) { device_delete_children(dev); return (error); } return (0); } int bcma_detach(device_t dev) { return (bhnd_generic_detach(dev)); } static device_t bcma_add_child(device_t dev, u_int order, const char *name, int unit) { struct bcma_devinfo *dinfo; device_t child; child = device_add_child_ordered(dev, order, name, unit); if (child == NULL) return (NULL); if ((dinfo = bcma_alloc_dinfo(dev)) == NULL) { device_delete_child(dev, child); return (NULL); } device_set_ivars(child, dinfo); return (child); } static void bcma_child_deleted(device_t dev, device_t child) { struct bhnd_softc *sc; struct bcma_devinfo *dinfo; sc = device_get_softc(dev); /* Call required bhnd(4) implementation */ bhnd_generic_child_deleted(dev, child); /* 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); } static int bcma_read_ivar(device_t dev, device_t child, int index, uintptr_t *result) { const struct bcma_devinfo *dinfo; const struct bhnd_core_info *ci; dinfo = device_get_ivars(child); ci = &dinfo->corecfg->core_info; switch (index) { case BHND_IVAR_VENDOR: *result = ci->vendor; return (0); case BHND_IVAR_DEVICE: *result = ci->device; return (0); case BHND_IVAR_HWREV: *result = ci->hwrev; return (0); case BHND_IVAR_DEVICE_CLASS: *result = bhnd_core_class(ci); return (0); case BHND_IVAR_VENDOR_NAME: *result = (uintptr_t) bhnd_vendor_name(ci->vendor); return (0); case BHND_IVAR_DEVICE_NAME: *result = (uintptr_t) bhnd_core_name(ci); return (0); case BHND_IVAR_CORE_INDEX: *result = ci->core_idx; return (0); case BHND_IVAR_CORE_UNIT: *result = ci->unit; return (0); case BHND_IVAR_PMU_INFO: *result = (uintptr_t) dinfo->pmu_info; return (0); default: return (ENOENT); } } static int bcma_write_ivar(device_t dev, device_t child, int index, uintptr_t value) { struct bcma_devinfo *dinfo; dinfo = device_get_ivars(child); switch (index) { case BHND_IVAR_VENDOR: case BHND_IVAR_DEVICE: case BHND_IVAR_HWREV: case BHND_IVAR_DEVICE_CLASS: case BHND_IVAR_VENDOR_NAME: case BHND_IVAR_DEVICE_NAME: case BHND_IVAR_CORE_INDEX: case BHND_IVAR_CORE_UNIT: return (EINVAL); case BHND_IVAR_PMU_INFO: dinfo->pmu_info = (struct bhnd_core_pmu_info *) value; return (0); default: return (ENOENT); } } static struct resource_list * bcma_get_resource_list(device_t dev, device_t child) { struct bcma_devinfo *dinfo = device_get_ivars(child); return (&dinfo->resources); } static int bcma_read_iost(device_t dev, device_t child, uint16_t *iost) { uint32_t value; int error; if ((error = bhnd_read_config(child, BCMA_DMP_IOSTATUS, &value, 4))) return (error); /* Return only the bottom 16 bits */ *iost = (value & BCMA_DMP_IOST_MASK); return (0); } static int bcma_read_ioctl(device_t dev, device_t child, uint16_t *ioctl) { uint32_t value; int error; if ((error = bhnd_read_config(child, BCMA_DMP_IOCTRL, &value, 4))) return (error); /* Return only the bottom 16 bits */ *ioctl = (value & BCMA_DMP_IOCTRL_MASK); return (0); } static int bcma_write_ioctl(device_t dev, device_t child, uint16_t value, uint16_t mask) { struct bcma_devinfo *dinfo; struct bhnd_resource *r; uint32_t ioctl; if (device_get_parent(child) != dev) return (EINVAL); dinfo = device_get_ivars(child); if ((r = dinfo->res_agent) == NULL) return (ENODEV); /* Write new value */ ioctl = bhnd_bus_read_4(r, BCMA_DMP_IOCTRL); ioctl &= ~(BCMA_DMP_IOCTRL_MASK & mask); ioctl |= (value & mask); bhnd_bus_write_4(r, BCMA_DMP_IOCTRL, ioctl); /* Perform read-back and wait for completion */ bhnd_bus_read_4(r, BCMA_DMP_IOCTRL); DELAY(10); return (0); } static bool bcma_is_hw_suspended(device_t dev, device_t child) { uint32_t rst; uint16_t ioctl; int error; /* Is core held in RESET? */ error = bhnd_read_config(child, BCMA_DMP_RESETCTRL, &rst, 4); if (error) { device_printf(child, "error reading HW reset state: %d\n", error); return (true); } if (rst & BCMA_DMP_RC_RESET) return (true); /* Is core clocked? */ error = bhnd_read_ioctl(child, &ioctl); if (error) { device_printf(child, "error reading HW ioctl register: %d\n", error); return (true); } if (!(ioctl & BHND_IOCTL_CLK_EN)) return (true); return (false); } static int bcma_reset_hw(device_t dev, device_t child, uint16_t ioctl) { struct bcma_devinfo *dinfo; struct bhnd_core_pmu_info *pm; struct bhnd_resource *r; int error; if (device_get_parent(child) != dev) return (EINVAL); dinfo = device_get_ivars(child); pm = dinfo->pmu_info; /* We require exclusive control over BHND_IOCTL_CLK_EN and * BHND_IOCTL_CLK_FORCE. */ if (ioctl & (BHND_IOCTL_CLK_EN | BHND_IOCTL_CLK_FORCE)) return (EINVAL); /* Can't suspend the core without access to the agent registers */ if ((r = dinfo->res_agent) == NULL) return (ENODEV); /* Place core into known RESET state */ if ((error = BHND_BUS_SUSPEND_HW(dev, child))) return (error); /* * Leaving the core in reset: * - Set the caller's IOCTL flags * - Enable clocks * - Force clock distribution to ensure propagation throughout the * core. */ error = bhnd_write_ioctl(child, ioctl | BHND_IOCTL_CLK_EN | BHND_IOCTL_CLK_FORCE, UINT16_MAX); if (error) return (error); /* Bring the core out of reset */ if ((error = bcma_dmp_write_reset(child, dinfo, 0x0))) return (error); /* Disable forced clock gating (leaving clock enabled) */ error = bhnd_write_ioctl(child, 0x0, BHND_IOCTL_CLK_FORCE); if (error) return (error); return (0); } static int bcma_suspend_hw(device_t dev, device_t child) { struct bcma_devinfo *dinfo; struct bhnd_core_pmu_info *pm; struct bhnd_resource *r; uint32_t rst; int error; if (device_get_parent(child) != dev) return (EINVAL); dinfo = device_get_ivars(child); pm = dinfo->pmu_info; /* Can't suspend the core without access to the agent registers */ if ((r = dinfo->res_agent) == NULL) return (ENODEV); /* Wait for any pending reset operations to clear */ if ((error = bcma_dmp_wait_reset(child, dinfo))) return (error); /* Already in reset? */ rst = bhnd_bus_read_4(r, BCMA_DMP_RESETCTRL); if (rst & BCMA_DMP_RC_RESET) return (0); /* Put core into reset */ if ((error = bcma_dmp_write_reset(child, dinfo, BCMA_DMP_RC_RESET))) return (error); /* Clear core flags */ if ((error = bhnd_write_ioctl(child, 0x0, UINT16_MAX))) return (error); /* Inform PMU that all outstanding request state should be discarded */ if (pm != NULL) { if ((error = BHND_PMU_CORE_RELEASE(pm->pm_pmu, pm))) return (error); } return (0); } static int bcma_read_config(device_t dev, device_t child, bus_size_t offset, void *value, u_int width) { struct bcma_devinfo *dinfo; struct bhnd_resource *r; /* Must be a directly attached child core */ if (device_get_parent(child) != dev) return (EINVAL); /* Fetch the agent registers */ dinfo = device_get_ivars(child); if ((r = dinfo->res_agent) == NULL) return (ENODEV); /* Verify bounds */ if (offset > rman_get_size(r->res)) return (EFAULT); if (rman_get_size(r->res) - offset < width) return (EFAULT); switch (width) { case 1: *((uint8_t *)value) = bhnd_bus_read_1(r, offset); return (0); case 2: *((uint16_t *)value) = bhnd_bus_read_2(r, offset); return (0); case 4: *((uint32_t *)value) = bhnd_bus_read_4(r, offset); return (0); default: return (EINVAL); } } static int bcma_write_config(device_t dev, device_t child, bus_size_t offset, const void *value, u_int width) { struct bcma_devinfo *dinfo; struct bhnd_resource *r; /* Must be a directly attached child core */ if (device_get_parent(child) != dev) return (EINVAL); /* Fetch the agent registers */ dinfo = device_get_ivars(child); if ((r = dinfo->res_agent) == NULL) return (ENODEV); /* Verify bounds */ if (offset > rman_get_size(r->res)) return (EFAULT); if (rman_get_size(r->res) - offset < width) return (EFAULT); switch (width) { case 1: bhnd_bus_write_1(r, offset, *(const uint8_t *)value); return (0); case 2: bhnd_bus_write_2(r, offset, *(const uint16_t *)value); return (0); case 4: bhnd_bus_write_4(r, offset, *(const uint32_t *)value); return (0); default: return (EINVAL); } } static u_int bcma_get_port_count(device_t dev, device_t child, bhnd_port_type type) { struct bcma_devinfo *dinfo; /* delegate non-bus-attached devices to our parent */ if (device_get_parent(child) != dev) return (BHND_BUS_GET_PORT_COUNT(device_get_parent(dev), child, type)); dinfo = device_get_ivars(child); switch (type) { case BHND_PORT_DEVICE: return (dinfo->corecfg->num_dev_ports); case BHND_PORT_BRIDGE: return (dinfo->corecfg->num_bridge_ports); case BHND_PORT_AGENT: return (dinfo->corecfg->num_wrapper_ports); default: device_printf(dev, "%s: unknown type (%d)\n", __func__, type); return (0); } } static u_int bcma_get_region_count(device_t dev, device_t child, bhnd_port_type type, u_int port_num) { struct bcma_devinfo *dinfo; struct bcma_sport_list *ports; struct bcma_sport *port; /* delegate non-bus-attached devices to our parent */ if (device_get_parent(child) != dev) return (BHND_BUS_GET_REGION_COUNT(device_get_parent(dev), child, type, port_num)); dinfo = device_get_ivars(child); ports = bcma_corecfg_get_port_list(dinfo->corecfg, type); STAILQ_FOREACH(port, ports, sp_link) { if (port->sp_num == port_num) return (port->sp_num_maps); } /* not found */ return (0); } static int bcma_get_port_rid(device_t dev, device_t child, bhnd_port_type port_type, u_int port_num, u_int region_num) { struct bcma_devinfo *dinfo; struct bcma_map *map; struct bcma_sport_list *ports; struct bcma_sport *port; dinfo = device_get_ivars(child); ports = bcma_corecfg_get_port_list(dinfo->corecfg, port_type); STAILQ_FOREACH(port, ports, sp_link) { if (port->sp_num != port_num) continue; STAILQ_FOREACH(map, &port->sp_maps, m_link) if (map->m_region_num == region_num) return map->m_rid; } return -1; } static int bcma_decode_port_rid(device_t dev, device_t child, int type, int rid, bhnd_port_type *port_type, u_int *port_num, u_int *region_num) { struct bcma_devinfo *dinfo; struct bcma_map *map; struct bcma_sport_list *ports; struct bcma_sport *port; dinfo = device_get_ivars(child); /* Ports are always memory mapped */ if (type != SYS_RES_MEMORY) return (EINVAL); /* Starting with the most likely device list, search all three port * lists */ bhnd_port_type types[] = { BHND_PORT_DEVICE, BHND_PORT_AGENT, BHND_PORT_BRIDGE }; for (int i = 0; i < nitems(types); i++) { ports = bcma_corecfg_get_port_list(dinfo->corecfg, types[i]); STAILQ_FOREACH(port, ports, sp_link) { STAILQ_FOREACH(map, &port->sp_maps, m_link) { if (map->m_rid != rid) continue; *port_type = port->sp_type; *port_num = port->sp_num; *region_num = map->m_region_num; return (0); } } } return (ENOENT); } static int bcma_get_region_addr(device_t dev, device_t child, bhnd_port_type port_type, u_int port_num, u_int region_num, bhnd_addr_t *addr, bhnd_size_t *size) { struct bcma_devinfo *dinfo; struct bcma_map *map; struct bcma_sport_list *ports; struct bcma_sport *port; dinfo = device_get_ivars(child); ports = bcma_corecfg_get_port_list(dinfo->corecfg, port_type); /* Search the port list */ STAILQ_FOREACH(port, ports, sp_link) { if (port->sp_num != port_num) continue; STAILQ_FOREACH(map, &port->sp_maps, m_link) { if (map->m_region_num != region_num) continue; /* Found! */ *addr = map->m_base; *size = map->m_size; return (0); } } return (ENOENT); } /** * 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); } /** * Scan the device enumeration ROM table, adding all valid discovered cores to * the bus. * * @param bus The bcma bus. */ int bcma_add_children(device_t bus) { bhnd_erom_t *erom; struct bcma_erom *bcma_erom; struct bhnd_erom_io *eio; const struct bhnd_chipid *cid; struct bcma_corecfg *corecfg; struct bcma_devinfo *dinfo; device_t child; int error; cid = BHND_BUS_GET_CHIPID(bus, bus); corecfg = NULL; /* Allocate our EROM parser */ eio = bhnd_erom_iores_new(bus, BCMA_EROM_RID); erom = bhnd_erom_alloc(&bcma_erom_parser, cid, eio); if (erom == NULL) { bhnd_erom_io_fini(eio); return (ENODEV); } /* 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) { error = ENXIO; goto cleanup; } /* 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)) device_disable(child); /* Issue bus callback for fully initialized child. */ BHND_BUS_CHILD_ADDED(bus, child); } /* EOF while parsing cores is expected */ if (error == ENOENT) error = 0; cleanup: bhnd_erom_free(erom); if (corecfg != NULL) bcma_free_corecfg(corecfg); if (error) device_delete_children(bus); return (error); } static device_method_t bcma_methods[] = { /* Device interface */ DEVMETHOD(device_probe, bcma_probe), DEVMETHOD(device_attach, bcma_attach), DEVMETHOD(device_detach, bcma_detach), /* Bus interface */ DEVMETHOD(bus_add_child, bcma_add_child), DEVMETHOD(bus_child_deleted, bcma_child_deleted), DEVMETHOD(bus_read_ivar, bcma_read_ivar), DEVMETHOD(bus_write_ivar, bcma_write_ivar), DEVMETHOD(bus_get_resource_list, bcma_get_resource_list), /* BHND interface */ DEVMETHOD(bhnd_bus_get_erom_class, bcma_get_erom_class), DEVMETHOD(bhnd_bus_read_ioctl, bcma_read_ioctl), DEVMETHOD(bhnd_bus_write_ioctl, bcma_write_ioctl), DEVMETHOD(bhnd_bus_read_iost, bcma_read_iost), DEVMETHOD(bhnd_bus_is_hw_suspended, bcma_is_hw_suspended), DEVMETHOD(bhnd_bus_reset_hw, bcma_reset_hw), DEVMETHOD(bhnd_bus_suspend_hw, bcma_suspend_hw), DEVMETHOD(bhnd_bus_read_config, bcma_read_config), DEVMETHOD(bhnd_bus_write_config, bcma_write_config), DEVMETHOD(bhnd_bus_get_port_count, bcma_get_port_count), DEVMETHOD(bhnd_bus_get_region_count, bcma_get_region_count), DEVMETHOD(bhnd_bus_get_port_rid, bcma_get_port_rid), 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 }; DEFINE_CLASS_1(bhnd, bcma_driver, bcma_methods, sizeof(struct bcma_softc), bhnd_driver); MODULE_VERSION(bcma, 1); MODULE_DEPEND(bcma, bhnd, 1, 1, 1); Index: head/sys/dev/bhnd/bcma/bcma_subr.c =================================================================== --- head/sys/dev/bhnd/bcma/bcma_subr.c (revision 326078) +++ head/sys/dev/bhnd/bcma/bcma_subr.c (revision 326079) @@ -1,431 +1,605 @@ /*- - * 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: * 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. */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include "bcma_dmp.h" #include "bcmavar.h" /* Return the resource ID for a device's agent register allocation */ #define BCMA_AGENT_RID(_dinfo) \ (BCMA_AGENT_RID_BASE + BCMA_DINFO_COREIDX(_dinfo)) /** * Allocate and initialize new core config structure. * * @param core_index Core index on the bus. * @param core_unit Core unit number. * @param vendor Core designer. * @param device Core identifier (e.g. part number). * @param hwrev Core revision. */ struct bcma_corecfg * bcma_alloc_corecfg(u_int core_index, int core_unit, uint16_t vendor, uint16_t device, uint8_t hwrev) { struct bcma_corecfg *cfg; cfg = malloc(sizeof(*cfg), M_BHND, M_NOWAIT); if (cfg == NULL) return NULL; cfg->core_info = (struct bhnd_core_info) { .vendor = vendor, .device = device, .hwrev = hwrev, .core_idx = core_index, .unit = core_unit }; STAILQ_INIT(&cfg->master_ports); cfg->num_master_ports = 0; STAILQ_INIT(&cfg->dev_ports); cfg->num_dev_ports = 0; STAILQ_INIT(&cfg->bridge_ports); cfg->num_bridge_ports = 0; STAILQ_INIT(&cfg->wrapper_ports); cfg->num_wrapper_ports = 0; return (cfg); } /** * Deallocate the given core config and any associated resources. * * @param corecfg Core info to be deallocated. */ void bcma_free_corecfg(struct bcma_corecfg *corecfg) { struct bcma_mport *mport, *mnext; struct bcma_sport *sport, *snext; STAILQ_FOREACH_SAFE(mport, &corecfg->master_ports, mp_link, mnext) { free(mport, M_BHND); } STAILQ_FOREACH_SAFE(sport, &corecfg->dev_ports, sp_link, snext) { bcma_free_sport(sport); } STAILQ_FOREACH_SAFE(sport, &corecfg->bridge_ports, sp_link, snext) { bcma_free_sport(sport); } STAILQ_FOREACH_SAFE(sport, &corecfg->wrapper_ports, sp_link, snext) { bcma_free_sport(sport); } free(corecfg, M_BHND); } /** * Return the @p cfg port list for @p type. * * @param cfg The core configuration. * @param type The requested port type. */ struct bcma_sport_list * bcma_corecfg_get_port_list(struct bcma_corecfg *cfg, bhnd_port_type type) { switch (type) { case BHND_PORT_DEVICE: return (&cfg->dev_ports); break; case BHND_PORT_BRIDGE: return (&cfg->bridge_ports); break; case BHND_PORT_AGENT: return (&cfg->wrapper_ports); break; default: return (NULL); } } /** * Populate the resource list and bcma_map RIDs using the maps defined on * @p ports. * * @param bus The requesting bus device. * @param dinfo The device info instance to be initialized. * @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; struct bcma_sport *port; bhnd_addr_t end; STAILQ_FOREACH(port, ports, sp_link) { STAILQ_FOREACH(map, &port->sp_maps, m_link) { /* * Create the corresponding device resource list entry. * * We necessarily skip registration if the region's * device memory range is not representable via * rman_res_t. * * When rman_res_t is migrated to uintmax_t, any * range should be representable. */ end = map->m_base + map->m_size; if (map->m_base <= RM_MAX_END && end <= RM_MAX_END) { map->m_rid = resource_list_add_next( &dinfo->resources, SYS_RES_MEMORY, map->m_base, end, map->m_size); } else if (bootverbose) { device_printf(bus, "core%u %s%u.%u: region %llx-%llx extends " "beyond supported addressable range\n", dinfo->corecfg->core_info.core_idx, bhnd_port_type_name(port->sp_type), port->sp_num, map->m_region_num, (unsigned long long) map->m_base, (unsigned long long) end); } } } } + /** + * 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. * * @retval NULL if allocation failed. */ struct bcma_devinfo * bcma_alloc_dinfo(device_t bus) { struct bcma_devinfo *dinfo; dinfo = malloc(sizeof(struct bcma_devinfo), M_BHND, M_NOWAIT|M_ZERO); if (dinfo == NULL) return (NULL); dinfo->corecfg = NULL; dinfo->res_agent = NULL; dinfo->rid_agent = -1; + STAILQ_INIT(&dinfo->intrs); + dinfo->num_intrs = 0; + resource_list_init(&dinfo->resources); return (dinfo); } /** * Initialize a device info structure previously allocated via * bcma_alloc_dinfo, assuming ownership of the provided core * 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. * * @retval 0 success * @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 */ dinfo->corecfg = corecfg; /* 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. * * @param bus The requesting bus device. * @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) bcma_free_corecfg(dinfo->corecfg); /* Release agent resource, if any */ if (dinfo->res_agent != NULL) { bhnd_release_resource(bus, SYS_RES_MEMORY, dinfo->rid_agent, 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. * * @param port_num Per-core port number. * @param port_type Port type. */ struct bcma_sport * bcma_alloc_sport(bcma_pid_t port_num, bhnd_port_type port_type) { struct bcma_sport *sport; sport = malloc(sizeof(struct bcma_sport), M_BHND, M_NOWAIT); if (sport == NULL) return NULL; sport->sp_num = port_num; sport->sp_type = port_type; sport->sp_num_maps = 0; STAILQ_INIT(&sport->sp_maps); return sport; } /** * Deallocate all resources associated with the given port descriptor. * * @param sport Port descriptor to be deallocated. */ void bcma_free_sport(struct bcma_sport *sport) { struct bcma_map *map, *mapnext; STAILQ_FOREACH_SAFE(map, &sport->sp_maps, m_link, mapnext) { free(map, M_BHND); } free(sport, M_BHND); } /** * Given a bcma(4) child's device info, spin waiting for the device's DMP * resetstatus register to clear. * * @param child The bcma(4) child device. * @param dinfo The @p child device info. * * @retval 0 success * @retval ENODEV if @p dinfo does not map an agent register resource. * @retval ETIMEDOUT if timeout occurs */ int bcma_dmp_wait_reset(device_t child, struct bcma_devinfo *dinfo) { uint32_t rst; if (dinfo->res_agent == NULL) return (ENODEV); /* 300us should be long enough, but there are references to this * requiring up to 10ms when performing reset of an 80211 core * after a MAC PSM microcode watchdog event. */ for (int i = 0; i < 10000; i += 10) { rst = bhnd_bus_read_4(dinfo->res_agent, BCMA_DMP_RESETSTATUS); if (rst == 0) return (0); DELAY(10); } device_printf(child, "BCMA_DMP_RESETSTATUS timeout\n"); return (ETIMEDOUT); } /** * Set the bcma(4) child's DMP resetctrl register value, and then wait * for all backplane operations to complete. * * @param child The bcma(4) child device. * @param dinfo The @p child device info. * @param value The new ioctrl value to set. * * @retval 0 success * @retval ENODEV if @p dinfo does not map an agent register resource. * @retval ETIMEDOUT if timeout occurs waiting for reset completion */ int bcma_dmp_write_reset(device_t child, struct bcma_devinfo *dinfo, uint32_t value) { if (dinfo->res_agent == NULL) return (ENODEV); bhnd_bus_write_4(dinfo->res_agent, BCMA_DMP_RESETCTRL, value); bhnd_bus_read_4(dinfo->res_agent, BCMA_DMP_RESETCTRL); /* read-back */ DELAY(10); return (bcma_dmp_wait_reset(child, dinfo)); } Index: head/sys/dev/bhnd/bcma/bcmavar.h =================================================================== --- head/sys/dev/bhnd/bcma/bcmavar.h (revision 326078) +++ head/sys/dev/bhnd/bcma/bcmavar.h (revision 326079) @@ -1,174 +1,197 @@ /*- - * 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: * 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 _BCMA_BCMAVAR_H_ #define _BCMA_BCMAVAR_H_ #include #include #include #include #include #include "bcma.h" /* * Internal definitions shared by bcma(4) driver implementations. */ /** Base resource ID for per-core agent register allocations */ #define BCMA_AGENT_RID_BASE 100 /** * Return the device's core index. * * @param _dinfo The bcma_devinfo instance to query. */ #define BCMA_DINFO_COREIDX(_dinfo) \ ((_dinfo)->corecfg->core_info.core_idx) /** BCMA port identifier. */ typedef u_int bcma_pid_t; #define BCMA_PID_MAX UINT_MAX /**< Maximum bcma_pid_t value */ /** BCMA per-port region map identifier. */ typedef u_int bcma_rmid_t; #define BCMA_RMID_MAX UINT_MAX /**< Maximum bcma_rmid_t value */ struct bcma_devinfo; struct bcma_corecfg; +struct bcma_intr; struct bcma_map; struct bcma_mport; struct bcma_sport; 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); struct bcma_sport_list *bcma_corecfg_get_port_list(struct bcma_corecfg *cfg, 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); int bcma_dmp_wait_reset(device_t child, struct bcma_devinfo *dinfo); int bcma_dmp_write_reset(device_t child, struct bcma_devinfo *dinfo, uint32_t value); /** BCMA master port descriptor */ struct bcma_mport { bcma_pid_t mp_num; /**< AXI port identifier (bus-unique) */ bcma_pid_t mp_vid; /**< AXI master virtual ID (core-unique) */ STAILQ_ENTRY(bcma_mport) mp_link; }; /** BCMA memory region descriptor */ struct bcma_map { bcma_rmid_t m_region_num; /**< region identifier (port-unique). */ bhnd_addr_t m_base; /**< base address */ bhnd_size_t m_size; /**< size */ int m_rid; /**< bus resource id, or -1. */ 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) */ bhnd_port_type sp_type; /**< port type */ u_long sp_num_maps; /**< number of regions mapped to this port */ STAILQ_HEAD(, bcma_map) sp_maps; 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 { struct bhnd_core_info core_info; /**< standard core info */ u_long num_master_ports; /**< number of master port descriptors. */ struct bcma_mport_list master_ports; /**< master port descriptors */ u_long num_dev_ports; /**< number of device slave port descriptors. */ struct bcma_sport_list dev_ports; /**< device port descriptors */ u_long num_bridge_ports; /**< number of bridge slave port descriptors. */ struct bcma_sport_list bridge_ports; /**< bridge port descriptors */ u_long num_wrapper_ports; /**< number of wrapper slave port descriptors. */ struct bcma_sport_list wrapper_ports; /**< wrapper port descriptors */ }; /** * BCMA per-device info */ struct bcma_devinfo { struct resource_list resources; /**< Slave port memory regions. */ struct bcma_corecfg *corecfg; /**< IP core/block config */ 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 */ }; /** BMCA per-instance state */ struct bcma_softc { struct bhnd_softc bhnd_sc; /**< bhnd state */ }; #endif /* _BCMA_BCMAVAR_H_ */ Index: head/sys/dev/bhnd/bhnd.c =================================================================== --- head/sys/dev/bhnd/bhnd.c (revision 326078) +++ head/sys/dev/bhnd/bhnd.c (revision 326079) @@ -1,1006 +1,1022 @@ /*- * 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: * 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. */ #include __FBSDID("$FreeBSD$"); /* * Broadcom Home Networking Division (HND) Bus Driver. * * The Broadcom HND family of devices consists of both SoCs and host-connected * networking chipsets containing a common family of Broadcom IP cores, * including an integrated MIPS and/or ARM cores. * * HND devices expose a nearly identical interface whether accessible over a * native SoC interconnect, or when connected via a host interface such as * PCIe. As a result, the majority of hardware support code should be re-usable * across host drivers for HND networking chipsets, as well as FreeBSD support * for Broadcom MIPS/ARM HND SoCs. * * Earlier HND models used the siba(4) on-chip interconnect, while later models * use bcma(4); the programming model is almost entirely independent * of the actual underlying interconect. */ #include #include #include #include #include #include #include #include #include #include #include #include "bhnd_chipc_if.h" #include "bhnd_nvram_if.h" #include "bhnd.h" #include "bhndvar.h" #include "bhnd_private.h" MALLOC_DEFINE(M_BHND, "bhnd", "bhnd bus data structures"); /** * bhnd_generic_probe_nomatch() reporting configuration. */ static const struct bhnd_nomatch { uint16_t vendor; /**< core designer */ uint16_t device; /**< core id */ bool if_verbose; /**< print when bootverbose is set. */ } bhnd_nomatch_table[] = { { BHND_MFGID_ARM, BHND_COREID_OOB_ROUTER, true }, { BHND_MFGID_ARM, BHND_COREID_EROM, true }, { BHND_MFGID_ARM, BHND_COREID_PL301, true }, { BHND_MFGID_ARM, BHND_COREID_APB_BRIDGE, true }, { BHND_MFGID_ARM, BHND_COREID_AXI_UNMAPPED, false }, { BHND_MFGID_INVALID, BHND_COREID_INVALID, false } }; static int bhnd_delete_children(struct bhnd_softc *sc); /** * Default bhnd(4) bus driver implementation of DEVICE_ATTACH(). * * This implementation calls device_probe_and_attach() for each of the device's * children, in bhnd probe order. */ int bhnd_generic_attach(device_t dev) { struct bhnd_softc *sc; int error; if (device_is_attached(dev)) return (EBUSY); sc = device_get_softc(dev); sc->dev = dev; /* Probe and attach all children */ if ((error = bhnd_bus_probe_children(dev))) { bhnd_delete_children(sc); return (error); } return (0); } /** * Detach and delete all children, in reverse of their attach order. */ static int bhnd_delete_children(struct bhnd_softc *sc) { device_t *devs; int ndevs; int error; /* Fetch children in detach order */ error = bhnd_bus_get_children(sc->dev, &devs, &ndevs, BHND_DEVICE_ORDER_DETACH); if (error) return (error); /* Perform detach */ for (int i = 0; i < ndevs; i++) { device_t child = devs[i]; /* Terminate on first error */ if ((error = device_delete_child(sc->dev, child))) goto cleanup; } cleanup: bhnd_bus_free_children(devs); return (error); } /** * Default bhnd(4) bus driver implementation of DEVICE_DETACH(). * * This implementation calls device_detach() for each of the device's * children, in reverse bhnd probe order, terminating if any call to * device_detach() fails. */ int bhnd_generic_detach(device_t dev) { struct bhnd_softc *sc; int error; if (!device_is_attached(dev)) return (EBUSY); sc = device_get_softc(dev); if ((error = bhnd_delete_children(sc))) return (error); return (0); } /** * Default bhnd(4) bus driver implementation of DEVICE_SHUTDOWN(). * * This implementation calls device_shutdown() for each of the device's * children, in reverse bhnd probe order, terminating if any call to * device_shutdown() fails. */ int bhnd_generic_shutdown(device_t dev) { device_t *devs; int ndevs; int error; if (!device_is_attached(dev)) return (EBUSY); /* Fetch children in detach order */ error = bhnd_bus_get_children(dev, &devs, &ndevs, BHND_DEVICE_ORDER_DETACH); if (error) return (error); /* Perform shutdown */ for (int i = 0; i < ndevs; i++) { device_t child = devs[i]; /* Terminate on first error */ if ((error = device_shutdown(child))) goto cleanup; } cleanup: bhnd_bus_free_children(devs); return (error); } /** * Default bhnd(4) bus driver implementation of DEVICE_RESUME(). * * This implementation calls BUS_RESUME_CHILD() for each of the device's * children in bhnd probe order, terminating if any call to BUS_RESUME_CHILD() * fails. */ int bhnd_generic_resume(device_t dev) { device_t *devs; int ndevs; int error; if (!device_is_attached(dev)) return (EBUSY); /* Fetch children in attach order */ error = bhnd_bus_get_children(dev, &devs, &ndevs, BHND_DEVICE_ORDER_ATTACH); if (error) return (error); /* Perform resume */ for (int i = 0; i < ndevs; i++) { device_t child = devs[i]; /* Terminate on first error */ if ((error = BUS_RESUME_CHILD(device_get_parent(child), child))) goto cleanup; } cleanup: bhnd_bus_free_children(devs); return (error); } /** * Default bhnd(4) bus driver implementation of DEVICE_SUSPEND(). * * This implementation calls BUS_SUSPEND_CHILD() for each of the device's * children in reverse bhnd probe order. If any call to BUS_SUSPEND_CHILD() * fails, the suspend operation is terminated and any devices that were * suspended are resumed immediately by calling their BUS_RESUME_CHILD() * methods. */ int bhnd_generic_suspend(device_t dev) { device_t *devs; int ndevs; int error; if (!device_is_attached(dev)) return (EBUSY); /* Fetch children in detach order */ error = bhnd_bus_get_children(dev, &devs, &ndevs, BHND_DEVICE_ORDER_DETACH); if (error) return (error); /* Perform suspend */ for (int i = 0; i < ndevs; i++) { device_t child = devs[i]; error = BUS_SUSPEND_CHILD(device_get_parent(child), child); /* On error, resume suspended devices and then terminate */ if (error) { for (int j = 0; j < i; j++) { BUS_RESUME_CHILD(device_get_parent(devs[j]), devs[j]); } goto cleanup; } } cleanup: bhnd_bus_free_children(devs); return (error); } /** * Default bhnd(4) bus driver implementation of BHND_BUS_GET_PROBE_ORDER(). * * This implementation determines probe ordering based on the device's class * and other properties, including whether the device is serving as a host * bridge. */ int bhnd_generic_get_probe_order(device_t dev, device_t child) { switch (bhnd_get_class(child)) { case BHND_DEVCLASS_CC: /* Must be early enough to provide NVRAM access to the * host bridge */ return (BHND_PROBE_ROOT + BHND_PROBE_ORDER_FIRST); case BHND_DEVCLASS_CC_B: /* fall through */ case BHND_DEVCLASS_PMU: return (BHND_PROBE_BUS + BHND_PROBE_ORDER_EARLY); case BHND_DEVCLASS_SOC_ROUTER: return (BHND_PROBE_BUS + BHND_PROBE_ORDER_LATE); case BHND_DEVCLASS_SOC_BRIDGE: return (BHND_PROBE_BUS + BHND_PROBE_ORDER_LAST); case BHND_DEVCLASS_CPU: return (BHND_PROBE_CPU + BHND_PROBE_ORDER_FIRST); case BHND_DEVCLASS_RAM: /* fall through */ case BHND_DEVCLASS_MEMC: return (BHND_PROBE_CPU + BHND_PROBE_ORDER_EARLY); case BHND_DEVCLASS_NVRAM: return (BHND_PROBE_RESOURCE + BHND_PROBE_ORDER_EARLY); 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_EROM: case BHND_DEVCLASS_OTHER: case BHND_DEVCLASS_INVALID: if (bhnd_bus_find_hostb_device(dev) == child) return (BHND_PROBE_ROOT + BHND_PROBE_ORDER_EARLY); return (BHND_PROBE_DEFAULT); default: return (BHND_PROBE_DEFAULT); } } /** * Default bhnd(4) bus driver implementation of BHND_BUS_ALLOC_PMU(). */ int bhnd_generic_alloc_pmu(device_t dev, device_t child) { struct bhnd_softc *sc; struct bhnd_resource *br; struct bhnd_core_pmu_info *pm; struct resource_list *rl; struct resource_list_entry *rle; device_t pmu_dev; bhnd_addr_t r_addr; bhnd_size_t r_size; bus_size_t pmu_regs; int error; GIANT_REQUIRED; /* for newbus */ sc = device_get_softc(dev); pm = bhnd_get_pmu_info(child); pmu_regs = BHND_CLK_CTL_ST; /* already allocated? */ if (pm != NULL) { panic("duplicate PMU allocation for %s", device_get_nameunit(child)); } /* Determine address+size of the core's PMU register block */ error = bhnd_get_region_addr(child, BHND_PORT_DEVICE, 0, 0, &r_addr, &r_size); if (error) { device_printf(sc->dev, "error fetching register block info for " "%s: %d\n", device_get_nameunit(child), error); return (error); } if (r_size < (pmu_regs + sizeof(uint32_t))) { device_printf(sc->dev, "pmu offset %#jx would overrun %s " "register block\n", (uintmax_t)pmu_regs, device_get_nameunit(child)); return (ENODEV); } /* Locate actual resource containing the core's register block */ if ((rl = BUS_GET_RESOURCE_LIST(dev, child)) == NULL) { device_printf(dev, "NULL resource list returned for %s\n", device_get_nameunit(child)); return (ENXIO); } if ((rle = resource_list_find(rl, SYS_RES_MEMORY, 0)) == NULL) { device_printf(dev, "cannot locate core register resource " "for %s\n", device_get_nameunit(child)); return (ENXIO); } if (rle->res == NULL) { device_printf(dev, "core register resource unallocated for " "%s\n", device_get_nameunit(child)); return (ENXIO); } if (r_addr+pmu_regs < rman_get_start(rle->res) || r_addr+pmu_regs >= rman_get_end(rle->res)) { device_printf(dev, "core register resource does not map PMU " "registers at %#jx\n for %s\n", r_addr+pmu_regs, device_get_nameunit(child)); return (ENXIO); } /* Adjust PMU register offset relative to the actual start address * of the core's register block allocation. * * XXX: The saved offset will be invalid if bus_adjust_resource is * used to modify the resource's start address. */ if (rman_get_start(rle->res) > r_addr) pmu_regs -= rman_get_start(rle->res) - r_addr; else pmu_regs -= r_addr - rman_get_start(rle->res); /* Retain PMU reference on behalf of our caller */ pmu_dev = bhnd_retain_provider(child, BHND_SERVICE_PMU); if (pmu_dev == NULL) { device_printf(sc->dev, "pmu unavailable; cannot allocate request state\n"); return (ENXIO); } /* Allocate and initialize PMU info */ br = malloc(sizeof(struct bhnd_resource), M_BHND, M_NOWAIT); if (br == NULL) { bhnd_release_provider(child, pmu_dev, BHND_SERVICE_PMU); return (ENOMEM); } br->res = rle->res; br->direct = ((rman_get_flags(rle->res) & RF_ACTIVE) != 0); pm = malloc(sizeof(*pm), M_BHND, M_NOWAIT); if (pm == NULL) { bhnd_release_provider(child, pmu_dev, BHND_SERVICE_PMU); free(br, M_BHND); return (ENOMEM); } pm->pm_dev = child; pm->pm_res = br; pm->pm_regs = pmu_regs; pm->pm_pmu = pmu_dev; bhnd_set_pmu_info(child, pm); return (0); } /** * Default bhnd(4) bus driver implementation of BHND_BUS_RELEASE_PMU(). */ int bhnd_generic_release_pmu(device_t dev, device_t child) { struct bhnd_softc *sc; struct bhnd_core_pmu_info *pm; int error; GIANT_REQUIRED; /* for newbus */ sc = device_get_softc(dev); /* dispatch release request */ pm = bhnd_get_pmu_info(child); if (pm == NULL) panic("pmu over-release for %s", device_get_nameunit(child)); if ((error = BHND_PMU_CORE_RELEASE(pm->pm_pmu, pm))) return (error); /* free PMU info */ bhnd_set_pmu_info(child, NULL); bhnd_release_provider(pm->pm_dev, pm->pm_pmu, BHND_SERVICE_PMU); free(pm->pm_res, M_BHND); free(pm, M_BHND); return (0); } /** * Default bhnd(4) bus driver implementation of BHND_BUS_REQUEST_CLOCK(). */ int bhnd_generic_request_clock(device_t dev, device_t child, bhnd_clock clock) { struct bhnd_softc *sc; struct bhnd_core_pmu_info *pm; sc = device_get_softc(dev); if ((pm = bhnd_get_pmu_info(child)) == NULL) panic("no active PMU request state"); /* dispatch request to PMU */ return (BHND_PMU_CORE_REQ_CLOCK(pm->pm_pmu, pm, clock)); } /** * Default bhnd(4) bus driver implementation of BHND_BUS_ENABLE_CLOCKS(). */ int bhnd_generic_enable_clocks(device_t dev, device_t child, uint32_t clocks) { struct bhnd_softc *sc; struct bhnd_core_pmu_info *pm; sc = device_get_softc(dev); if ((pm = bhnd_get_pmu_info(child)) == NULL) panic("no active PMU request state"); /* dispatch request to PMU */ return (BHND_PMU_CORE_EN_CLOCKS(pm->pm_pmu, pm, clocks)); } /** * Default bhnd(4) bus driver implementation of BHND_BUS_REQUEST_EXT_RSRC(). */ int bhnd_generic_request_ext_rsrc(device_t dev, device_t child, u_int rsrc) { struct bhnd_softc *sc; struct bhnd_core_pmu_info *pm; sc = device_get_softc(dev); if ((pm = bhnd_get_pmu_info(child)) == NULL) panic("no active PMU request state"); /* dispatch request to PMU */ return (BHND_PMU_CORE_REQ_EXT_RSRC(pm->pm_pmu, pm, rsrc)); } /** * Default bhnd(4) bus driver implementation of BHND_BUS_RELEASE_EXT_RSRC(). */ int bhnd_generic_release_ext_rsrc(device_t dev, device_t child, u_int rsrc) { struct bhnd_softc *sc; struct bhnd_core_pmu_info *pm; sc = device_get_softc(dev); if ((pm = bhnd_get_pmu_info(child)) == NULL) panic("no active PMU request state"); /* dispatch request to PMU */ return (BHND_PMU_CORE_RELEASE_EXT_RSRC(pm->pm_pmu, pm, rsrc)); } /** * Default bhnd(4) bus driver implementation of BHND_BUS_IS_REGION_VALID(). * * This implementation assumes that port and region numbers are 0-indexed and * are allocated non-sparsely, using BHND_BUS_GET_PORT_COUNT() and * BHND_BUS_GET_REGION_COUNT() to determine if @p port and @p region fall * within the defined range. */ static bool bhnd_generic_is_region_valid(device_t dev, device_t child, bhnd_port_type type, u_int port, u_int region) { if (port >= bhnd_get_port_count(child, type)) return (false); if (region >= bhnd_get_region_count(child, type, port)) return (false); return (true); } /** * Default bhnd(4) bus driver implementation of BHND_BUS_GET_NVRAM_VAR(). * * This implementation searches @p dev for a registered NVRAM child device. * * If no NVRAM device is registered with @p dev, the request is delegated to * the BHND_BUS_GET_NVRAM_VAR() method on the parent of @p dev. */ int bhnd_generic_get_nvram_var(device_t dev, device_t child, const char *name, void *buf, size_t *size, bhnd_nvram_type type) { struct bhnd_softc *sc; device_t nvram, parent; int error; sc = device_get_softc(dev); /* If a NVRAM device is available, consult it first */ nvram = bhnd_retain_provider(child, BHND_SERVICE_NVRAM); if (nvram != NULL) { error = BHND_NVRAM_GETVAR(nvram, name, buf, size, type); bhnd_release_provider(child, nvram, BHND_SERVICE_NVRAM); return (error); } /* Otherwise, try to delegate to parent */ if ((parent = device_get_parent(dev)) == NULL) return (ENODEV); return (BHND_BUS_GET_NVRAM_VAR(device_get_parent(dev), child, name, buf, size, type)); } /** * Default bhnd(4) bus driver implementation of BUS_PRINT_CHILD(). * * This implementation requests the device's struct resource_list via * BUS_GET_RESOURCE_LIST. */ int bhnd_generic_print_child(device_t dev, device_t child) { struct resource_list *rl; int retval = 0; retval += bus_print_child_header(dev, child); rl = BUS_GET_RESOURCE_LIST(dev, child); if (rl != NULL) { retval += resource_list_print_type(rl, "mem", SYS_RES_MEMORY, "%#jx"); retval += resource_list_print_type(rl, "irq", SYS_RES_IRQ, "%#jd"); } retval += printf(" at core %u", bhnd_get_core_index(child)); retval += bus_print_child_domain(dev, child); retval += bus_print_child_footer(dev, child); return (retval); } /** * Default bhnd(4) bus driver implementation of BUS_PROBE_NOMATCH(). * * This implementation requests the device's struct resource_list via * BUS_GET_RESOURCE_LIST. */ void bhnd_generic_probe_nomatch(device_t dev, device_t child) { struct resource_list *rl; const struct bhnd_nomatch *nm; bool report; /* Fetch reporting configuration for this device */ report = true; for (nm = bhnd_nomatch_table; nm->device != BHND_COREID_INVALID; nm++) { if (nm->vendor != bhnd_get_vendor(child)) continue; if (nm->device != bhnd_get_device(child)) continue; report = false; if (bootverbose && nm->if_verbose) report = true; break; } if (!report) return; /* Print the non-matched device info */ device_printf(dev, "<%s %s>", bhnd_get_vendor_name(child), bhnd_get_device_name(child)); rl = BUS_GET_RESOURCE_LIST(dev, child); if (rl != NULL) { resource_list_print_type(rl, "mem", SYS_RES_MEMORY, "%#jx"); resource_list_print_type(rl, "irq", SYS_RES_IRQ, "%#jd"); } printf(" at core %u (no driver attached)\n", bhnd_get_core_index(child)); } /** * Default implementation of BUS_CHILD_PNPINFO_STR(). */ static int bhnd_child_pnpinfo_str(device_t dev, device_t child, char *buf, size_t buflen) { if (device_get_parent(child) != dev) { return (BUS_CHILD_PNPINFO_STR(device_get_parent(dev), child, buf, buflen)); } snprintf(buf, buflen, "vendor=0x%hx device=0x%hx rev=0x%hhx", bhnd_get_vendor(child), bhnd_get_device(child), bhnd_get_hwrev(child)); return (0); } /** * Default implementation of BUS_CHILD_LOCATION_STR(). */ static int bhnd_child_location_str(device_t dev, device_t child, char *buf, size_t buflen) { bhnd_addr_t addr; bhnd_size_t size; if (device_get_parent(child) != dev) { return (BUS_CHILD_LOCATION_STR(device_get_parent(dev), child, buf, buflen)); } if (bhnd_get_region_addr(child, BHND_PORT_DEVICE, 0, 0, &addr, &size)) { /* No device default port/region */ if (buflen > 0) *buf = '\0'; return (0); } snprintf(buf, buflen, "port0.0=0x%llx", (unsigned long long) addr); return (0); } /** * Default bhnd(4) bus driver implementation of BUS_CHILD_DELETED(). * * This implementation manages internal bhnd(4) state, and must be called * by subclassing drivers. */ void bhnd_generic_child_deleted(device_t dev, device_t child) { struct bhnd_softc *sc; sc = device_get_softc(dev); /* Free device info */ if (bhnd_get_pmu_info(child) != NULL) { /* Releasing PMU requests automatically would be nice, * but we can't reference per-core PMU register * resource after driver detach */ panic("%s leaked device pmu state\n", device_get_nameunit(child)); } } /** * Helper function for implementing BUS_SUSPEND_CHILD(). * * TODO: Power management * * If @p child is not a direct child of @p dev, suspension is delegated to * the @p dev parent. */ int bhnd_generic_suspend_child(device_t dev, device_t child) { if (device_get_parent(child) != dev) BUS_SUSPEND_CHILD(device_get_parent(dev), child); return bus_generic_suspend_child(dev, child); } /** * Helper function for implementing BUS_RESUME_CHILD(). * * TODO: Power management * * If @p child is not a direct child of @p dev, suspension is delegated to * the @p dev parent. */ int bhnd_generic_resume_child(device_t dev, device_t child) { if (device_get_parent(child) != dev) BUS_RESUME_CHILD(device_get_parent(dev), child); 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 * indirect, and these methods will never be called. */ #define BHND_IO_READ(_type, _name, _method) \ static _type \ bhnd_read_ ## _name (device_t dev, device_t child, \ struct bhnd_resource *r, bus_size_t offset) \ { \ return (BHND_BUS_READ_ ## _method( \ device_get_parent(dev), child, r, offset)); \ } #define BHND_IO_WRITE(_type, _name, _method) \ static void \ bhnd_write_ ## _name (device_t dev, device_t child, \ struct bhnd_resource *r, bus_size_t offset, _type value) \ { \ return (BHND_BUS_WRITE_ ## _method( \ device_get_parent(dev), child, r, offset, \ value)); \ } #define BHND_IO_MISC(_type, _op, _method) \ static void \ bhnd_ ## _op (device_t dev, device_t child, \ struct bhnd_resource *r, bus_size_t offset, _type datap, \ bus_size_t count) \ { \ BHND_BUS_ ## _method(device_get_parent(dev), child, r, \ offset, datap, count); \ } #define BHND_IO_METHODS(_type, _size) \ BHND_IO_READ(_type, _size, _size) \ BHND_IO_WRITE(_type, _size, _size) \ \ BHND_IO_READ(_type, stream_ ## _size, STREAM_ ## _size) \ BHND_IO_WRITE(_type, stream_ ## _size, STREAM_ ## _size) \ \ BHND_IO_MISC(_type*, read_multi_ ## _size, \ READ_MULTI_ ## _size) \ BHND_IO_MISC(_type*, write_multi_ ## _size, \ WRITE_MULTI_ ## _size) \ \ BHND_IO_MISC(_type*, read_multi_stream_ ## _size, \ READ_MULTI_STREAM_ ## _size) \ BHND_IO_MISC(_type*, write_multi_stream_ ## _size, \ WRITE_MULTI_STREAM_ ## _size) \ \ BHND_IO_MISC(_type, set_multi_ ## _size, SET_MULTI_ ## _size) \ BHND_IO_MISC(_type, set_region_ ## _size, SET_REGION_ ## _size) \ \ BHND_IO_MISC(_type*, read_region_ ## _size, \ READ_REGION_ ## _size) \ BHND_IO_MISC(_type*, write_region_ ## _size, \ WRITE_REGION_ ## _size) \ \ BHND_IO_MISC(_type*, read_region_stream_ ## _size, \ READ_REGION_STREAM_ ## _size) \ BHND_IO_MISC(_type*, write_region_stream_ ## _size, \ WRITE_REGION_STREAM_ ## _size) \ BHND_IO_METHODS(uint8_t, 1); BHND_IO_METHODS(uint16_t, 2); BHND_IO_METHODS(uint32_t, 4); static void bhnd_barrier(device_t dev, device_t child, struct bhnd_resource *r, bus_size_t offset, bus_size_t length, int flags) { BHND_BUS_BARRIER(device_get_parent(dev), child, r, offset, length, flags); } static device_method_t bhnd_methods[] = { /* Device interface */ \ DEVMETHOD(device_attach, bhnd_generic_attach), DEVMETHOD(device_detach, bhnd_generic_detach), DEVMETHOD(device_shutdown, bhnd_generic_shutdown), DEVMETHOD(device_suspend, bhnd_generic_suspend), DEVMETHOD(device_resume, bhnd_generic_resume), /* Bus interface */ DEVMETHOD(bus_child_deleted, bhnd_generic_child_deleted), DEVMETHOD(bus_probe_nomatch, bhnd_generic_probe_nomatch), DEVMETHOD(bus_print_child, bhnd_generic_print_child), DEVMETHOD(bus_child_pnpinfo_str, bhnd_child_pnpinfo_str), DEVMETHOD(bus_child_location_str, bhnd_child_location_str), DEVMETHOD(bus_suspend_child, bhnd_generic_suspend_child), DEVMETHOD(bus_resume_child, bhnd_generic_resume_child), DEVMETHOD(bus_set_resource, bus_generic_rl_set_resource), DEVMETHOD(bus_get_resource, bus_generic_rl_get_resource), DEVMETHOD(bus_delete_resource, bus_generic_rl_delete_resource), DEVMETHOD(bus_alloc_resource, bus_generic_rl_alloc_resource), DEVMETHOD(bus_adjust_resource, bus_generic_adjust_resource), DEVMETHOD(bus_release_resource, bus_generic_rl_release_resource), 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), DEVMETHOD(bus_describe_intr, bus_generic_describe_intr), DEVMETHOD(bus_get_dma_tag, bus_generic_get_dma_tag), /* BHND interface */ DEVMETHOD(bhnd_bus_get_chipid, bhnd_bus_generic_get_chipid), DEVMETHOD(bhnd_bus_is_hw_disabled, bhnd_bus_generic_is_hw_disabled), DEVMETHOD(bhnd_bus_read_board_info, bhnd_bus_generic_read_board_info), DEVMETHOD(bhnd_bus_get_probe_order, bhnd_generic_get_probe_order), DEVMETHOD(bhnd_bus_alloc_pmu, bhnd_generic_alloc_pmu), DEVMETHOD(bhnd_bus_release_pmu, bhnd_generic_release_pmu), DEVMETHOD(bhnd_bus_request_clock, bhnd_generic_request_clock), DEVMETHOD(bhnd_bus_enable_clocks, bhnd_generic_enable_clocks), DEVMETHOD(bhnd_bus_request_ext_rsrc, bhnd_generic_request_ext_rsrc), DEVMETHOD(bhnd_bus_release_ext_rsrc, bhnd_generic_release_ext_rsrc), DEVMETHOD(bhnd_bus_is_region_valid, bhnd_generic_is_region_valid), DEVMETHOD(bhnd_bus_get_nvram_var, bhnd_generic_get_nvram_var), /* BHND interface (bus I/O) */ DEVMETHOD(bhnd_bus_read_1, bhnd_read_1), DEVMETHOD(bhnd_bus_read_2, bhnd_read_2), DEVMETHOD(bhnd_bus_read_4, bhnd_read_4), DEVMETHOD(bhnd_bus_write_1, bhnd_write_1), DEVMETHOD(bhnd_bus_write_2, bhnd_write_2), DEVMETHOD(bhnd_bus_write_4, bhnd_write_4), DEVMETHOD(bhnd_bus_read_stream_1, bhnd_read_stream_1), DEVMETHOD(bhnd_bus_read_stream_2, bhnd_read_stream_2), DEVMETHOD(bhnd_bus_read_stream_4, bhnd_read_stream_4), DEVMETHOD(bhnd_bus_write_stream_1, bhnd_write_stream_1), DEVMETHOD(bhnd_bus_write_stream_2, bhnd_write_stream_2), DEVMETHOD(bhnd_bus_write_stream_4, bhnd_write_stream_4), DEVMETHOD(bhnd_bus_read_multi_1, bhnd_read_multi_1), DEVMETHOD(bhnd_bus_read_multi_2, bhnd_read_multi_2), DEVMETHOD(bhnd_bus_read_multi_4, bhnd_read_multi_4), DEVMETHOD(bhnd_bus_write_multi_1, bhnd_write_multi_1), DEVMETHOD(bhnd_bus_write_multi_2, bhnd_write_multi_2), DEVMETHOD(bhnd_bus_write_multi_4, bhnd_write_multi_4), DEVMETHOD(bhnd_bus_read_multi_stream_1, bhnd_read_multi_stream_1), DEVMETHOD(bhnd_bus_read_multi_stream_2, bhnd_read_multi_stream_2), DEVMETHOD(bhnd_bus_read_multi_stream_4, bhnd_read_multi_stream_4), DEVMETHOD(bhnd_bus_write_multi_stream_1,bhnd_write_multi_stream_1), DEVMETHOD(bhnd_bus_write_multi_stream_2,bhnd_write_multi_stream_2), DEVMETHOD(bhnd_bus_write_multi_stream_4,bhnd_write_multi_stream_4), DEVMETHOD(bhnd_bus_set_multi_1, bhnd_set_multi_1), DEVMETHOD(bhnd_bus_set_multi_2, bhnd_set_multi_2), DEVMETHOD(bhnd_bus_set_multi_4, bhnd_set_multi_4), DEVMETHOD(bhnd_bus_set_region_1, bhnd_set_region_1), DEVMETHOD(bhnd_bus_set_region_2, bhnd_set_region_2), DEVMETHOD(bhnd_bus_set_region_4, bhnd_set_region_4), DEVMETHOD(bhnd_bus_read_region_1, bhnd_read_region_1), DEVMETHOD(bhnd_bus_read_region_2, bhnd_read_region_2), DEVMETHOD(bhnd_bus_read_region_4, bhnd_read_region_4), DEVMETHOD(bhnd_bus_write_region_1, bhnd_write_region_1), DEVMETHOD(bhnd_bus_write_region_2, bhnd_write_region_2), DEVMETHOD(bhnd_bus_write_region_4, bhnd_write_region_4), DEVMETHOD(bhnd_bus_read_region_stream_1,bhnd_read_region_stream_1), DEVMETHOD(bhnd_bus_read_region_stream_2,bhnd_read_region_stream_2), DEVMETHOD(bhnd_bus_read_region_stream_4,bhnd_read_region_stream_4), DEVMETHOD(bhnd_bus_write_region_stream_1, bhnd_write_region_stream_1), DEVMETHOD(bhnd_bus_write_region_stream_2, bhnd_write_region_stream_2), DEVMETHOD(bhnd_bus_write_region_stream_4, bhnd_write_region_stream_4), DEVMETHOD(bhnd_bus_barrier, bhnd_barrier), DEVMETHOD_END }; devclass_t bhnd_devclass; /**< bhnd bus. */ devclass_t bhnd_hostb_devclass; /**< bhnd bus host bridge. */ devclass_t bhnd_nvram_devclass; /**< bhnd NVRAM device */ DEFINE_CLASS_0(bhnd, bhnd_driver, bhnd_methods, sizeof(struct bhnd_softc)); MODULE_VERSION(bhnd, 1); Index: head/sys/dev/bhnd/bhnd.h =================================================================== --- head/sys/dev/bhnd/bhnd.h (revision 326078) +++ head/sys/dev/bhnd/bhnd.h (revision 326079) @@ -1,1603 +1,1634 @@ /*- * 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: * 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 _BHND_BHND_H_ #define _BHND_BHND_H_ #include #include #include #include #include #include "bhnd_ids.h" #include "bhnd_types.h" #include "bhnd_erom_types.h" #include "bhnd_debug.h" #include "bhnd_bus_if.h" #include "bhnd_match.h" #include "nvram/bhnd_nvram.h" struct bhnd_core_pmu_info; extern devclass_t bhnd_devclass; extern devclass_t bhnd_hostb_devclass; extern devclass_t bhnd_nvram_devclass; #define BHND_CHIPID_MAX_NAMELEN 32 /**< maximum buffer required for a bhnd_format_chip_id() */ /** * bhnd child instance variables */ enum bhnd_device_vars { BHND_IVAR_VENDOR, /**< Designer's JEP-106 manufacturer ID. */ BHND_IVAR_DEVICE, /**< Part number */ BHND_IVAR_HWREV, /**< Core revision */ BHND_IVAR_DEVICE_CLASS, /**< Core class (@sa bhnd_devclass_t) */ BHND_IVAR_VENDOR_NAME, /**< Core vendor name */ BHND_IVAR_DEVICE_NAME, /**< Core name */ BHND_IVAR_CORE_INDEX, /**< Bus-assigned core number */ BHND_IVAR_CORE_UNIT, /**< Bus-assigned core unit number, assigned sequentially (starting at 0) for each vendor/device pair. */ BHND_IVAR_PMU_INFO, /**< Internal bus-managed PMU state */ }; /** * bhnd device probe priority bands. */ enum { BHND_PROBE_ROOT = 0, /**< Nexus or host bridge */ BHND_PROBE_BUS = 1000, /**< Buses and bridges */ BHND_PROBE_CPU = 2000, /**< CPU devices */ BHND_PROBE_INTERRUPT = 3000, /**< Interrupt controllers. */ BHND_PROBE_TIMER = 4000, /**< Timers and clocks. */ BHND_PROBE_RESOURCE = 5000, /**< Resource discovery (including NVRAM/SPROM) */ BHND_PROBE_DEFAULT = 6000, /**< Default device priority */ }; /** * Constants defining fine grained ordering within a BHND_PROBE_* priority band. * * Example: * @code * BHND_PROBE_BUS + BHND_PROBE_ORDER_FIRST * @endcode */ enum { BHND_PROBE_ORDER_FIRST = 0, BHND_PROBE_ORDER_EARLY = 25, BHND_PROBE_ORDER_MIDDLE = 50, BHND_PROBE_ORDER_LATE = 75, BHND_PROBE_ORDER_LAST = 100 }; /** * Per-core IOCTL flags common to all bhnd(4) cores. */ enum { BHND_IOCTL_BIST = 0x8000, /**< Initiate a built-in self-test (BIST). Must be cleared after BIST results are read via BHND_IOST_BIST_* */ BHND_IOCTL_PME = 0x4000, /**< Enable posting of power management events by the core. */ BHND_IOCTL_CFLAGS = 0x3FFC, /**< Reserved for core-specific ioctl flags. */ BHND_IOCTL_CLK_FORCE = 0x0002, /**< Force disable of clock gating, resulting in all clocks being distributed within the core. Should be set when asserting/deasserting reset to ensure the reset signal fully propagates to the entire core. */ BHND_IOCTL_CLK_EN = 0x0001, /**< If cleared, the core clock will be disabled. Should be set during normal operation, and cleared when the core is held in reset. */ }; /** * Per-core IOST flags common to all bhnd(4) cores. */ enum { BHND_IOST_BIST_DONE = 0x8000, /**< Set upon BIST completion (see BHND_IOCTL_BIST), and cleared if 0 is written to BHND_IOCTL_BIST. */ BHND_IOST_BIST_FAIL = 0x4000, /**< Set upon detection of a BIST error; the value is unspecified if BIST has not completed and BHND_IOST_BIST_DONE is not set. */ BHND_IOST_CLK = 0x2000, /**< Set if the core has requested that gated clocks be enabled, or cleared otherwise. The value is undefined if a core does not support clock gating. */ BHND_IOST_DMA64 = 0x1000, /**< Set if this core supports 64-bit DMA */ BHND_IOST_CFLAGS = 0x0FFC, /**< Reserved for core-specific status flags. */ }; /* * Simplified accessors for bhnd device ivars */ #define BHND_ACCESSOR(var, ivar, type) \ __BUS_ACCESSOR(bhnd, var, BHND, ivar, type) BHND_ACCESSOR(vendor, VENDOR, uint16_t); BHND_ACCESSOR(device, DEVICE, uint16_t); BHND_ACCESSOR(hwrev, HWREV, uint8_t); BHND_ACCESSOR(class, DEVICE_CLASS, bhnd_devclass_t); BHND_ACCESSOR(vendor_name, VENDOR_NAME, const char *); BHND_ACCESSOR(device_name, DEVICE_NAME, const char *); BHND_ACCESSOR(core_index, CORE_INDEX, u_int); BHND_ACCESSOR(core_unit, CORE_UNIT, int); BHND_ACCESSOR(pmu_info, PMU_INFO, struct bhnd_core_pmu_info *); #undef BHND_ACCESSOR /** * A bhnd(4) board descriptor. */ struct bhnd_board_info { uint16_t board_vendor; /**< PCI-SIG vendor ID (even on non-PCI * devices). * * On PCI devices, this will generally * be the subsystem vendor ID, but the * value may be overridden in device * NVRAM. */ uint16_t board_type; /**< Board type (See BHND_BOARD_*) * * On PCI devices, this will generally * be the subsystem device ID, but the * value may be overridden in device * NVRAM. */ uint16_t board_rev; /**< Board revision. */ uint8_t board_srom_rev; /**< Board SROM format revision */ uint32_t board_flags; /**< Board flags (see BHND_BFL_*) */ uint32_t board_flags2; /**< Board flags 2 (see BHND_BFL2_*) */ uint32_t board_flags3; /**< Board flags 3 (see BHND_BFL3_*) */ }; /** * Chip Identification * * This is read from the ChipCommon ID register; on earlier bhnd(4) devices * where ChipCommon is unavailable, known values must be supplied. */ struct bhnd_chipid { uint16_t chip_id; /**< chip id (BHND_CHIPID_*) */ uint8_t chip_rev; /**< chip revision */ uint8_t chip_pkg; /**< chip package (BHND_PKGID_*) */ uint8_t chip_type; /**< chip type (BHND_CHIPTYPE_*) */ bhnd_addr_t enum_addr; /**< chip_type-specific enumeration * address; either the siba(4) base * core register block, or the bcma(4) * EROM core address. */ uint8_t ncores; /**< number of cores, if known. 0 if * not available. */ }; /** * A bhnd(4) core descriptor. */ struct bhnd_core_info { uint16_t vendor; /**< JEP-106 vendor (BHND_MFGID_*) */ uint16_t device; /**< device */ uint16_t hwrev; /**< hardware revision */ u_int core_idx; /**< bus-assigned core index */ int unit; /**< bus-assigned core unit */ }; /** * A bhnd(4) bus resource. * * This provides an abstract interface to per-core resources that may require * bus-level remapping of address windows prior to access. */ struct bhnd_resource { struct resource *res; /**< the system resource. */ bool direct; /**< false if the resource requires * bus window remapping before it * is MMIO accessible. */ }; /** Wrap the active resource @p _r in a bhnd_resource structure */ #define BHND_DIRECT_RESOURCE(_r) ((struct bhnd_resource) { \ .res = (_r), \ .direct = true, \ }) /** * Device quirk table descriptor. */ struct bhnd_device_quirk { struct bhnd_device_match desc; /**< device match descriptor */ uint32_t quirks; /**< quirk flags */ }; #define BHND_CORE_QUIRK(_rev, _flags) \ {{ 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) }, \ (_flags) } #define BHND_DEVICE_QUIRK_END { { BHND_MATCH_ANY }, 0 } #define BHND_DEVICE_QUIRK_IS_END(_q) \ (((_q)->desc.m.match_flags == 0) && (_q)->quirks == 0) enum { BHND_DF_ANY = 0, BHND_DF_HOSTB = (1<<0), /**< core is serving as the bus' host * bridge. implies BHND_DF_ADAPTER */ BHND_DF_SOC = (1<<1), /**< core is attached to a native bus (BHND_ATTACH_NATIVE) */ BHND_DF_ADAPTER = (1<<2), /**< core is attached to a bridged * adapter (BHND_ATTACH_ADAPTER) */ }; /** Device probe table descriptor */ struct bhnd_device { const struct bhnd_device_match core; /**< core match descriptor */ const char *desc; /**< device description, or NULL. */ const struct bhnd_device_quirk *quirks_table; /**< quirks table for this device, or NULL */ uint32_t device_flags; /**< required BHND_DF_* flags */ }; #define _BHND_DEVICE(_vendor, _device, _desc, _quirks, \ _flags, ...) \ { { BHND_MATCH_CORE(BHND_MFGID_ ## _vendor, \ BHND_COREID_ ## _device) }, _desc, _quirks, \ _flags } #define BHND_DEVICE(_vendor, _device, _desc, _quirks, ...) \ _BHND_DEVICE(_vendor, _device, _desc, _quirks, \ ## __VA_ARGS__, 0) #define BHND_DEVICE_END { { BHND_MATCH_ANY }, NULL, NULL, 0 } #define BHND_DEVICE_IS_END(_d) \ (BHND_MATCH_IS_ANY(&(_d)->core) && (_d)->desc == NULL) /** * bhnd device sort order. */ typedef enum { BHND_DEVICE_ORDER_ATTACH, /**< sort by bhnd(4) device attach order; child devices should be probed/attached in this order */ BHND_DEVICE_ORDER_DETACH, /**< sort by bhnd(4) device detach order; child devices should be detached, suspended, and shutdown in this order */ } bhnd_device_order; /** * A registry of bhnd service providers. */ struct bhnd_service_registry { STAILQ_HEAD(,bhnd_service_entry) entries; /**< registered services */ struct mtx lock; /**< state lock */ }; /** * bhnd service provider flags. */ enum { BHND_SPF_INHERITED = (1<<0), /**< service provider reference was inherited from a parent bus, and should be deregistered when the last active reference is released */ }; const char *bhnd_vendor_name(uint16_t vendor); const char *bhnd_port_type_name(bhnd_port_type port_type); const char *bhnd_nvram_src_name(bhnd_nvram_src nvram_src); const char *bhnd_find_core_name(uint16_t vendor, uint16_t device); bhnd_devclass_t bhnd_find_core_class(uint16_t vendor, uint16_t device); const char *bhnd_core_name(const struct bhnd_core_info *ci); bhnd_devclass_t bhnd_core_class(const struct bhnd_core_info *ci); int bhnd_format_chip_id(char *buffer, size_t size, uint16_t chip_id); device_t bhnd_bus_match_child(device_t bus, const struct bhnd_core_match *desc); device_t bhnd_bus_find_child(device_t bus, bhnd_devclass_t class, int unit); int bhnd_bus_get_children(device_t bus, device_t **devlistp, int *devcountp, bhnd_device_order order); void bhnd_bus_free_children(device_t *devlist); int bhnd_bus_probe_children(device_t bus); int bhnd_sort_devices(device_t *devlist, size_t devcount, bhnd_device_order order); device_t bhnd_find_bridge_root(device_t dev, devclass_t bus_class); const struct bhnd_core_info *bhnd_match_core( const struct bhnd_core_info *cores, u_int num_cores, const struct bhnd_core_match *desc); const struct bhnd_core_info *bhnd_find_core( const struct bhnd_core_info *cores, u_int num_cores, bhnd_devclass_t class); struct bhnd_core_match bhnd_core_get_match_desc( const struct bhnd_core_info *core); bool bhnd_cores_equal( const struct bhnd_core_info *lhs, const struct bhnd_core_info *rhs); bool bhnd_core_matches( const struct bhnd_core_info *core, const struct bhnd_core_match *desc); bool bhnd_chip_matches( const struct bhnd_chipid *chipid, const struct bhnd_chip_match *desc); bool bhnd_board_matches( const struct bhnd_board_info *info, const struct bhnd_board_match *desc); bool bhnd_hwrev_matches(uint16_t hwrev, const struct bhnd_hwrev_match *desc); bool bhnd_device_matches(device_t dev, const struct bhnd_device_match *desc); const struct bhnd_device *bhnd_device_lookup(device_t dev, const struct bhnd_device *table, size_t entry_size); uint32_t bhnd_device_quirks(device_t dev, const struct bhnd_device *table, size_t entry_size); struct bhnd_core_info bhnd_get_core_info(device_t dev); int bhnd_alloc_resources(device_t dev, struct resource_spec *rs, struct bhnd_resource **res); void bhnd_release_resources(device_t dev, const struct resource_spec *rs, struct bhnd_resource **res); struct bhnd_chipid bhnd_parse_chipid(uint32_t idreg, bhnd_addr_t enum_addr); int bhnd_chipid_fixed_ncores( const struct bhnd_chipid *cid, uint16_t chipc_hwrev, uint8_t *ncores); int bhnd_read_chipid(device_t dev, struct resource_spec *rs, bus_size_t chipc_offset, struct bhnd_chipid *result); void bhnd_set_custom_core_desc(device_t dev, const char *name); void bhnd_set_default_core_desc(device_t dev); void bhnd_set_default_bus_desc(device_t dev, const struct bhnd_chipid *chip_id); int bhnd_nvram_getvar_str(device_t dev, const char *name, char *buf, size_t len, size_t *rlen); int bhnd_nvram_getvar_uint(device_t dev, const char *name, void *value, int width); int bhnd_nvram_getvar_uint8(device_t dev, const char *name, uint8_t *value); int bhnd_nvram_getvar_uint16(device_t dev, const char *name, uint16_t *value); int bhnd_nvram_getvar_uint32(device_t dev, const char *name, uint32_t *value); int bhnd_nvram_getvar_int(device_t dev, const char *name, void *value, int width); int bhnd_nvram_getvar_int8(device_t dev, const char *name, int8_t *value); int bhnd_nvram_getvar_int16(device_t dev, const char *name, int16_t *value); int bhnd_nvram_getvar_int32(device_t dev, const char *name, int32_t *value); int bhnd_nvram_getvar_array(device_t dev, const char *name, void *buf, size_t count, bhnd_nvram_type type); int bhnd_service_registry_init( struct bhnd_service_registry *bsr); int bhnd_service_registry_fini( struct bhnd_service_registry *bsr); int bhnd_service_registry_add( struct bhnd_service_registry *bsr, device_t provider, bhnd_service_t service, uint32_t flags); int bhnd_service_registry_remove( struct bhnd_service_registry *bsr, device_t provider, bhnd_service_t service); device_t bhnd_service_registry_retain( struct bhnd_service_registry *bsr, bhnd_service_t service); bool bhnd_service_registry_release( struct bhnd_service_registry *bsr, device_t provider, bhnd_service_t service); int bhnd_bus_generic_register_provider( device_t dev, device_t child, device_t provider, bhnd_service_t service); int bhnd_bus_generic_deregister_provider( device_t dev, device_t child, device_t provider, bhnd_service_t service); device_t bhnd_bus_generic_retain_provider(device_t dev, device_t child, bhnd_service_t service); void bhnd_bus_generic_release_provider(device_t dev, device_t child, device_t provider, bhnd_service_t service); int bhnd_bus_generic_sr_register_provider( device_t dev, device_t child, device_t provider, bhnd_service_t service); int bhnd_bus_generic_sr_deregister_provider( device_t dev, device_t child, device_t provider, bhnd_service_t service); device_t bhnd_bus_generic_sr_retain_provider(device_t dev, device_t child, bhnd_service_t service); void bhnd_bus_generic_sr_release_provider(device_t dev, device_t child, device_t provider, bhnd_service_t service); bool bhnd_bus_generic_is_hw_disabled(device_t dev, device_t child); bool bhnd_bus_generic_is_region_valid(device_t dev, device_t child, bhnd_port_type type, u_int port, u_int region); int bhnd_bus_generic_get_nvram_var(device_t dev, device_t child, const char *name, void *buf, size_t *size, bhnd_nvram_type type); const struct bhnd_chipid *bhnd_bus_generic_get_chipid(device_t dev, device_t child); int bhnd_bus_generic_read_board_info(device_t dev, device_t child, struct bhnd_board_info *info); struct bhnd_resource *bhnd_bus_generic_alloc_resource (device_t dev, device_t child, int type, int *rid, rman_res_t start, rman_res_t end, rman_res_t count, u_int flags); int bhnd_bus_generic_release_resource (device_t dev, device_t child, int type, int rid, struct bhnd_resource *r); int bhnd_bus_generic_activate_resource (device_t dev, device_t child, int type, int rid, struct bhnd_resource *r); 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 * * @param driver A bhnd bus driver instance. */ static inline bhnd_erom_class_t * bhnd_driver_get_erom_class(driver_t *driver) { return (BHND_BUS_GET_EROM_CLASS(driver)); } /** * Return the active host bridge core for the bhnd bus, if any, or NULL if * not found. * * @param dev A bhnd bus device. */ static inline device_t bhnd_bus_find_hostb_device(device_t dev) { return (BHND_BUS_FIND_HOSTB_DEVICE(dev)); } /** * Register a provider for a given @p service. * * @param dev The device to register as a service provider * with its parent bus. * @param service The service for which @p dev will be registered. * * @retval 0 success * @retval EEXIST if an entry for @p service already exists. * @retval non-zero if registering @p dev otherwise fails, a regular * unix error code will be returned. */ static inline int bhnd_register_provider(device_t dev, bhnd_service_t service) { return (BHND_BUS_REGISTER_PROVIDER(device_get_parent(dev), dev, dev, service)); } /** * Attempt to remove a service provider registration for @p dev. * * @param dev The device to be deregistered as a service provider. * @param service The service for which @p dev will be deregistered, or * BHND_SERVICE_INVALID to remove all service registrations * for @p dev. * * @retval 0 success * @retval EBUSY if active references to @p dev exist; @see * bhnd_retain_provider() and bhnd_release_provider(). */ static inline int bhnd_deregister_provider(device_t dev, bhnd_service_t service) { return (BHND_BUS_DEREGISTER_PROVIDER(device_get_parent(dev), dev, dev, service)); } /** * Retain and return a reference to the registered @p service provider, if any. * * @param dev The requesting device. * @param service The service for which a provider should be returned. * * On success, the caller assumes ownership the returned provider, and * is responsible for releasing this reference via * BHND_BUS_RELEASE_PROVIDER(). * * @retval device_t success * @retval NULL if no provider is registered for @p service. */ static inline device_t bhnd_retain_provider(device_t dev, bhnd_service_t service) { return (BHND_BUS_RETAIN_PROVIDER(device_get_parent(dev), dev, service)); } /** * Release a reference to a provider device previously returned by * bhnd_retain_provider(). * * @param dev The requesting device. * @param provider The provider to be released. * @param service The service for which @p provider was previously retained. */ static inline void bhnd_release_provider(device_t dev, device_t provider, bhnd_service_t service) { return (BHND_BUS_RELEASE_PROVIDER(device_get_parent(dev), dev, provider, service)); } /** * Return true if the hardware components required by @p dev are known to be * unpopulated or otherwise unusable. * * In some cases, enumerated devices may have pins that are left floating, or * the hardware may otherwise be non-functional; this method allows a parent * device to explicitly specify if a successfully enumerated @p dev should * be disabled. * * @param dev A bhnd bus child device. */ static inline bool bhnd_is_hw_disabled(device_t dev) { return (BHND_BUS_IS_HW_DISABLED(device_get_parent(dev), dev)); } /** * Return the BHND chip identification info for the bhnd bus. * * @param dev A bhnd bus child device. */ static inline const struct bhnd_chipid * bhnd_get_chipid(device_t dev) { return (BHND_BUS_GET_CHIPID(device_get_parent(dev), dev)); }; /** * Read the current value of a bhnd(4) device's per-core I/O control register. * * @param dev The bhnd bus child device to be queried. * @param[out] ioctl On success, the I/O control register value. * * @retval 0 success * @retval EINVAL If @p child is not a direct child of @p dev. * @retval ENODEV If agent/config space for @p child is unavailable. * @retval non-zero If reading the IOCTL register otherwise fails, a regular * unix error code will be returned. */ static inline int bhnd_read_ioctl(device_t dev, uint16_t *ioctl) { return (BHND_BUS_READ_IOCTL(device_get_parent(dev), dev, ioctl)); } /** * Write @p value and @p mask to a bhnd(4) device's per-core I/O control * register. * * @param dev The bhnd bus child device for which the IOCTL register will be * written. * @param value The value to be written (see BHND_IOCTL_*). * @param mask Only the bits defined by @p mask will be updated from @p value. * * @retval 0 success * @retval EINVAL If @p child is not a direct child of @p dev. * @retval ENODEV If agent/config space for @p child is unavailable. * @retval non-zero If writing the IOCTL register otherwise fails, a regular * unix error code will be returned. */ static inline int bhnd_write_ioctl(device_t dev, uint16_t value, uint16_t mask) { return (BHND_BUS_WRITE_IOCTL(device_get_parent(dev), dev, value, mask)); } /** * Read the current value of a bhnd(4) device's per-core I/O status register. * * @param dev The bhnd bus child device to be queried. * @param[out] iost On success, the I/O status register value. * * @retval 0 success * @retval EINVAL If @p child is not a direct child of @p dev. * @retval ENODEV If agent/config space for @p child is unavailable. * @retval non-zero If reading the IOST register otherwise fails, a regular * unix error code will be returned. */ static inline int bhnd_read_iost(device_t dev, uint16_t *iost) { return (BHND_BUS_READ_IOST(device_get_parent(dev), dev, iost)); } /** * Return true if the given bhnd device's hardware is currently held * in a RESET state or otherwise not clocked (BHND_IOCTL_CLK_EN). * * @param dev The device to query. * * @retval true If @p dev is held in RESET or not clocked (BHND_IOCTL_CLK_EN), * or an error occured determining @p dev's hardware state. * @retval false If @p dev is clocked and is not held in RESET. */ static inline bool bhnd_is_hw_suspended(device_t dev) { return (BHND_BUS_IS_HW_SUSPENDED(device_get_parent(dev), dev)); } /** * Place the bhnd(4) device's hardware into a reset state, and then bring the * hardware out of reset with BHND_IOCTL_CLK_EN and @p ioctl flags set. * * Any clock or resource PMU requests previously made by @p dev will be * invalidated. * * @param dev The device to be reset. * @param ioctl Device-specific core ioctl flags to be supplied on reset * (see BHND_IOCTL_*). * * @retval 0 success * @retval non-zero error */ static inline int bhnd_reset_hw(device_t dev, uint16_t ioctl) { return (BHND_BUS_RESET_HW(device_get_parent(dev), dev, ioctl)); } /** * Suspend @p child's hardware in a low-power reset state. * * Any clock or resource PMU requests previously made by @p dev will be * invalidated. * * The hardware may be brought out of reset via bhnd_reset_hw(). * * @param dev The device to be suspended. * * @retval 0 success * @retval non-zero error */ static inline int bhnd_suspend_hw(device_t dev) { return (BHND_BUS_SUSPEND_HW(device_get_parent(dev), dev)); } /** * If supported by the chipset, return the clock source for the given clock. * * This function is only supported on early PWRCTL-equipped chipsets * that expose clock management via their host bridge interface. Currently, * this includes PCI (not PCIe) devices, with ChipCommon core revisions 0-9. * * @param dev A bhnd bus child device. * @param clock The clock for which a clock source will be returned. * * @retval bhnd_clksrc The clock source for @p clock. * @retval BHND_CLKSRC_UNKNOWN If @p clock is unsupported, or its * clock source is not known to the bus. */ static inline bhnd_clksrc bhnd_pwrctl_get_clksrc(device_t dev, bhnd_clock clock) { return (BHND_BUS_PWRCTL_GET_CLKSRC(device_get_parent(dev), dev, clock)); } /** * If supported by the chipset, gate @p clock * * This function is only supported on early PWRCTL-equipped chipsets * that expose clock management via their host bridge interface. Currently, * this includes PCI (not PCIe) devices, with ChipCommon core revisions 0-9. * * @param dev A bhnd bus child device. * @param clock The clock to be disabled. * * @retval 0 success * @retval ENODEV If bus-level clock source management is not supported. * @retval ENXIO If bus-level management of @p clock is not supported. */ static inline int bhnd_pwrctl_gate_clock(device_t dev, bhnd_clock clock) { return (BHND_BUS_PWRCTL_GATE_CLOCK(device_get_parent(dev), dev, clock)); } /** * If supported by the chipset, ungate @p clock * * This function is only supported on early PWRCTL-equipped chipsets * that expose clock management via their host bridge interface. Currently, * this includes PCI (not PCIe) devices, with ChipCommon core revisions 0-9. * * @param dev A bhnd bus child device. * @param clock The clock to be enabled. * * @retval 0 success * @retval ENODEV If bus-level clock source management is not supported. * @retval ENXIO If bus-level management of @p clock is not supported. */ static inline int bhnd_pwrctl_ungate_clock(device_t dev, bhnd_clock clock) { return (BHND_BUS_PWRCTL_UNGATE_CLOCK(device_get_parent(dev), dev, clock)); } /** * Return the BHND attachment type of the parent bhnd bus. * * @param dev A bhnd bus child device. * * @retval BHND_ATTACH_ADAPTER if the bus is resident on a bridged adapter, * such as a WiFi chipset. * @retval BHND_ATTACH_NATIVE if the bus provides hardware services (clock, * CPU, etc) to a directly attached native host. */ static inline bhnd_attach_type bhnd_get_attach_type (device_t dev) { return (BHND_BUS_GET_ATTACH_TYPE(device_get_parent(dev), dev)); } /** * Attempt to read the BHND board identification from the bhnd bus. * * This relies on NVRAM access, and will fail if a valid NVRAM device cannot * be found, or is not yet attached. * * @param dev The parent of @p child. * @param child The bhnd device requesting board info. * @param[out] info On success, will be populated with the bhnd(4) device's * board information. * * @retval 0 success * @retval ENODEV No valid NVRAM source could be found. * @retval non-zero If reading @p name otherwise fails, a regular unix * error code will be returned. */ static inline int bhnd_read_board_info(device_t dev, struct bhnd_board_info *info) { return (BHND_BUS_READ_BOARD_INFO(device_get_parent(dev), dev, info)); } /** - * 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 * 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. + * @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)); } /** * Allocate and enable per-core PMU request handling for @p child. * * The region containing the core's PMU register block (if any) must be * allocated via bus_alloc_resource(9) (or bhnd_alloc_resource) before * calling bhnd_alloc_pmu(), and must not be released until after * calling bhnd_release_pmu(). * * @param dev The parent of @p child. * @param child The requesting bhnd device. * * @retval 0 success * @retval non-zero If allocating PMU request state otherwise fails, a * regular unix error code will be returned. */ static inline int bhnd_alloc_pmu(device_t dev) { return (BHND_BUS_ALLOC_PMU(device_get_parent(dev), dev)); } /** * Release any per-core PMU resources allocated for @p child. Any outstanding * PMU requests are are discarded. * * @param dev The parent of @p child. * @param child The requesting bhnd device. * * @retval 0 success * @retval non-zero If releasing PMU request state otherwise fails, a * regular unix error code will be returned, and * the core state will be left unmodified. */ static inline int bhnd_release_pmu(device_t dev) { return (BHND_BUS_RELEASE_PMU(device_get_parent(dev), dev)); } /** * Request that @p clock (or faster) be routed to @p dev. * * @note A driver must ask the bhnd bus to allocate clock request state * via bhnd_alloc_pmu() before it can request clock resources. * * @note Any outstanding PMU clock requests will be discarded upon calling * BHND_BUS_RESET_HW() or BHND_BUS_SUSPEND_HW(). * * @param dev The bhnd(4) device to which @p clock should be routed. * @param clock The requested clock source. * * @retval 0 success * @retval ENODEV If an unsupported clock was requested. * @retval ENXIO If the PMU has not been initialized or is otherwise unvailable, */ static inline int bhnd_request_clock(device_t dev, bhnd_clock clock) { return (BHND_BUS_REQUEST_CLOCK(device_get_parent(dev), dev, clock)); } /** * Request that @p clocks be powered on behalf of @p dev. * * This will power any clock sources (e.g. XTAL, PLL, etc) required for * @p clocks and wait until they are ready, discarding any previous * requests by @p dev. * * @note A driver must ask the bhnd bus to allocate clock request state * via bhnd_alloc_pmu() before it can request clock resources. * * @note Any outstanding PMU clock requests will be discarded upon calling * BHND_BUS_RESET_HW() or BHND_BUS_SUSPEND_HW(). * * @param dev The requesting bhnd(4) device. * @param clocks The clock(s) to be enabled. * * @retval 0 success * @retval ENODEV If an unsupported clock was requested. * @retval ENXIO If the PMU has not been initialized or is otherwise unvailable. */ static inline int bhnd_enable_clocks(device_t dev, uint32_t clocks) { return (BHND_BUS_ENABLE_CLOCKS(device_get_parent(dev), dev, clocks)); } /** * Power up an external PMU-managed resource assigned to @p dev. * * @note A driver must ask the bhnd bus to allocate PMU request state * via bhnd_alloc_pmu() before it can request PMU resources. * * @note Any outstanding PMU resource requests will be released upon calling * bhnd_reset_hw() or bhnd_suspend_hw(). * * @param dev The requesting bhnd(4) device. * @param rsrc The core-specific external resource identifier. * * @retval 0 success * @retval ENODEV If the PMU does not support @p rsrc. * @retval ENXIO If the PMU has not been initialized or is otherwise unvailable. */ static inline int bhnd_request_ext_rsrc(device_t dev, u_int rsrc) { return (BHND_BUS_REQUEST_EXT_RSRC(device_get_parent(dev), dev, rsrc)); } /** * Power down an external PMU-managed resource assigned to @p dev. * * A driver must ask the bhnd bus to allocate PMU request state * via bhnd_alloc_pmu() before it can request PMU resources. * * @param dev The requesting bhnd(4) device. * @param rsrc The core-specific external resource identifier. * * @retval 0 success * @retval ENODEV If the PMU does not support @p rsrc. * @retval ENXIO If the PMU has not been initialized or is otherwise unvailable. */ static inline int bhnd_release_ext_rsrc(device_t dev, u_int rsrc) { return (BHND_BUS_RELEASE_EXT_RSRC(device_get_parent(dev), dev, rsrc)); } /** * Read @p width bytes at @p offset from the bus-specific agent/config * space of @p dev. * * @param dev The bhnd device for which @p offset should be read. * @param offset The offset to be read. * @param[out] value On success, the will be set to the @p width value read * at @p offset. * @param width The size of the access. Must be 1, 2 or 4 bytes. * * The exact behavior of this method is bus-specific. In the case of * bcma(4), this method provides access to the first agent port of @p child. * * @note Device drivers should only use this API for functionality * that is not available via another bhnd(4) function. * * @retval 0 success * @retval EINVAL If @p child is not a direct child of @p dev. * @retval EINVAL If @p width is not one of 1, 2, or 4 bytes. * @retval ENODEV If accessing agent/config space for @p child is unsupported. * @retval EFAULT If reading @p width at @p offset exceeds the bounds of * the mapped agent/config space for @p child. */ static inline uint32_t bhnd_read_config(device_t dev, bus_size_t offset, void *value, u_int width) { return (BHND_BUS_READ_CONFIG(device_get_parent(dev), dev, offset, value, width)); } /** * Write @p width bytes at @p offset to the bus-specific agent/config * space of @p dev. * * @param dev The bhnd device for which @p offset should be read. * @param offset The offset to be written. * @param value A pointer to the value to be written. * @param width The size of @p value. Must be 1, 2 or 4 bytes. * * The exact behavior of this method is bus-specific. In the case of * bcma(4), this method provides access to the first agent port of @p child. * * @note Device drivers should only use this API for functionality * that is not available via another bhnd(4) function. * * @retval 0 success * @retval EINVAL If @p child is not a direct child of @p dev. * @retval EINVAL If @p width is not one of 1, 2, or 4 bytes. * @retval ENODEV If accessing agent/config space for @p child is unsupported. * @retval EFAULT If reading @p width at @p offset exceeds the bounds of * the mapped agent/config space for @p child. */ static inline int bhnd_write_config(device_t dev, bus_size_t offset, const void *value, u_int width) { return (BHND_BUS_WRITE_CONFIG(device_get_parent(dev), dev, offset, value, width)); } /** * Read an NVRAM variable, coerced to the requested @p type. * * @param dev A bhnd bus child device. * @param name The NVRAM variable name. * @param[out] buf A buffer large enough to hold @p len bytes. On * success, the requested value will be written to * this buffer. This argment may be NULL if * the value is not desired. * @param[in,out] len The maximum capacity of @p buf. On success, * will be set to the actual size of the requested * value. * @param type The desired data representation to be written * to @p buf. * * @retval 0 success * @retval ENOENT The requested variable was not found. * @retval ENODEV No valid NVRAM source could be found. * @retval ENOMEM If a buffer of @p size is too small to hold the * requested value. * @retval EOPNOTSUPP If the value cannot be coerced to @p type. * @retval ERANGE If value coercion would overflow @p type. * @retval non-zero If reading @p name otherwise fails, a regular unix * error code will be returned. */ static inline int bhnd_nvram_getvar(device_t dev, const char *name, void *buf, size_t *len, bhnd_nvram_type type) { return (BHND_BUS_GET_NVRAM_VAR(device_get_parent(dev), dev, name, buf, len, type)); } /** * Allocate a resource from a device's parent bhnd(4) bus. * * @param dev The device requesting resource ownership. * @param type The type of resource to allocate. This may be any type supported * by the standard bus APIs. * @param rid The bus-specific handle identifying the resource being allocated. * @param start The start address of the resource. * @param end The end address of the resource. * @param count The size of the resource. * @param flags The flags for the resource to be allocated. These may be any * values supported by the standard bus APIs. * * To request the resource's default addresses, pass @p start and * @p end values of @c 0 and @c ~0, respectively, and * a @p count of @c 1. * * @retval NULL The resource could not be allocated. * @retval resource The allocated resource. */ static inline struct bhnd_resource * bhnd_alloc_resource(device_t dev, int type, int *rid, rman_res_t start, rman_res_t end, rman_res_t count, u_int flags) { return BHND_BUS_ALLOC_RESOURCE(device_get_parent(dev), dev, type, rid, start, end, count, flags); } /** * Allocate a resource from a device's parent bhnd(4) bus, using the * resource's default start, end, and count values. * * @param dev The device requesting resource ownership. * @param type The type of resource to allocate. This may be any type supported * by the standard bus APIs. * @param rid The bus-specific handle identifying the resource being allocated. * @param flags The flags for the resource to be allocated. These may be any * values supported by the standard bus APIs. * * @retval NULL The resource could not be allocated. * @retval resource The allocated resource. */ static inline struct bhnd_resource * bhnd_alloc_resource_any(device_t dev, int type, int *rid, u_int flags) { return bhnd_alloc_resource(dev, type, rid, 0, ~0, 1, flags); } /** * Activate a previously allocated bhnd resource. * * @param dev The device holding ownership of the allocated resource. * @param type The type of the resource. * @param rid The bus-specific handle identifying the resource. * @param r A pointer to the resource returned by bhnd_alloc_resource or * BHND_BUS_ALLOC_RESOURCE. * * @retval 0 success * @retval non-zero an error occurred while activating the resource. */ static inline int bhnd_activate_resource(device_t dev, int type, int rid, struct bhnd_resource *r) { return BHND_BUS_ACTIVATE_RESOURCE(device_get_parent(dev), dev, type, rid, r); } /** * Deactivate a previously activated bhnd resource. * * @param dev The device holding ownership of the activated resource. * @param type The type of the resource. * @param rid The bus-specific handle identifying the resource. * @param r A pointer to the resource returned by bhnd_alloc_resource or * BHND_BUS_ALLOC_RESOURCE. * * @retval 0 success * @retval non-zero an error occurred while activating the resource. */ static inline int bhnd_deactivate_resource(device_t dev, int type, int rid, struct bhnd_resource *r) { return BHND_BUS_DEACTIVATE_RESOURCE(device_get_parent(dev), dev, type, rid, r); } /** * Free a resource allocated by bhnd_alloc_resource(). * * @param dev The device holding ownership of the resource. * @param type The type of the resource. * @param rid The bus-specific handle identifying the resource. * @param r A pointer to the resource returned by bhnd_alloc_resource or * BHND_ALLOC_RESOURCE. * * @retval 0 success * @retval non-zero an error occurred while activating the resource. */ static inline int bhnd_release_resource(device_t dev, int type, int rid, struct bhnd_resource *r) { return BHND_BUS_RELEASE_RESOURCE(device_get_parent(dev), dev, type, rid, r); } /** * Return true if @p region_num is a valid region on @p port_num of * @p type attached to @p dev. * * @param dev A bhnd bus child device. * @param type The port type being queried. * @param port_num The port number being queried. * @param region_num The region number being queried. */ static inline bool bhnd_is_region_valid(device_t dev, bhnd_port_type type, u_int port_num, u_int region_num) { return (BHND_BUS_IS_REGION_VALID(device_get_parent(dev), dev, type, port_num, region_num)); } /** * Return the number of ports of type @p type attached to @p def. * * @param dev A bhnd bus child device. * @param type The port type being queried. */ static inline u_int bhnd_get_port_count(device_t dev, bhnd_port_type type) { return (BHND_BUS_GET_PORT_COUNT(device_get_parent(dev), dev, type)); } /** * Return the number of memory regions mapped to @p child @p port of * type @p type. * * @param dev A bhnd bus child device. * @param port The port number being queried. * @param type The port type being queried. */ static inline u_int bhnd_get_region_count(device_t dev, bhnd_port_type type, u_int port) { return (BHND_BUS_GET_REGION_COUNT(device_get_parent(dev), dev, type, port)); } /** * Return the resource-ID for a memory region on the given device port. * * @param dev A bhnd bus child device. * @param type The port type. * @param port The port identifier. * @param region The identifier of the memory region on @p port. * * @retval int The RID for the given @p port and @p region on @p device. * @retval -1 No such port/region found. */ static inline int bhnd_get_port_rid(device_t dev, bhnd_port_type type, u_int port, u_int region) { return BHND_BUS_GET_PORT_RID(device_get_parent(dev), dev, type, port, region); } /** * Decode a port / region pair on @p dev defined by @p rid. * * @param dev A bhnd bus child device. * @param type The resource type. * @param rid The resource identifier. * @param[out] port_type The decoded port type. * @param[out] port The decoded port identifier. * @param[out] region The decoded region identifier. * * @retval 0 success * @retval non-zero No matching port/region found. */ static inline int bhnd_decode_port_rid(device_t dev, int type, int rid, bhnd_port_type *port_type, u_int *port, u_int *region) { return BHND_BUS_DECODE_PORT_RID(device_get_parent(dev), dev, type, rid, port_type, port, region); } /** * Get the address and size of @p region on @p port. * * @param dev A bhnd bus child device. * @param port_type The port type. * @param port The port identifier. * @param region The identifier of the memory region on @p port. * @param[out] region_addr The region's base address. * @param[out] region_size The region's size. * * @retval 0 success * @retval non-zero No matching port/region found. */ static inline int bhnd_get_region_addr(device_t dev, bhnd_port_type port_type, u_int port, u_int region, bhnd_addr_t *region_addr, bhnd_size_t *region_size) { return BHND_BUS_GET_REGION_ADDR(device_get_parent(dev), dev, port_type, port, region, region_addr, region_size); } /* * bhnd bus-level equivalents of the bus_(read|write|set|barrier|...) * macros (compatible with bhnd_resource). * * Generated with bhnd/tools/bus_macro.sh */ #define bhnd_bus_barrier(r, o, l, f) \ ((r)->direct) ? \ bus_barrier((r)->res, (o), (l), (f)) : \ BHND_BUS_BARRIER( \ device_get_parent(rman_get_device((r)->res)), \ rman_get_device((r)->res), (r), (o), (l), (f)) #define bhnd_bus_read_1(r, o) \ ((r)->direct) ? \ bus_read_1((r)->res, (o)) : \ BHND_BUS_READ_1( \ device_get_parent(rman_get_device((r)->res)), \ rman_get_device((r)->res), (r), (o)) #define bhnd_bus_read_multi_1(r, o, d, c) \ ((r)->direct) ? \ bus_read_multi_1((r)->res, (o), (d), (c)) : \ BHND_BUS_READ_MULTI_1( \ device_get_parent(rman_get_device((r)->res)), \ rman_get_device((r)->res), (r), (o), (d), (c)) #define bhnd_bus_read_region_1(r, o, d, c) \ ((r)->direct) ? \ bus_read_region_1((r)->res, (o), (d), (c)) : \ BHND_BUS_READ_REGION_1( \ device_get_parent(rman_get_device((r)->res)), \ rman_get_device((r)->res), (r), (o), (d), (c)) #define bhnd_bus_write_1(r, o, v) \ ((r)->direct) ? \ bus_write_1((r)->res, (o), (v)) : \ BHND_BUS_WRITE_1( \ device_get_parent(rman_get_device((r)->res)), \ rman_get_device((r)->res), (r), (o), (v)) #define bhnd_bus_write_multi_1(r, o, d, c) \ ((r)->direct) ? \ bus_write_multi_1((r)->res, (o), (d), (c)) : \ BHND_BUS_WRITE_MULTI_1( \ device_get_parent(rman_get_device((r)->res)), \ rman_get_device((r)->res), (r), (o), (d), (c)) #define bhnd_bus_write_region_1(r, o, d, c) \ ((r)->direct) ? \ bus_write_region_1((r)->res, (o), (d), (c)) : \ BHND_BUS_WRITE_REGION_1( \ device_get_parent(rman_get_device((r)->res)), \ rman_get_device((r)->res), (r), (o), (d), (c)) #define bhnd_bus_read_stream_1(r, o) \ ((r)->direct) ? \ bus_read_stream_1((r)->res, (o)) : \ BHND_BUS_READ_STREAM_1( \ device_get_parent(rman_get_device((r)->res)), \ rman_get_device((r)->res), (r), (o)) #define bhnd_bus_read_multi_stream_1(r, o, d, c) \ ((r)->direct) ? \ bus_read_multi_stream_1((r)->res, (o), (d), (c)) : \ BHND_BUS_READ_MULTI_STREAM_1( \ device_get_parent(rman_get_device((r)->res)), \ rman_get_device((r)->res), (r), (o), (d), (c)) #define bhnd_bus_read_region_stream_1(r, o, d, c) \ ((r)->direct) ? \ bus_read_region_stream_1((r)->res, (o), (d), (c)) : \ BHND_BUS_READ_REGION_STREAM_1( \ device_get_parent(rman_get_device((r)->res)), \ rman_get_device((r)->res), (r), (o), (d), (c)) #define bhnd_bus_write_stream_1(r, o, v) \ ((r)->direct) ? \ bus_write_stream_1((r)->res, (o), (v)) : \ BHND_BUS_WRITE_STREAM_1( \ device_get_parent(rman_get_device((r)->res)), \ rman_get_device((r)->res), (r), (o), (v)) #define bhnd_bus_write_multi_stream_1(r, o, d, c) \ ((r)->direct) ? \ bus_write_multi_stream_1((r)->res, (o), (d), (c)) : \ BHND_BUS_WRITE_MULTI_STREAM_1( \ device_get_parent(rman_get_device((r)->res)), \ rman_get_device((r)->res), (r), (o), (d), (c)) #define bhnd_bus_write_region_stream_1(r, o, d, c) \ ((r)->direct) ? \ bus_write_region_stream_1((r)->res, (o), (d), (c)) : \ BHND_BUS_WRITE_REGION_STREAM_1( \ device_get_parent(rman_get_device((r)->res)), \ rman_get_device((r)->res), (r), (o), (d), (c)) #define bhnd_bus_set_multi_1(r, o, v, c) \ ((r)->direct) ? \ bus_set_multi_1((r)->res, (o), (v), (c)) : \ BHND_BUS_SET_MULTI_1( \ device_get_parent(rman_get_device((r)->res)), \ rman_get_device((r)->res), (r), (o), (v), (c)) #define bhnd_bus_set_region_1(r, o, v, c) \ ((r)->direct) ? \ bus_set_region_1((r)->res, (o), (v), (c)) : \ BHND_BUS_SET_REGION_1( \ device_get_parent(rman_get_device((r)->res)), \ rman_get_device((r)->res), (r), (o), (v), (c)) #define bhnd_bus_read_2(r, o) \ ((r)->direct) ? \ bus_read_2((r)->res, (o)) : \ BHND_BUS_READ_2( \ device_get_parent(rman_get_device((r)->res)), \ rman_get_device((r)->res), (r), (o)) #define bhnd_bus_read_multi_2(r, o, d, c) \ ((r)->direct) ? \ bus_read_multi_2((r)->res, (o), (d), (c)) : \ BHND_BUS_READ_MULTI_2( \ device_get_parent(rman_get_device((r)->res)), \ rman_get_device((r)->res), (r), (o), (d), (c)) #define bhnd_bus_read_region_2(r, o, d, c) \ ((r)->direct) ? \ bus_read_region_2((r)->res, (o), (d), (c)) : \ BHND_BUS_READ_REGION_2( \ device_get_parent(rman_get_device((r)->res)), \ rman_get_device((r)->res), (r), (o), (d), (c)) #define bhnd_bus_write_2(r, o, v) \ ((r)->direct) ? \ bus_write_2((r)->res, (o), (v)) : \ BHND_BUS_WRITE_2( \ device_get_parent(rman_get_device((r)->res)), \ rman_get_device((r)->res), (r), (o), (v)) #define bhnd_bus_write_multi_2(r, o, d, c) \ ((r)->direct) ? \ bus_write_multi_2((r)->res, (o), (d), (c)) : \ BHND_BUS_WRITE_MULTI_2( \ device_get_parent(rman_get_device((r)->res)), \ rman_get_device((r)->res), (r), (o), (d), (c)) #define bhnd_bus_write_region_2(r, o, d, c) \ ((r)->direct) ? \ bus_write_region_2((r)->res, (o), (d), (c)) : \ BHND_BUS_WRITE_REGION_2( \ device_get_parent(rman_get_device((r)->res)), \ rman_get_device((r)->res), (r), (o), (d), (c)) #define bhnd_bus_read_stream_2(r, o) \ ((r)->direct) ? \ bus_read_stream_2((r)->res, (o)) : \ BHND_BUS_READ_STREAM_2( \ device_get_parent(rman_get_device((r)->res)), \ rman_get_device((r)->res), (r), (o)) #define bhnd_bus_read_multi_stream_2(r, o, d, c) \ ((r)->direct) ? \ bus_read_multi_stream_2((r)->res, (o), (d), (c)) : \ BHND_BUS_READ_MULTI_STREAM_2( \ device_get_parent(rman_get_device((r)->res)), \ rman_get_device((r)->res), (r), (o), (d), (c)) #define bhnd_bus_read_region_stream_2(r, o, d, c) \ ((r)->direct) ? \ bus_read_region_stream_2((r)->res, (o), (d), (c)) : \ BHND_BUS_READ_REGION_STREAM_2( \ device_get_parent(rman_get_device((r)->res)), \ rman_get_device((r)->res), (r), (o), (d), (c)) #define bhnd_bus_write_stream_2(r, o, v) \ ((r)->direct) ? \ bus_write_stream_2((r)->res, (o), (v)) : \ BHND_BUS_WRITE_STREAM_2( \ device_get_parent(rman_get_device((r)->res)), \ rman_get_device((r)->res), (r), (o), (v)) #define bhnd_bus_write_multi_stream_2(r, o, d, c) \ ((r)->direct) ? \ bus_write_multi_stream_2((r)->res, (o), (d), (c)) : \ BHND_BUS_WRITE_MULTI_STREAM_2( \ device_get_parent(rman_get_device((r)->res)), \ rman_get_device((r)->res), (r), (o), (d), (c)) #define bhnd_bus_write_region_stream_2(r, o, d, c) \ ((r)->direct) ? \ bus_write_region_stream_2((r)->res, (o), (d), (c)) : \ BHND_BUS_WRITE_REGION_STREAM_2( \ device_get_parent(rman_get_device((r)->res)), \ rman_get_device((r)->res), (r), (o), (d), (c)) #define bhnd_bus_set_multi_2(r, o, v, c) \ ((r)->direct) ? \ bus_set_multi_2((r)->res, (o), (v), (c)) : \ BHND_BUS_SET_MULTI_2( \ device_get_parent(rman_get_device((r)->res)), \ rman_get_device((r)->res), (r), (o), (v), (c)) #define bhnd_bus_set_region_2(r, o, v, c) \ ((r)->direct) ? \ bus_set_region_2((r)->res, (o), (v), (c)) : \ BHND_BUS_SET_REGION_2( \ device_get_parent(rman_get_device((r)->res)), \ rman_get_device((r)->res), (r), (o), (v), (c)) #define bhnd_bus_read_4(r, o) \ ((r)->direct) ? \ bus_read_4((r)->res, (o)) : \ BHND_BUS_READ_4( \ device_get_parent(rman_get_device((r)->res)), \ rman_get_device((r)->res), (r), (o)) #define bhnd_bus_read_multi_4(r, o, d, c) \ ((r)->direct) ? \ bus_read_multi_4((r)->res, (o), (d), (c)) : \ BHND_BUS_READ_MULTI_4( \ device_get_parent(rman_get_device((r)->res)), \ rman_get_device((r)->res), (r), (o), (d), (c)) #define bhnd_bus_read_region_4(r, o, d, c) \ ((r)->direct) ? \ bus_read_region_4((r)->res, (o), (d), (c)) : \ BHND_BUS_READ_REGION_4( \ device_get_parent(rman_get_device((r)->res)), \ rman_get_device((r)->res), (r), (o), (d), (c)) #define bhnd_bus_write_4(r, o, v) \ ((r)->direct) ? \ bus_write_4((r)->res, (o), (v)) : \ BHND_BUS_WRITE_4( \ device_get_parent(rman_get_device((r)->res)), \ rman_get_device((r)->res), (r), (o), (v)) #define bhnd_bus_write_multi_4(r, o, d, c) \ ((r)->direct) ? \ bus_write_multi_4((r)->res, (o), (d), (c)) : \ BHND_BUS_WRITE_MULTI_4( \ device_get_parent(rman_get_device((r)->res)), \ rman_get_device((r)->res), (r), (o), (d), (c)) #define bhnd_bus_write_region_4(r, o, d, c) \ ((r)->direct) ? \ bus_write_region_4((r)->res, (o), (d), (c)) : \ BHND_BUS_WRITE_REGION_4( \ device_get_parent(rman_get_device((r)->res)), \ rman_get_device((r)->res), (r), (o), (d), (c)) #define bhnd_bus_read_stream_4(r, o) \ ((r)->direct) ? \ bus_read_stream_4((r)->res, (o)) : \ BHND_BUS_READ_STREAM_4( \ device_get_parent(rman_get_device((r)->res)), \ rman_get_device((r)->res), (r), (o)) #define bhnd_bus_read_multi_stream_4(r, o, d, c) \ ((r)->direct) ? \ bus_read_multi_stream_4((r)->res, (o), (d), (c)) : \ BHND_BUS_READ_MULTI_STREAM_4( \ device_get_parent(rman_get_device((r)->res)), \ rman_get_device((r)->res), (r), (o), (d), (c)) #define bhnd_bus_read_region_stream_4(r, o, d, c) \ ((r)->direct) ? \ bus_read_region_stream_4((r)->res, (o), (d), (c)) : \ BHND_BUS_READ_REGION_STREAM_4( \ device_get_parent(rman_get_device((r)->res)), \ rman_get_device((r)->res), (r), (o), (d), (c)) #define bhnd_bus_write_stream_4(r, o, v) \ ((r)->direct) ? \ bus_write_stream_4((r)->res, (o), (v)) : \ BHND_BUS_WRITE_STREAM_4( \ device_get_parent(rman_get_device((r)->res)), \ rman_get_device((r)->res), (r), (o), (v)) #define bhnd_bus_write_multi_stream_4(r, o, d, c) \ ((r)->direct) ? \ bus_write_multi_stream_4((r)->res, (o), (d), (c)) : \ BHND_BUS_WRITE_MULTI_STREAM_4( \ device_get_parent(rman_get_device((r)->res)), \ rman_get_device((r)->res), (r), (o), (d), (c)) #define bhnd_bus_write_region_stream_4(r, o, d, c) \ ((r)->direct) ? \ bus_write_region_stream_4((r)->res, (o), (d), (c)) : \ BHND_BUS_WRITE_REGION_STREAM_4( \ device_get_parent(rman_get_device((r)->res)), \ rman_get_device((r)->res), (r), (o), (d), (c)) #define bhnd_bus_set_multi_4(r, o, v, c) \ ((r)->direct) ? \ bus_set_multi_4((r)->res, (o), (v), (c)) : \ BHND_BUS_SET_MULTI_4( \ device_get_parent(rman_get_device((r)->res)), \ rman_get_device((r)->res), (r), (o), (v), (c)) #define bhnd_bus_set_region_4(r, o, v, c) \ ((r)->direct) ? \ bus_set_region_4((r)->res, (o), (v), (c)) : \ BHND_BUS_SET_REGION_4( \ device_get_parent(rman_get_device((r)->res)), \ rman_get_device((r)->res), (r), (o), (v), (c)) #endif /* _BHND_BHND_H_ */ Index: head/sys/dev/bhnd/bhnd_bus_if.m =================================================================== --- head/sys/dev/bhnd/bhnd_bus_if.m (revision 326078) +++ head/sys/dev/bhnd/bhnd_bus_if.m (revision 326079) @@ -1,1574 +1,1617 @@ #- # 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: # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR # IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES # OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. # IN NO EVENT SHALL 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. # # $FreeBSD$ #include #include #include #include #include INTERFACE bhnd_bus; # # bhnd(4) bus interface # HEADER { /* forward declarations */ struct bhnd_board_info; struct bhnd_core_info; struct bhnd_chipid; struct bhnd_devinfo; struct bhnd_resource; } CODE { #include #include static bhnd_erom_class_t * bhnd_bus_null_get_erom_class(driver_t *driver) { return (NULL); } static struct bhnd_chipid * bhnd_bus_null_get_chipid(device_t dev, device_t child) { panic("bhnd_bus_get_chipid unimplemented"); } static int bhnd_bus_null_read_ioctl(device_t dev, device_t child, uint16_t *ioctl) { panic("bhnd_bus_read_ioctl unimplemented"); } static int bhnd_bus_null_write_ioctl(device_t dev, device_t child, uint16_t value, uint16_t mask) { panic("bhnd_bus_write_ioctl unimplemented"); } static int bhnd_bus_null_read_iost(device_t dev, device_t child, uint16_t *iost) { panic("bhnd_bus_read_iost unimplemented"); } static bool bhnd_bus_null_is_hw_suspended(device_t dev, device_t child) { panic("bhnd_bus_is_hw_suspended unimplemented"); } static int bhnd_bus_null_reset_hw(device_t dev, device_t child, uint16_t ioctl) { panic("bhnd_bus_reset_hw unimplemented"); } static int bhnd_bus_null_suspend_hw(device_t dev, device_t child) { panic("bhnd_bus_suspend_hw unimplemented"); } static bhnd_attach_type bhnd_bus_null_get_attach_type(device_t dev, device_t child) { panic("bhnd_bus_get_attach_type unimplemented"); } static bhnd_clksrc bhnd_bus_null_pwrctl_get_clksrc(device_t dev, device_t child, bhnd_clock clock) { return (BHND_CLKSRC_UNKNOWN); } static int bhnd_bus_null_pwrctl_gate_clock(device_t dev, device_t child, bhnd_clock clock) { return (ENODEV); } static int bhnd_bus_null_pwrctl_ungate_clock(device_t dev, device_t child, bhnd_clock clock) { return (ENODEV); } static int bhnd_bus_null_read_board_info(device_t dev, device_t child, struct bhnd_board_info *info) { 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) { } static int bhnd_bus_null_alloc_pmu(device_t dev, device_t child) { panic("bhnd_bus_alloc_pmu unimplemented"); } static int bhnd_bus_null_release_pmu(device_t dev, device_t child) { panic("bhnd_bus_release_pmu unimplemented"); } static int bhnd_bus_null_request_clock(device_t dev, device_t child, bhnd_clock clock) { panic("bhnd_bus_request_clock unimplemented"); } static int bhnd_bus_null_enable_clocks(device_t dev, device_t child, uint32_t clocks) { panic("bhnd_bus_enable_clocks unimplemented"); } static int bhnd_bus_null_request_ext_rsrc(device_t dev, device_t child, u_int rsrc) { panic("bhnd_bus_request_ext_rsrc unimplemented"); } static int bhnd_bus_null_release_ext_rsrc(device_t dev, device_t child, u_int rsrc) { panic("bhnd_bus_release_ext_rsrc unimplemented"); } static int bhnd_bus_null_read_config(device_t dev, device_t child, bus_size_t offset, void *value, u_int width) { panic("bhnd_bus_null_read_config unimplemented"); } static void bhnd_bus_null_write_config(device_t dev, device_t child, bus_size_t offset, void *value, u_int width) { panic("bhnd_bus_null_write_config unimplemented"); } static device_t bhnd_bus_null_find_hostb_device(device_t dev) { return (NULL); } static struct bhnd_service_registry * bhnd_bus_null_get_service_registry(device_t dev) { panic("bhnd_bus_get_service_registry unimplemented"); } static bool bhnd_bus_null_is_hw_disabled(device_t dev, device_t child) { panic("bhnd_bus_is_hw_disabled unimplemented"); } static int bhnd_bus_null_get_probe_order(device_t dev, device_t child) { 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) { return (-1); } static int bhnd_bus_null_decode_port_rid(device_t dev, device_t child, int type, int rid, bhnd_port_type *port_type, u_int *port, u_int *region) { return (ENOENT); } static int bhnd_bus_null_get_region_addr(device_t dev, device_t child, bhnd_port_type type, u_int port, u_int region, bhnd_addr_t *addr, bhnd_size_t *size) { return (ENOENT); } static int bhnd_bus_null_get_nvram_var(device_t dev, device_t child, const char *name, void *buf, size_t *size, bhnd_nvram_type type) { return (ENODEV); } } /** * Return the bhnd(4) bus driver's device enumeration parser class. * * @param driver The bhnd bus driver instance. */ STATICMETHOD bhnd_erom_class_t * get_erom_class { driver_t *driver; } DEFAULT bhnd_bus_null_get_erom_class; /** * Register a shared bus @p provider for a given @p service. * * @param dev The parent of @p child. * @param child The requesting child device. * @param provider The service provider to register. * @param service The service for which @p provider will be registered. * * @retval 0 success * @retval EEXIST if an entry for @p service already exists. * @retval non-zero if registering @p provider otherwise fails, a regular * unix error code will be returned. */ METHOD int register_provider { device_t dev; device_t child; device_t provider; bhnd_service_t service; } DEFAULT bhnd_bus_generic_register_provider; /** * Attempt to remove the @p service provider registration for @p provider. * * @param dev The parent of @p child. * @param child The requesting child device. * @param provider The service provider to be deregistered. * @param service The service for which @p provider will be deregistered, * or BHND_SERVICE_INVALID to remove all service * registrations for @p provider. * * @retval 0 success * @retval EBUSY if active references to @p provider exist; @see * BHND_BUS_RETAIN_PROVIDER() and * BHND_BUS_RELEASE_PROVIDER(). */ METHOD int deregister_provider { device_t dev; device_t child; device_t provider; bhnd_service_t service; } DEFAULT bhnd_bus_generic_deregister_provider; /** * Retain and return a reference to the registered @p service provider, if any. * * @param dev The parent of @p child. * @param child The requesting child device. * @param service The service for which a provider should be returned. * * On success, the caller assumes ownership the returned provider, and * is responsible for releasing this reference via * BHND_BUS_RELEASE_PROVIDER(). * * @retval device_t success * @retval NULL if no provider is registered for @p service. */ METHOD device_t retain_provider { device_t dev; device_t child; bhnd_service_t service; } DEFAULT bhnd_bus_generic_retain_provider; /** * Release a reference to a service provider previously returned by * BHND_BUS_RETAIN_PROVIDER(). * * @param dev The parent of @p child. * @param child The requesting child device. * @param provider The provider to be released. * @param service The service for which @p provider was previously * retained. */ METHOD void release_provider { device_t dev; device_t child; device_t provider; bhnd_service_t service; } DEFAULT bhnd_bus_generic_release_provider; /** * Return a struct bhnd_service_registry. * * Used by drivers which use bhnd_bus_generic_sr_register_provider() etc. * to implement service provider registration. It should return a service * registry that may be used to resolve provider requests from @p child. * * @param dev The parent of @p child. * @param child The requesting child device. */ METHOD struct bhnd_service_registry * get_service_registry { device_t dev; device_t child; } DEFAULT bhnd_bus_null_get_service_registry; /** * Return the active host bridge core for the bhnd bus, if any. * * @param dev The bhnd bus device. * * @retval device_t if a hostb device exists * @retval NULL if no hostb device is found. */ METHOD device_t find_hostb_device { device_t dev; } DEFAULT bhnd_bus_null_find_hostb_device; /** * Return true if the hardware components required by @p child are unpopulated * or otherwise unusable. * * In some cases, enumerated devices may have pins that are left floating, or * the hardware may otherwise be non-functional; this method allows a parent * device to explicitly specify if a successfully enumerated @p child should * be disabled. * * @param dev The device whose child is being examined. * @param child The child device. */ METHOD bool is_hw_disabled { device_t dev; device_t child; } DEFAULT bhnd_bus_null_is_hw_disabled; /** * Return the probe (and attach) order for @p child. * * All devices on the bhnd(4) bus will be probed, attached, or resumed in * ascending order; they will be suspended, shutdown, and detached in * descending order. * * The following device methods will be dispatched in ascending probe order * by the bus: * * - DEVICE_PROBE() * - DEVICE_ATTACH() * - DEVICE_RESUME() * * The following device methods will be dispatched in descending probe order * by the bus: * * - DEVICE_SHUTDOWN() * - DEVICE_DETACH() * - DEVICE_SUSPEND() * * @param dev The device whose child is being examined. * @param child The child device. * * Refer to BHND_PROBE_* and BHND_PROBE_ORDER_* for the standard set of * priorities. */ METHOD int get_probe_order { device_t dev; device_t child; } DEFAULT bhnd_bus_null_get_probe_order; /** * Return the BHND chip identification for the parent bus. * * @param dev The device whose child is being examined. * @param child The child device. */ METHOD const struct bhnd_chipid * get_chipid { device_t dev; device_t child; } DEFAULT bhnd_bus_null_get_chipid; /** * Return the BHND attachment type of the parent bus. * * @param dev The device whose child is being examined. * @param child The child device. * * @retval BHND_ATTACH_ADAPTER if the bus is resident on a bridged adapter, * such as a WiFi chipset. * @retval BHND_ATTACH_NATIVE if the bus provides hardware services (clock, * CPU, etc) to a directly attached native host. */ METHOD bhnd_attach_type get_attach_type { device_t dev; device_t child; } DEFAULT bhnd_bus_null_get_attach_type; /** * Attempt to read the BHND board identification from the parent bus. * * This relies on NVRAM access, and will fail if a valid NVRAM device cannot * be found, or is not yet attached. * * @param dev The parent of @p child. * @param child The bhnd device requesting board info. * @param[out] info On success, will be populated with the bhnd(4) device's * board information. * * @retval 0 success * @retval ENODEV No valid NVRAM source could be found. * @retval non-zero If reading @p name otherwise fails, a regular unix * error code will be returned. */ METHOD int read_board_info { device_t dev; device_t child; struct bhnd_board_info *info; } 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 * after @p child's bus state is fully initialized. * * @param dev The bhnd bus whose child is being added. * @param child The child added to @p dev. */ METHOD void child_added { device_t dev; device_t child; } DEFAULT bhnd_bus_null_child_added; /** * Read the current value of @p child's I/O control register. * * @param dev The bhnd bus parent of @p child. * @param child The bhnd device for which the I/O control register should be * read. * @param[out] ioctl On success, the I/O control register value. * * @retval 0 success * @retval EINVAL If @p child is not a direct child of @p dev. * @retval ENODEV If agent/config space for @p child is unavailable. * @retval non-zero If reading the IOCTL register otherwise fails, a regular * unix error code will be returned. */ METHOD int read_ioctl { device_t dev; device_t child; uint16_t *ioctl; } DEFAULT bhnd_bus_null_read_ioctl; /** * Write @p value with @p mask to @p child's I/O control register. * * @param dev The bhnd bus parent of @p child. * @param child The bhnd device for which the I/O control register should * be updated. * @param value The value to be written (see also BHND_IOCTL_*). * @param mask Only the bits defined by @p mask will be updated from @p value. * * @retval 0 success * @retval EINVAL If @p child is not a direct child of @p dev. * @retval ENODEV If agent/config space for @p child is unavailable. * @retval non-zero If writing the IOCTL register otherwise fails, a regular * unix error code will be returned. */ METHOD int write_ioctl { device_t dev; device_t child; uint16_t value; uint16_t mask; } DEFAULT bhnd_bus_null_write_ioctl; /** * Read the current value of @p child's I/O status register. * * @param dev The bhnd bus parent of @p child. * @param child The bhnd device for which the I/O status register should be * read. * @param[out] iost On success, the I/O status register value. * * @retval 0 success * @retval EINVAL If @p child is not a direct child of @p dev. * @retval ENODEV If agent/config space for @p child is unavailable. * @retval non-zero If reading the IOST register otherwise fails, a regular * unix error code will be returned. */ METHOD int read_iost { device_t dev; device_t child; uint16_t *iost; } DEFAULT bhnd_bus_null_read_iost; /** * Return true if the given bhnd device's hardware is currently held * in a RESET state or otherwise not clocked (BHND_IOCTL_CLK_EN). * * @param dev The bhnd bus parent of @p child. * @param child The device to query. * * @retval true If @p child is held in RESET or not clocked (BHND_IOCTL_CLK_EN), * or an error occured determining @p child's hardware state. * @retval false If @p child is clocked and is not held in RESET. */ METHOD bool is_hw_suspended { device_t dev; device_t child; } DEFAULT bhnd_bus_null_is_hw_suspended; /** * Place the bhnd(4) device's hardware into a reset state, and then bring the * hardware out of reset with BHND_IOCTL_CLK_EN and @p ioctl flags set. * * Any clock or resource PMU requests previously made by @p child will be * invalidated. * * @param dev The bhnd bus parent of @p child. * @param child The device to be reset. * @param ioctl Device-specific core ioctl flags to be supplied on reset * (see BHND_IOCTL_*). * * @retval 0 success * @retval non-zero error */ METHOD int reset_hw { device_t dev; device_t child; uint16_t ioctl; } DEFAULT bhnd_bus_null_reset_hw; /** * Suspend @p child's hardware in a low-power reset state. * * Any clock or resource PMU requests previously made by @p dev will be * invalidated. * * The hardware may be brought out of reset via bhnd_reset_hw(). * * @param dev The bhnd bus parent of @P child. * @param dev The device to be suspended. * * @retval 0 success * @retval non-zero error */ METHOD int suspend_hw { device_t dev; device_t child; } DEFAULT bhnd_bus_null_suspend_hw; /** * If supported by the chipset, return the clock source for the given clock. * * This function is only supported on early PWRCTL-equipped chipsets * that expose clock management via their host bridge interface. Currently, * this includes PCI (not PCIe) devices, with ChipCommon core revisions 0-9. * * @param dev The parent of @p child. * @param child The bhnd device requesting a clock source. * @param clock The clock for which a clock source will be returned. * * @retval bhnd_clksrc The clock source for @p clock. * @retval BHND_CLKSRC_UNKNOWN If @p clock is unsupported, or its * clock source is not known to the bus. */ METHOD bhnd_clksrc pwrctl_get_clksrc { device_t dev; device_t child; bhnd_clock clock; } DEFAULT bhnd_bus_null_pwrctl_get_clksrc; /** * If supported by the chipset, gate the clock source for @p clock * * This function is only supported on early PWRCTL-equipped chipsets * that expose clock management via their host bridge interface. Currently, * this includes PCI (not PCIe) devices, with ChipCommon core revisions 0-9. * * @param dev The parent of @p child. * @param child The bhnd device requesting clock gating. * @param clock The clock to be disabled. * * @retval 0 success * @retval ENODEV If bus-level clock source management is not supported. * @retval ENXIO If bus-level management of @p clock is not supported. */ METHOD int pwrctl_gate_clock { device_t dev; device_t child; bhnd_clock clock; } DEFAULT bhnd_bus_null_pwrctl_gate_clock; /** * If supported by the chipset, ungate the clock source for @p clock * * This function is only supported on early PWRCTL-equipped chipsets * that expose clock management via their host bridge interface. Currently, * this includes PCI (not PCIe) devices, with ChipCommon core revisions 0-9. * * @param dev The parent of @p child. * @param child The bhnd device requesting clock gating. * @param clock The clock to be enabled. * * @retval 0 success * @retval ENODEV If bus-level clock source management is not supported. * @retval ENXIO If bus-level management of @p clock is not supported. */ METHOD int pwrctl_ungate_clock { device_t dev; device_t child; bhnd_clock clock; } DEFAULT bhnd_bus_null_pwrctl_ungate_clock; /** * Allocate and enable per-core PMU request handling for @p child. * * The region containing the core's PMU register block (if any) must be * allocated via bus_alloc_resource(9) (or bhnd_alloc_resource) before * calling BHND_BUS_ALLOC_PMU(), and must not be released until after * calling BHND_BUS_RELEASE_PMU(). * * @param dev The parent of @p child. * @param child The requesting bhnd device. */ METHOD int alloc_pmu { device_t dev; device_t child; } DEFAULT bhnd_bus_null_alloc_pmu; /** * Release per-core PMU resources allocated for @p child. Any * outstanding PMU requests are discarded. * * @param dev The parent of @p child. * @param child The requesting bhnd device. */ METHOD int release_pmu { device_t dev; device_t child; } DEFAULT bhnd_bus_null_release_pmu; /** * Request that @p clock (or faster) be routed to @p child. * * @note A driver must ask the bhnd bus to allocate PMU request state * via BHND_BUS_ALLOC_PMU() before it can request clock resources. * * @note Any outstanding PMU clock requests will be discarded upon calling * BHND_BUS_RESET_HW() or BHND_BUS_SUSPEND_HW(). * * @param dev The parent of @p child. * @param child The bhnd device requesting @p clock. * @param clock The requested clock source. * * @retval 0 success * @retval ENODEV If an unsupported clock was requested. * @retval ENXIO If the PMU has not been initialized or is otherwise unvailable. */ METHOD int request_clock { device_t dev; device_t child; bhnd_clock clock; } DEFAULT bhnd_bus_null_request_clock; /** * Request that @p clocks be powered on behalf of @p child. * * This will power on clock sources (e.g. XTAL, PLL, etc) required for * @p clocks and wait until they are ready, discarding any previous * requests by @p child. * * @note A driver must ask the bhnd bus to allocate PMU request state * via BHND_BUS_ALLOC_PMU() before it can request clock resources. * * @note Any outstanding PMU clock requests will be discarded upon calling * BHND_BUS_RESET_HW() or BHND_BUS_SUSPEND_HW(). * * @param dev The parent of @p child. * @param child The bhnd device requesting @p clock. * @param clock The requested clock source. * * @retval 0 success * @retval ENODEV If an unsupported clock was requested. * @retval ENXIO If the PMU has not been initialized or is otherwise unvailable. */ METHOD int enable_clocks { device_t dev; device_t child; uint32_t clocks; } DEFAULT bhnd_bus_null_enable_clocks; /** * Power up an external PMU-managed resource assigned to @p child. * * @note A driver must ask the bhnd bus to allocate PMU request state * via BHND_BUS_ALLOC_PMU() before it can request PMU resources. * * @note Any outstanding PMU resource requests will be released upon calling * BHND_BUS_RESET_HW() or BHND_BUS_SUSPEND_HW(). * * @param dev The parent of @p child. * @param child The bhnd device requesting @p rsrc. * @param rsrc The core-specific external resource identifier. * * @retval 0 success * @retval ENODEV If the PMU does not support @p rsrc. * @retval ENXIO If the PMU has not been initialized or is otherwise unvailable. */ METHOD int request_ext_rsrc { device_t dev; device_t child; u_int rsrc; } DEFAULT bhnd_bus_null_request_ext_rsrc; /** * Power down an external PMU-managed resource assigned to @p child. * * @note A driver must ask the bhnd bus to allocate PMU request state * via BHND_BUS_ALLOC_PMU() before it can request PMU resources. * * @param dev The parent of @p child. * @param child The bhnd device requesting @p rsrc. * @param rsrc The core-specific external resource number. * * @retval 0 success * @retval ENODEV If the PMU does not support @p rsrc. * @retval ENXIO If the PMU has not been initialized or is otherwise unvailable. */ METHOD int release_ext_rsrc { device_t dev; device_t child; u_int rsrc; } DEFAULT bhnd_bus_null_release_ext_rsrc; /** * Read @p width bytes at @p offset from the bus-specific agent/config * space of @p child. * * @param dev The parent of @p child. * @param child The bhnd device for which @p offset should be read. * @param offset The offset to be read. * @param[out] value On success, the bytes read at @p offset. * @param width The size of the access. Must be 1, 2 or 4 bytes. * * The exact behavior of this method is bus-specific. On a bcma(4) bus, this * method provides access to the first agent port of @p child; on a siba(4) bus, * this method provides access to the core's CFG0 register block. * * @note Device drivers should only use this API for functionality * that is not available via another bhnd(4) function. * * @retval 0 success * @retval EINVAL If @p child is not a direct child of @p dev. * @retval EINVAL If @p width is not one of 1, 2, or 4 bytes. * @retval ENODEV If accessing agent/config space for @p child is unsupported. * @retval EFAULT If reading @p width at @p offset exceeds the bounds of * the mapped agent/config space for @p child. */ METHOD int read_config { device_t dev; device_t child; bus_size_t offset; void *value; u_int width; } DEFAULT bhnd_bus_null_read_config; /** * Read @p width bytes at @p offset from the bus-specific agent/config * space of @p child. * * @param dev The parent of @p child. * @param child The bhnd device for which @p offset should be read. * @param offset The offset to be written. * @param value A pointer to the value to be written. * @param width The size of @p value. Must be 1, 2 or 4 bytes. * * The exact behavior of this method is bus-specific. In the case of * bcma(4), this method provides access to the first agent port of @p child. * * @note Device drivers should only use this API for functionality * that is not available via another bhnd(4) function. * * @retval 0 success * @retval EINVAL If @p child is not a direct child of @p dev. * @retval EINVAL If @p width is not one of 1, 2, or 4 bytes. * @retval ENODEV If accessing agent/config space for @p child is unsupported. * @retval EFAULT If reading @p width at @p offset exceeds the bounds of * the mapped agent/config space for @p child. */ METHOD int write_config { device_t dev; device_t child; bus_size_t offset; const void *value; u_int width; } DEFAULT bhnd_bus_null_write_config; /** * Allocate a bhnd resource. * * This method's semantics are functionally identical to the bus API of the same * name; refer to BUS_ALLOC_RESOURCE for complete documentation. */ METHOD struct bhnd_resource * alloc_resource { device_t dev; device_t child; int type; int *rid; rman_res_t start; rman_res_t end; rman_res_t count; u_int flags; } DEFAULT bhnd_bus_generic_alloc_resource; /** * Release a bhnd resource. * * This method's semantics are functionally identical to the bus API of the same * name; refer to BUS_RELEASE_RESOURCE for complete documentation. */ METHOD int release_resource { device_t dev; device_t child; int type; int rid; struct bhnd_resource *res; } DEFAULT bhnd_bus_generic_release_resource; /** * Activate a bhnd resource. * * This method's semantics are functionally identical to the bus API of the same * name; refer to BUS_ACTIVATE_RESOURCE for complete documentation. */ METHOD int activate_resource { device_t dev; device_t child; int type; int rid; struct bhnd_resource *r; } DEFAULT bhnd_bus_generic_activate_resource; /** * Deactivate a bhnd resource. * * This method's semantics are functionally identical to the bus API of the same * name; refer to BUS_DEACTIVATE_RESOURCE for complete documentation. */ METHOD int deactivate_resource { device_t dev; device_t child; int type; 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 * @p type attached to @p child. * * @param dev The device whose child is being examined. * @param child The child device. * @param type The port type being queried. * @param port_num The port number being queried. * @param region_num The region number being queried. */ METHOD bool is_region_valid { device_t dev; device_t child; bhnd_port_type type; u_int port_num; u_int region_num; }; /** * Return the number of ports of type @p type attached to @p child. * * @param dev The device whose child is being examined. * @param child The child device. * @param type The port type being queried. */ METHOD u_int get_port_count { device_t dev; device_t child; bhnd_port_type type; }; /** * Return the number of memory regions mapped to @p child @p port of * type @p type. * * @param dev The device whose child is being examined. * @param child The child device. * @param port The port number being queried. * @param type The port type being queried. */ METHOD u_int get_region_count { device_t dev; device_t child; bhnd_port_type type; u_int port; }; /** * Return the SYS_RES_MEMORY resource-ID for a port/region pair attached to * @p child. * * @param dev The bus device. * @param child The bhnd child. * @param port_type The port type. * @param port_num The index of the child interconnect port. * @param region_num The index of the port-mapped address region. * * @retval -1 No such port/region found. */ METHOD int get_port_rid { device_t dev; device_t child; bhnd_port_type port_type; u_int port_num; u_int region_num; } DEFAULT bhnd_bus_null_get_port_rid; /** * Decode a port / region pair on @p child defined by @p type and @p rid. * * @param dev The bus device. * @param child The bhnd child. * @param type The resource type. * @param rid The resource ID. * @param[out] port_type The port's type. * @param[out] port The port identifier. * @param[out] region The identifier of the memory region on @p port. * * @retval 0 success * @retval non-zero No matching type/rid found. */ METHOD int decode_port_rid { device_t dev; device_t child; int type; int rid; bhnd_port_type *port_type; u_int *port; u_int *region; } DEFAULT bhnd_bus_null_decode_port_rid; /** * Get the address and size of @p region on @p port. * * @param dev The bus device. * @param child The bhnd child. * @param port_type The port type. * @param port The port identifier. * @param region The identifier of the memory region on @p port. * @param[out] region_addr The region's base address. * @param[out] region_size The region's size. * * @retval 0 success * @retval non-zero No matching port/region found. */ METHOD int get_region_addr { device_t dev; device_t child; bhnd_port_type port_type; u_int port; u_int region; bhnd_addr_t *region_addr; bhnd_size_t *region_size; } DEFAULT bhnd_bus_null_get_region_addr; /** * Read an NVRAM variable. * * It is the responsibility of the bus to delegate this request to * the appropriate NVRAM child device, or to a parent bus implementation. * * @param dev The bus device. * @param child The requesting device. * @param name The NVRAM variable name. * @param[out] buf On success, the requested value will be written * to this buffer. This argment may be NULL if * the value is not desired. * @param[in,out] size The capacity of @p buf. On success, will be set * to the actual size of the requested value. * @param type The data type to be written to @p buf. * * @retval 0 success * @retval ENOENT The requested variable was not found. * @retval ENOMEM If @p buf is non-NULL and a buffer of @p size is too * small to hold the requested value. * @retval ENODEV No valid NVRAM source could be found. * @retval EFTYPE If the @p name's data type cannot be coerced to @p type. * @retval ERANGE If value coercion would overflow @p type. * @retval non-zero If reading @p name otherwise fails, a regular unix * error code will be returned. */ METHOD int get_nvram_var { device_t dev; device_t child; const char *name; void *buf; size_t *size; bhnd_nvram_type type; } DEFAULT bhnd_bus_null_get_nvram_var; /** An implementation of bus_read_1() compatible with bhnd_resource */ METHOD uint8_t read_1 { device_t dev; device_t child; struct bhnd_resource *r; bus_size_t offset; } /** An implementation of bus_read_2() compatible with bhnd_resource */ METHOD uint16_t read_2 { device_t dev; device_t child; struct bhnd_resource *r; bus_size_t offset; } /** An implementation of bus_read_4() compatible with bhnd_resource */ METHOD uint32_t read_4 { device_t dev; device_t child; struct bhnd_resource *r; bus_size_t offset; } /** An implementation of bus_write_1() compatible with bhnd_resource */ METHOD void write_1 { device_t dev; device_t child; struct bhnd_resource *r; bus_size_t offset; uint8_t value; } /** An implementation of bus_write_2() compatible with bhnd_resource */ METHOD void write_2 { device_t dev; device_t child; struct bhnd_resource *r; bus_size_t offset; uint16_t value; } /** An implementation of bus_write_4() compatible with bhnd_resource */ METHOD void write_4 { device_t dev; device_t child; struct bhnd_resource *r; bus_size_t offset; uint32_t value; } /** An implementation of bus_read_stream_1() compatible with bhnd_resource */ METHOD uint8_t read_stream_1 { device_t dev; device_t child; struct bhnd_resource *r; bus_size_t offset; } /** An implementation of bus_read_stream_2() compatible with bhnd_resource */ METHOD uint16_t read_stream_2 { device_t dev; device_t child; struct bhnd_resource *r; bus_size_t offset; } /** An implementation of bus_read_stream_4() compatible with bhnd_resource */ METHOD uint32_t read_stream_4 { device_t dev; device_t child; struct bhnd_resource *r; bus_size_t offset; } /** An implementation of bus_write_stream_1() compatible with bhnd_resource */ METHOD void write_stream_1 { device_t dev; device_t child; struct bhnd_resource *r; bus_size_t offset; uint8_t value; } /** An implementation of bus_write_stream_2() compatible with bhnd_resource */ METHOD void write_stream_2 { device_t dev; device_t child; struct bhnd_resource *r; bus_size_t offset; uint16_t value; } /** An implementation of bus_write_stream_4() compatible with bhnd_resource */ METHOD void write_stream_4 { device_t dev; device_t child; struct bhnd_resource *r; bus_size_t offset; uint32_t value; } /** An implementation of bus_read_multi_1() compatible with bhnd_resource */ METHOD void read_multi_1 { device_t dev; device_t child; struct bhnd_resource *r; bus_size_t offset; uint8_t *datap; bus_size_t count; } /** An implementation of bus_read_multi_2() compatible with bhnd_resource */ METHOD void read_multi_2 { device_t dev; device_t child; struct bhnd_resource *r; bus_size_t offset; uint16_t *datap; bus_size_t count; } /** An implementation of bus_read_multi_4() compatible with bhnd_resource */ METHOD void read_multi_4 { device_t dev; device_t child; struct bhnd_resource *r; bus_size_t offset; uint32_t *datap; bus_size_t count; } /** An implementation of bus_write_multi_1() compatible with bhnd_resource */ METHOD void write_multi_1 { device_t dev; device_t child; struct bhnd_resource *r; bus_size_t offset; uint8_t *datap; bus_size_t count; } /** An implementation of bus_write_multi_2() compatible with bhnd_resource */ METHOD void write_multi_2 { device_t dev; device_t child; struct bhnd_resource *r; bus_size_t offset; uint16_t *datap; bus_size_t count; } /** An implementation of bus_write_multi_4() compatible with bhnd_resource */ METHOD void write_multi_4 { device_t dev; device_t child; struct bhnd_resource *r; bus_size_t offset; uint32_t *datap; bus_size_t count; } /** An implementation of bus_read_multi_stream_1() compatible * bhnd_resource */ METHOD void read_multi_stream_1 { device_t dev; device_t child; struct bhnd_resource *r; bus_size_t offset; uint8_t *datap; bus_size_t count; } /** An implementation of bus_read_multi_stream_2() compatible * bhnd_resource */ METHOD void read_multi_stream_2 { device_t dev; device_t child; struct bhnd_resource *r; bus_size_t offset; uint16_t *datap; bus_size_t count; } /** An implementation of bus_read_multi_stream_4() compatible * bhnd_resource */ METHOD void read_multi_stream_4 { device_t dev; device_t child; struct bhnd_resource *r; bus_size_t offset; uint32_t *datap; bus_size_t count; } /** An implementation of bus_write_multi_stream_1() compatible * bhnd_resource */ METHOD void write_multi_stream_1 { device_t dev; device_t child; struct bhnd_resource *r; bus_size_t offset; uint8_t *datap; bus_size_t count; } /** An implementation of bus_write_multi_stream_2() compatible with * bhnd_resource */ METHOD void write_multi_stream_2 { device_t dev; device_t child; struct bhnd_resource *r; bus_size_t offset; uint16_t *datap; bus_size_t count; } /** An implementation of bus_write_multi_stream_4() compatible with * bhnd_resource */ METHOD void write_multi_stream_4 { device_t dev; device_t child; struct bhnd_resource *r; bus_size_t offset; uint32_t *datap; bus_size_t count; } /** An implementation of bus_set_multi_1() compatible with bhnd_resource */ METHOD void set_multi_1 { device_t dev; device_t child; struct bhnd_resource *r; bus_size_t offset; uint8_t value; bus_size_t count; } /** An implementation of bus_set_multi_2() compatible with bhnd_resource */ METHOD void set_multi_2 { device_t dev; device_t child; struct bhnd_resource *r; bus_size_t offset; uint16_t value; bus_size_t count; } /** An implementation of bus_set_multi_4() compatible with bhnd_resource */ METHOD void set_multi_4 { device_t dev; device_t child; struct bhnd_resource *r; bus_size_t offset; uint32_t value; bus_size_t count; } /** An implementation of bus_set_region_1() compatible with bhnd_resource */ METHOD void set_region_1 { device_t dev; device_t child; struct bhnd_resource *r; bus_size_t offset; uint8_t value; bus_size_t count; } /** An implementation of bus_set_region_2() compatible with bhnd_resource */ METHOD void set_region_2 { device_t dev; device_t child; struct bhnd_resource *r; bus_size_t offset; uint16_t value; bus_size_t count; } /** An implementation of bus_set_region_4() compatible with bhnd_resource */ METHOD void set_region_4 { device_t dev; device_t child; struct bhnd_resource *r; bus_size_t offset; uint32_t value; bus_size_t count; } /** An implementation of bus_read_region_1() compatible with bhnd_resource */ METHOD void read_region_1 { device_t dev; device_t child; struct bhnd_resource *r; bus_size_t offset; uint8_t *datap; bus_size_t count; } /** An implementation of bus_read_region_2() compatible with bhnd_resource */ METHOD void read_region_2 { device_t dev; device_t child; struct bhnd_resource *r; bus_size_t offset; uint16_t *datap; bus_size_t count; } /** An implementation of bus_read_region_4() compatible with bhnd_resource */ METHOD void read_region_4 { device_t dev; device_t child; struct bhnd_resource *r; bus_size_t offset; uint32_t *datap; bus_size_t count; } /** An implementation of bus_read_region_stream_1() compatible with * bhnd_resource */ METHOD void read_region_stream_1 { device_t dev; device_t child; struct bhnd_resource *r; bus_size_t offset; uint8_t *datap; bus_size_t count; } /** An implementation of bus_read_region_stream_2() compatible with * bhnd_resource */ METHOD void read_region_stream_2 { device_t dev; device_t child; struct bhnd_resource *r; bus_size_t offset; uint16_t *datap; bus_size_t count; } /** An implementation of bus_read_region_stream_4() compatible with * bhnd_resource */ METHOD void read_region_stream_4 { device_t dev; device_t child; struct bhnd_resource *r; bus_size_t offset; uint32_t *datap; bus_size_t count; } /** An implementation of bus_write_region_1() compatible with bhnd_resource */ METHOD void write_region_1 { device_t dev; device_t child; struct bhnd_resource *r; bus_size_t offset; uint8_t *datap; bus_size_t count; } /** An implementation of bus_write_region_2() compatible with bhnd_resource */ METHOD void write_region_2 { device_t dev; device_t child; struct bhnd_resource *r; bus_size_t offset; uint16_t *datap; bus_size_t count; } /** An implementation of bus_write_region_4() compatible with bhnd_resource */ METHOD void write_region_4 { device_t dev; device_t child; struct bhnd_resource *r; bus_size_t offset; uint32_t *datap; bus_size_t count; } /** An implementation of bus_write_region_stream_1() compatible with * bhnd_resource */ METHOD void write_region_stream_1 { device_t dev; device_t child; struct bhnd_resource *r; bus_size_t offset; uint8_t *datap; bus_size_t count; } /** An implementation of bus_write_region_stream_2() compatible with * bhnd_resource */ METHOD void write_region_stream_2 { device_t dev; device_t child; struct bhnd_resource *r; bus_size_t offset; uint16_t *datap; bus_size_t count; } /** An implementation of bus_write_region_stream_4() compatible with * bhnd_resource */ METHOD void write_region_stream_4 { device_t dev; device_t child; struct bhnd_resource *r; bus_size_t offset; uint32_t *datap; bus_size_t count; } /** An implementation of bus_barrier() compatible with bhnd_resource */ METHOD void barrier { device_t dev; device_t child; struct bhnd_resource *r; bus_size_t offset; bus_size_t length; int flags; } Index: head/sys/dev/bhnd/bhnd_ids.h =================================================================== --- head/sys/dev/bhnd/bhnd_ids.h (revision 326078) +++ head/sys/dev/bhnd/bhnd_ids.h (revision 326079) @@ -1,1127 +1,1131 @@ /*- * Copyright (c) 2015-2016 Landon Fuller * Copyright (c) 1999-2015, Broadcom Corporation * * This file is derived from the bcmdevs.h header contributed by Broadcom * to Android's bcmdhd driver module, later revisions of bcmdevs.h distributed * with the dd-wrt project, and the hndsoc.h header distributed with Broadcom's * initial brcm80211 Linux driver release as contributed to the Linux staging * repository. * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * * $FreeBSD$ */ #ifndef _BHND_BHND_IDS_H_ #define _BHND_BHND_IDS_H_ /* * JEDEC JEP-106 Core Vendor IDs * * These are the JEDEC JEP-106 manufacturer ID representions (with ARM's * non-standard 4-bit continutation code), as used in ARM's PrimeCell * identification registers, bcma(4) EROM core descriptors, etc. * * @note * Bus implementations that predate the adoption of ARM IP * will need to convert bus-specific vendor IDs to their BHND_MFGID * JEP-106 equivalents. * * @par ARM 4-bit Continuation Code * * BHND MFGIDs are encoded using ARM's non-standard 4-bit continuation code * format: * * @code{.unparsed} * [11:8 ][7:0 ] * [cont code][mfg id] * @endcode * * The 4-bit continuation code field specifies the number of JEP-106 * continuation codes that prefix the manufacturer's ID code. In the case of * ARM's JEP-106 ID of `0x7F 0x7F 0x7F 0x7F 0x3B`, the four 0x7F continuations * are encoded as '4' in the 4-bit continuation code field (i.e. 0x43B). */ #define BHND_MFGID_ARM 0x043b /**< arm JEP-106 vendor id */ #define BHND_MFGID_BCM 0x04bf /**< broadcom JEP-106 vendor id */ #define BHND_MFGID_MIPS 0x04a7 /**< mips JEP-106 vendor id */ #define BHND_MFGID_INVALID 0x0000 /**< invalid JEP-106 vendor id */ /* * OCP (Open Core Protocol) Vendor IDs. * * OCP-IP assigned vendor codes are used by siba(4) */ #define OCP_VENDOR_BCM 0x4243 /**< Broadcom OCP vendor id */ /* PCI vendor IDs */ #define PCI_VENDOR_EPIGRAM 0xfeda #define PCI_VENDOR_BROADCOM 0x14e4 #define PCI_VENDOR_3COM 0x10b7 #define PCI_VENDOR_NETGEAR 0x1385 #define PCI_VENDOR_DIAMOND 0x1092 #define PCI_VENDOR_INTEL 0x8086 #define PCI_VENDOR_DELL 0x1028 #define PCI_VENDOR_HP 0x103c #define PCI_VENDOR_HP_COMPAQ 0x0e11 #define PCI_VENDOR_APPLE 0x106b #define PCI_VENDOR_SI_IMAGE 0x1095 /* Silicon Image, used by Arasan SDIO Host */ #define PCI_VENDOR_BUFFALO 0x1154 /* Buffalo vendor id */ #define PCI_VENDOR_TI 0x104c /* Texas Instruments */ #define PCI_VENDOR_RICOH 0x1180 /* Ricoh */ #define PCI_VENDOR_JMICRON 0x197b /* PCMCIA vendor IDs */ #define PCMCIA_VENDOR_BROADCOM 0x02d0 /* SDIO vendor IDs */ #define SDIO_VENDOR_BROADCOM 0x00BF /* USB dongle VID/PIDs */ #define USB_VID_BROADCOM 0x0a5c #define USB_PID_BCM4328 0xbd12 #define USB_PID_BCM4322 0xbd13 #define USB_PID_BCM4319 0xbd16 #define USB_PID_BCM43236 0xbd17 #define USB_PID_BCM4332 0xbd18 #define USB_PID_BCM4330 0xbd19 #define USB_PID_BCM4334 0xbd1a #define USB_PID_BCM43239 0xbd1b #define USB_PID_BCM4324 0xbd1c #define USB_PID_BCM4360 0xbd1d #define USB_PID_BCM43143 0xbd1e #define USB_PID_BCM43242 0xbd1f #define USB_PID_BCM43342 0xbd21 #define USB_PID_BCM4335 0xbd20 #define USB_PID_BCM4350 0xbd23 #define USB_PID_BCM43341 0xbd22 #define USB_PID_BCM_DNGL_BDC 0x0bdc /* BDC USB device controller IP? */ #define USB_PID_BCM_DNGL_JTAG 0x4a44 /* HW USB BLOCK [CPULESS USB] PIDs */ #define USB_PID_CCM_HWUSB_43239 43239 /* PCI Device IDs */ #define PCI_DEVID_BCM4210 0x1072 /* never used */ #define PCI_DEVID_BCM4230 0x1086 /* never used */ #define PCI_DEVID_BCM4401_ENET 0x170c /* 4401b0 production enet cards */ #define PCI_DEVID_BCM3352 0x3352 /* bcm3352 device id */ #define PCI_DEVID_BCM3360 0x3360 /* bcm3360 device id */ #define PCI_DEVID_BCM4211 0x4211 #define PCI_DEVID_BCM4231 0x4231 #define PCI_DEVID_BCM4301 0x4301 /* 4031 802.11b */ #define PCI_DEVID_BCM4303_D11B 0x4303 /* 4303 802.11b */ #define PCI_DEVID_BCM4306 0x4306 /* 4306 802.11b/g */ #define PCI_DEVID_BCM4307 0x4307 /* 4307 802.11b, 10/100 ethernet, V.92 modem */ #define PCI_DEVID_BCM4311_D11G 0x4311 /* 4311 802.11b/g id */ #define PCI_DEVID_BCM4311_D11DUAL 0x4312 /* 4311 802.11a/b/g id */ #define PCI_DEVID_BCM4311_D11A 0x4313 /* 4311 802.11a id */ #define PCI_DEVID_BCM4328_D11DUAL 0x4314 /* 4328/4312 802.11a/g id */ #define PCI_DEVID_BCM4328_D11G 0x4315 /* 4328/4312 802.11g id */ #define PCI_DEVID_BCM4328_D11A 0x4316 /* 4328/4312 802.11a id */ #define PCI_DEVID_BCM4318_D11G 0x4318 /* 4318 802.11b/g id */ #define PCI_DEVID_BCM4318_D11DUAL 0x4319 /* 4318 802.11a/b/g id */ #define PCI_DEVID_BCM4318_D11A 0x431a /* 4318 802.11a id */ #define PCI_DEVID_BCM4325_D11DUAL 0x431b /* 4325 802.11a/g id */ #define PCI_DEVID_BCM4325_D11G 0x431c /* 4325 802.11g id */ #define PCI_DEVID_BCM4325_D11A 0x431d /* 4325 802.11a id */ #define PCI_DEVID_BCM4306_D11G 0x4320 /* 4306 802.11g */ #define PCI_DEVID_BCM4306_D11A 0x4321 /* 4306 802.11a */ #define PCI_DEVID_BCM4306_UART 0x4322 /* 4306 uart */ #define PCI_DEVID_BCM4306_V90 0x4323 /* 4306 v90 codec */ #define PCI_DEVID_BCM4306_D11DUAL 0x4324 /* 4306 dual A+B */ #define PCI_DEVID_BCM4306_D11G_ID2 0x4325 /* BCM4306_D11G; INF w/loose binding war */ #define PCI_DEVID_BCM4321_D11N 0x4328 /* 4321 802.11n dualband id */ #define PCI_DEVID_BCM4321_D11N2G 0x4329 /* 4321 802.11n 2.4Ghz band id */ #define PCI_DEVID_BCM4321_D11N5G 0x432a /* 4321 802.11n 5Ghz band id */ #define PCI_DEVID_BCM4322_D11N 0x432b /* 4322 802.11n dualband device */ #define PCI_DEVID_BCM4322_D11N2G 0x432c /* 4322 802.11n 2.4GHz device */ #define PCI_DEVID_BCM4322_D11N5G 0x432d /* 4322 802.11n 5GHz device */ #define PCI_DEVID_BCM4329_D11N 0x432e /* 4329 802.11n dualband device */ #define PCI_DEVID_BCM4329_D11N2G 0x432f /* 4329 802.11n 2.4G device */ #define PCI_DEVID_BCM4329_D11N5G 0x4330 /* 4329 802.11n 5G device */ #define PCI_DEVID_BCM4315_D11DUAL 0x4334 /* 4315 802.11a/g id */ #define PCI_DEVID_BCM4315_D11G 0x4335 /* 4315 802.11g id */ #define PCI_DEVID_BCM4315_D11A 0x4336 /* 4315 802.11a id */ #define PCI_DEVID_BCM4319_D11N 0x4337 /* 4319 802.11n dualband device */ #define PCI_DEVID_BCM4319_D11N2G 0x4338 /* 4319 802.11n 2.4G device */ #define PCI_DEVID_BCM4319_D11N5G 0x4339 /* 4319 802.11n 5G device */ #define PCI_DEVID_BCM43231_D11N2G 0x4340 /* 43231 802.11n 2.4GHz device */ #define PCI_DEVID_BCM43221_D11N2G 0x4341 /* 43221 802.11n 2.4GHz device */ #define PCI_DEVID_BCM43222_D11N 0x4350 /* 43222 802.11n dualband device */ #define PCI_DEVID_BCM43222_D11N2G 0x4351 /* 43222 802.11n 2.4GHz device */ #define PCI_DEVID_BCM43222_D11N5G 0x4352 /* 43222 802.11n 5GHz device */ #define PCI_DEVID_BCM43224_D11N 0x4353 /* 43224 802.11n dualband device */ #define PCI_DEVID_BCM43224_D11N_ID_VEN1 0x0576 /* Vendor specific 43224 802.11n db device */ #define PCI_DEVID_BCM43226_D11N 0x4354 /* 43226 802.11n dualband device */ #define PCI_DEVID_BCM43236_D11N 0x4346 /* 43236 802.11n dualband device */ #define PCI_DEVID_BCM43236_D11N2G 0x4347 /* 43236 802.11n 2.4GHz device */ #define PCI_DEVID_BCM43236_D11N5G 0x4348 /* 43236 802.11n 5GHz device */ #define PCI_DEVID_BCM43225_D11N2G 0x4357 /* 43225 802.11n 2.4GHz device */ #define PCI_DEVID_BCM43421_D11N 0xA99D /* 43421 802.11n dualband device */ #define PCI_DEVID_BCM4313_D11N2G 0x4727 /* 4313 802.11n 2.4G device */ #define PCI_DEVID_BCM4330_D11N 0x4360 /* 4330 802.11n dualband device */ #define PCI_DEVID_BCM4330_D11N2G 0x4361 /* 4330 802.11n 2.4G device */ #define PCI_DEVID_BCM4330_D11N5G 0x4362 /* 4330 802.11n 5G device */ #define PCI_DEVID_BCM4336_D11N 0x4343 /* 4336 802.11n 2.4GHz device */ #define PCI_DEVID_BCM6362_D11N 0x435f /* 6362 802.11n dualband device */ #define PCI_DEVID_BCM6362_D11N2G 0x433f /* 6362 802.11n 2.4Ghz band id */ #define PCI_DEVID_BCM6362_D11N5G 0x434f /* 6362 802.11n 5Ghz band id */ #define PCI_DEVID_BCM4331_D11N 0x4331 /* 4331 802.11n dualband id */ #define PCI_DEVID_BCM4331_D11N2G 0x4332 /* 4331 802.11n 2.4Ghz band id */ #define PCI_DEVID_BCM4331_D11N5G 0x4333 /* 4331 802.11n 5Ghz band id */ #define PCI_DEVID_BCM43237_D11N 0x4355 /* 43237 802.11n dualband device */ #define PCI_DEVID_BCM43237_D11N5G 0x4356 /* 43237 802.11n 5GHz device */ #define PCI_DEVID_BCM43227_D11N2G 0x4358 /* 43228 802.11n 2.4GHz device */ #define PCI_DEVID_BCM43228_D11N 0x4359 /* 43228 802.11n DualBand device */ #define PCI_DEVID_BCM43228_D11N5G 0x435a /* 43228 802.11n 5GHz device */ #define PCI_DEVID_BCM43362_D11N 0x4363 /* 43362 802.11n 2.4GHz device */ #define PCI_DEVID_BCM43239_D11N 0x4370 /* 43239 802.11n dualband device */ #define PCI_DEVID_BCM4324_D11N 0x4374 /* 4324 802.11n dualband device */ #define PCI_DEVID_BCM43217_D11N2G 0x43a9 /* 43217 802.11n 2.4GHz device */ #define PCI_DEVID_BCM43131_D11N2G 0x43aa /* 43131 802.11n 2.4GHz device */ #define PCI_DEVID_BCM4314_D11N2G 0x4364 /* 4314 802.11n 2.4G device */ #define PCI_DEVID_BCM43142_D11N2G 0x4365 /* 43142 802.11n 2.4G device */ #define PCI_DEVID_BCM43143_D11N2G 0x4366 /* 43143 802.11n 2.4G device */ #define PCI_DEVID_BCM4334_D11N 0x4380 /* 4334 802.11n dualband device */ #define PCI_DEVID_BCM4334_D11N2G 0x4381 /* 4334 802.11n 2.4G device */ #define PCI_DEVID_BCM4334_D11N5G 0x4382 /* 4334 802.11n 5G device */ #define PCI_DEVID_BCM43342_D11N 0x4383 /* 43342 802.11n dualband device */ #define PCI_DEVID_BCM43342_D11N2G 0x4384 /* 43342 802.11n 2.4G device */ #define PCI_DEVID_BCM43342_D11N5G 0x4385 /* 43342 802.11n 5G device */ #define PCI_DEVID_BCM43341_D11N 0x4386 /* 43341 802.11n dualband device */ #define PCI_DEVID_BCM43341_D11N2G 0x4387 /* 43341 802.11n 2.4G device */ #define PCI_DEVID_BCM43341_D11N5G 0x4388 /* 43341 802.11n 5G device */ #define PCI_DEVID_BCM4360_D11AC 0x43a0 #define PCI_DEVID_BCM4360_D11AC2G 0x43a1 #define PCI_DEVID_BCM4360_D11AC5G 0x43a2 #define PCI_DEVID_BCM4335_D11AC 0x43ae #define PCI_DEVID_BCM4335_D11AC2G 0x43af #define PCI_DEVID_BCM4335_D11AC5G 0x43b0 #define PCI_DEVID_BCM4352_D11AC 0x43b1 /* 4352 802.11ac dualband device */ #define PCI_DEVID_BCM4352_D11AC2G 0x43b2 /* 4352 802.11ac 2.4G device */ #define PCI_DEVID_BCM4352_D11AC5G 0x43b3 /* 4352 802.11ac 5G device */ #define PCI_DEVID_PCIXX21_FLASHMEDIA0 0x8033 /* TI PCI xx21 Standard Host Controller */ #define PCI_DEVID_PCIXX21_SDIOH0 0x8034 /* TI PCI xx21 Standard Host Controller */ /* PCI Subsystem Vendor IDs */ #define PCI_SUBVENDOR_BCM943228HMB 0x0607 #define PCI_SUBVENDOR_BCM94313HMGBL 0x0608 #define PCI_SUBVENDOR_BCM94313HMG 0x0609 #define PCI_SUBVENDOR_BCM943142HM 0x0611 /* PCI Subsystem Device IDs */ #define PCI_SUBDEVID_BCM43143_D11N2G 0x4366 /* 43143 802.11n 2.4G device */ #define PCI_SUBDEVID_BCM43242_D11N 0x4367 /* 43242 802.11n dualband device */ #define PCI_SUBDEVID_BCM43242_D11N2G 0x4368 /* 43242 802.11n 2.4G device */ #define PCI_SUBDEVID_BCM43242_D11N5G 0x4369 /* 43242 802.11n 5G device */ #define PCI_SUBDEVID_BCM4350_D11AC 0x43a3 #define PCI_SUBDEVID_BCM4350_D11AC2G 0x43a4 #define PCI_SUBDEVID_BCM4350_D11AC5G 0x43a5 #define PCI_SUBDEVID_BCMGPRS_UART 0x4333 /* Uart id used by 4306/gprs card */ #define PCI_SUBDEVID_BCMGPRS2_UART 0x4344 /* Uart id used by 4306/gprs card */ #define PCI_SUBDEVID_BCM_FPGA_JTAGM 0x43f0 /* FPGA jtagm device id */ #define PCI_SUBDEVID_BCM_JTAGM 0x43f1 /* BCM jtagm device id */ #define PCI_SUBDEVID_BCM_SDIOH_FPGA 0x43f2 /* sdio host fpga */ #define PCI_SUBDEVID_BCM_SDIOH 0x43f3 /* BCM sdio host id */ #define PCI_SUBDEVID_BCM_SDIOD_FPGA 0x43f4 /* sdio device fpga */ #define PCI_SUBDEVID_BCM_SPIH_FPGA 0x43f5 /* PCI SPI Host Controller FPGA */ #define PCI_SUBDEVID_BCM_SPIH 0x43f6 /* Synopsis SPI Host Controller */ #define PCI_SUBDEVID_BCM_MIMO_FPGA 0x43f8 /* FPGA mimo minimacphy device id */ #define PCI_SUBDEVID_BCM_JTAGM2 0x43f9 /* PCI_SUBDEVID_BCM alternate jtagm device id */ #define PCI_SUBDEVID_BCM_SDHCI_FPGA 0x43fa /* Standard SDIO Host Controller FPGA */ #define PCI_SUBDEVID_BCM4402_ENET 0x4402 /* 4402 enet */ #define PCI_SUBDEVID_BCM4402_V90 0x4403 /* 4402 v90 codec */ #define PCI_SUBDEVID_BCM4410 0x4410 /* bcm44xx family pci iline */ #define PCI_SUBDEVID_BCM4412 0x4412 /* bcm44xx family pci enet */ #define PCI_SUBDEVID_BCM4430 0x4430 /* bcm44xx family cardbus iline */ #define PCI_SUBDEVID_BCM4432 0x4432 /* bcm44xx family cardbus enet */ #define PCI_SUBDEVID_BCM4704_ENET 0x4706 /* 4704 enet (Use 47XX_ENET_ID instead!) */ #define PCI_SUBDEVID_BCM4710 0x4710 /* 4710 primary function 0 */ #define PCI_SUBDEVID_BCM47XX_AUDIO 0x4711 /* 47xx audio codec */ #define PCI_SUBDEVID_BCM47XX_V90 0x4712 /* 47xx v90 codec */ #define PCI_SUBDEVID_BCM47XX_ENET 0x4713 /* 47xx enet */ #define PCI_SUBDEVID_BCM47XX_EXT 0x4714 /* 47xx external i/f */ #define PCI_SUBDEVID_BCM47XX_GMAC 0x4715 /* 47xx Unimac based GbE */ #define PCI_SUBDEVID_BCM47XX_USBH 0x4716 /* 47xx usb host */ #define PCI_SUBDEVID_BCM47XX_USBD 0x4717 /* 47xx usb device */ #define PCI_SUBDEVID_BCM47XX_IPSEC 0x4718 /* 47xx ipsec */ #define PCI_SUBDEVID_BCM47XX_ROBO 0x4719 /* 47xx/53xx roboswitch core */ #define PCI_SUBDEVID_BCM47XX_USB20H 0x471a /* 47xx usb 2.0 host */ #define PCI_SUBDEVID_BCM47XX_USB20D 0x471b /* 47xx usb 2.0 device */ #define PCI_SUBDEVID_BCM47XX_ATA100 0x471d /* 47xx parallel ATA */ #define PCI_SUBDEVID_BCM47XX_SATAXOR 0x471e /* 47xx serial ATA & XOR DMA */ #define PCI_SUBDEVID_BCM47XX_GIGETH 0x471f /* 47xx GbE (5700) */ #define PCI_SUBDEVID_BCM4712_MIPS 0x4720 /* 4712 base devid */ #define PCI_SUBDEVID_BCM4716 0x4722 /* 4716 base devid */ #define PCI_SUBDEVID_BCM47XX_USB30H 0x472a /* 47xx usb 3.0 host */ #define PCI_SUBDEVID_BCM47XX_USB30D 0x472b /* 47xx usb 3.0 device */ #define PCI_SUBDEVID_BCM47XX_SMBUS_EMU 0x47fe /* 47xx emulated SMBus device */ #define PCI_SUBDEVID_BCM47XX_XOR_EMU 0x47ff /* 47xx emulated XOR engine */ #define PCI_SUBDEVID_BCM_EPI41210 0xa0fa /* bcm4210 */ #define PCI_SUBDEVID_BCM_EPI41230 0xa10e /* bcm4230 */ #define PCI_SUBDEVID_BCM_JINVANI_SDIOH 0x4743 /* Jinvani SDIO Gold Host */ #define PCI_SUBDEVID_BCM27XX_SDIOH 0x2702 /* PCI_SUBDEVID_BCM27xx Standard SDIO Host */ #define PCI_SUBDEVID_BCM_PCIXX21_FLASHMEDIA 0x803b /* TI PCI xx21 Standard Host Controller */ #define PCI_SUBDEVID_BCM_PCIXX21_SDIOH 0x803c /* TI PCI xx21 Standard Host Controller */ #define PCI_SUBDEVID_BCM_R5C822_SDIOH 0x0822 /* Ricoh Co Ltd R5C822 SD/SDIO/MMC/MS/MSPro Host */ #define PCI_SUBDEVID_BCM_JMICRON_SDIOH 0x2381 /* JMicron Standard SDIO Host Controller */ /* Broadcom ChipCommon Chip IDs */ #define BHND_CHIPID_BCM4306 0x4306 /* 4306 chipcommon chipid */ #define BHND_CHIPID_BCM4311 0x4311 /* 4311 PCIe 802.11a/b/g */ #define BHND_CHIPID_BCM43111 43111 /* 43111 chipcommon chipid (OTP chipid) */ #define BHND_CHIPID_BCM43112 43112 /* 43112 chipcommon chipid (OTP chipid) */ #define BHND_CHIPID_BCM4312 0x4312 /* 4312 chipcommon chipid */ #define BHND_CHIPID_BCM4313 0x4313 /* 4313 chip id */ #define BHND_CHIPID_BCM43131 43131 /* 43131 chip id (OTP chipid) */ #define BHND_CHIPID_BCM4315 0x4315 /* 4315 chip id */ #define BHND_CHIPID_BCM4318 0x4318 /* 4318 chipcommon chipid */ #define BHND_CHIPID_BCM4319 0x4319 /* 4319 chip id */ #define BHND_CHIPID_BCM4320 0x4320 /* 4320 chipcommon chipid */ #define BHND_CHIPID_BCM4321 0x4321 /* 4321 chipcommon chipid */ #define BHND_CHIPID_BCM43217 43217 /* 43217 chip id (OTP chipid) */ #define BHND_CHIPID_BCM4322 0x4322 /* 4322 chipcommon chipid */ #define BHND_CHIPID_BCM43221 43221 /* 43221 chipcommon chipid (OTP chipid) */ #define BHND_CHIPID_BCM43222 43222 /* 43222 chipcommon chipid */ #define BHND_CHIPID_BCM43224 43224 /* 43224 chipcommon chipid */ #define BHND_CHIPID_BCM43225 43225 /* 43225 chipcommon chipid */ #define BHND_CHIPID_BCM43227 43227 /* 43227 chipcommon chipid */ #define BHND_CHIPID_BCM43228 43228 /* 43228 chipcommon chipid */ #define BHND_CHIPID_BCM43226 43226 /* 43226 chipcommon chipid */ #define BHND_CHIPID_BCM43231 43231 /* 43231 chipcommon chipid (OTP chipid) */ #define BHND_CHIPID_BCM43234 43234 /* 43234 chipcommon chipid */ #define BHND_CHIPID_BCM43235 43235 /* 43235 chipcommon chipid */ #define BHND_CHIPID_BCM43236 43236 /* 43236 chipcommon chipid */ #define BHND_CHIPID_BCM43237 43237 /* 43237 chipcommon chipid */ #define BHND_CHIPID_BCM43238 43238 /* 43238 chipcommon chipid */ #define BHND_CHIPID_BCM43239 43239 /* 43239 chipcommon chipid */ #define BHND_CHIPID_BCM43420 43420 /* 43222 chipcommon chipid (OTP, RBBU) */ #define BHND_CHIPID_BCM43421 43421 /* 43224 chipcommon chipid (OTP, RBBU) */ #define BHND_CHIPID_BCM43428 43428 /* 43228 chipcommon chipid (OTP, RBBU) */ #define BHND_CHIPID_BCM43431 43431 /* 4331 chipcommon chipid (OTP, RBBU) */ #define BHND_CHIPID_BCM43460 43460 /* 4360 chipcommon chipid (OTP, RBBU) */ #define BHND_CHIPID_BCM43462 0xA9C6 /* 43462 chipcommon chipid */ #define BHND_CHIPID_BCM4325 0x4325 /* 4325 chip id */ #define BHND_CHIPID_BCM4328 0x4328 /* 4328 chip id */ #define BHND_CHIPID_BCM4329 0x4329 /* 4329 chipcommon chipid */ #define BHND_CHIPID_BCM4331 0x4331 /* 4331 chipcommon chipid */ #define BHND_CHIPID_BCM4336 0x4336 /* 4336 chipcommon chipid */ #define BHND_CHIPID_BCM43362 43362 /* 43362 chipcommon chipid */ #define BHND_CHIPID_BCM4330 0x4330 /* 4330 chipcommon chipid */ #define BHND_CHIPID_BCM6362 0x6362 /* 6362 chipcommon chipid */ #define BHND_CHIPID_BCM4314 0x4314 /* 4314 chipcommon chipid */ #define BHND_CHIPID_BCM43142 43142 /* 43142 chipcommon chipid */ #define BHND_CHIPID_BCM43143 43143 /* 43143 chipcommon chipid */ #define BHND_CHIPID_BCM4324 0x4324 /* 4324 chipcommon chipid */ #define BHND_CHIPID_BCM43242 43242 /* 43242 chipcommon chipid */ #define BHND_CHIPID_BCM43243 43243 /* 43243 chipcommon chipid */ #define BHND_CHIPID_BCM4334 0x4334 /* 4334 chipcommon chipid */ #define BHND_CHIPID_BCM4335 0x4335 /* 4335 chipcommon chipid */ #define BHND_CHIPID_BCM4360 0x4360 /* 4360 chipcommon chipid */ #define BHND_CHIPID_BCM43602 0xaa52 /* 43602 chipcommon chipid */ #define BHND_CHIPID_BCM4352 0x4352 /* 4352 chipcommon chipid */ #define BHND_CHIPID_BCM43526 0xAA06 #define BHND_CHIPID_BCM43341 43341 /* 43341 chipcommon chipid */ #define BHND_CHIPID_BCM43342 43342 /* 43342 chipcommon chipid */ #define BHND_CHIPID_BCM4335 0x4335 #define BHND_CHIPID_BCM4350 0x4350 /* 4350 chipcommon chipid */ #define BHND_CHIPID_BCM4342 4342 /* 4342 chipcommon chipid (OTP, RBBU) */ #define BHND_CHIPID_BCM4402 0x4402 /* 4402 chipid */ #define BHND_CHIPID_BCM4704 0x4704 /* 4704 chipcommon chipid */ #define BHND_CHIPID_BCM4706 0x5300 /* 4706 chipcommon chipid */ #define BHND_CHIPID_BCM4707 53010 /* 4707 chipcommon chipid */ #define BHND_CHIPID_BCM53018 53018 /* 53018 chipcommon chipid */ #define BHND_CHIPID_IS_BCM4707(chipid) \ (((chipid) == BHND_CHIPID_BCM4707) || \ ((chipid) == BHND_CHIPID_BCM53018)) #define BHND_CHIPID_BCM4710 0x4710 /* 4710 chipid */ #define BHND_CHIPID_BCM4712 0x4712 /* 4712 chipcommon chipid */ #define BHND_CHIPID_BCM4716 0x4716 /* 4716 chipcommon chipid */ #define BHND_CHIPID_BCM47162 47162 /* 47162 chipcommon chipid */ #define BHND_CHIPID_BCM4748 0x4748 /* 4716 chipcommon chipid (OTP, RBBU) */ #define BHND_CHIPID_BCM4749 0x4749 /* 5357 chipcommon chipid (OTP, RBBU) */ #define BHND_CHIPID_BCM4785 0x4785 /* 4785 chipcommon chipid */ #define BHND_CHIPID_BCM5350 0x5350 /* 5350 chipcommon chipid */ #define BHND_CHIPID_BCM5352 0x5352 /* 5352 chipcommon chipid */ #define BHND_CHIPID_BCM5354 0x5354 /* 5354 chipcommon chipid */ #define BHND_CHIPID_BCM5365 0x5365 /* 5365 chipcommon chipid */ #define BHND_CHIPID_BCM5356 0x5356 /* 5356 chipcommon chipid */ #define BHND_CHIPID_BCM5357 0x5357 /* 5357 chipcommon chipid */ #define BHND_CHIPID_BCM53572 53572 /* 53572 chipcommon chipid */ /* Broadcom ChipCommon Package IDs */ #define BHND_PKGID_BCM4303 2 /* 4303 package id */ #define BHND_PKGID_BCM4309 1 /* 4309 package id */ #define BHND_PKGID_BCM4712LARGE 0 /* 340pin 4712 package id */ #define BHND_PKGID_BCM4712SMALL 1 /* 200pin 4712 package id */ #define BHND_PKGID_BCM4712MID 2 /* 225pin 4712 package id */ #define BHND_PKGID_BCM4328USBD11G 2 /* 4328 802.11g USB package id */ #define BHND_PKGID_BCM4328USBDUAL 3 /* 4328 802.11a/g USB package id */ #define BHND_PKGID_BCM4328SDIOD11G 4 /* 4328 802.11g SDIO package id */ #define BHND_PKGID_BCM4328SDIODUAL 5 /* 4328 802.11a/g SDIO package id */ #define BHND_PKGID_BCM4329_289PIN 0 /* 4329 289-pin package id */ #define BHND_PKGID_BCM4329_182PIN 1 /* 4329N 182-pin package id */ #define BHND_PKGID_BCM5354E 1 /* 5354E package id */ #define BHND_PKGID_BCM4716 8 /* 4716 package id */ #define BHND_PKGID_BCM4717 9 /* 4717 package id */ #define BHND_PKGID_BCM4718 10 /* 4718 package id */ #define BHND_PKGID_BCM5356_NONMODE 1 /* 5356 package without nmode suppport */ #define BHND_PKGID_BCM5358U 8 /* 5358U package id */ #define BHND_PKGID_BCM5358 9 /* 5358 package id */ #define BHND_PKGID_BCM47186 10 /* 47186 package id */ #define BHND_PKGID_BCM5357 11 /* 5357 package id */ #define BHND_PKGID_BCM5356U 12 /* 5356U package id */ #define BHND_PKGID_BCM53572 8 /* 53572 package id */ #define BHND_PKGID_BCM5357C0 8 /* 5357c0 package id (the same as 53572) */ #define BHND_PKGID_BCM47188 9 /* 47188 package id */ #define BHND_PKGID_BCM5358C0 0xa /* 5358c0 package id */ #define BHND_PKGID_BCM5356C0 0xb /* 5356c0 package id */ #define BHND_PKGID_BCM4331TT 8 /* 4331 12x12 package id */ #define BHND_PKGID_BCM4331TN 9 /* 4331 12x9 package id */ #define BHND_PKGID_BCM4331TNA0 0xb /* 4331 12x9 package id */ #define BHND_PKGID_BCM4706L 1 /* 4706L package id */ #define BHND_PKGID_HDLSIM5350 1 /* HDL simulator package id for a 5350 */ #define BHND_PKGID_HDLSIM 14 /* HDL simulator package id */ #define BHND_PKGID_HWSIM 15 /* Hardware simulator package id */ #define BHND_PKGID_BCM43224_FAB_CSM 0x8 /* the chip is manufactured by CSM */ #define BHND_PKGID_BCM43224_FAB_SMIC 0xa /* the chip is manufactured by SMIC */ #define BHND_PKGID_BCM4336_WLBGA 0x8 #define BHND_PKGID_BCM4330_WLBGA 0x0 #define BHND_PKGID_BCM4314PCIE_ARM (8 | 0) /* 4314 QFN PCI package id, bit 3 tie high */ #define BHND_PKGID_BCM4314SDIO (8 | 1) /* 4314 QFN SDIO package id */ #define BHND_PKGID_BCM4314PCIE (8 | 2) /* 4314 QFN PCI (ARM-less) package id */ #define BHND_PKGID_BCM4314SDIO_ARM (8 | 3) /* 4314 QFN SDIO (ARM-less) package id */ #define BHND_PKGID_BCM4314SDIO_FPBGA (8 | 4) /* 4314 FpBGA SDIO package id */ #define BHND_PKGID_BCM4314DEV (8 | 6) /* 4314 Development package id */ #define BHND_PKGID_BCM4707 1 /* 4707 package id */ #define BHND_PKGID_BCM4708 2 /* 4708 package id */ #define BHND_PKGID_BCM4709 0 /* 4709 package id */ #define BHND_PKGID_BCM4335_WLCSP (0x0) /* WLCSP Module/Mobile SDIO/HSIC. */ #define BHND_PKGID_BCM4335_FCBGA (0x1) /* FCBGA PC/Embedded/Media PCIE/SDIO */ #define BHND_PKGID_BCM4335_WLBGA (0x2) /* WLBGA COB/Mobile SDIO/HSIC. */ #define BHND_PKGID_BCM4335_FCBGAD (0x3) /* FCBGA Debug Debug/Dev All if's. */ #define BHND_PKGID_PKG_MASK_BCM4335 (0x3) /* Broadcom Core IDs */ #define BHND_COREID_INVALID 0x700 /* Invalid coreid */ #define BHND_COREID_CC 0x800 /* chipcommon core */ #define BHND_COREID_ILINE20 0x801 /* iline20 core */ #define BHND_COREID_SRAM 0x802 /* sram core */ #define BHND_COREID_SDRAM 0x803 /* sdram core */ #define BHND_COREID_PCI 0x804 /* pci core */ #define BHND_COREID_MIPS 0x805 /* mips core */ #define BHND_COREID_ENET 0x806 /* enet mac core */ #define BHND_COREID_CODEC 0x807 /* v90 codec core */ #define BHND_COREID_USB 0x808 /* usb 1.1 host/device core */ #define BHND_COREID_ADSL 0x809 /* ADSL core */ #define BHND_COREID_ILINE100 0x80a /* iline100 core */ #define BHND_COREID_IPSEC 0x80b /* ipsec core */ #define BHND_COREID_UTOPIA 0x80c /* utopia core */ #define BHND_COREID_PCMCIA 0x80d /* pcmcia core */ #define BHND_COREID_SOCRAM 0x80e /* internal memory core */ #define BHND_COREID_MEMC 0x80f /* memc sdram core */ #define BHND_COREID_OFDM 0x810 /* OFDM phy core */ #define BHND_COREID_EXTIF 0x811 /* external interface core */ #define BHND_COREID_D11 0x812 /* 802.11 MAC core */ #define BHND_COREID_APHY 0x813 /* 802.11a phy core */ #define BHND_COREID_BPHY 0x814 /* 802.11b phy core */ #define BHND_COREID_GPHY 0x815 /* 802.11g phy core */ #define BHND_COREID_MIPS33 0x816 /* mips3302 core */ #define BHND_COREID_USB11H 0x817 /* usb 1.1 host core */ #define BHND_COREID_USB11D 0x818 /* usb 1.1 device core */ #define BHND_COREID_USB20H 0x819 /* usb 2.0 host core */ #define BHND_COREID_USB20D 0x81a /* usb 2.0 device core */ #define BHND_COREID_SDIOH 0x81b /* sdio host core */ #define BHND_COREID_ROBO 0x81c /* roboswitch core */ #define BHND_COREID_ATA100 0x81d /* parallel ATA core */ #define BHND_COREID_SATAXOR 0x81e /* serial ATA & XOR DMA core */ #define BHND_COREID_GIGETH 0x81f /* gigabit ethernet core */ #define BHND_COREID_PCIE 0x820 /* pci express core */ #define BHND_COREID_NPHY 0x821 /* 802.11n 2x2 phy core */ #define BHND_COREID_SRAMC 0x822 /* SRAM controller core */ #define BHND_COREID_MINIMAC 0x823 /* MINI MAC/phy core */ #define BHND_COREID_ARM11 0x824 /* ARM 1176 core */ #define BHND_COREID_ARM7S 0x825 /* ARM7tdmi-s core */ #define BHND_COREID_LPPHY 0x826 /* 802.11a/b/g phy core */ #define BHND_COREID_PMU 0x827 /* PMU core */ #define BHND_COREID_SSNPHY 0x828 /* 802.11n single-stream phy core */ #define BHND_COREID_SDIOD 0x829 /* SDIO device core */ #define BHND_COREID_ARMCM3 0x82a /* ARM Cortex M3 core */ #define BHND_COREID_HTPHY 0x82b /* 802.11n 4x4 phy core */ #define BHND_COREID_MIPS74K 0x82c /* mips 74k core */ #define BHND_COREID_GMAC 0x82d /* Gigabit MAC core */ #define BHND_COREID_DMEMC 0x82e /* DDR1/2 memory controller core */ #define BHND_COREID_PCIERC 0x82f /* PCIE Root Complex core */ #define BHND_COREID_OCP 0x830 /* OCP2OCP bridge core */ #define BHND_COREID_SC 0x831 /* shared common core */ #define BHND_COREID_AHB 0x832 /* OCP2AHB bridge core */ #define BHND_COREID_SPIH 0x833 /* SPI host core */ #define BHND_COREID_I2S 0x834 /* I2S core */ #define BHND_COREID_DMEMS 0x835 /* SDR/DDR1 memory controller core */ #define BHND_COREID_UBUS_SHIM 0x837 /* SHIM component in ubus/6362 */ #define BHND_COREID_PCIE2 0x83c /* pci express (gen2) core */ /* ARM/AMBA Core IDs */ #define BHND_COREID_APB_BRIDGE 0x135 /* BP135 AMBA AXI-APB bridge */ #define BHND_COREID_PL301 0x301 /* PL301 AMBA AXI Interconnect */ #define BHND_COREID_EROM 0x366 /* Enumeration ROM */ #define BHND_COREID_OOB_ROUTER 0x367 /* OOB router core ID */ #define BHND_COREID_AXI_UNMAPPED 0xfff /* AXI "Default Slave"; maps all unused address * ranges, returning DECERR on read or write. */ /* Northstar Plus and BCM4706 Core IDs */ #define BHND_COREID_4706_CC 0x500 /* chipcommon core */ #define BHND_COREID_NS_PCIE2 0x501 /* pci express (gen2) core */ #define BHND_COREID_NS_DMA 0x502 /* dma core */ #define BHND_COREID_NS_SDIO 0x503 /* sdio host core */ #define BHND_COREID_NS_USB20H 0x504 /* usb 2.0 host core */ #define BHND_COREID_NS_USB30H 0x505 /* usb 3.0 host core */ #define BHND_COREID_NS_A9JTAG 0x506 /* ARM Cortex A9 JTAG core */ #define BHND_COREID_NS_DDR23_MEMC 0x507 /* DDR2/3 cadence/denali memory controller core () */ #define BHND_COREID_NS_ROM 0x508 /* device ROM core */ #define BHND_COREID_NS_NAND 0x509 /* NAND flash controller core */ #define BHND_COREID_NS_QSPI 0x50a /* QSPI flash controller core */ #define BHND_COREID_NS_CC_B 0x50b /* chipcommon `b' (auxiliary) core */ #define BHND_COREID_4706_SOCRAM 0x50e /* internal memory core */ #define BHND_COREID_IHOST_ARMCA9 0x510 /* ARM Cortex A9 core */ #define BHND_COREID_4706_GMAC_CMN 0x5dc /* Gigabit MAC common core */ #define BHND_COREID_4706_GMAC 0x52d /* Gigabit MAC core */ #define BHND_COREID_AMEMC 0x52e /* DDR1/2 cadence/denali memory controller core */ /* ARM PrimeCell Peripherial IDs. These were derived from inspection of the * PrimeCell-compatible BCM4331 cores, but due to lack of documentation, the * surmised core name/description may be incorrect. */ #define BHND_PRIMEID_EROM 0x364 /* Enumeration ROM's primecell ID */ #define BHND_PRIMEID_SWRAP 0x368 /* PL368 Device Management Interface (Slave) */ #define BHND_PRIMEID_MWRAP 0x369 /* PL369 Device Management Interface (Master) */ /* Core HW Revision Numbers */ #define BHND_HWREV_INVALID 0xFF /* Invalid hardware revision ID */ /* Chip Types */ #define BHND_CHIPTYPE_SIBA 0 /**< siba(4) interconnect */ #define BHND_CHIPTYPE_BCMA 1 /**< bcma(4) interconnect */ #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 */ #define BHND_BFL_BTCOEX 0x00000001 /* Board supports BTCOEX */ #define BHND_BFL_PACTRL 0x00000002 /* Board has gpio 9 controlling the PA */ #define BHND_BFL_AIRLINEMODE 0x00000004 /* Board implements gpio 13 radio disable indication, UNUSED */ #define BHND_BFL_ADCDIV 0x00000008 /* Board has the rssi ADC divider */ #define BHND_BFL_DIS_256QAM 0x00000008 #define BHND_BFL_ENETROBO 0x00000010 /* Board has robo switch or core */ #define BHND_BFL_NOPLLDOWN 0x00000020 /* Not ok to power down the chip pll and oscillator */ #define BHND_BFL_CCKHIPWR 0x00000040 /* Can do high-power CCK transmission */ #define BHND_BFL_ENETADM 0x00000080 /* Board has ADMtek switch */ #define BHND_BFL_ENETVLAN 0x00000100 /* Board has VLAN capability */ #define BHND_BFL_LTECOEX 0x00000200 /* Board has LTE coex capability */ #define BHND_BFL_NOPCI 0x00000400 /* Board leaves PCI floating */ #define BHND_BFL_FEM 0x00000800 /* Board supports the Front End Module */ #define BHND_BFL_EXTLNA 0x00001000 /* Board has an external LNA in 2.4GHz band */ #define BHND_BFL_HGPA 0x00002000 /* Board has a high gain PA */ #define BHND_BFL_BTC2WIRE_ALTGPIO 0x00004000 /* Board's BTC 2wire is in the alternate gpios OBSLETE */ #define BHND_BFL_ALTIQ 0x00008000 /* Alternate I/Q settings */ #define BHND_BFL_NOPA 0x00010000 /* Board has no PA */ #define BHND_BFL_RSSIINV 0x00020000 /* Board's RSSI uses positive slope(not TSSI) */ #define BHND_BFL_PAREF 0x00040000 /* Board uses the PARef LDO */ #define BHND_BFL_3TSWITCH 0x00080000 /* Board uses a triple throw switch shared with BT */ #define BHND_BFL_PHASESHIFT 0x00100000 /* Board can support phase shifter */ #define BHND_BFL_BUCKBOOST 0x00200000 /* Power topology uses BUCKBOOST */ #define BHND_BFL_FEM_BT 0x00400000 /* Board has FEM and switch to share antenna w/ BT */ #define BHND_BFL_RXCHAIN_OFF_BT 0x00400000 /* one rxchain is to be shut off when BT is active */ #define BHND_BFL_NOCBUCK 0x00800000 /* Power topology doesn't use CBUCK */ #define BHND_BFL_CCKFAVOREVM 0x01000000 /* Favor CCK EVM over spectral mask */ #define BHND_BFL_PALDO 0x02000000 /* Power topology uses PALDO */ #define BHND_BFL_LNLDO2_2P5 0x04000000 /* Select 2.5V as LNLDO2 output voltage */ #define BHND_BFL_FASTPWR 0x08000000 #define BHND_BFL_UCPWRCTL_MININDX 0x08000000 /* Enforce min power index to avoid FEM damage */ #define BHND_BFL_EXTLNA_5GHz 0x10000000 /* Board has an external LNA in 5GHz band */ #define BHND_BFL_TRSW_1by2 0x20000000 /* Board has 2 TRSW's in 1by2 designs */ #define BHND_BFL_GAINBOOSTA01 0x20000000 /* 5g Gainboost for core0 and core1 */ #define BHND_BFL_LO_TRSW_R_5GHz 0x40000000 /* In 5G do not throw TRSW to T for clipLO gain */ #define BHND_BFL_ELNA_GAINDEF 0x80000000 /* Backoff InitGain based on elna_2g/5g field * when this flag is set */ #define BHND_BFL_EXTLNA_TX 0x20000000 /* Temp boardflag to indicate to */ /* Boardflags2 */ #define BHND_BFL2_RXBB_INT_REG_DIS 0x00000001 /* Board has an external rxbb regulator */ #define BHND_BFL2_APLL_WAR 0x00000002 /* Flag to implement alternative A-band PLL settings */ #define BHND_BFL2_TXPWRCTRL_EN 0x00000004 /* Board permits enabling TX Power Control */ #define BHND_BFL2_2X4_DIV 0x00000008 /* Board supports the 2X4 diversity switch */ #define BHND_BFL2_5G_PWRGAIN 0x00000010 /* Board supports 5G band power gain */ #define BHND_BFL2_PCIEWAR_OVR 0x00000020 /* Board overrides ASPM and Clkreq settings */ #define BHND_BFL2_CAESERS_BRD 0x00000040 /* Board is Caesers brd (unused by sw) */ #define BHND_BFL2_BTC3WIRE 0x00000080 /* Board support legacy 3 wire or 4 wire */ #define BHND_BFL2_BTCLEGACY 0x00000080 /* Board support legacy 3/4 wire, to replace * BHND_BFL2_BTC3WIRE */ #define BHND_BFL2_SKWRKFEM_BRD 0x00000100 /* 4321mcm93 board uses Skyworks FEM */ #define BHND_BFL2_SPUR_WAR 0x00000200 /* Board has a WAR for clock-harmonic spurs */ #define BHND_BFL2_GPLL_WAR 0x00000400 /* Flag to narrow G-band PLL loop b/w */ #define BHND_BFL2_TRISTATE_LED 0x00000800 /* Tri-state the LED */ #define BHND_BFL2_SINGLEANT_CCK 0x00001000 /* Tx CCK pkts on Ant 0 only */ #define BHND_BFL2_2G_SPUR_WAR 0x00002000 /* WAR to reduce and avoid clock-harmonic spurs in 2G */ #define BHND_BFL2_BPHY_ALL_TXCORES 0x00004000 /* Transmit bphy frames using all tx cores */ #define BHND_BFL2_FCC_BANDEDGE_WAR 0x00008000 /* Activates WAR to improve FCC bandedge performance */ #define BHND_BFL2_GPLL_WAR2 0x00010000 /* Flag to widen G-band PLL loop b/w */ #define BHND_BFL2_IPALVLSHIFT_3P3 0x00020000 #define BHND_BFL2_INTERNDET_TXIQCAL 0x00040000 /* Use internal envelope detector for TX IQCAL */ #define BHND_BFL2_XTALBUFOUTEN 0x00080000 /* Keep the buffered Xtal output from radio on */ /* Most drivers will turn it off without this flag */ /* to save power. */ #define BHND_BFL2_ANAPACTRL_2G 0x00100000 /* 2G ext PAs are controlled by analog PA ctrl lines */ #define BHND_BFL2_ANAPACTRL_5G 0x00200000 /* 5G ext PAs are controlled by analog PA ctrl lines */ #define BHND_BFL2_ELNACTRL_TRSW_2G 0x00400000 /* AZW4329: 2G gmode_elna_gain controls TR Switch */ #define BHND_BFL2_BT_SHARE_ANT0 0x00800000 /* WLAN/BT share antenna 0 */ #define BHND_BFL2_BT_SHARE_BM_BIT0 0x00800000 /* bit 0 of WLAN/BT shared core bitmap */ #define BHND_BFL2_TEMPSENSE_HIGHER 0x01000000 /* The tempsense threshold can sustain higher value * than programmed. The exact delta is decided by * driver per chip/boardtype. This can be used * when tempsense qualification happens after shipment */ #define BHND_BFL2_BTC3WIREONLY 0x02000000 /* standard 3 wire btc only. 4 wire not supported */ #define BHND_BFL2_PWR_NOMINAL 0x04000000 /* 0: power reduction on, 1: no power reduction */ #define BHND_BFL2_EXTLNA_PWRSAVE 0x08000000 /* boardflag to enable ucode to apply power save * ucode control of eLNA during Tx */ #define BHND_BFL2_4313_RADIOREG 0x10000000 /* board rework */ #define BHND_BFL2_DYNAMIC_VMID 0x10000000 /* boardflag to enable dynamic Vmid idle TSSI CAL */ #define BHND_BFL2_SDR_EN 0x20000000 /* SDR enabled or disabled */ #define BHND_BFL2_LNA1BYPFORTR2G 0x40000000 /* acphy, enable lna1 bypass for clip gain, 2g */ #define BHND_BFL2_LNA1BYPFORTR5G 0x80000000 /* acphy, enable lna1 bypass for clip gain, 5g */ /* SROM 11 - 11ac boardflag definitions */ #define BHND_BFL_SROM11_BTCOEX 0x00000001 /* Board supports BTCOEX */ #define BHND_BFL_SROM11_WLAN_BT_SH_XTL 0x00000002 /* bluetooth and wlan share same crystal */ #define BHND_BFL_SROM11_EXTLNA 0x00001000 /* Board has an external LNA in 2.4GHz band */ #define BHND_BFL_SROM11_EXTLNA_5GHz 0x10000000 /* Board has an external LNA in 5GHz band */ #define BHND_BFL_SROM11_GAINBOOSTA01 0x20000000 /* 5g Gainboost for core0 and core1 */ #define BHND_BFL2_SROM11_APLL_WAR 0x00000002 /* Flag to implement alternative A-band PLL settings */ #define BHND_BFL2_SROM11_ANAPACTRL_2G 0x00100000 /* 2G ext PAs are ctrl-ed by analog PA ctrl lines */ #define BHND_BFL2_SROM11_ANAPACTRL_5G 0x00200000 /* 5G ext PAs are ctrl-ed by analog PA ctrl lines */ /* Boardflags3 */ #define BHND_BFL3_FEMCTRL_SUB 0x00000007 /* acphy, subrevs of femctrl on top of srom_femctrl */ #define BHND_BFL3_RCAL_WAR 0x00000008 /* acphy, rcal war active on this board (4335a0) */ #define BHND_BFL3_TXGAINTBLID 0x00000070 /* acphy, txgain table id */ #define BHND_BFL3_TXGAINTBLID_SHIFT 0x4 /* acphy, txgain table id shift bit */ #define BHND_BFL3_TSSI_DIV_WAR 0x00000080 /* acphy, Separate paparam for 20/40/80 */ #define BHND_BFL3_TSSI_DIV_WAR_SHIFT 0x7 /* acphy, Separate paparam for 20/40/80 shift bit */ #define BHND_BFL3_FEMTBL_FROM_NVRAM 0x00000100 /* acphy, femctrl table is read from nvram */ #define BHND_BFL3_FEMTBL_FROM_NVRAM_SHIFT 0x8 /* acphy, femctrl table is read from nvram */ #define BHND_BFL3_AGC_CFG_2G 0x00000200 /* acphy, gain control configuration for 2G */ #define BHND_BFL3_AGC_CFG_5G 0x00000400 /* acphy, gain control configuration for 5G */ #define BHND_BFL3_PPR_BIT_EXT 0x00000800 /* acphy, bit position for 1bit extension for ppr */ #define BHND_BFL3_PPR_BIT_EXT_SHIFT 11 /* acphy, bit shift for 1bit extension for ppr */ #define BHND_BFL3_BBPLL_SPR_MODE_DIS 0x00001000 /* acphy, disables bbpll spur modes */ #define BHND_BFL3_RCAL_OTP_VAL_EN 0x00002000 /* acphy, to read rcal_trim value from otp */ #define BHND_BFL3_2GTXGAINTBL_BLANK 0x00004000 /* acphy, blank the first X ticks of 2g gaintbl */ #define BHND_BFL3_2GTXGAINTBL_BLANK_SHIFT 14 /* acphy, blank the first X ticks of 2g gaintbl */ #define BHND_BFL3_5GTXGAINTBL_BLANK 0x00008000 /* acphy, blank the first X ticks of 5g gaintbl */ #define BHND_BFL3_5GTXGAINTBL_BLANK_SHIFT 15 /* acphy, blank the first X ticks of 5g gaintbl */ #define BHND_BFL3_BT_SHARE_BM_BIT1 0x40000000 /* bit 1 of WLAN/BT shared core bitmap */ #define BHND_BFL3_PHASETRACK_MAX_ALPHABETA 0x00010000 /* acphy, to max out alpha,beta to 511 */ #define BHND_BFL3_PHASETRACK_MAX_ALPHABETA_SHIFT 16 /* acphy, to max out alpha,beta to 511 */ #define BHND_BFL3_BT_SHARE_BM_BIT1 0x40000000 /* bit 1 of WLAN/BT shared core bitmap */ #define BHND_BFL3_EN_NONBRCM_TXBF 0x10000000 /* acphy, enable non-brcm TXBF */ #define BHND_BFL3_EN_P2PLINK_TXBF 0x20000000 /* acphy, enable TXBF in p2p links */ /* board specific GPIO assignment, gpio 0-3 are also customer-configurable led */ #define BHND_GPIO_BOARD_BTC3W_IN 0x850 /* bit 4 is RF_ACTIVE, bit 6 is STATUS, bit 11 is PRI */ #define BHND_GPIO_BOARD_BTC3W_OUT 0x020 /* bit 5 is TX_CONF */ #define BHND_GPIO_BOARD_BTCMOD_IN 0x010 /* bit 4 is the alternate BT Coexistence Input */ #define BHND_GPIO_BOARD_BTCMOD_OUT 0x020 /* bit 5 is the alternate BT Coexistence Out */ #define BHND_GPIO_BOARD_BTC_IN 0x080 /* bit 7 is BT Coexistence Input */ #define BHND_GPIO_BOARD_BTC_OUT 0x100 /* bit 8 is BT Coexistence Out */ #define BHND_GPIO_BOARD_PACTRL 0x200 /* bit 9 controls the PA on new 4306 boards */ #define BHND_GPIO_BOARD_12 0x1000 /* gpio 12 */ #define BHND_GPIO_BOARD_13 0x2000 /* gpio 13 */ #define BHND_GPIO_BOARD_BTC4_IN 0x0800 /* gpio 11, coex4, in */ #define BHND_GPIO_BOARD_BTC4_BT 0x2000 /* gpio 12, coex4, bt active */ #define BHND_GPIO_BOARD_BTC4_STAT 0x4000 /* gpio 14, coex4, status */ #define BHND_GPIO_BOARD_BTC4_WLAN 0x8000 /* gpio 15, coex4, wlan active */ #define BHND_GPIO_BOARD_1_WLAN_PWR 0x02 /* throttle WLAN power on X21 board */ #define BHND_GPIO_BOARD_3_WLAN_PWR 0x08 /* throttle WLAN power on X28 board */ #define BHND_GPIO_BOARD_4_WLAN_PWR 0x10 /* throttle WLAN power on X19 board */ #define BHND_GPIO_BTC4W_OUT_4312 0x010 /* bit 4 is BT_IODISABLE */ #define BHND_GPIO_BTC4W_OUT_43224 0x020 /* bit 5 is BT_IODISABLE */ #define BHND_GPIO_BTC4W_OUT_43224_SHARED 0x0e0 /* bit 5 is BT_IODISABLE */ #define BHND_GPIO_BTC4W_OUT_43225 0x0e0 /* bit 5 BT_IODISABLE, bit 6 SW_BT, bit 7 SW_WL */ #define BHND_GPIO_BTC4W_OUT_43421 0x020 /* bit 5 is BT_IODISABLE */ #define BHND_GPIO_BTC4W_OUT_4313 0x060 /* bit 5 SW_BT, bit 6 SW_WL */ #define BHND_GPIO_BTC4W_OUT_4331_SHARED 0x010 /* GPIO 4 */ /* Board Types */ #define BHND_BOARD_BU4710 0x0400 #define BHND_BOARD_VSIM4710 0x0401 #define BHND_BOARD_QT4710 0x0402 #define BHND_BOARD_BU4309 0x040a #define BHND_BOARD_BCM94309CB 0x040b #define BHND_BOARD_BCM94309MP 0x040c #define BHND_BOARD_BCM4309AP 0x040d #define BHND_BOARD_BCM94302MP 0x040e #define BHND_BOARD_BU4306 0x0416 #define BHND_BOARD_BCM94306CB 0x0417 #define BHND_BOARD_BCM94306MP 0x0418 #define BHND_BOARD_BCM94710D 0x041a #define BHND_BOARD_BCM94710R1 0x041b #define BHND_BOARD_BCM94710R4 0x041c #define BHND_BOARD_BCM94710AP 0x041d #define BHND_BOARD_BU2050 0x041f #define BHND_BOARD_BCM94309G 0x0421 #define BHND_BOARD_BU4704 0x0423 #define BHND_BOARD_BU4702 0x0424 #define BHND_BOARD_BCM94306PC 0x0425 /* pcmcia 3.3v 4306 card */ #define BHND_BOARD_BCM94702MN 0x0428 /* BCM4702 1U CompactPCI Board */ #define BHND_BOARD_BCM94702CPCI 0x0429 /* BCM4702 with BCM95380 VLAN Router */ #define BHND_BOARD_BCM95380RR 0x042a /* cb4306 with SiGe PA */ #define BHND_BOARD_BCM94306CBSG 0x042b /* cb4306 with SiGe PA */ #define BHND_BOARD_PCSG94306 0x042d /* bu4704 with sdram */ #define BHND_BOARD_BU4704SD 0x042e /* Dual 11a/11g Router */ #define BHND_BOARD_BCM94704AGR 0x042f /* 11a-only minipci */ #define BHND_BOARD_BCM94308MP 0x0430 #define BHND_BOARD_BU4712 0x0444 #define BHND_BOARD_BU4712SD 0x045d #define BHND_BOARD_BU4712L 0x045f /* BCM4712 boards */ #define BHND_BOARD_BCM94712AP 0x0445 #define BHND_BOARD_BCM94712P 0x0446 /* BCM4318 boards */ #define BHND_BOARD_BU4318 0x0447 #define BHND_BOARD_CB4318 0x0448 #define BHND_BOARD_MPG4318 0x0449 #define BHND_BOARD_MP4318 0x044a #define BHND_BOARD_SD4318 0x044b /* BCM4313 boards */ #define BHND_BOARD_BCM94313BU 0x050f #define BHND_BOARD_BCM94313HM 0x0510 #define BHND_BOARD_BCM94313EPA 0x0511 #define BHND_BOARD_BCM94313HMG 0x051C /* BCM63XX boards */ #define BHND_BOARD_BCM96338 0x6338 #define BHND_BOARD_BCM96348 0x6348 #define BHND_BOARD_BCM96358 0x6358 #define BHND_BOARD_BCM96368 0x6368 /* Another mp4306 with SiGe */ #define BHND_BOARD_BCM94306P 0x044c /* mp4303 */ #define BHND_BOARD_BCM94303MP 0x044e /* mpsgh4306 */ #define BHND_BOARD_BCM94306MPSGH 0x044f /* BRCM 4306 w/ Front End Modules */ #define BHND_BOARD_BCM94306MPM 0x0450 #define BHND_BOARD_BCM94306MPL 0x0453 /* 4712agr */ #define BHND_BOARD_BCM94712AGR 0x0451 /* pcmcia 4303 */ #define BHND_BOARD_PC4303 0x0454 /* 5350K */ #define BHND_BOARD_BCM95350K 0x0455 /* 5350R */ #define BHND_BOARD_BCM95350R 0x0456 /* 4306mplna */ #define BHND_BOARD_BCM94306MPLNA 0x0457 /* 4320 boards */ #define BHND_BOARD_BU4320 0x0458 #define BHND_BOARD_BU4320S 0x0459 #define BHND_BOARD_BCM94320PH 0x045a /* 4306mph */ #define BHND_BOARD_BCM94306MPH 0x045b /* 4306pciv */ #define BHND_BOARD_BCM94306PCIV 0x045c #define BHND_BOARD_BU4712SD 0x045d #define BHND_BOARD_BCM94320PFLSH 0x045e #define BHND_BOARD_BU4712L 0x045f #define BHND_BOARD_BCM94712LGR 0x0460 #define BHND_BOARD_BCM94320R 0x0461 #define BHND_BOARD_BU5352 0x0462 #define BHND_BOARD_BCM94318MPGH 0x0463 #define BHND_BOARD_BU4311 0x0464 #define BHND_BOARD_BCM94311MC 0x0465 #define BHND_BOARD_BCM94311MCAG 0x0466 #define BHND_BOARD_BCM95352GR 0x0467 /* bcm95351agr */ #define BHND_BOARD_BCM95351AGR 0x0470 /* bcm94704mpcb */ #define BHND_BOARD_BCM94704MPCB 0x0472 /* 4785 boards */ #define BHND_BOARD_BU4785 0x0478 /* 4321 boards */ #define BHND_BOARD_BCM4321BU 0x046b #define BHND_BOARD_BCM4321BUE 0x047c #define BHND_BOARD_BCM4321MP 0x046c #define BHND_BOARD_BCM4321CB2 0x046d #define BHND_BOARD_BCM4321CB2_AG 0x0066 #define BHND_BOARD_BCM4321MC 0x046e /* 4328 boards */ #define BHND_BOARD_BU4328 0x0481 #define BHND_BOARD_BCM4328SDG 0x0482 #define BHND_BOARD_BCM4328SDAG 0x0483 #define BHND_BOARD_BCM4328UG 0x0484 #define BHND_BOARD_BCM4328UAG 0x0485 #define BHND_BOARD_BCM4328PC 0x0486 #define BHND_BOARD_BCM4328CF 0x0487 /* 4325 boards */ #define BHND_BOARD_BCM94325DEVBU 0x0490 #define BHND_BOARD_BCM94325BGABU 0x0491 #define BHND_BOARD_BCM94325SDGWB 0x0492 #define BHND_BOARD_BCM94325SDGMDL 0x04aa #define BHND_BOARD_BCM94325SDGMDL2 0x04c6 #define BHND_BOARD_BCM94325SDGMDL3 0x04c9 #define BHND_BOARD_BCM94325SDABGWBA 0x04e1 /* 4322 boards */ #define BHND_BOARD_BCM94322MC 0x04a4 #define BHND_BOARD_BCM94322USB 0x04a8 /* dualband */ #define BHND_BOARD_BCM94322HM 0x04b0 #define BHND_BOARD_BCM94322USB2D 0x04bf /* single band discrete front end */ /* 4312 boards */ #define BHND_BOARD_BCM4312MCGSG 0x04b5 /* 4315 boards */ #define BHND_BOARD_BCM94315DEVBU 0x04c2 #define BHND_BOARD_BCM94315USBGP 0x04c7 #define BHND_BOARD_BCM94315BGABU 0x04ca #define BHND_BOARD_BCM94315USBGP41 0x04cb /* 4319 boards */ #define BHND_BOARD_BCM94319DEVBU 0X04e5 #define BHND_BOARD_BCM94319USB 0X04e6 #define BHND_BOARD_BCM94319SD 0X04e7 /* 4716 boards */ #define BHND_BOARD_BCM94716NR2 0x04cd /* 4319 boards */ #define BHND_BOARD_BCM94319DEVBU 0X04e5 #define BHND_BOARD_BCM94319USBNP4L 0X04e6 #define BHND_BOARD_BCM94319WLUSBN4L 0X04e7 #define BHND_BOARD_BCM94319SDG 0X04ea #define BHND_BOARD_BCM94319LCUSBSDN4L 0X04eb #define BHND_BOARD_BCM94319USBB 0x04ee #define BHND_BOARD_BCM94319LCSDN4L 0X0507 #define BHND_BOARD_BCM94319LSUSBN4L 0X0508 #define BHND_BOARD_BCM94319SDNA4L 0X0517 #define BHND_BOARD_BCM94319SDELNA4L 0X0518 #define BHND_BOARD_BCM94319SDELNA6L 0X0539 #define BHND_BOARD_BCM94319ARCADYAN 0X0546 #define BHND_BOARD_BCM94319WINDSOR 0x0561 #define BHND_BOARD_BCM94319MLAP 0x0562 #define BHND_BOARD_BCM94319SDNA 0x058b #define BHND_BOARD_BCM94319BHEMU3 0x0563 #define BHND_BOARD_BCM94319SDHMB 0x058c #define BHND_BOARD_BCM94319SDBREF 0x05a1 #define BHND_BOARD_BCM94319USBSDB 0x05a2 /* 4329 boards */ #define BHND_BOARD_BCM94329AGB 0X04b9 #define BHND_BOARD_BCM94329TDKMDL1 0X04ba #define BHND_BOARD_BCM94329TDKMDL11 0X04fc #define BHND_BOARD_BCM94329OLYMPICN18 0X04fd #define BHND_BOARD_BCM94329OLYMPICN90 0X04fe #define BHND_BOARD_BCM94329OLYMPICN90U 0X050c #define BHND_BOARD_BCM94329OLYMPICN90M 0X050b #define BHND_BOARD_BCM94329AGBF 0X04ff #define BHND_BOARD_BCM94329OLYMPICX17 0X0504 #define BHND_BOARD_BCM94329OLYMPICX17M 0X050a #define BHND_BOARD_BCM94329OLYMPICX17U 0X0509 #define BHND_BOARD_BCM94329OLYMPICUNO 0X0564 #define BHND_BOARD_BCM94329MOTOROLA 0X0565 #define BHND_BOARD_BCM94329OLYMPICLOCO 0X0568 /* 4336 SDIO board types */ #define BHND_BOARD_BCM94336SD_WLBGABU 0x0511 #define BHND_BOARD_BCM94336SD_WLBGAREF 0x0519 #define BHND_BOARD_BCM94336SDGP 0x0538 #define BHND_BOARD_BCM94336SDG 0x0519 #define BHND_BOARD_BCM94336SDGN 0x0538 #define BHND_BOARD_BCM94336SDGFC 0x056B /* 4330 SDIO board types */ #define BHND_BOARD_BCM94330SDG 0x0528 #define BHND_BOARD_BCM94330SD_FCBGABU 0x052e #define BHND_BOARD_BCM94330SD_WLBGABU 0x052f #define BHND_BOARD_BCM94330SD_FCBGA 0x0530 #define BHND_BOARD_BCM94330FCSDAGB 0x0532 #define BHND_BOARD_BCM94330OLYMPICAMG 0x0549 #define BHND_BOARD_BCM94330OLYMPICAMGEPA 0x054F #define BHND_BOARD_BCM94330OLYMPICUNO3 0x0551 #define BHND_BOARD_BCM94330WLSDAGB 0x0547 #define BHND_BOARD_BCM94330CSPSDAGBB 0x054A /* 43224 boards */ #define BHND_BOARD_BCM943224X21 0x056e #define BHND_BOARD_BCM943224X21_FCC 0x00d1 #define BHND_BOARD_BCM943224X21B 0x00e9 #define BHND_BOARD_BCM943224M93 0x008b #define BHND_BOARD_BCM943224M93A 0x0090 #define BHND_BOARD_BCM943224X16 0x0093 #define BHND_BOARD_BCM94322X9 0x008d #define BHND_BOARD_BCM94322M35e 0x008e /* 43228 Boards */ #define BHND_BOARD_BCM943228BU8 0x0540 #define BHND_BOARD_BCM943228BU9 0x0541 #define BHND_BOARD_BCM943228BU 0x0542 #define BHND_BOARD_BCM943227HM4L 0x0543 #define BHND_BOARD_BCM943227HMB 0x0544 #define BHND_BOARD_BCM943228HM4L 0x0545 #define BHND_BOARD_BCM943228SD 0x0573 /* 43239 Boards */ #define BHND_BOARD_BCM943239MOD 0x05ac #define BHND_BOARD_BCM943239REF 0x05aa /* 4331 boards */ #define BHND_BOARD_BCM94331X19 0x00D6 /* X19B */ #define BHND_BOARD_BCM94331X28 0x00E4 /* X28 */ #define BHND_BOARD_BCM94331X28B 0x010E /* X28B */ #define BHND_BOARD_BCM94331PCIEBT3Ax BCM94331X28 #define BHND_BOARD_BCM94331X12_2G 0x00EC /* X12 2G */ #define BHND_BOARD_BCM94331X12_5G 0x00ED /* X12 5G */ #define BHND_BOARD_BCM94331X29B 0x00EF /* X29B */ #define BHND_BOARD_BCM94331X29D 0x010F /* X29D */ #define BHND_BOARD_BCM94331CSAX BCM94331X29B #define BHND_BOARD_BCM94331X19C 0x00F5 /* X19C */ #define BHND_BOARD_BCM94331X33 0x00F4 /* X33 */ #define BHND_BOARD_BCM94331BU 0x0523 #define BHND_BOARD_BCM94331S9BU 0x0524 #define BHND_BOARD_BCM94331MC 0x0525 #define BHND_BOARD_BCM94331MCI 0x0526 #define BHND_BOARD_BCM94331PCIEBT4 0x0527 #define BHND_BOARD_BCM94331HM 0x0574 #define BHND_BOARD_BCM94331PCIEDUAL 0x059B #define BHND_BOARD_BCM94331MCH5 0x05A9 #define BHND_BOARD_BCM94331CS 0x05C6 #define BHND_BOARD_BCM94331CD 0x05DA /* 4314 Boards */ #define BHND_BOARD_BCM94314BU 0x05b1 /* 53572 Boards */ #define BHND_BOARD_BCM953572BU 0x058D #define BHND_BOARD_BCM953572NR2 0x058E #define BHND_BOARD_BCM947188NR2 0x058F #define BHND_BOARD_BCM953572SDRNR2 0x0590 /* 43236 boards */ #define BHND_BOARD_BCM943236OLYMPICSULLEY 0x594 #define BHND_BOARD_BCM943236PREPROTOBLU2O3 0x5b9 #define BHND_BOARD_BCM943236USBELNA 0x5f8 /* 4314 Boards */ #define BHND_BOARD_BCM94314BUSDIO 0x05c8 #define BHND_BOARD_BCM94314BGABU 0x05c9 #define BHND_BOARD_BCM94314HMEPA 0x05ca #define BHND_BOARD_BCM94314HMEPABK 0x05cb #define BHND_BOARD_BCM94314SUHMEPA 0x05cc #define BHND_BOARD_BCM94314SUHM 0x05cd #define BHND_BOARD_BCM94314HM 0x05d1 /* 4334 Boards */ #define BHND_BOARD_BCM94334FCAGBI 0x05df #define BHND_BOARD_BCM94334WLAGBI 0x05dd /* 4335 Boards */ #define BHND_BOARD_BCM94335X52 0x0114 /* 4345 Boards */ #define BHND_BOARD_BCM94345 0x0687 /* 4360 Boards */ #define BHND_BOARD_BCM94360X52C 0X0117 #define BHND_BOARD_BCM94360X52D 0X0137 #define BHND_BOARD_BCM94360X29C 0X0112 #define BHND_BOARD_BCM94360X29CP2 0X0134 #define BHND_BOARD_BCM94360X51 0x0111 #define BHND_BOARD_BCM94360X51P2 0x0129 #define BHND_BOARD_BCM94360X51A 0x0135 #define BHND_BOARD_BCM94360X51B 0x0136 #define BHND_BOARD_BCM94360CS 0x061B #define BHND_BOARD_BCM94360J28_D11AC2G 0x0c00 #define BHND_BOARD_BCM94360J28_D11AC5G 0x0c01 #define BHND_BOARD_BCM94360USBH5_D11AC5G 0x06aa /* 4350 Boards */ #define BHND_BOARD_BCM94350X52B 0X0116 #define BHND_BOARD_BCM94350X14 0X0131 /* 43217 Boards */ #define BHND_BOARD_BCM943217BU 0x05d5 #define BHND_BOARD_BCM943217HM2L 0x05d6 #define BHND_BOARD_BCM943217HMITR2L 0x05d7 /* 43142 Boards */ #define BHND_BOARD_BCM943142HM 0x05e0 /* 43341 Boards */ #define BHND_BOARD_BCM943341WLABGS 0x062d /* 43342 Boards */ #define BHND_BOARD_BCM943342FCAGBI 0x0641 /* 43602 Boards, unclear yet what boards will be created. */ #define BHND_BOARD_BCM943602RSVD1 0x06a5 #define BHND_BOARD_BCM943602RSVD2 0x06a6 #define BHND_BOARD_BCM943602X87 0X0133 #define BHND_BOARD_BCM943602X238 0X0132 /* 4354 board types */ #define BHND_BOARD_BCM94354WLSAGBI 0x06db #define BHND_BOARD_BCM94354Z 0x0707 /* # of GPIO pins */ #define BHND_BCM43XX_GPIO_NUMPINS 32 /* These values are used by dhd USB host driver. */ #define BHND_USB_RDL_RAM_BASE_4319 0x60000000 #define BHND_USB_RDL_RAM_BASE_4329 0x60000000 #define BHND_USB_RDL_RAM_SIZE_4319 0x48000 #define BHND_USB_RDL_RAM_SIZE_4329 0x48000 #define BHND_USB_RDL_RAM_SIZE_43236 0x70000 #define BHND_USB_RDL_RAM_BASE_43236 0x60000000 #define BHND_USB_RDL_RAM_SIZE_4328 0x60000 #define BHND_USB_RDL_RAM_BASE_4328 0x80000000 #define BHND_USB_RDL_RAM_SIZE_4322 0x60000 #define BHND_USB_RDL_RAM_BASE_4322 0x60000000 #define BHND_USB_RDL_RAM_SIZE_4360 0xA0000 #define BHND_USB_RDL_RAM_BASE_4360 0x60000000 #define BHND_USB_RDL_RAM_SIZE_43242 0x90000 #define BHND_USB_RDL_RAM_BASE_43242 0x60000000 #define BHND_USB_RDL_RAM_SIZE_43143 0x70000 #define BHND_USB_RDL_RAM_BASE_43143 0x60000000 #define BHND_USB_RDL_RAM_SIZE_4350 0xC0000 #define BHND_USB_RDL_RAM_BASE_4350 0x180800 /* generic defs for nvram "muxenab" bits * Note: these differ for 4335a0. refer bcmchipc.h for specific mux options. */ #define BHND_NVRAM_MUXENAB_UART 0x00000001 #define BHND_NVRAM_MUXENAB_GPIO 0x00000002 #define BHND_NVRAM_MUXENAB_ERCX 0x00000004 /* External Radio BT coex */ #define BHND_NVRAM_MUXENAB_JTAG 0x00000008 #define BHND_NVRAM_MUXENAB_HOST_WAKE 0x00000010 /* configure GPIO for SDIO host_wake */ #define BHND_NVRAM_MUXENAB_I2S_EN 0x00000020 #define BHND_NVRAM_MUXENAB_I2S_MASTER 0x00000040 #define BHND_NVRAM_MUXENAB_I2S_FULL 0x00000080 #define BHND_NVRAM_MUXENAB_SFLASH 0x00000100 #define BHND_NVRAM_MUXENAB_RFSWCTRL0 0x00000200 #define BHND_NVRAM_MUXENAB_RFSWCTRL1 0x00000400 #define BHND_NVRAM_MUXENAB_RFSWCTRL2 0x00000800 #define BHND_NVRAM_MUXENAB_SECI 0x00001000 #define BHND_NVRAM_MUXENAB_BT_LEGACY 0x00002000 #define BHND_NVRAM_MUXENAB_HOST_WAKE1 0x00004000 /* configure alternative GPIO for SDIO host_wake */ /* Boot flags */ #define BHND_BOOTFLAG_FLASH_KERNEL_NFLASH 0x00000001 #define BHND_BOOTFLAG_FLASH_BOOT_NFLASH 0x00000002 #endif /* _BHND_BHND_IDS_H_ */ Index: head/sys/dev/bhnd/bhnd_match.h =================================================================== --- head/sys/dev/bhnd/bhnd_match.h (revision 326078) +++ head/sys/dev/bhnd/bhnd_match.h (revision 326079) @@ -1,297 +1,308 @@ /*- * Copyright (c) 2015-2016 Landon Fuller * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer, * 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 _BHND_BHND_MATCH_H_ #define _BHND_BHND_MATCH_H_ #include "bhnd_types.h" /** * A hardware revision match descriptor. */ struct bhnd_hwrev_match { uint16_t start; /**< first revision, or BHND_HWREV_INVALID to match on any revision. */ uint16_t end; /**< last revision, or BHND_HWREV_INVALID to match on any revision. */ }; /* Copy match field @p _name from @p _src */ #define _BHND_COPY_MATCH_FIELD(_src, _name) \ .m.match._name = (_src)->m.match._name, \ ._name = (_src)->_name /* Set match field @p _name with @p _value */ #define _BHND_SET_MATCH_FIELD(_name, _value) \ .m.match._name = 1, ._name = _value /** * Wildcard hardware revision match descriptor. */ #define BHND_HWREV_ANY { BHND_HWREV_INVALID, BHND_HWREV_INVALID } #define BHND_HWREV_IS_ANY(_m) \ ((_m)->start == BHND_HWREV_INVALID && (_m)->end == BHND_HWREV_INVALID) /** * Hardware revision match descriptor for an inclusive range. * * @param _start The first applicable hardware revision. * @param _end The last applicable hardware revision, or BHND_HWREV_INVALID * to match on any revision. */ #define BHND_HWREV_RANGE(_start, _end) { _start, _end } /** * Hardware revision match descriptor for a single revision. * * @param _hwrev The hardware revision to match on. */ #define BHND_HWREV_EQ(_hwrev) BHND_HWREV_RANGE(_hwrev, _hwrev) /** * Hardware revision match descriptor for any revision equal to or greater * than @p _start. * * @param _start The first hardware revision to match on. */ #define BHND_HWREV_GTE(_start) BHND_HWREV_RANGE(_start, BHND_HWREV_INVALID) /** * Hardware revision match descriptor for any revision equal to or less * than @p _end. * * @param _end The last hardware revision to match on. */ #define BHND_HWREV_LTE(_end) BHND_HWREV_RANGE(0, _end) /** * A bhnd(4) core match descriptor. */ struct bhnd_core_match { /** Select fields to be matched */ union { uint8_t match_flags; struct { uint8_t core_vendor:1, core_id:1, core_rev:1, core_class:1, core_idx:1, core_unit:1, flags_unused:2; } match; } m; uint16_t core_vendor; /**< required JEP106 device vendor */ uint16_t core_id; /**< required core ID */ struct bhnd_hwrev_match core_rev; /**< matching core revisions. */ bhnd_devclass_t core_class; /**< required bhnd class */ u_int core_idx; /**< required core index */ int core_unit; /**< required core unit */ }; #define _BHND_CORE_MATCH_COPY(_src) \ _BHND_COPY_MATCH_FIELD(_src, core_vendor), \ _BHND_COPY_MATCH_FIELD(_src, core_id), \ _BHND_COPY_MATCH_FIELD(_src, core_rev), \ _BHND_COPY_MATCH_FIELD(_src, core_class), \ _BHND_COPY_MATCH_FIELD(_src, core_idx), \ _BHND_COPY_MATCH_FIELD(_src, core_unit) \ #define BHND_MATCH_CORE_VENDOR(_v) _BHND_SET_MATCH_FIELD(core_vendor, _v) #define BHND_MATCH_CORE_ID(_id) _BHND_SET_MATCH_FIELD(core_id, _id) #define BHND_MATCH_CORE_REV(_rev) _BHND_SET_MATCH_FIELD(core_rev, \ BHND_ ## _rev) #define BHND_MATCH_CORE_CLASS(_cls) _BHND_SET_MATCH_FIELD(core_class, _cls) #define BHND_MATCH_CORE_IDX(_idx) _BHND_SET_MATCH_FIELD(core_idx, _idx) #define BHND_MATCH_CORE_UNIT(_unit) _BHND_SET_MATCH_FIELD(core_unit, _unit) /** * Match against the given @p _vendor and @p _id, */ #define BHND_MATCH_CORE(_vendor, _id) \ BHND_MATCH_CORE_VENDOR(_vendor), \ BHND_MATCH_CORE_ID(_id) /** * A bhnd(4) chip match descriptor. */ struct bhnd_chip_match { /** Select fields to be matched */ union { uint8_t match_flags; struct { uint8_t chip_id:1, chip_rev:1, chip_pkg:1, - flags_unused:5; + chip_type:1, + flags_unused:4; } match; } m; 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. */ struct bhnd_board_match { /** Select fields to be matched */ union { uint8_t match_flags; struct { uint8_t board_vendor:1, board_type:1, board_rev:1, board_srom_rev:1, flags_unused:4; } match; } m; uint16_t board_vendor; /**< required board vendor */ uint16_t board_type; /**< required board type */ struct bhnd_hwrev_match board_rev; /**< matching board revisions */ struct bhnd_hwrev_match board_srom_rev; /**< matching board srom revisions */ }; #define _BHND_BOARD_MATCH_COPY(_src) \ _BHND_COPY_MATCH_FIELD(_src, board_vendor), \ _BHND_COPY_MATCH_FIELD(_src, board_type), \ _BHND_COPY_MATCH_FIELD(_src, board_rev), \ _BHND_COPY_MATCH_FIELD(_src, board_srom_rev) /** Set the required board vendor within a bhnd match descriptor */ #define BHND_MATCH_BOARD_VENDOR(_v) _BHND_SET_MATCH_FIELD(board_vendor, _v) /** Set the required board type within a bhnd match descriptor */ #define BHND_MATCH_BOARD_TYPE(_type) _BHND_SET_MATCH_FIELD(board_type, \ BHND_BOARD_ ## _type) /** Set the required SROM revision range within a bhnd match descriptor */ #define BHND_MATCH_SROMREV(_rev) _BHND_SET_MATCH_FIELD(board_srom_rev, \ BHND_HWREV_ ## _rev) /** Set the required board revision range within a bhnd match descriptor */ #define BHND_MATCH_BOARD_REV(_rev) _BHND_SET_MATCH_FIELD(board_rev, \ BHND_ ## _rev) /** Set the required board vendor and type within a bhnd match descriptor */ #define BHND_MATCH_BOARD(_vend, _type) \ BHND_MATCH_BOARD_VENDOR(_vend), BHND_MATCH_BOARD_TYPE(_type) /** * A bhnd(4) device match descriptor. * * @warning Matching on board attributes relies on NVRAM access, and will * fail if a valid NVRAM device cannot be found, or is not yet attached. */ 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, core_class:1, core_idx:1, core_unit:1, 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; uint16_t core_vendor; /**< required JEP106 device vendor */ uint16_t core_id; /**< required core ID */ struct bhnd_hwrev_match core_rev; /**< matching core revisions. */ bhnd_devclass_t core_class; /**< required bhnd class */ u_int core_idx; /**< required core index */ int core_unit; /**< required core unit */ 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 */ struct bhnd_hwrev_match board_rev; /**< matching board revisions */ struct bhnd_hwrev_match board_srom_rev; /**< matching board srom revisions */ }; /** Define a wildcard match requirement (matches on any device). */ #define BHND_MATCH_ANY .m.match_flags = 0 #define BHND_MATCH_IS_ANY(_m) \ ((_m)->m.match_flags == 0) #endif /* _BHND_BHND_MATCH_H_ */ Index: head/sys/dev/bhnd/bhnd_subr.c =================================================================== --- head/sys/dev/bhnd/bhnd_subr.c (revision 326078) +++ head/sys/dev/bhnd/bhnd_subr.c (revision 326079) @@ -1,2319 +1,2333 @@ /*- * 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: * 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. */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include "nvram/bhnd_nvram.h" #include "bhnd_chipc_if.h" #include "bhnd_nvram_if.h" #include "bhnd_nvram_map.h" #include "bhndreg.h" #include "bhndvar.h" #include "bhnd_private.h" static void bhnd_service_registry_free_entry( struct bhnd_service_entry *entry); static int compare_ascending_probe_order(const void *lhs, const void *rhs); static int compare_descending_probe_order(const void *lhs, const void *rhs); /* BHND core device description table. */ static const struct bhnd_core_desc { uint16_t vendor; uint16_t device; bhnd_devclass_t class; const char *desc; } bhnd_core_descs[] = { #define BHND_CDESC(_mfg, _cid, _cls, _desc) \ { BHND_MFGID_ ## _mfg, BHND_COREID_ ## _cid, \ BHND_DEVCLASS_ ## _cls, _desc } BHND_CDESC(BCM, CC, CC, "ChipCommon I/O Controller"), BHND_CDESC(BCM, ILINE20, OTHER, "iLine20 HPNA"), BHND_CDESC(BCM, SRAM, RAM, "SRAM"), BHND_CDESC(BCM, SDRAM, RAM, "SDRAM"), BHND_CDESC(BCM, PCI, PCI, "PCI Bridge"), BHND_CDESC(BCM, MIPS, CPU, "BMIPS CPU"), BHND_CDESC(BCM, ENET, ENET_MAC, "Fast Ethernet MAC"), BHND_CDESC(BCM, CODEC, OTHER, "V.90 Modem Codec"), BHND_CDESC(BCM, USB, USB_DUAL, "USB 1.1 Device/Host Controller"), BHND_CDESC(BCM, ADSL, OTHER, "ADSL Core"), BHND_CDESC(BCM, ILINE100, OTHER, "iLine100 HPNA"), BHND_CDESC(BCM, IPSEC, OTHER, "IPsec Accelerator"), BHND_CDESC(BCM, UTOPIA, OTHER, "UTOPIA ATM Core"), BHND_CDESC(BCM, PCMCIA, PCCARD, "PCMCIA Bridge"), BHND_CDESC(BCM, SOCRAM, RAM, "Internal Memory"), BHND_CDESC(BCM, MEMC, MEMC, "MEMC SDRAM Controller"), BHND_CDESC(BCM, OFDM, OTHER, "OFDM PHY"), BHND_CDESC(BCM, EXTIF, OTHER, "External Interface"), BHND_CDESC(BCM, D11, WLAN, "802.11 MAC/PHY/Radio"), BHND_CDESC(BCM, APHY, WLAN_PHY, "802.11a PHY"), BHND_CDESC(BCM, BPHY, WLAN_PHY, "802.11b PHY"), BHND_CDESC(BCM, GPHY, WLAN_PHY, "802.11g PHY"), BHND_CDESC(BCM, MIPS33, CPU, "BMIPS33 CPU"), BHND_CDESC(BCM, USB11H, USB_HOST, "USB 1.1 Host Controller"), BHND_CDESC(BCM, USB11D, USB_DEV, "USB 1.1 Device Controller"), BHND_CDESC(BCM, USB20H, USB_HOST, "USB 2.0 Host Controller"), BHND_CDESC(BCM, USB20D, USB_DEV, "USB 2.0 Device Controller"), BHND_CDESC(BCM, SDIOH, OTHER, "SDIO Host Controller"), BHND_CDESC(BCM, ROBO, OTHER, "RoboSwitch"), BHND_CDESC(BCM, ATA100, OTHER, "Parallel ATA Controller"), BHND_CDESC(BCM, SATAXOR, OTHER, "SATA DMA/XOR Controller"), BHND_CDESC(BCM, GIGETH, ENET_MAC, "Gigabit Ethernet MAC"), BHND_CDESC(BCM, PCIE, PCIE, "PCIe Bridge"), BHND_CDESC(BCM, NPHY, WLAN_PHY, "802.11n 2x2 PHY"), BHND_CDESC(BCM, SRAMC, MEMC, "SRAM Controller"), BHND_CDESC(BCM, MINIMAC, OTHER, "MINI MAC/PHY"), BHND_CDESC(BCM, ARM11, CPU, "ARM1176 CPU"), BHND_CDESC(BCM, ARM7S, CPU, "ARM7TDMI-S CPU"), BHND_CDESC(BCM, LPPHY, WLAN_PHY, "802.11a/b/g PHY"), BHND_CDESC(BCM, PMU, PMU, "PMU"), BHND_CDESC(BCM, SSNPHY, WLAN_PHY, "802.11n Single-Stream PHY"), BHND_CDESC(BCM, SDIOD, OTHER, "SDIO Device Core"), BHND_CDESC(BCM, ARMCM3, CPU, "ARM Cortex-M3 CPU"), BHND_CDESC(BCM, HTPHY, WLAN_PHY, "802.11n 4x4 PHY"), BHND_CDESC(MIPS,MIPS74K, CPU, "MIPS74k CPU"), BHND_CDESC(BCM, GMAC, ENET_MAC, "Gigabit MAC core"), BHND_CDESC(BCM, DMEMC, MEMC, "DDR1/DDR2 Memory Controller"), BHND_CDESC(BCM, PCIERC, OTHER, "PCIe Root Complex"), BHND_CDESC(BCM, OCP, SOC_BRIDGE, "OCP to OCP Bridge"), BHND_CDESC(BCM, SC, OTHER, "Shared Common Core"), BHND_CDESC(BCM, AHB, SOC_BRIDGE, "OCP to AHB Bridge"), BHND_CDESC(BCM, SPIH, OTHER, "SPI Host Controller"), BHND_CDESC(BCM, I2S, OTHER, "I2S Digital Audio Interface"), BHND_CDESC(BCM, DMEMS, MEMC, "SDR/DDR1 Memory Controller"), BHND_CDESC(BCM, UBUS_SHIM, OTHER, "BCM6362/UBUS WLAN SHIM"), BHND_CDESC(BCM, PCIE2, PCIE, "PCIe Bridge (Gen2)"), BHND_CDESC(ARM, APB_BRIDGE, SOC_BRIDGE, "BP135 AMBA3 AXI to APB Bridge"), BHND_CDESC(ARM, PL301, SOC_ROUTER, "PL301 AMBA3 Interconnect"), BHND_CDESC(ARM, EROM, EROM, "PL366 Device Enumeration ROM"), BHND_CDESC(ARM, OOB_ROUTER, OTHER, "PL367 OOB Interrupt Router"), BHND_CDESC(ARM, AXI_UNMAPPED, OTHER, "Unmapped Address Ranges"), BHND_CDESC(BCM, 4706_CC, CC, "ChipCommon I/O Controller"), BHND_CDESC(BCM, NS_PCIE2, PCIE, "PCIe Bridge (Gen2)"), BHND_CDESC(BCM, NS_DMA, OTHER, "DMA engine"), BHND_CDESC(BCM, NS_SDIO, OTHER, "SDIO 3.0 Host Controller"), BHND_CDESC(BCM, NS_USB20H, USB_HOST, "USB 2.0 Host Controller"), BHND_CDESC(BCM, NS_USB30H, USB_HOST, "USB 3.0 Host Controller"), BHND_CDESC(BCM, NS_A9JTAG, OTHER, "ARM Cortex A9 JTAG Interface"), BHND_CDESC(BCM, NS_DDR23_MEMC, MEMC, "Denali DDR2/DD3 Memory Controller"), BHND_CDESC(BCM, NS_ROM, NVRAM, "System ROM"), BHND_CDESC(BCM, NS_NAND, NVRAM, "NAND Flash Controller"), BHND_CDESC(BCM, NS_QSPI, NVRAM, "QSPI Flash Controller"), BHND_CDESC(BCM, NS_CC_B, CC_B, "ChipCommon B Auxiliary I/O Controller"), BHND_CDESC(BCM, 4706_SOCRAM, RAM, "Internal Memory"), BHND_CDESC(BCM, IHOST_ARMCA9, CPU, "ARM Cortex A9 CPU"), BHND_CDESC(BCM, 4706_GMAC_CMN, ENET, "Gigabit MAC (Common)"), BHND_CDESC(BCM, 4706_GMAC, ENET_MAC, "Gigabit MAC"), BHND_CDESC(BCM, AMEMC, MEMC, "Denali DDR1/DDR2 Memory Controller"), #undef BHND_CDESC /* Derived from inspection of the BCM4331 cores that provide PrimeCell * IDs. Due to lack of documentation, the surmised device name/purpose * provided here may be incorrect. */ { BHND_MFGID_ARM, BHND_PRIMEID_EROM, BHND_DEVCLASS_OTHER, "PL364 Device Enumeration ROM" }, { BHND_MFGID_ARM, BHND_PRIMEID_SWRAP, BHND_DEVCLASS_OTHER, "PL368 Device Management Interface" }, { BHND_MFGID_ARM, BHND_PRIMEID_MWRAP, BHND_DEVCLASS_OTHER, "PL369 Device Management Interface" }, { 0, 0, 0, NULL } }; /** * Return the name for a given JEP106 manufacturer ID. * * @param vendor A JEP106 Manufacturer ID, including the non-standard ARM 4-bit * JEP106 continuation code. */ const char * bhnd_vendor_name(uint16_t vendor) { switch (vendor) { case BHND_MFGID_ARM: return "ARM"; case BHND_MFGID_BCM: return "Broadcom"; case BHND_MFGID_MIPS: return "MIPS"; default: return "unknown"; } } /** * Return the name of a port type. */ const char * bhnd_port_type_name(bhnd_port_type port_type) { switch (port_type) { case BHND_PORT_DEVICE: return ("device"); case BHND_PORT_BRIDGE: return ("bridge"); case BHND_PORT_AGENT: return ("agent"); default: return "unknown"; } } /** * Return the name of an NVRAM source. */ const char * bhnd_nvram_src_name(bhnd_nvram_src nvram_src) { switch (nvram_src) { case BHND_NVRAM_SRC_FLASH: return ("flash"); case BHND_NVRAM_SRC_OTP: return ("OTP"); case BHND_NVRAM_SRC_SPROM: return ("SPROM"); case BHND_NVRAM_SRC_UNKNOWN: return ("none"); default: return ("unknown"); } } static const struct bhnd_core_desc * bhnd_find_core_desc(uint16_t vendor, uint16_t device) { for (u_int i = 0; bhnd_core_descs[i].desc != NULL; i++) { if (bhnd_core_descs[i].vendor != vendor) continue; if (bhnd_core_descs[i].device != device) continue; return (&bhnd_core_descs[i]); } return (NULL); } /** * Return a human-readable name for a BHND core. * * @param vendor The core designer's JEDEC-106 Manufacturer ID * @param device The core identifier. */ const char * bhnd_find_core_name(uint16_t vendor, uint16_t device) { const struct bhnd_core_desc *desc; if ((desc = bhnd_find_core_desc(vendor, device)) == NULL) return ("unknown"); return desc->desc; } /** * Return the device class for a BHND core. * * @param vendor The core designer's JEDEC-106 Manufacturer ID * @param device The core identifier. */ bhnd_devclass_t bhnd_find_core_class(uint16_t vendor, uint16_t device) { const struct bhnd_core_desc *desc; if ((desc = bhnd_find_core_desc(vendor, device)) == NULL) return (BHND_DEVCLASS_OTHER); return desc->class; } /** * Return a human-readable name for a BHND core. * * @param ci The core's info record. */ const char * bhnd_core_name(const struct bhnd_core_info *ci) { return bhnd_find_core_name(ci->vendor, ci->device); } /** * Return the device class for a BHND core. * * @param ci The core's info record. */ bhnd_devclass_t bhnd_core_class(const struct bhnd_core_info *ci) { return bhnd_find_core_class(ci->vendor, ci->device); } /** * Write a human readable name representation of the given * BHND_CHIPID_* constant to @p buffer. * * @param buffer Output buffer, or NULL to compute the required size. * @param size Capacity of @p buffer, in bytes. * @param chip_id Chip ID to be formatted. * * @return Returns the required number of bytes on success, or a negative * integer on failure. No more than @p size-1 characters be written, with * the @p size'th set to '\0'. * * @sa BHND_CHIPID_MAX_NAMELEN */ int bhnd_format_chip_id(char *buffer, size_t size, uint16_t chip_id) { /* All hex formatted IDs are within the range of 0x4000-0x9C3F (40000-1) */ if (chip_id >= 0x4000 && chip_id <= 0x9C3F) return (snprintf(buffer, size, "BCM%hX", chip_id)); else return (snprintf(buffer, size, "BCM%hu", chip_id)); } /** * Initialize a core info record with data from from a bhnd-attached @p dev. * * @param dev A bhnd device. * @param core The record to be initialized. */ struct bhnd_core_info bhnd_get_core_info(device_t dev) { return (struct bhnd_core_info) { .vendor = bhnd_get_vendor(dev), .device = bhnd_get_device(dev), .hwrev = bhnd_get_hwrev(dev), .core_idx = bhnd_get_core_index(dev), .unit = bhnd_get_core_unit(dev) }; } /** * Find a @p class child device with @p unit on @p bus. * * @param bus The bhnd-compatible bus to be searched. * @param class The device class to match on. * @param unit The core unit number; specify -1 to return the first match * regardless of unit number. * * @retval device_t if a matching child device is found. * @retval NULL if no matching child device is found. */ device_t bhnd_bus_find_child(device_t bus, bhnd_devclass_t class, int unit) { struct bhnd_core_match md = { BHND_MATCH_CORE_CLASS(class), BHND_MATCH_CORE_UNIT(unit) }; if (unit == -1) md.m.match.core_unit = 0; return bhnd_bus_match_child(bus, &md); } /** * Find the first child device on @p bus that matches @p desc. * * @param bus The bhnd-compatible bus to be searched. * @param desc A match descriptor. * * @retval device_t if a matching child device is found. * @retval NULL if no matching child device is found. */ device_t bhnd_bus_match_child(device_t bus, const struct bhnd_core_match *desc) { device_t *devlistp; device_t match; int devcnt; int error; error = device_get_children(bus, &devlistp, &devcnt); if (error != 0) return (NULL); match = NULL; for (int i = 0; i < devcnt; i++) { struct bhnd_core_info ci = bhnd_get_core_info(devlistp[i]); if (bhnd_core_matches(&ci, desc)) { match = devlistp[i]; goto done; } } done: free(devlistp, M_TEMP); return match; } /** * Retrieve an ordered list of all device instances currently connected to * @p bus, returning a pointer to the array in @p devlistp and the count * in @p ndevs. * * The memory allocated for the table must be freed via * bhnd_bus_free_children(). * * @param bus The bhnd-compatible bus to be queried. * @param[out] devlist The array of devices. * @param[out] devcount The number of devices in @p devlistp * @param order The order in which devices will be returned * in @p devlist. * * @retval 0 success * @retval non-zero if an error occurs, a regular unix error code will * be returned. */ int bhnd_bus_get_children(device_t bus, device_t **devlist, int *devcount, bhnd_device_order order) { int error; /* Fetch device array */ if ((error = device_get_children(bus, devlist, devcount))) return (error); /* Perform requested sorting */ if ((error = bhnd_sort_devices(*devlist, *devcount, order))) { bhnd_bus_free_children(*devlist); return (error); } return (0); } /** * Free any memory allocated in a previous call to bhnd_bus_get_children(). * * @param devlist The device array returned by bhnd_bus_get_children(). */ void bhnd_bus_free_children(device_t *devlist) { free(devlist, M_TEMP); } /** * Perform in-place sorting of an array of bhnd device instances. * * @param devlist An array of bhnd devices. * @param devcount The number of devices in @p devs. * @param order The sort order to be used. */ int bhnd_sort_devices(device_t *devlist, size_t devcount, bhnd_device_order order) { int (*compare)(const void *, const void *); switch (order) { case BHND_DEVICE_ORDER_ATTACH: compare = compare_ascending_probe_order; break; case BHND_DEVICE_ORDER_DETACH: compare = compare_descending_probe_order; break; default: printf("unknown sort order: %d\n", order); return (EINVAL); } qsort(devlist, devcount, sizeof(*devlist), compare); return (0); } /* * Ascending comparison of bhnd device's probe order. */ static int compare_ascending_probe_order(const void *lhs, const void *rhs) { device_t ldev, rdev; int lorder, rorder; ldev = (*(const device_t *) lhs); rdev = (*(const device_t *) rhs); lorder = BHND_BUS_GET_PROBE_ORDER(device_get_parent(ldev), ldev); rorder = BHND_BUS_GET_PROBE_ORDER(device_get_parent(rdev), rdev); if (lorder < rorder) { return (-1); } else if (lorder > rorder) { return (1); } else { return (0); } } /* * Descending comparison of bhnd device's probe order. */ static int compare_descending_probe_order(const void *lhs, const void *rhs) { return (compare_ascending_probe_order(rhs, lhs)); } /** * Call device_probe_and_attach() for each of the bhnd bus device's * children, in bhnd attach order. * * @param bus The bhnd-compatible bus for which all children should be probed * and attached. */ int bhnd_bus_probe_children(device_t bus) { device_t *devs; int ndevs; int error; /* Fetch children in attach order */ error = bhnd_bus_get_children(bus, &devs, &ndevs, BHND_DEVICE_ORDER_ATTACH); if (error) return (error); /* Probe and attach all children */ for (int i = 0; i < ndevs; i++) { device_t child = devs[i]; device_probe_and_attach(child); } bhnd_bus_free_children(devs); return (0); } /** * Walk up the bhnd device hierarchy to locate the root device * to which the bhndb bridge is attached. * * This can be used from within bhnd host bridge drivers to locate the * actual upstream host device. * * @param dev A bhnd device. * @param bus_class The expected bus (e.g. "pci") to which the bridge root * should be attached. * * @retval device_t if a matching parent device is found. * @retval NULL @p dev is not attached via a bhndb bus * @retval NULL no parent device is attached via @p bus_class. */ device_t bhnd_find_bridge_root(device_t dev, devclass_t bus_class) { devclass_t bhndb_class; device_t parent; KASSERT(device_get_devclass(device_get_parent(dev)) == bhnd_devclass, ("%s not a bhnd device", device_get_nameunit(dev))); bhndb_class = devclass_find("bhndb"); /* Walk the device tree until we hit a bridge */ parent = dev; while ((parent = device_get_parent(parent)) != NULL) { if (device_get_devclass(parent) == bhndb_class) break; } /* No bridge? */ if (parent == NULL) return (NULL); /* Search for a parent attached to the expected bus class */ while ((parent = device_get_parent(parent)) != NULL) { device_t bus; bus = device_get_parent(parent); if (bus != NULL && device_get_devclass(bus) == bus_class) return (parent); } /* Not found */ return (NULL); } /** * Find the first core in @p cores that matches @p desc. * * @param cores The table to search. * @param num_cores The length of @p cores. * @param desc A match descriptor. * * @retval bhnd_core_info if a matching core is found. * @retval NULL if no matching core is found. */ const struct bhnd_core_info * bhnd_match_core(const struct bhnd_core_info *cores, u_int num_cores, const struct bhnd_core_match *desc) { for (u_int i = 0; i < num_cores; i++) { if (bhnd_core_matches(&cores[i], desc)) return &cores[i]; } return (NULL); } /** * Find the first core in @p cores with the given @p class. * * @param cores The table to search. * @param num_cores The length of @p cores. * @param desc A match descriptor. * * @retval bhnd_core_info if a matching core is found. * @retval NULL if no matching core is found. */ const struct bhnd_core_info * bhnd_find_core(const struct bhnd_core_info *cores, u_int num_cores, bhnd_devclass_t class) { struct bhnd_core_match md = { BHND_MATCH_CORE_CLASS(class) }; return bhnd_match_core(cores, num_cores, &md); } /** * Create an equality match descriptor for @p core. * * @param core The core info to be matched on. * @param desc On return, will be populated with a match descriptor for @p core. */ struct bhnd_core_match bhnd_core_get_match_desc(const struct bhnd_core_info *core) { return ((struct bhnd_core_match) { BHND_MATCH_CORE_VENDOR(core->vendor), BHND_MATCH_CORE_ID(core->device), BHND_MATCH_CORE_REV(HWREV_EQ(core->hwrev)), BHND_MATCH_CORE_CLASS(bhnd_core_class(core)), BHND_MATCH_CORE_IDX(core->core_idx), BHND_MATCH_CORE_UNIT(core->unit) }); } /** * Return true if the @p lhs is equal to @p rhs * * @param lhs The first bhnd core descriptor to compare. * @param rhs The second bhnd core descriptor to compare. * * @retval true if @p lhs is equal to @p rhs * @retval false if @p lhs is not equal to @p rhs */ bool bhnd_cores_equal(const struct bhnd_core_info *lhs, const struct bhnd_core_info *rhs) { struct bhnd_core_match md; /* Use an equality match descriptor to perform the comparison */ md = bhnd_core_get_match_desc(rhs); return (bhnd_core_matches(lhs, &md)); } /** * Return true if the @p core matches @p desc. * * @param core A bhnd core descriptor. * @param desc A match descriptor to compare against @p core. * * @retval true if @p core matches @p match * @retval false if @p core does not match @p match. */ bool bhnd_core_matches(const struct bhnd_core_info *core, const struct bhnd_core_match *desc) { if (desc->m.match.core_vendor && desc->core_vendor != core->vendor) return (false); if (desc->m.match.core_id && desc->core_id != core->device) return (false); if (desc->m.match.core_unit && desc->core_unit != core->unit) return (false); if (desc->m.match.core_rev && !bhnd_hwrev_matches(core->hwrev, &desc->core_rev)) return (false); if (desc->m.match.core_idx && desc->core_idx != core->core_idx) return (false); if (desc->m.match.core_class && desc->core_class != bhnd_core_class(core)) return (false); return true; } /** * Return true if the @p chip matches @p desc. * * @param chip A bhnd chip identifier. * @param desc A match descriptor to compare against @p chip. * * @retval true if @p chip matches @p match * @retval false if @p chip does not match @p match. */ bool bhnd_chip_matches(const struct bhnd_chipid *chip, const struct bhnd_chip_match *desc) { if (desc->m.match.chip_id && chip->chip_id != desc->chip_id) return (false); if (desc->m.match.chip_pkg && chip->chip_pkg != desc->chip_pkg) return (false); if (desc->m.match.chip_rev && !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); } /** * Return true if the @p board matches @p desc. * * @param board The bhnd board info. * @param desc A match descriptor to compare against @p board. * * @retval true if @p chip matches @p match * @retval false if @p chip does not match @p match. */ bool bhnd_board_matches(const struct bhnd_board_info *board, const struct bhnd_board_match *desc) { if (desc->m.match.board_srom_rev && !bhnd_hwrev_matches(board->board_srom_rev, &desc->board_srom_rev)) return (false); if (desc->m.match.board_vendor && board->board_vendor != desc->board_vendor) return (false); if (desc->m.match.board_type && board->board_type != desc->board_type) return (false); if (desc->m.match.board_rev && !bhnd_hwrev_matches(board->board_rev, &desc->board_rev)) return (false); return (true); } /** * Return true if the @p hwrev matches @p desc. * * @param hwrev A bhnd hardware revision. * @param desc A match descriptor to compare against @p core. * * @retval true if @p hwrev matches @p match * @retval false if @p hwrev does not match @p match. */ bool bhnd_hwrev_matches(uint16_t hwrev, const struct bhnd_hwrev_match *desc) { if (desc->start != BHND_HWREV_INVALID && desc->start > hwrev) return false; if (desc->end != BHND_HWREV_INVALID && desc->end < hwrev) return false; return true; } /** * Return true if the @p dev matches @p desc. * * @param dev A bhnd device. * @param desc A match descriptor to compare against @p dev. * * @retval true if @p dev matches @p match * @retval false if @p dev does not match @p match. */ bool bhnd_device_matches(device_t dev, const struct bhnd_device_match *desc) { struct bhnd_core_info core; const struct bhnd_chipid *chip; struct bhnd_board_info board; device_t parent; int error; /* Construct individual match descriptors */ struct bhnd_core_match m_core = { _BHND_CORE_MATCH_COPY(desc) }; struct bhnd_chip_match m_chip = { _BHND_CHIP_MATCH_COPY(desc) }; struct bhnd_board_match m_board = { _BHND_BOARD_MATCH_COPY(desc) }; /* Fetch and match core info */ if (m_core.m.match_flags) { /* Only applicable to bhnd-attached cores */ parent = device_get_parent(dev); if (device_get_devclass(parent) != bhnd_devclass) { device_printf(dev, "attempting to match core " "attributes against non-core device\n"); return (false); } core = bhnd_get_core_info(dev); if (!bhnd_core_matches(&core, &m_core)) return (false); } /* Fetch and match chip info */ if (m_chip.m.match_flags) { chip = bhnd_get_chipid(dev); if (!bhnd_chip_matches(chip, &m_chip)) return (false); } /* Fetch and match board info. * * This is not available until after NVRAM is up; earlier device * matches should not include board requirements */ if (m_board.m.match_flags) { if ((error = bhnd_read_board_info(dev, &board))) { device_printf(dev, "failed to read required board info " "during device matching: %d\n", error); return (false); } if (!bhnd_board_matches(&board, &m_board)) return (false); } /* All matched */ return (true); } /** * Search @p table for an entry matching @p dev. * * @param dev A bhnd device to match against @p table. * @param table The device table to search. * @param entry_size The @p table entry size, in bytes. * * @retval bhnd_device the first matching device, if any. * @retval NULL if no matching device is found in @p table. */ const struct bhnd_device * bhnd_device_lookup(device_t dev, const struct bhnd_device *table, size_t entry_size) { const struct bhnd_device *entry; device_t hostb, parent; bhnd_attach_type attach_type; uint32_t dflags; parent = device_get_parent(dev); hostb = bhnd_bus_find_hostb_device(parent); attach_type = bhnd_get_attach_type(dev); for (entry = table; !BHND_DEVICE_IS_END(entry); entry = (const struct bhnd_device *) ((const char *) entry + entry_size)) { /* match core info */ if (!bhnd_device_matches(dev, &entry->core)) continue; /* match device flags */ dflags = entry->device_flags; /* hostb implies BHND_ATTACH_ADAPTER requirement */ if (dflags & BHND_DF_HOSTB) dflags |= BHND_DF_ADAPTER; if (dflags & BHND_DF_ADAPTER) if (attach_type != BHND_ATTACH_ADAPTER) continue; if (dflags & BHND_DF_HOSTB) if (dev != hostb) continue; if (dflags & BHND_DF_SOC) if (attach_type != BHND_ATTACH_NATIVE) continue; /* device found */ return (entry); } /* not found */ return (NULL); } /** * Scan the device @p table for all quirk flags applicable to @p dev. * * @param dev A bhnd device to match against @p table. * @param table The device table to search. * * @return returns all matching quirk flags. */ uint32_t bhnd_device_quirks(device_t dev, const struct bhnd_device *table, size_t entry_size) { const struct bhnd_device *dent; const struct bhnd_device_quirk *qent, *qtable; uint32_t quirks; /* Locate the device entry */ if ((dent = bhnd_device_lookup(dev, table, entry_size)) == NULL) return (0); /* Quirks table is optional */ qtable = dent->quirks_table; if (qtable == NULL) return (0); /* Collect matching device quirk entries */ quirks = 0; for (qent = qtable; !BHND_DEVICE_QUIRK_IS_END(qent); qent++) { if (bhnd_device_matches(dev, &qent->desc)) quirks |= qent->quirks; } return (quirks); } /** * Allocate bhnd(4) resources defined in @p rs from a parent bus. * * @param dev The device requesting ownership of the resources. * @param rs A standard bus resource specification. This will be updated * with the allocated resource's RIDs. * @param res On success, the allocated bhnd resources. * * @retval 0 success * @retval non-zero if allocation of any non-RF_OPTIONAL resource fails, * all allocated resources will be released and a regular * unix error code will be returned. */ int bhnd_alloc_resources(device_t dev, struct resource_spec *rs, struct bhnd_resource **res) { /* Initialize output array */ for (u_int i = 0; rs[i].type != -1; i++) res[i] = NULL; for (u_int i = 0; rs[i].type != -1; i++) { res[i] = bhnd_alloc_resource_any(dev, rs[i].type, &rs[i].rid, rs[i].flags); /* Clean up all allocations on failure */ if (res[i] == NULL && !(rs[i].flags & RF_OPTIONAL)) { bhnd_release_resources(dev, rs, res); return (ENXIO); } } return (0); } /** * Release bhnd(4) resources defined in @p rs from a parent bus. * * @param dev The device that owns the resources. * @param rs A standard bus resource specification previously initialized * by @p bhnd_alloc_resources. * @param res The bhnd resources to be released. */ void bhnd_release_resources(device_t dev, const struct resource_spec *rs, struct bhnd_resource **res) { for (u_int i = 0; rs[i].type != -1; i++) { if (res[i] == NULL) continue; bhnd_release_resource(dev, rs[i].type, rs[i].rid, res[i]); res[i] = NULL; } } /** * Parse the CHIPC_ID_* fields from the ChipCommon CHIPC_ID * register, returning its bhnd_chipid representation. * * @param idreg The CHIPC_ID register value. * @param enum_addr The enumeration address to include in the result. * * @warning * On early siba(4) devices, the ChipCommon core does not provide * a valid CHIPC_ID_NUMCORE field. On these ChipCommon revisions * (see CHIPC_NCORES_MIN_HWREV()), this function will parse and return * an invalid `ncores` value. */ struct bhnd_chipid bhnd_parse_chipid(uint32_t idreg, bhnd_addr_t enum_addr) { struct bhnd_chipid result; /* Fetch the basic chip info */ result.chip_id = CHIPC_GET_BITS(idreg, CHIPC_ID_CHIP); result.chip_pkg = CHIPC_GET_BITS(idreg, CHIPC_ID_PKG); result.chip_rev = CHIPC_GET_BITS(idreg, CHIPC_ID_REV); result.chip_type = CHIPC_GET_BITS(idreg, CHIPC_ID_BUS); result.ncores = CHIPC_GET_BITS(idreg, CHIPC_ID_NUMCORE); result.enum_addr = enum_addr; return (result); } /** * Determine the correct core count for a chip identification value that * may contain an invalid core count. * * On some early siba(4) devices (see CHIPC_NCORES_MIN_HWREV()), the ChipCommon * core does not provide a valid CHIPC_ID_NUMCORE field. * * @param cid The chip identification to be queried. * @param chipc_hwrev The hardware revision of the ChipCommon core from which * @p cid was parsed. * @param[out] ncores On success, will be set to the correct core count. * * @retval 0 If the core count is already correct, or was mapped to a * a correct value. * @retval EINVAL If the core count is incorrect, but the chip was not * recognized. */ int bhnd_chipid_fixed_ncores(const struct bhnd_chipid *cid, uint16_t chipc_hwrev, uint8_t *ncores) { /* bcma(4), and most siba(4) devices */ if (CHIPC_NCORES_MIN_HWREV(chipc_hwrev)) { *ncores = cid->ncores; return (0); } /* broken siba(4) chipsets */ switch (cid->chip_id) { case BHND_CHIPID_BCM4306: *ncores = 6; break; case BHND_CHIPID_BCM4704: *ncores = 9; break; case BHND_CHIPID_BCM5365: /* * BCM5365 does support ID_NUMCORE in at least * some of its revisions, but for unknown * reasons, Broadcom's drivers always exclude * the ChipCommon revision (0x5) used by BCM5365 * from the set of revisions supporting * ID_NUMCORE, and instead supply a fixed value. * * Presumably, at least some of these devices * shipped with a broken ID_NUMCORE value. */ *ncores = 7; break; default: return (EINVAL); } return (0); } /** * Allocate the resource defined by @p rs via @p dev, use it * to read the ChipCommon ID register relative to @p chipc_offset, * then release the resource. * * @param dev The device owning @p rs. * @param rs A resource spec that encompasses the ChipCommon register block. * @param chipc_offset The offset of the ChipCommon registers within @p rs. * @param[out] result the chip identification data. * * @retval 0 success * @retval non-zero if the ChipCommon identification data could not be read. */ int bhnd_read_chipid(device_t dev, struct resource_spec *rs, bus_size_t chipc_offset, struct bhnd_chipid *result) { struct resource *res; bhnd_addr_t enum_addr; uint32_t reg; uint8_t chip_type; int error, rid, rtype; rid = rs->rid; rtype = rs->type; error = 0; /* Allocate the ChipCommon window resource and fetch the chipid data */ res = bus_alloc_resource_any(dev, rtype, &rid, RF_ACTIVE); if (res == NULL) { device_printf(dev, "failed to allocate bhnd chipc resource\n"); return (ENXIO); } /* Fetch the basic chip info */ reg = bus_read_4(res, chipc_offset + CHIPC_ID); chip_type = CHIPC_GET_BITS(reg, CHIPC_ID_BUS); /* Fetch the EROMPTR */ if (BHND_CHIPTYPE_HAS_EROM(chip_type)) { enum_addr = bus_read_4(res, chipc_offset + CHIPC_EROMPTR); } else if (chip_type == BHND_CHIPTYPE_SIBA) { /* siba(4) uses the ChipCommon base address as the enumeration * address */ enum_addr = BHND_DEFAULT_CHIPC_ADDR; } else { device_printf(dev, "unknown chip type %hhu\n", chip_type); error = ENODEV; goto cleanup; } *result = bhnd_parse_chipid(reg, enum_addr); /* Fix the core count on early siba(4) devices */ if (chip_type == BHND_CHIPTYPE_SIBA) { uint32_t idh; uint16_t chipc_hwrev; /* * We need the ChipCommon revision to determine whether * the ncore field is valid. * * We can safely assume the siba IDHIGH register is mapped * within the chipc register block. */ idh = bus_read_4(res, SB0_REG_ABS(SIBA_CFG0_IDHIGH)); chipc_hwrev = SIBA_IDH_CORE_REV(idh); error = bhnd_chipid_fixed_ncores(result, chipc_hwrev, &result->ncores); if (error) goto cleanup; } cleanup: /* Clean up */ bus_release_resource(dev, rtype, rid, res); return (error); } /** * Read an NVRAM variable's NUL-terminated string value. * * @param dev A bhnd bus child device. * @param name The NVRAM variable name. * @param[out] buf A buffer large enough to hold @p len bytes. On * success, the NUL-terminated string value will be * written to this buffer. This argment may be NULL if * the value is not desired. * @param len The maximum capacity of @p buf. * @param[out] rlen On success, will be set to the actual size of * the requested value (including NUL termination). This * argment may be NULL if the size is not desired. * * @retval 0 success * @retval ENOENT The requested variable was not found. * @retval ENODEV No valid NVRAM source could be found. * @retval ENOMEM If @p buf is non-NULL and a buffer of @p len is too * small to hold the requested value. * @retval EFTYPE If the variable data cannot be coerced to a valid * string representation. * @retval ERANGE If value coercion would overflow @p type. * @retval non-zero If reading @p name otherwise fails, a regular unix * error code will be returned. */ int bhnd_nvram_getvar_str(device_t dev, const char *name, char *buf, size_t len, size_t *rlen) { size_t larg; int error; larg = len; error = bhnd_nvram_getvar(dev, name, buf, &larg, BHND_NVRAM_TYPE_STRING); if (rlen != NULL) *rlen = larg; return (error); } /** * Read an NVRAM variable's unsigned integer value. * * @param dev A bhnd bus child device. * @param name The NVRAM variable name. * @param[out] value On success, the requested value will be written * to this pointer. * @param width The output integer type width (1, 2, or * 4 bytes). * * @retval 0 success * @retval ENOENT The requested variable was not found. * @retval ENODEV No valid NVRAM source could be found. * @retval EFTYPE If the variable data cannot be coerced to a * a valid unsigned integer representation. * @retval ERANGE If value coercion would overflow (or underflow) an * unsigned representation of the given @p width. * @retval non-zero If reading @p name otherwise fails, a regular unix * error code will be returned. */ int bhnd_nvram_getvar_uint(device_t dev, const char *name, void *value, int width) { bhnd_nvram_type type; size_t len; switch (width) { case 1: type = BHND_NVRAM_TYPE_UINT8; break; case 2: type = BHND_NVRAM_TYPE_UINT16; break; case 4: type = BHND_NVRAM_TYPE_UINT32; break; default: device_printf(dev, "unsupported NVRAM integer width: %d\n", width); return (EINVAL); } len = width; return (bhnd_nvram_getvar(dev, name, value, &len, type)); } /** * Read an NVRAM variable's unsigned 8-bit integer value. * * @param dev A bhnd bus child device. * @param name The NVRAM variable name. * @param[out] value On success, the requested value will be written * to this pointer. * * @retval 0 success * @retval ENOENT The requested variable was not found. * @retval ENODEV No valid NVRAM source could be found. * @retval EFTYPE If the variable data cannot be coerced to a * a valid unsigned integer representation. * @retval ERANGE If value coercion would overflow (or underflow) uint8_t. * @retval non-zero If reading @p name otherwise fails, a regular unix * error code will be returned. */ int bhnd_nvram_getvar_uint8(device_t dev, const char *name, uint8_t *value) { return (bhnd_nvram_getvar_uint(dev, name, value, sizeof(*value))); } /** * Read an NVRAM variable's unsigned 16-bit integer value. * * @param dev A bhnd bus child device. * @param name The NVRAM variable name. * @param[out] value On success, the requested value will be written * to this pointer. * * @retval 0 success * @retval ENOENT The requested variable was not found. * @retval ENODEV No valid NVRAM source could be found. * @retval EFTYPE If the variable data cannot be coerced to a * a valid unsigned integer representation. * @retval ERANGE If value coercion would overflow (or underflow) * uint16_t. * @retval non-zero If reading @p name otherwise fails, a regular unix * error code will be returned. */ int bhnd_nvram_getvar_uint16(device_t dev, const char *name, uint16_t *value) { return (bhnd_nvram_getvar_uint(dev, name, value, sizeof(*value))); } /** * Read an NVRAM variable's unsigned 32-bit integer value. * * @param dev A bhnd bus child device. * @param name The NVRAM variable name. * @param[out] value On success, the requested value will be written * to this pointer. * * @retval 0 success * @retval ENOENT The requested variable was not found. * @retval ENODEV No valid NVRAM source could be found. * @retval EFTYPE If the variable data cannot be coerced to a * a valid unsigned integer representation. * @retval ERANGE If value coercion would overflow (or underflow) * uint32_t. * @retval non-zero If reading @p name otherwise fails, a regular unix * error code will be returned. */ int bhnd_nvram_getvar_uint32(device_t dev, const char *name, uint32_t *value) { return (bhnd_nvram_getvar_uint(dev, name, value, sizeof(*value))); } /** * Read an NVRAM variable's signed integer value. * * @param dev A bhnd bus child device. * @param name The NVRAM variable name. * @param[out] value On success, the requested value will be written * to this pointer. * @param width The output integer type width (1, 2, or * 4 bytes). * * @retval 0 success * @retval ENOENT The requested variable was not found. * @retval ENODEV No valid NVRAM source could be found. * @retval EFTYPE If the variable data cannot be coerced to a * a valid integer representation. * @retval ERANGE If value coercion would overflow (or underflow) an * signed representation of the given @p width. * @retval non-zero If reading @p name otherwise fails, a regular unix * error code will be returned. */ int bhnd_nvram_getvar_int(device_t dev, const char *name, void *value, int width) { bhnd_nvram_type type; size_t len; switch (width) { case 1: type = BHND_NVRAM_TYPE_INT8; break; case 2: type = BHND_NVRAM_TYPE_INT16; break; case 4: type = BHND_NVRAM_TYPE_INT32; break; default: device_printf(dev, "unsupported NVRAM integer width: %d\n", width); return (EINVAL); } len = width; return (bhnd_nvram_getvar(dev, name, value, &len, type)); } /** * Read an NVRAM variable's signed 8-bit integer value. * * @param dev A bhnd bus child device. * @param name The NVRAM variable name. * @param[out] value On success, the requested value will be written * to this pointer. * * @retval 0 success * @retval ENOENT The requested variable was not found. * @retval ENODEV No valid NVRAM source could be found. * @retval EFTYPE If the variable data cannot be coerced to a * a valid integer representation. * @retval ERANGE If value coercion would overflow (or underflow) int8_t. * @retval non-zero If reading @p name otherwise fails, a regular unix * error code will be returned. */ int bhnd_nvram_getvar_int8(device_t dev, const char *name, int8_t *value) { return (bhnd_nvram_getvar_int(dev, name, value, sizeof(*value))); } /** * Read an NVRAM variable's signed 16-bit integer value. * * @param dev A bhnd bus child device. * @param name The NVRAM variable name. * @param[out] value On success, the requested value will be written * to this pointer. * * @retval 0 success * @retval ENOENT The requested variable was not found. * @retval ENODEV No valid NVRAM source could be found. * @retval EFTYPE If the variable data cannot be coerced to a * a valid integer representation. * @retval ERANGE If value coercion would overflow (or underflow) * int16_t. * @retval non-zero If reading @p name otherwise fails, a regular unix * error code will be returned. */ int bhnd_nvram_getvar_int16(device_t dev, const char *name, int16_t *value) { return (bhnd_nvram_getvar_int(dev, name, value, sizeof(*value))); } /** * Read an NVRAM variable's signed 32-bit integer value. * * @param dev A bhnd bus child device. * @param name The NVRAM variable name. * @param[out] value On success, the requested value will be written * to this pointer. * * @retval 0 success * @retval ENOENT The requested variable was not found. * @retval ENODEV No valid NVRAM source could be found. * @retval EFTYPE If the variable data cannot be coerced to a * a valid integer representation. * @retval ERANGE If value coercion would overflow (or underflow) * int32_t. * @retval non-zero If reading @p name otherwise fails, a regular unix * error code will be returned. */ int bhnd_nvram_getvar_int32(device_t dev, const char *name, int32_t *value) { return (bhnd_nvram_getvar_int(dev, name, value, sizeof(*value))); } /** * Read an NVRAM variable's array value. * * @param dev A bhnd bus child device. * @param name The NVRAM variable name. * @param[out] buf A buffer large enough to hold @p size bytes. * On success, the requested value will be written * to this buffer. * @param[in,out] size The required number of bytes to write to * @p buf. * @param type The desired array element data representation. * * @retval 0 success * @retval ENOENT The requested variable was not found. * @retval ENODEV No valid NVRAM source could be found. * @retval ENXIO If less than @p size bytes are available. * @retval ENOMEM If a buffer of @p size is too small to hold the * requested value. * @retval EFTYPE If the variable data cannot be coerced to a * a valid instance of @p type. * @retval ERANGE If value coercion would overflow (or underflow) a * representation of @p type. * @retval non-zero If reading @p name otherwise fails, a regular unix * error code will be returned. */ int bhnd_nvram_getvar_array(device_t dev, const char *name, void *buf, size_t size, bhnd_nvram_type type) { size_t nbytes; int error; /* Attempt read */ nbytes = size; if ((error = bhnd_nvram_getvar(dev, name, buf, &nbytes, type))) return (error); /* Verify that the expected number of bytes were fetched */ if (nbytes < size) return (ENXIO); return (0); } /** * Initialize a service provider registry. * * @param bsr The service registry to initialize. * * @retval 0 success * @retval non-zero if an error occurs initializing the service registry, * a regular unix error code will be returned. */ int bhnd_service_registry_init(struct bhnd_service_registry *bsr) { STAILQ_INIT(&bsr->entries); mtx_init(&bsr->lock, "bhnd_service_registry lock", NULL, MTX_DEF); return (0); } /** * Release all resources held by @p bsr. * * @param bsr A service registry instance previously successfully * initialized via bhnd_service_registry_init(). * * @retval 0 success * @retval EBUSY if active references to service providers registered * with @p bsr exist. */ int bhnd_service_registry_fini(struct bhnd_service_registry *bsr) { struct bhnd_service_entry *entry, *enext; /* Remove everthing we can */ mtx_lock(&bsr->lock); STAILQ_FOREACH_SAFE(entry, &bsr->entries, link, enext) { if (entry->refs > 0) continue; STAILQ_REMOVE(&bsr->entries, entry, bhnd_service_entry, link); free(entry, M_BHND); } if (!STAILQ_EMPTY(&bsr->entries)) { mtx_unlock(&bsr->lock); return (EBUSY); } mtx_unlock(&bsr->lock); mtx_destroy(&bsr->lock); return (0); } /** * Register a @p provider for the given @p service. * * @param bsr Service registry to be modified. * @param provider Service provider to register. * @param service Service for which @p provider will be registered. * @param flags Service provider flags (see BHND_SPF_*). * * @retval 0 success * @retval EEXIST if an entry for @p service already exists. * @retval EINVAL if @p service is BHND_SERVICE_ANY. * @retval non-zero if registering @p provider otherwise fails, a regular * unix error code will be returned. */ int bhnd_service_registry_add(struct bhnd_service_registry *bsr, device_t provider, bhnd_service_t service, uint32_t flags) { struct bhnd_service_entry *entry; if (service == BHND_SERVICE_ANY) return (EINVAL); mtx_lock(&bsr->lock); /* Is a service provider already registered? */ STAILQ_FOREACH(entry, &bsr->entries, link) { if (entry->service == service) { mtx_unlock(&bsr->lock); return (EEXIST); } } /* Initialize and insert our new entry */ entry = malloc(sizeof(*entry), M_BHND, M_NOWAIT); if (entry == NULL) { mtx_unlock(&bsr->lock); return (ENOMEM); } entry->provider = provider; entry->service = service; entry->flags = flags; refcount_init(&entry->refs, 0); STAILQ_INSERT_HEAD(&bsr->entries, entry, link); mtx_unlock(&bsr->lock); return (0); } /** * Free an unreferenced registry entry. * * @param entry The entry to be deallocated. */ static void bhnd_service_registry_free_entry(struct bhnd_service_entry *entry) { KASSERT(entry->refs == 0, ("provider has active references")); free(entry, M_BHND); } /** * Attempt to remove the @p service provider registration for @p provider. * * @param bsr The service registry to be modified. * @param provider The service provider to be deregistered. * @param service The service for which @p provider will be deregistered, * or BHND_SERVICE_ANY to remove all service * registrations for @p provider. * * @retval 0 success * @retval EBUSY if active references to @p provider exist; @see * bhnd_service_registry_retain() and * bhnd_service_registry_release(). */ int bhnd_service_registry_remove(struct bhnd_service_registry *bsr, device_t provider, bhnd_service_t service) { struct bhnd_service_entry *entry, *enext; mtx_lock(&bsr->lock); #define BHND_PROV_MATCH(_e) \ ((_e)->provider == provider && \ (service == BHND_SERVICE_ANY || (_e)->service == service)) /* Validate matching provider entries before making any * modifications */ STAILQ_FOREACH(entry, &bsr->entries, link) { /* Skip non-matching entries */ if (!BHND_PROV_MATCH(entry)) continue; /* Entry is in use? */ if (entry->refs > 0) { mtx_unlock(&bsr->lock); return (EBUSY); } } /* We can now safely remove matching entries */ STAILQ_FOREACH_SAFE(entry, &bsr->entries, link, enext) { /* Skip non-matching entries */ if (!BHND_PROV_MATCH(entry)) continue; /* Remove from list */ STAILQ_REMOVE(&bsr->entries, entry, bhnd_service_entry, link); /* Free provider entry */ bhnd_service_registry_free_entry(entry); } #undef BHND_PROV_MATCH mtx_unlock(&bsr->lock); return (0); } /** * Retain and return a reference to a registered @p service provider, if any. * * @param bsr The service registry to be queried. * @param service The service for which a provider should be returned. * * On success, the caller assumes ownership the returned provider, and * is responsible for releasing this reference via * bhnd_service_registry_release(). * * @retval device_t success * @retval NULL if no provider is registered for @p service. */ device_t bhnd_service_registry_retain(struct bhnd_service_registry *bsr, bhnd_service_t service) { struct bhnd_service_entry *entry; mtx_lock(&bsr->lock); STAILQ_FOREACH(entry, &bsr->entries, link) { if (entry->service != service) continue; /* With a live refcount, entry is gauranteed to remain alive * after we release our lock */ refcount_acquire(&entry->refs); mtx_unlock(&bsr->lock); return (entry->provider); } mtx_unlock(&bsr->lock); /* Not found */ return (NULL); } /** * Release a reference to a service provider previously returned by * bhnd_service_registry_retain(). * * If this is the last reference to an inherited service provider registration * (@see BHND_SPF_INHERITED), the registration will also be removed, and * true will be returned. * * @param bsr The service registry from which @p provider * was returned. * @param provider The provider to be released. * @param service The service for which @p provider was previously * retained. * @retval true The inherited service provider registration was removed; * the caller should release its own reference to the * provider. * @retval false The service provider was not inherited, or active * references to the provider remain. */ bool bhnd_service_registry_release(struct bhnd_service_registry *bsr, device_t provider, bhnd_service_t service) { struct bhnd_service_entry *entry; /* Exclusive lock, as we need to prevent any new references to the * entry from being taken if it's to be removed */ mtx_lock(&bsr->lock); STAILQ_FOREACH(entry, &bsr->entries, link) { bool removed; if (entry->provider != provider) continue; if (entry->service != service) continue; if (refcount_release(&entry->refs) && (entry->flags & BHND_SPF_INHERITED)) { /* If an inherited entry is no longer actively * referenced, remove the local registration and inform * the caller. */ STAILQ_REMOVE(&bsr->entries, entry, bhnd_service_entry, link); bhnd_service_registry_free_entry(entry); removed = true; } else { removed = false; } mtx_unlock(&bsr->lock); return (removed); } /* Caller owns a reference, but no such provider is registered? */ panic("invalid service provider reference"); } /** * Using the bhnd(4) bus-level core information and a custom core name, * populate @p dev's device description. * * @param dev A bhnd-bus attached device. * @param dev_name The core's name (e.g. "SDIO Device Core") */ void bhnd_set_custom_core_desc(device_t dev, const char *dev_name) { const char *vendor_name; char *desc; vendor_name = bhnd_get_vendor_name(dev); asprintf(&desc, M_BHND, "%s %s, rev %hhu", vendor_name, dev_name, bhnd_get_hwrev(dev)); if (desc != NULL) { device_set_desc_copy(dev, desc); free(desc, M_BHND); } else { device_set_desc(dev, dev_name); } } /** * Using the bhnd(4) bus-level core information, populate @p dev's device * description. * * @param dev A bhnd-bus attached device. */ void bhnd_set_default_core_desc(device_t dev) { bhnd_set_custom_core_desc(dev, bhnd_get_device_name(dev)); } /** * Using the bhnd @p chip_id, populate the bhnd(4) bus @p dev's device * description. * * @param dev A bhnd-bus attached device. */ void bhnd_set_default_bus_desc(device_t dev, const struct bhnd_chipid *chip_id) { const char *bus_name; char *desc; char chip_name[BHND_CHIPID_MAX_NAMELEN]; /* Determine chip type's bus name */ switch (chip_id->chip_type) { case BHND_CHIPTYPE_SIBA: bus_name = "SIBA bus"; break; case BHND_CHIPTYPE_BCMA: case BHND_CHIPTYPE_BCMA_ALT: bus_name = "BCMA bus"; break; case BHND_CHIPTYPE_UBUS: bus_name = "UBUS bus"; break; default: bus_name = "Unknown Type"; break; } /* Format chip name */ bhnd_format_chip_id(chip_name, sizeof(chip_name), chip_id->chip_id); /* Format and set device description */ asprintf(&desc, M_BHND, "%s %s", chip_name, bus_name); if (desc != NULL) { device_set_desc_copy(dev, desc); free(desc, M_BHND); } else { device_set_desc(dev, bus_name); } } /** * Helper function for implementing BHND_BUS_REGISTER_PROVIDER(). * * This implementation delegates the request to the BHND_BUS_REGISTER_PROVIDER() * method on the parent of @p dev. If no parent exists, the implementation * will return an error. */ int bhnd_bus_generic_register_provider(device_t dev, device_t child, device_t provider, bhnd_service_t service) { device_t parent = device_get_parent(dev); if (parent != NULL) { return (BHND_BUS_REGISTER_PROVIDER(parent, child, provider, service)); } return (ENXIO); } /** * Helper function for implementing BHND_BUS_DEREGISTER_PROVIDER(). * * This implementation delegates the request to the * BHND_BUS_DEREGISTER_PROVIDER() method on the parent of @p dev. If no parent * exists, the implementation will panic. */ int bhnd_bus_generic_deregister_provider(device_t dev, device_t child, device_t provider, bhnd_service_t service) { device_t parent = device_get_parent(dev); if (parent != NULL) { return (BHND_BUS_DEREGISTER_PROVIDER(parent, child, provider, service)); } panic("missing BHND_BUS_DEREGISTER_PROVIDER()"); } /** * Helper function for implementing BHND_BUS_RETAIN_PROVIDER(). * * This implementation delegates the request to the * BHND_BUS_DEREGISTER_PROVIDER() method on the parent of @p dev. If no parent * exists, the implementation will return NULL. */ device_t bhnd_bus_generic_retain_provider(device_t dev, device_t child, bhnd_service_t service) { device_t parent = device_get_parent(dev); if (parent != NULL) { return (BHND_BUS_RETAIN_PROVIDER(parent, child, service)); } return (NULL); } /** * Helper function for implementing BHND_BUS_RELEASE_PROVIDER(). * * This implementation delegates the request to the * BHND_BUS_DEREGISTER_PROVIDER() method on the parent of @p dev. If no parent * exists, the implementation will panic. */ void bhnd_bus_generic_release_provider(device_t dev, device_t child, device_t provider, bhnd_service_t service) { device_t parent = device_get_parent(dev); if (parent != NULL) { return (BHND_BUS_RELEASE_PROVIDER(parent, child, provider, service)); } panic("missing BHND_BUS_RELEASE_PROVIDER()"); } /** * Helper function for implementing BHND_BUS_REGISTER_PROVIDER(). * * This implementation uses the bhnd_service_registry_add() function to * do most of the work. It calls BHND_BUS_GET_SERVICE_REGISTRY() to find * a suitable service registry to edit. */ int bhnd_bus_generic_sr_register_provider(device_t dev, device_t child, device_t provider, bhnd_service_t service) { struct bhnd_service_registry *bsr; bsr = BHND_BUS_GET_SERVICE_REGISTRY(dev, child); KASSERT(bsr != NULL, ("NULL service registry")); return (bhnd_service_registry_add(bsr, provider, service, 0)); } /** * Helper function for implementing BHND_BUS_DEREGISTER_PROVIDER(). * * This implementation uses the bhnd_service_registry_remove() function to * do most of the work. It calls BHND_BUS_GET_SERVICE_REGISTRY() to find * a suitable service registry to edit. */ int bhnd_bus_generic_sr_deregister_provider(device_t dev, device_t child, device_t provider, bhnd_service_t service) { struct bhnd_service_registry *bsr; bsr = BHND_BUS_GET_SERVICE_REGISTRY(dev, child); KASSERT(bsr != NULL, ("NULL service registry")); return (bhnd_service_registry_remove(bsr, provider, service)); } /** * Helper function for implementing BHND_BUS_RETAIN_PROVIDER(). * * This implementation uses the bhnd_service_registry_retain() function to * do most of the work. It calls BHND_BUS_GET_SERVICE_REGISTRY() to find * a suitable service registry. * * If a local provider for the service is not available, and a parent device is * available, this implementation will attempt to fetch and locally register * a service provider reference from the parent of @p dev. */ device_t bhnd_bus_generic_sr_retain_provider(device_t dev, device_t child, bhnd_service_t service) { struct bhnd_service_registry *bsr; device_t parent, provider; int error; bsr = BHND_BUS_GET_SERVICE_REGISTRY(dev, child); KASSERT(bsr != NULL, ("NULL service registry")); /* * Attempt to fetch a service provider reference from either the local * service registry, or if not found, from our parent. * * If we fetch a provider from our parent, we register the provider * with the local service registry to prevent conflicting local * registrations from being added. */ while (1) { /* Check the local service registry first */ provider = bhnd_service_registry_retain(bsr, service); if (provider != NULL) return (provider); /* Otherwise, try to delegate to our parent (if any) */ if ((parent = device_get_parent(dev)) == NULL) return (NULL); provider = BHND_BUS_RETAIN_PROVIDER(parent, dev, service); if (provider == NULL) return (NULL); /* Register the inherited service registration with the local * registry */ error = bhnd_service_registry_add(bsr, provider, service, BHND_SPF_INHERITED); if (error) { BHND_BUS_RELEASE_PROVIDER(parent, dev, provider, service); if (error == EEXIST) { /* A valid service provider was registered * concurrently; retry fetching from the local * registry */ continue; } device_printf(dev, "failed to register service " "provider: %d\n", error); return (NULL); } } } /** * Helper function for implementing BHND_BUS_RELEASE_PROVIDER(). * * This implementation uses the bhnd_service_registry_release() function to * do most of the work. It calls BHND_BUS_GET_SERVICE_REGISTRY() to find * a suitable service registry. */ void bhnd_bus_generic_sr_release_provider(device_t dev, device_t child, device_t provider, bhnd_service_t service) { struct bhnd_service_registry *bsr; bsr = BHND_BUS_GET_SERVICE_REGISTRY(dev, child); KASSERT(bsr != NULL, ("NULL service registry")); /* Release the provider reference; if the refcount hits zero on an * inherited reference, true will be returned, and we need to drop * our own bus reference to the provider */ if (!bhnd_service_registry_release(bsr, provider, service)) return; /* Drop our reference to the borrowed provider */ BHND_BUS_RELEASE_PROVIDER(device_get_parent(dev), dev, provider, service); } /** * Helper function for implementing BHND_BUS_IS_HW_DISABLED(). * * If a parent device is available, this implementation delegates the * request to the BHND_BUS_IS_HW_DISABLED() method on the parent of @p dev. * * If no parent device is available (i.e. on a the bus root), the hardware * is assumed to be usable and false is returned. */ bool bhnd_bus_generic_is_hw_disabled(device_t dev, device_t child) { if (device_get_parent(dev) != NULL) return (BHND_BUS_IS_HW_DISABLED(device_get_parent(dev), child)); return (false); } /** * Helper function for implementing BHND_BUS_GET_CHIPID(). * * This implementation delegates the request to the BHND_BUS_GET_CHIPID() * method on the parent of @p dev. If no parent exists, the implementation * will panic. */ const struct bhnd_chipid * bhnd_bus_generic_get_chipid(device_t dev, device_t child) { if (device_get_parent(dev) != NULL) return (BHND_BUS_GET_CHIPID(device_get_parent(dev), child)); panic("missing BHND_BUS_GET_CHIPID()"); } /* nvram board_info population macros for bhnd_bus_generic_read_board_info() */ #define BHND_GV(_dest, _name) \ bhnd_nvram_getvar_uint(child, BHND_NVAR_ ## _name, &_dest, \ sizeof(_dest)) #define REQ_BHND_GV(_dest, _name) do { \ if ((error = BHND_GV(_dest, _name))) { \ device_printf(dev, \ "error reading " __STRING(_name) ": %d\n", error); \ return (error); \ } \ } while(0) #define OPT_BHND_GV(_dest, _name, _default) do { \ if ((error = BHND_GV(_dest, _name))) { \ if (error != ENOENT) { \ device_printf(dev, \ "error reading " \ __STRING(_name) ": %d\n", error); \ return (error); \ } \ _dest = _default; \ } \ } while(0) /** * Helper function for implementing BHND_BUS_READ_BOARDINFO(). * * This implementation populates @p info with information from NVRAM, * defaulting board_vendor and board_type fields to 0 if the * requested variables cannot be found. * * This behavior is correct for most SoCs, but must be overridden on * bridged (PCI, PCMCIA, etc) devices to produce a complete bhnd_board_info * result. */ int bhnd_bus_generic_read_board_info(device_t dev, device_t child, struct bhnd_board_info *info) { int error; OPT_BHND_GV(info->board_vendor, BOARDVENDOR, 0); OPT_BHND_GV(info->board_type, BOARDTYPE, 0); /* srom >= 2 */ REQ_BHND_GV(info->board_rev, BOARDREV); OPT_BHND_GV(info->board_srom_rev,SROMREV, 0); /* missing in some SoC NVRAM */ REQ_BHND_GV(info->board_flags, BOARDFLAGS); OPT_BHND_GV(info->board_flags2, BOARDFLAGS2, 0); /* srom >= 4 */ OPT_BHND_GV(info->board_flags3, BOARDFLAGS3, 0); /* srom >= 11 */ return (0); } #undef BHND_GV #undef BHND_GV_REQ #undef BHND_GV_OPT /** * Helper function for implementing BHND_BUS_GET_NVRAM_VAR(). * * This implementation searches @p dev for a usable NVRAM child device. * * If no usable child device is found on @p dev, the request is delegated to * the BHND_BUS_GET_NVRAM_VAR() method on the parent of @p dev. */ int bhnd_bus_generic_get_nvram_var(device_t dev, device_t child, const char *name, void *buf, size_t *size, bhnd_nvram_type type) { device_t nvram; device_t parent; /* Make sure we're holding Giant for newbus */ GIANT_REQUIRED; /* Look for a directly-attached NVRAM child */ if ((nvram = device_find_child(dev, "bhnd_nvram", -1)) != NULL) return BHND_NVRAM_GETVAR(nvram, name, buf, size, type); /* Try to delegate to parent */ if ((parent = device_get_parent(dev)) == NULL) return (ENODEV); return (BHND_BUS_GET_NVRAM_VAR(device_get_parent(dev), child, name, buf, size, type)); } /** * Helper function for implementing BHND_BUS_ALLOC_RESOURCE(). * * This implementation of BHND_BUS_ALLOC_RESOURCE() delegates allocation * of the underlying resource to BUS_ALLOC_RESOURCE(), and activation * to @p dev's BHND_BUS_ACTIVATE_RESOURCE(). */ struct bhnd_resource * bhnd_bus_generic_alloc_resource(device_t dev, device_t child, int type, int *rid, rman_res_t start, rman_res_t end, rman_res_t count, u_int flags) { struct bhnd_resource *br; struct resource *res; int error; br = NULL; res = NULL; /* Allocate the real bus resource (without activating it) */ res = BUS_ALLOC_RESOURCE(dev, child, type, rid, start, end, count, (flags & ~RF_ACTIVE)); if (res == NULL) return (NULL); /* Allocate our bhnd resource wrapper. */ br = malloc(sizeof(struct bhnd_resource), M_BHND, M_NOWAIT); if (br == NULL) goto failed; br->direct = false; br->res = res; /* Attempt activation */ if (flags & RF_ACTIVE) { error = BHND_BUS_ACTIVATE_RESOURCE(dev, child, type, *rid, br); if (error) goto failed; } return (br); failed: if (res != NULL) BUS_RELEASE_RESOURCE(dev, child, type, *rid, res); free(br, M_BHND); return (NULL); } /** * Helper function for implementing BHND_BUS_RELEASE_RESOURCE(). * * This implementation of BHND_BUS_RELEASE_RESOURCE() delegates release of * the backing resource to BUS_RELEASE_RESOURCE(). */ int bhnd_bus_generic_release_resource(device_t dev, device_t child, int type, int rid, struct bhnd_resource *r) { int error; if ((error = BUS_RELEASE_RESOURCE(dev, child, type, rid, r->res))) return (error); free(r, M_BHND); return (0); } /** * Helper function for implementing BHND_BUS_ACTIVATE_RESOURCE(). * * This implementation of BHND_BUS_ACTIVATE_RESOURCE() first calls the * BHND_BUS_ACTIVATE_RESOURCE() method of the parent of @p dev. * * If this fails, and if @p dev is the direct parent of @p child, standard * resource activation is attempted via bus_activate_resource(). This enables * direct use of the bhnd(4) resource APIs on devices that may not be attached * to a parent bhnd bus or bridge. */ int bhnd_bus_generic_activate_resource(device_t dev, device_t child, int type, int rid, struct bhnd_resource *r) { int error; bool passthrough; passthrough = (device_get_parent(child) != dev); /* Try to delegate to the parent */ if (device_get_parent(dev) != NULL) { error = BHND_BUS_ACTIVATE_RESOURCE(device_get_parent(dev), child, type, rid, r); } else { error = ENODEV; } /* If bhnd(4) activation has failed and we're the child's direct * parent, try falling back on standard resource activation. */ if (error && !passthrough) { error = bus_activate_resource(child, type, rid, r->res); if (!error) r->direct = true; } return (error); } /** * Helper function for implementing BHND_BUS_DEACTIVATE_RESOURCE(). * * This implementation of BHND_BUS_ACTIVATE_RESOURCE() simply calls the * BHND_BUS_ACTIVATE_RESOURCE() method of the parent of @p dev. */ int bhnd_bus_generic_deactivate_resource(device_t dev, device_t child, int type, int rid, struct bhnd_resource *r) { if (device_get_parent(dev) != NULL) return (BHND_BUS_DEACTIVATE_RESOURCE(device_get_parent(dev), child, type, rid, r)); 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 (revision 326078) +++ head/sys/dev/bhnd/bhndb/bhnd_bhndb.c (revision 326079) @@ -1,149 +1,191 @@ /*- * 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: * 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. */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include "bhndbvar.h" /* * bhnd(4) driver mix-in providing a shared common methods for * bhnd devices attached via a bhndb bridge. */ static int bhnd_bhndb_read_board_info(device_t dev, device_t child, struct bhnd_board_info *info) { int error; /* Initialize with NVRAM-derived values */ if ((error = bhnd_bus_generic_read_board_info(dev, child, info))) return (error); /* Let the bridge fill in any additional data */ return (BHNDB_POPULATE_BOARD_INFO(device_get_parent(dev), dev, info)); } static bhnd_attach_type bhnd_bhndb_get_attach_type(device_t dev, device_t child) { /* It's safe to assume that a bridged device is always an adapter */ return (BHND_ATTACH_ADAPTER); } static bool bhnd_bhndb_is_hw_disabled(device_t dev, device_t child) { struct bhnd_core_info core = bhnd_get_core_info(child); /* Delegate to parent bridge */ return (BHNDB_IS_CORE_DISABLED(device_get_parent(dev), dev, &core)); } static device_t bhnd_bhndb_find_hostb_device(device_t dev) { struct bhnd_core_info core; struct bhnd_core_match md; int error; /* Ask the bridge for the hostb core info */ if ((error = BHNDB_GET_HOSTB_CORE(device_get_parent(dev), dev, &core))) return (NULL); /* Find the corresponding bus device */ md = bhnd_core_get_match_desc(&core); return (bhnd_bus_match_child(dev, &md)); } 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) { /* Delegate to parent bridge */ return (BHND_BUS_PWRCTL_GET_CLKSRC(device_get_parent(dev), child, clock)); } static int bhnd_bhndb_pwrctl_gate_clock(device_t dev, device_t child, bhnd_clock clock) { /* Delegate to parent bridge */ return (BHND_BUS_PWRCTL_GATE_CLOCK(device_get_parent(dev), child, clock)); } static int bhnd_bhndb_pwrctl_ungate_clock(device_t dev, device_t child, bhnd_clock clock) { /* Delegate to parent bridge */ return (BHND_BUS_PWRCTL_UNGATE_CLOCK(device_get_parent(dev), child, 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), DEVMETHOD(bhnd_bus_pwrctl_ungate_clock, bhnd_bhndb_pwrctl_ungate_clock), DEVMETHOD_END }; DEFINE_CLASS_0(bhnd, bhnd_bhndb_driver, bhnd_bhndb_methods, 0); Index: head/sys/dev/bhnd/bhndb/bhndb.c =================================================================== --- head/sys/dev/bhnd/bhndb/bhndb.c (revision 326078) +++ head/sys/dev/bhnd/bhndb/bhndb.c (revision 326079) @@ -1,1920 +1,2171 @@ /*- * 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: * 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. */ #include __FBSDID("$FreeBSD$"); /* * Abstract BHND Bridge Device Driver * * Provides generic support for bridging from a parent bus (such as PCI) to * a BHND-compatible bus (e.g. bcma or siba). */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "bhnd_chipc_if.h" #include "bhnd_nvram_if.h" #include "bhndbvar.h" #include "bhndb_bus_if.h" #include "bhndb_hwdata.h" #include "bhndb_private.h" /* Debugging flags */ static u_long bhndb_debug = 0; TUNABLE_ULONG("hw.bhndb.debug", &bhndb_debug); enum { BHNDB_DEBUG_PRIO = 1 << 0, }; #define BHNDB_DEBUG(_type) (BHNDB_DEBUG_ ## _type & bhndb_debug) static bool bhndb_hw_matches(struct bhndb_softc *sc, struct bhnd_core_info *cores, u_int ncores, const struct bhndb_hw *hw); static int bhndb_init_region_cfg(struct bhndb_softc *sc, bhnd_erom_t *erom, struct bhndb_resources *r, struct bhnd_core_info *cores, u_int ncores, const struct bhndb_hw_priority *table); static int bhndb_find_hwspec(struct bhndb_softc *sc, struct bhnd_core_info *cores, u_int ncores, const struct bhndb_hw **hw); bhndb_addrspace bhndb_get_addrspace(struct bhndb_softc *sc, device_t child); static struct rman *bhndb_get_rman(struct bhndb_softc *sc, device_t child, int type); static int bhndb_init_child_resource(struct resource *r, struct resource *parent, bhnd_size_t offset, bhnd_size_t size); static int bhndb_activate_static_region( struct bhndb_softc *sc, struct bhndb_region *region, device_t child, int type, int rid, struct resource *r); static int bhndb_try_activate_resource( struct bhndb_softc *sc, device_t child, int type, int rid, struct resource *r, bool *indirect); static inline struct bhndb_dw_alloc *bhndb_io_resource(struct bhndb_softc *sc, bus_addr_t addr, bus_size_t size, bus_size_t *offset); /** * Default bhndb(4) implementation of DEVICE_PROBE(). * * This function provides the default bhndb implementation of DEVICE_PROBE(), * and is compatible with bhndb(4) bridges attached via bhndb_attach_bridge(). */ int bhndb_generic_probe(device_t dev) { return (BUS_PROBE_NOWILDCARD); } static void bhndb_probe_nomatch(device_t dev, device_t child) { const char *name; name = device_get_name(child); if (name == NULL) name = "unknown device"; device_printf(dev, "<%s> (no driver attached)\n", name); } static int bhndb_print_child(device_t dev, device_t child) { struct bhndb_softc *sc; struct resource_list *rl; int retval = 0; sc = device_get_softc(dev); retval += bus_print_child_header(dev, child); rl = BUS_GET_RESOURCE_LIST(dev, child); if (rl != NULL) { retval += resource_list_print_type(rl, "mem", SYS_RES_MEMORY, "%#jx"); retval += resource_list_print_type(rl, "irq", SYS_RES_IRQ, "%jd"); } retval += bus_print_child_domain(dev, child); retval += bus_print_child_footer(dev, child); return (retval); } static int bhndb_child_pnpinfo_str(device_t bus, device_t child, char *buf, size_t buflen) { *buf = '\0'; return (0); } static int bhndb_child_location_str(device_t dev, device_t child, char *buf, size_t buflen) { struct bhndb_softc *sc; sc = device_get_softc(dev); snprintf(buf, buflen, "base=0x%llx", (unsigned long long) sc->chipid.enum_addr); return (0); } /** * Return true if @p cores matches the @p hw specification. * * @param sc BHNDB device state. * @param cores A device table to match against. * @param ncores The number of cores in @p cores. * @param hw The hardware description to be matched against. */ static bool bhndb_hw_matches(struct bhndb_softc *sc, struct bhnd_core_info *cores, u_int ncores, const struct bhndb_hw *hw) { for (u_int i = 0; i < hw->num_hw_reqs; i++) { const struct bhnd_core_match *match; bool found; match = &hw->hw_reqs[i]; found = false; for (u_int d = 0; d < ncores; d++) { struct bhnd_core_info *core = &cores[d]; if (BHNDB_IS_CORE_DISABLED(sc->dev, sc->bus_dev, core)) continue; if (!bhnd_core_matches(core, match)) continue; found = true; break; } if (!found) return (false); } return (true); } /** * Initialize the region maps and priority configuration in @p br using * the priority @p table and the set of cores enumerated by @p erom. * * @param sc The bhndb device state. * @param br The resource state to be configured. * @param erom EROM parser used to enumerate @p cores. * @param cores All cores enumerated on the bridged bhnd bus. * @param ncores The length of @p cores. * @param table Hardware priority table to be used to determine the relative * priorities of per-core port resources. */ static int bhndb_init_region_cfg(struct bhndb_softc *sc, bhnd_erom_t *erom, struct bhndb_resources *br, struct bhnd_core_info *cores, u_int ncores, const struct bhndb_hw_priority *table) { const struct bhndb_hw_priority *hp; bhnd_addr_t addr; bhnd_size_t size; size_t prio_low, prio_default, prio_high; int error; /* The number of port regions per priority band that must be accessible * via dynamic register windows */ prio_low = 0; prio_default = 0; prio_high = 0; /* * Register bridge regions covering all statically mapped ports. */ for (u_int i = 0; i < ncores; i++) { const struct bhndb_regwin *regw; struct bhnd_core_info *core; struct bhnd_core_match md; core = &cores[i]; md = bhnd_core_get_match_desc(core); for (regw = br->cfg->register_windows; regw->win_type != BHNDB_REGWIN_T_INVALID; regw++) { /* Only core windows are supported */ if (regw->win_type != BHNDB_REGWIN_T_CORE) continue; /* Skip non-matching cores. */ if (!bhndb_regwin_match_core(regw, core)) continue; /* Fetch the base address of the mapped port */ error = bhnd_erom_lookup_core_addr(erom, &md, regw->d.core.port_type, regw->d.core.port, regw->d.core.region, NULL, &addr, &size); if (error) { /* Skip non-applicable register windows */ if (error == ENOENT) continue; return (error); } /* * Always defer to the register window's size. * * If the port size is smaller than the window size, * this ensures that we fully utilize register windows * larger than the referenced port. * * If the port size is larger than the window size, this * ensures that we do not directly map the allocations * within the region to a too-small window. */ size = regw->win_size; /* * Add to the bus region list. * * The window priority for a statically mapped * region is always HIGH. */ error = bhndb_add_resource_region(br, addr, size, BHNDB_PRIORITY_HIGH, regw); if (error) return (error); } } /* * Perform priority accounting and register bridge regions for all * ports defined in the priority table */ for (u_int i = 0; i < ncores; i++) { struct bhndb_region *region; struct bhnd_core_info *core; struct bhnd_core_match md; core = &cores[i]; md = bhnd_core_get_match_desc(core); /* * Skip priority accounting for cores that ... */ /* ... do not require bridge resources */ if (BHNDB_IS_CORE_DISABLED(sc->dev, sc->bus_dev, core)) continue; /* ... do not have a priority table entry */ hp = bhndb_hw_priority_find_core(table, core); if (hp == NULL) continue; /* ... are explicitly disabled in the priority table. */ if (hp->priority == BHNDB_PRIORITY_NONE) continue; /* Determine the number of dynamic windows required and * register their bus_region entries. */ for (u_int i = 0; i < hp->num_ports; i++) { const struct bhndb_port_priority *pp; pp = &hp->ports[i]; /* Fetch the address+size of the mapped port. */ error = bhnd_erom_lookup_core_addr(erom, &md, pp->type, pp->port, pp->region, NULL, &addr, &size); if (error) { /* Skip ports not defined on this device */ if (error == ENOENT) continue; return (error); } /* Skip ports with an existing static mapping */ region = bhndb_find_resource_region(br, addr, size); if (region != NULL && region->static_regwin != NULL) continue; /* Define a dynamic region for this port */ error = bhndb_add_resource_region(br, addr, size, pp->priority, NULL); if (error) return (error); /* Update port mapping counts */ switch (pp->priority) { case BHNDB_PRIORITY_NONE: break; case BHNDB_PRIORITY_LOW: prio_low++; break; case BHNDB_PRIORITY_DEFAULT: prio_default++; break; case BHNDB_PRIORITY_HIGH: prio_high++; break; } } } /* Determine the minimum priority at which we'll allocate direct * register windows from our dynamic pool */ size_t prio_total = prio_low + prio_default + prio_high; if (prio_total <= br->dwa_count) { /* low+default+high priority regions get windows */ br->min_prio = BHNDB_PRIORITY_LOW; } else if (prio_default + prio_high <= br->dwa_count) { /* default+high priority regions get windows */ br->min_prio = BHNDB_PRIORITY_DEFAULT; } else { /* high priority regions get windows */ br->min_prio = BHNDB_PRIORITY_HIGH; } if (BHNDB_DEBUG(PRIO)) { struct bhndb_region *region; const char *direct_msg, *type_msg; bhndb_priority_t prio, prio_min; prio_min = br->min_prio; device_printf(sc->dev, "min_prio: %d\n", prio_min); STAILQ_FOREACH(region, &br->bus_regions, link) { prio = region->priority; direct_msg = prio >= prio_min ? "direct" : "indirect"; type_msg = region->static_regwin ? "static" : "dynamic"; device_printf(sc->dev, "region 0x%llx+0x%llx priority " "%u %s/%s\n", (unsigned long long) region->addr, (unsigned long long) region->size, region->priority, direct_msg, type_msg); } } return (0); } /** * Find a hardware specification for @p dev. * * @param sc The bhndb device state. * @param cores All cores enumerated on the bridged bhnd bus. * @param ncores The length of @p cores. * @param[out] hw On success, the matched hardware specification. * with @p dev. * * @retval 0 success * @retval non-zero if an error occurs fetching device info for comparison. */ static int bhndb_find_hwspec(struct bhndb_softc *sc, struct bhnd_core_info *cores, u_int ncores, const struct bhndb_hw **hw) { const struct bhndb_hw *next, *hw_table; /* Search for the first matching hardware config. */ hw_table = BHNDB_BUS_GET_HARDWARE_TABLE(sc->parent_dev, sc->dev); for (next = hw_table; next->hw_reqs != NULL; next++) { if (!bhndb_hw_matches(sc, cores, ncores, next)) continue; /* Found */ *hw = next; return (0); } return (ENOENT); } /** * Helper function that must be called by subclass bhndb(4) drivers * when implementing DEVICE_ATTACH() before calling any bhnd(4) or bhndb(4) * APIs on the bridge device. * * This function will add a bridged bhnd(4) child device with a device order of * BHND_PROBE_BUS. Any subclass bhndb(4) driver may use the BHND_PROBE_* * priority bands to add additional devices that will be attached in * their preferred order relative to the bridged bhnd(4) bus. * * @param dev The bridge device to attach. * @param cid The bridged device's chip identification. * @param cores The bridged device's core table. * @param ncores The number of cores in @p cores. * @param bridge_core Core info for the bhnd(4) core serving as the host * bridge. * @param erom_class An erom parser class that may be used to parse * the bridged device's device enumeration table. */ int bhndb_attach(device_t dev, struct bhnd_chipid *cid, struct bhnd_core_info *cores, u_int ncores, struct bhnd_core_info *bridge_core, bhnd_erom_class_t *erom_class) { struct bhndb_devinfo *dinfo; struct bhndb_softc *sc; const struct bhndb_hw *hw; const struct bhndb_hwcfg *hwcfg; const struct bhndb_hw_priority *hwprio; struct bhnd_erom_io *eio; bhnd_erom_t *erom; int error; sc = device_get_softc(dev); sc->dev = dev; sc->parent_dev = device_get_parent(dev); sc->bridge_core = *bridge_core; sc->chipid = *cid; if ((error = bhnd_service_registry_init(&sc->services))) return (error); BHNDB_LOCK_INIT(sc); erom = NULL; /* Find a matching bridge hardware configuration */ if ((error = bhndb_find_hwspec(sc, cores, ncores, &hw))) { device_printf(sc->dev, "unable to identify device, " " using generic bridge resource definitions\n"); hwcfg = BHNDB_BUS_GET_GENERIC_HWCFG(sc->parent_dev, dev); hw = NULL; } else { hwcfg = hw->cfg; } if (hw != NULL && (bootverbose || BHNDB_DEBUG(PRIO))) { device_printf(sc->dev, "%s resource configuration\n", hw->name); } /* Allocate bridge resource state using the discovered hardware * configuration */ sc->bus_res = bhndb_alloc_resources(sc->dev, sc->parent_dev, hwcfg); if (sc->bus_res == NULL) { device_printf(sc->dev, "failed to allocate bridge resource " "state\n"); error = ENOMEM; goto failed; } /* Add our bridged bus device */ sc->bus_dev = BUS_ADD_CHILD(dev, BHND_PROBE_BUS, "bhnd", -1); if (sc->bus_dev == NULL) { error = ENXIO; goto failed; } dinfo = device_get_ivars(sc->bus_dev); dinfo->addrspace = BHNDB_ADDRSPACE_BRIDGED; /* We can now use bhndb to perform bridging of SYS_RES_MEMORY resources; * we use this to instantiate an erom parser instance */ eio = bhnd_erom_iores_new(sc->bus_dev, 0); if ((erom = bhnd_erom_alloc(erom_class, cid, eio)) == NULL) { bhnd_erom_io_fini(eio); error = ENXIO; goto failed; } /* Populate our resource priority configuration */ hwprio = BHNDB_BUS_GET_HARDWARE_PRIO(sc->parent_dev, sc->dev); error = bhndb_init_region_cfg(sc, erom, sc->bus_res, cores, ncores, hwprio); if (error) { device_printf(sc->dev, "failed to initialize resource " "priority configuration: %d\n", error); goto failed; } /* Free our erom instance */ bhnd_erom_free(erom); erom = NULL; return (0); failed: BHNDB_LOCK_DESTROY(sc); if (sc->bus_res != NULL) bhndb_free_resources(sc->bus_res); if (erom != NULL) bhnd_erom_free(erom); bhnd_service_registry_fini(&sc->services); return (error); } /** * Default bhndb(4) implementation of DEVICE_DETACH(). * * This function detaches any child devices, and if successful, releases all * resources held by the bridge device. */ int bhndb_generic_detach(device_t dev) { struct bhndb_softc *sc; int error; sc = device_get_softc(dev); /* Detach children */ if ((error = bus_generic_detach(dev))) return (error); /* Clean up our service registry */ if ((error = bhnd_service_registry_fini(&sc->services))) return (error); /* Clean up our driver state. */ bhndb_free_resources(sc->bus_res); BHNDB_LOCK_DESTROY(sc); return (0); } /** * Default bhndb(4) implementation of DEVICE_SUSPEND(). * * This function calls bus_generic_suspend() (or implements equivalent * behavior). */ int bhndb_generic_suspend(device_t dev) { return (bus_generic_suspend(dev)); } /** * Default bhndb(4) implementation of DEVICE_RESUME(). * * This function calls bus_generic_resume() (or implements equivalent * behavior). */ int bhndb_generic_resume(device_t dev) { struct bhndb_softc *sc; struct bhndb_resources *bus_res; struct bhndb_dw_alloc *dwa; int error; sc = device_get_softc(dev); bus_res = sc->bus_res; /* Guarantee that all in-use dynamic register windows are mapped to * their previously configured target address. */ BHNDB_LOCK(sc); for (size_t i = 0; i < bus_res->dwa_count; i++) { dwa = &bus_res->dw_alloc[i]; /* Skip regions that were not previously used */ if (bhndb_dw_is_free(bus_res, dwa) && dwa->target == 0x0) continue; /* Otherwise, ensure the register window is correct before * any children attempt MMIO */ error = BHNDB_SET_WINDOW_ADDR(dev, dwa->win, dwa->target); if (error) break; } BHNDB_UNLOCK(sc); /* Error restoring hardware state; children cannot be safely resumed */ if (error) { device_printf(dev, "Unable to restore hardware configuration; " "cannot resume: %d\n", error); return (error); } return (bus_generic_resume(dev)); } /** * Default implementation of BHNDB_SUSPEND_RESOURCE. */ static void bhndb_suspend_resource(device_t dev, device_t child, int type, struct resource *r) { struct bhndb_softc *sc; struct bhndb_dw_alloc *dwa; sc = device_get_softc(dev); /* Non-MMIO resources (e.g. IRQs) are handled solely by our parent */ if (type != SYS_RES_MEMORY) return; BHNDB_LOCK(sc); dwa = bhndb_dw_find_resource(sc->bus_res, r); if (dwa == NULL) { BHNDB_UNLOCK(sc); return; } if (BHNDB_DEBUG(PRIO)) device_printf(child, "suspend resource type=%d 0x%jx+0x%jx\n", type, rman_get_start(r), rman_get_size(r)); /* Release the resource's window reference */ bhndb_dw_release(sc->bus_res, dwa, r); BHNDB_UNLOCK(sc); } /** * Default implementation of BHNDB_RESUME_RESOURCE. */ static int bhndb_resume_resource(device_t dev, device_t child, int type, struct resource *r) { struct bhndb_softc *sc; sc = device_get_softc(dev); /* Non-MMIO resources (e.g. IRQs) are handled solely by our parent */ if (type != SYS_RES_MEMORY) return (0); /* Inactive resources don't require reallocation of bridge resources */ if (!(rman_get_flags(r) & RF_ACTIVE)) return (0); if (BHNDB_DEBUG(PRIO)) device_printf(child, "resume resource type=%d 0x%jx+0x%jx\n", type, rman_get_start(r), rman_get_size(r)); return (bhndb_try_activate_resource(sc, rman_get_device(r), type, rman_get_rid(r), r, NULL)); } /** * Default bhndb(4) implementation of BUS_READ_IVAR(). */ static int bhndb_read_ivar(device_t dev, device_t child, int index, uintptr_t *result) { return (ENOENT); } /** * Default bhndb(4) implementation of BUS_WRITE_IVAR(). */ static int bhndb_write_ivar(device_t dev, device_t child, int index, uintptr_t value) { return (ENOENT); } /** * Return the address space for the given @p child device. */ bhndb_addrspace bhndb_get_addrspace(struct bhndb_softc *sc, device_t child) { struct bhndb_devinfo *dinfo; device_t imd_dev; /* Find the directly attached parent of the requesting device */ imd_dev = child; while (imd_dev != NULL && device_get_parent(imd_dev) != sc->dev) imd_dev = device_get_parent(imd_dev); if (imd_dev == NULL) panic("bhndb address space request for non-child device %s\n", device_get_nameunit(child)); dinfo = device_get_ivars(imd_dev); return (dinfo->addrspace); } /** * Return the rman instance for a given resource @p type, if any. * * @param sc The bhndb device state. * @param child The requesting child. * @param type The resource type (e.g. SYS_RES_MEMORY, SYS_RES_IRQ, ...) */ static struct rman * bhndb_get_rman(struct bhndb_softc *sc, device_t child, int type) { switch (bhndb_get_addrspace(sc, child)) { case BHNDB_ADDRSPACE_NATIVE: switch (type) { case SYS_RES_MEMORY: return (&sc->bus_res->ht_mem_rman); case SYS_RES_IRQ: return (NULL); default: return (NULL); } case BHNDB_ADDRSPACE_BRIDGED: switch (type) { 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); } } /* Quieten gcc */ return (NULL); } /** * Default implementation of BUS_ADD_CHILD() */ static device_t bhndb_add_child(device_t dev, u_int order, const char *name, int unit) { struct bhndb_devinfo *dinfo; device_t child; child = device_add_child_ordered(dev, order, name, unit); if (child == NULL) return (NULL); dinfo = malloc(sizeof(struct bhndb_devinfo), M_BHND, M_NOWAIT); if (dinfo == NULL) { device_delete_child(dev, child); return (NULL); } dinfo->addrspace = BHNDB_ADDRSPACE_NATIVE; resource_list_init(&dinfo->resources); device_set_ivars(child, dinfo); return (child); } /** * Default implementation of BUS_CHILD_DELETED(). */ static void bhndb_child_deleted(device_t dev, device_t child) { struct bhndb_devinfo *dinfo = device_get_ivars(child); if (dinfo != NULL) { resource_list_free(&dinfo->resources); free(dinfo, M_BHND); } device_set_ivars(child, NULL); } /** * Default implementation of BHNDB_GET_CHIPID(). */ static const struct bhnd_chipid * bhndb_get_chipid(device_t dev, device_t child) { struct bhndb_softc *sc = device_get_softc(dev); return (&sc->chipid); } /** * Default implementation of BHNDB_IS_CORE_DISABLED(). */ static bool bhndb_is_core_disabled(device_t dev, device_t child, struct bhnd_core_info *core) { struct bhndb_softc *sc; sc = device_get_softc(dev); /* Try to defer to the bhndb bus parent */ if (BHNDB_BUS_IS_CORE_DISABLED(sc->parent_dev, dev, core)) return (true); /* Otherwise, we treat bridge-capable cores as unpopulated if they're * not the configured host bridge */ if (BHND_DEVCLASS_SUPPORTS_HOSTB(bhnd_core_class(core))) return (!bhnd_cores_equal(core, &sc->bridge_core)); /* Assume the core is populated */ return (false); } /** * Default bhndb(4) implementation of BHNDB_GET_HOSTB_CORE(). * * This function uses a heuristic valid on all known PCI/PCIe/PCMCIA-bridged * bhnd(4) devices. */ static int bhndb_get_hostb_core(device_t dev, device_t child, struct bhnd_core_info *core) { struct bhndb_softc *sc = device_get_softc(dev); *core = sc->bridge_core; return (0); } /** * Default bhndb(4) implementation of BHND_BUS_GET_SERVICE_REGISTRY(). */ static struct bhnd_service_registry * bhndb_get_service_registry(device_t dev, device_t child) { struct bhndb_softc *sc = device_get_softc(dev); return (&sc->services); } /** * Default bhndb(4) implementation of BUS_ALLOC_RESOURCE(). */ static struct resource * bhndb_alloc_resource(device_t dev, device_t child, int type, int *rid, rman_res_t start, rman_res_t end, rman_res_t count, u_int flags) { struct bhndb_softc *sc; struct resource_list_entry *rle; struct resource *rv; struct rman *rm; int error; bool passthrough, isdefault; sc = device_get_softc(dev); passthrough = (device_get_parent(child) != dev); isdefault = RMAN_IS_DEFAULT_RANGE(start, end); rle = NULL; /* Fetch the resource manager */ rm = bhndb_get_rman(sc, child, type); if (rm == NULL) { /* Delegate to our parent device's bus; the requested * resource type isn't handled locally. */ return (BUS_ALLOC_RESOURCE(device_get_parent(sc->parent_dev), child, type, rid, start, end, count, flags)); } /* Populate defaults */ if (!passthrough && isdefault) { /* Fetch the resource list entry. */ rle = resource_list_find(BUS_GET_RESOURCE_LIST(dev, child), type, *rid); if (rle == NULL) { device_printf(dev, "default resource %#x type %d for child %s " "not found\n", *rid, type, device_get_nameunit(child)); return (NULL); } if (rle->res != NULL) { device_printf(dev, "resource entry %#x type %d for child %s is busy\n", *rid, type, device_get_nameunit(child)); return (NULL); } start = rle->start; end = rle->end; count = ulmax(count, rle->count); } /* Validate resource addresses */ if (start > end || count > ((end - start) + 1)) return (NULL); /* Make our reservation */ rv = rman_reserve_resource(rm, start, end, count, flags & ~RF_ACTIVE, child); if (rv == NULL) return (NULL); rman_set_rid(rv, *rid); /* Activate */ if (flags & RF_ACTIVE) { error = bus_activate_resource(child, type, *rid, rv); if (error) { device_printf(dev, "failed to activate entry %#x type %d for " "child %s: %d\n", *rid, type, device_get_nameunit(child), error); rman_release_resource(rv); return (NULL); } } /* Update child's resource list entry */ if (rle != NULL) { rle->res = rv; rle->start = rman_get_start(rv); rle->end = rman_get_end(rv); rle->count = rman_get_size(rv); } return (rv); } /** * Default bhndb(4) implementation of BUS_RELEASE_RESOURCE(). */ static int bhndb_release_resource(device_t dev, device_t child, int type, int rid, struct resource *r) { struct bhndb_softc *sc; struct resource_list_entry *rle; bool passthrough; int error; 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 (bhndb_get_rman(sc, child, type) == NULL) { return (BUS_RELEASE_RESOURCE(device_get_parent(sc->parent_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); } /** * Default bhndb(4) implementation of BUS_ADJUST_RESOURCE(). */ static int bhndb_adjust_resource(device_t dev, device_t child, int type, struct resource *r, rman_res_t start, rman_res_t end) { struct bhndb_softc *sc; struct rman *rm; rman_res_t mstart, mend; int error; sc = device_get_softc(dev); error = 0; /* Delegate to our parent device's bus if the requested resource type * isn't handled locally. */ rm = bhndb_get_rman(sc, child, type); if (rm == NULL) { return (BUS_ADJUST_RESOURCE(device_get_parent(sc->parent_dev), child, type, r, start, end)); } /* Verify basic constraints */ if (end <= start) return (EINVAL); if (!rman_is_region_manager(r, rm)) return (ENXIO); BHNDB_LOCK(sc); /* If not active, allow any range permitted by the resource manager */ 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; if (start < mstart || end > mend) { error = EINVAL; goto done; } /* Fall through */ done: if (!error) error = rman_adjust_resource(r, start, end); BHNDB_UNLOCK(sc); return (error); } /** * Initialize child resource @p r with a virtual address, tag, and handle * copied from @p parent, adjusted to contain only the range defined by * @p offsize and @p size. * * @param r The register to be initialized. * @param parent The parent bus resource that fully contains the subregion. * @param offset The subregion offset within @p parent. * @param size The subregion size. * @p r. */ static int bhndb_init_child_resource(struct resource *r, struct resource *parent, bhnd_size_t offset, bhnd_size_t size) { bus_space_handle_t bh, child_bh; bus_space_tag_t bt; uintptr_t vaddr; int error; /* Fetch the parent resource's real bus values */ vaddr = (uintptr_t) rman_get_virtual(parent); bt = rman_get_bustag(parent); bh = rman_get_bushandle(parent); /* Configure child resource with window-adjusted real bus values */ vaddr += offset; error = bus_space_subregion(bt, bh, offset, size, &child_bh); if (error) return (error); rman_set_virtual(r, (void *) vaddr); rman_set_bustag(r, bt); rman_set_bushandle(r, child_bh); return (0); } /** * Attempt activation of a fixed register window mapping for @p child. * * @param sc BHNDB device state. * @param region The static region definition capable of mapping @p r. * @param child A child requesting resource activation. * @param type Resource type. * @param rid Resource identifier. * @param r Resource to be activated. * * @retval 0 if @p r was activated successfully * @retval ENOENT if no fixed register window was found. * @retval non-zero if @p r could not be activated. */ static int bhndb_activate_static_region(struct bhndb_softc *sc, struct bhndb_region *region, device_t child, int type, int rid, struct resource *r) { struct resource *bridge_res; const struct bhndb_regwin *win; bhnd_size_t parent_offset; rman_res_t r_start, r_size; int error; win = region->static_regwin; KASSERT(win != NULL && BHNDB_REGWIN_T_IS_STATIC(win->win_type), ("can't activate non-static region")); r_start = rman_get_start(r); r_size = rman_get_size(r); /* Find the corresponding bridge resource */ bridge_res = bhndb_host_resource_for_regwin(sc->bus_res->res, win); if (bridge_res == NULL) return (ENXIO); /* Calculate subregion offset within the parent resource */ parent_offset = r_start - region->addr; parent_offset += win->win_offset; /* Configure resource with its real bus values. */ error = bhndb_init_child_resource(r, bridge_res, parent_offset, r_size); if (error) return (error); /* Mark active */ if ((error = rman_activate_resource(r))) return (error); return (0); } /** * Attempt to allocate/retain a dynamic register window for @p r, returning * the retained window. * * @param sc The bhndb driver state. * @param r The resource for which a window will be retained. */ static struct bhndb_dw_alloc * bhndb_retain_dynamic_window(struct bhndb_softc *sc, struct resource *r) { struct bhndb_dw_alloc *dwa; rman_res_t r_start, r_size; int error; BHNDB_LOCK_ASSERT(sc, MA_OWNED); r_start = rman_get_start(r); r_size = rman_get_size(r); /* Look for an existing dynamic window we can reference */ dwa = bhndb_dw_find_mapping(sc->bus_res, r_start, r_size); if (dwa != NULL) { if (bhndb_dw_retain(sc->bus_res, dwa, r) == 0) return (dwa); return (NULL); } /* Otherwise, try to reserve a free window */ dwa = bhndb_dw_next_free(sc->bus_res); if (dwa == NULL) { /* No free windows */ return (NULL); } /* Window must be large enough to map the entire resource */ if (dwa->win->win_size < rman_get_size(r)) return (NULL); /* Set the window target */ error = bhndb_dw_set_addr(sc->dev, sc->bus_res, dwa, rman_get_start(r), rman_get_size(r)); if (error) { device_printf(sc->dev, "dynamic window initialization " "for 0x%llx-0x%llx failed: %d\n", (unsigned long long) r_start, (unsigned long long) r_start + r_size - 1, error); return (NULL); } /* Add our reservation */ if (bhndb_dw_retain(sc->bus_res, dwa, r)) return (NULL); return (dwa); } /** * Activate a resource using any viable static or dynamic register window. * * @param sc The bhndb driver state. * @param child The child holding ownership of @p r. * @param type The type of the resource to be activated. * @param rid The resource ID of @p r. * @param r The resource to be activated * @param[out] indirect On error and if not NULL, will be set to 'true' if * the caller should instead use an indirect resource mapping. * * @retval 0 success * @retval non-zero activation failed. */ static int bhndb_try_activate_resource(struct bhndb_softc *sc, device_t child, int type, int rid, struct resource *r, bool *indirect) { struct bhndb_region *region; struct bhndb_dw_alloc *dwa; bhndb_priority_t dw_priority; rman_res_t r_start, r_size; rman_res_t parent_offset; int error; 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); /* Activate native addrspace resources using the host address space */ if (bhndb_get_addrspace(sc, child) == BHNDB_ADDRSPACE_NATIVE) { struct resource *parent; /* Find the bridge resource referenced by the child */ parent = bhndb_host_resource_for_range(sc->bus_res->res, type, r_start, r_size); if (parent == NULL) { device_printf(sc->dev, "host resource not found " "for 0x%llx-0x%llx\n", (unsigned long long) r_start, (unsigned long long) r_start + r_size - 1); return (ENOENT); } /* Initialize child resource with the real bus values */ error = bhndb_init_child_resource(r, parent, r_start - rman_get_start(parent), r_size); if (error) return (error); /* Try to activate child resource */ return (rman_activate_resource(r)); } /* Default to low priority */ dw_priority = BHNDB_PRIORITY_LOW; /* Look for a bus region matching the resource's address range */ region = bhndb_find_resource_region(sc->bus_res, r_start, r_size); if (region != NULL) dw_priority = region->priority; /* Prefer static mappings over consuming a dynamic windows. */ if (region && region->static_regwin) { error = bhndb_activate_static_region(sc, region, child, type, rid, r); if (error) device_printf(sc->dev, "static window allocation " "for 0x%llx-0x%llx failed\n", (unsigned long long) r_start, (unsigned long long) r_start + r_size - 1); return (error); } /* A dynamic window will be required; is this resource high enough * priority to be reserved a dynamic window? */ if (dw_priority < sc->bus_res->min_prio) { if (indirect) *indirect = true; return (ENOMEM); } /* Find and retain a usable window */ BHNDB_LOCK(sc); { dwa = bhndb_retain_dynamic_window(sc, r); } BHNDB_UNLOCK(sc); if (dwa == NULL) { if (indirect) *indirect = true; return (ENOMEM); } /* Configure resource with its real bus values. */ parent_offset = dwa->win->win_offset; parent_offset += r_start - dwa->target; error = bhndb_init_child_resource(r, dwa->parent_res, parent_offset, dwa->win->win_size); if (error) goto failed; /* Mark active */ if ((error = rman_activate_resource(r))) goto failed; return (0); failed: /* Release our region allocation. */ BHNDB_LOCK(sc); bhndb_dw_release(sc->bus_res, dwa, r); BHNDB_UNLOCK(sc); return (error); } /** * 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, struct resource *r) { struct bhndb_softc *sc = device_get_softc(dev); /* Delegate directly to our parent device's bus if the requested * resource type isn't handled locally. */ if (bhndb_get_rman(sc, child, type) == NULL) { return (BUS_ACTIVATE_RESOURCE(device_get_parent(sc->parent_dev), child, type, rid, r)); } return (bhndb_try_activate_resource(sc, child, type, rid, r, NULL)); } /** * Default bhndb(4) implementation of BUS_DEACTIVATE_RESOURCE(). */ static int bhndb_deactivate_resource(device_t dev, device_t child, int type, int rid, struct resource *r) { struct bhndb_dw_alloc *dwa; struct bhndb_softc *sc; struct rman *rm; int error; sc = device_get_softc(dev); /* Delegate directly to our parent device's bus if the requested * resource type isn't handled locally. */ rm = bhndb_get_rman(sc, child, type); if (rm == NULL) { return (BUS_DEACTIVATE_RESOURCE( device_get_parent(sc->parent_dev), child, type, rid, r)); } /* Mark inactive */ 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); + } } /** * Default bhndb(4) implementation of BUS_GET_RESOURCE_LIST(). */ static struct resource_list * bhndb_get_resource_list(device_t dev, device_t child) { struct bhndb_devinfo *dinfo = device_get_ivars(child); return (&dinfo->resources); } /** * 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, int type, int rid, struct bhnd_resource *r) { struct bhndb_softc *sc; struct bhndb_region *region; + bhndb_priority_t r_prio; rman_res_t r_start, r_size; int error; bool indirect; KASSERT(!r->direct, ("direct flag set on inactive resource")); KASSERT(!(rman_get_flags(r->res) & RF_ACTIVE), ("RF_ACTIVE set on inactive resource")); sc = device_get_softc(dev); /* Delegate directly to BUS_ACTIVATE_RESOURCE() if the requested * resource type isn't handled locally. */ if (bhndb_get_rman(sc, child, type) == NULL) { error = BUS_ACTIVATE_RESOURCE(dev, child, type, rid, r->res); if (error == 0) r->direct = true; return (error); } 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 */ error = bhndb_try_activate_resource(sc, child, type, rid, r->res, &indirect); if (!error) { r->direct = true; } else if (indirect) { /* The request was valid, but no viable register window is * available; indirection must be employed. */ error = 0; r->direct = false; } if (BHNDB_DEBUG(PRIO) && bhndb_get_addrspace(sc, child) == BHNDB_ADDRSPACE_BRIDGED) { device_printf(child, "activated 0x%llx-0x%llx as %s " "resource\n", (unsigned long long) r_start, (unsigned long long) r_start + r_size - 1, r->direct ? "direct" : "indirect"); } return (error); } /** * Default bhndb(4) implementation of BHND_BUS_DEACTIVATE_RESOURCE(). */ static int bhndb_deactivate_bhnd_resource(device_t dev, device_t child, int type, int rid, struct bhnd_resource *r) { int error; /* Indirect resources don't require activation */ if (!r->direct) return (0); KASSERT(rman_get_flags(r->res) & RF_ACTIVE, ("RF_ACTIVE not set on direct resource")); /* Perform deactivation */ error = BUS_DEACTIVATE_RESOURCE(dev, child, type, rid, r->res); if (!error) r->direct = false; return (error); } /** * Slow path for bhndb_io_resource(). * * Iterates over the existing allocated dynamic windows looking for a viable * in-use region; the first matching region is returned. */ static struct bhndb_dw_alloc * bhndb_io_resource_slow(struct bhndb_softc *sc, bus_addr_t addr, bus_size_t size, bus_size_t *offset) { struct bhndb_resources *br; struct bhndb_dw_alloc *dwa; BHNDB_LOCK_ASSERT(sc, MA_OWNED); br = sc->bus_res; /* Search for an existing dynamic mapping of this address range. * Static regions are not searched, as a statically mapped * region would never be allocated as an indirect resource. */ for (size_t i = 0; i < br->dwa_count; i++) { const struct bhndb_regwin *win; dwa = &br->dw_alloc[i]; win = dwa->win; KASSERT(win->win_type == BHNDB_REGWIN_T_DYN, ("invalid register window type")); /* Verify the range */ if (addr < dwa->target) continue; if (addr + size > dwa->target + win->win_size) continue; /* Found */ *offset = dwa->win->win_offset; *offset += addr - dwa->target; return (dwa); } /* not found */ return (NULL); } /** * Return a borrowed reference to a bridge resource allocation record capable * of handling bus I/O requests of @p size at @p addr. * * This will either return a reference to an existing allocation * record mapping the requested space, or will configure and return a free * allocation record. * * Will panic if a usable record cannot be found. * * @param sc Bridge driver state. * @param addr The I/O target address. * @param size The size of the I/O operation to be performed at @p addr. * @param[out] offset The offset within the returned resource at which * to perform the I/O request. */ static inline struct bhndb_dw_alloc * bhndb_io_resource(struct bhndb_softc *sc, bus_addr_t addr, bus_size_t size, bus_size_t *offset) { struct bhndb_resources *br; struct bhndb_dw_alloc *dwa; int error; BHNDB_LOCK_ASSERT(sc, MA_OWNED); br = sc->bus_res; /* Try to fetch a free window */ dwa = bhndb_dw_next_free(br); /* * If no dynamic windows are available, look for an existing * region that maps the target range. * * If none are found, this is a child driver bug -- our window * over-commit should only fail in the case where a child driver leaks * resources, or perform operations out-of-order. * * Broadcom HND chipsets are designed to not require register window * swapping during execution; as long as the child devices are * attached/detached correctly, using the hardware's required order * of operations, there should always be a window available for the * current operation. */ if (dwa == NULL) { dwa = bhndb_io_resource_slow(sc, addr, size, offset); if (dwa == NULL) { panic("register windows exhausted attempting to map " "0x%llx-0x%llx\n", (unsigned long long) addr, (unsigned long long) addr+size-1); } return (dwa); } /* Adjust the window if the I/O request won't fit in the current * target range. */ if (addr < dwa->target || addr > dwa->target + dwa->win->win_size || (dwa->target + dwa->win->win_size) - addr < size) { error = bhndb_dw_set_addr(sc->dev, sc->bus_res, dwa, addr, size); if (error) { panic("failed to set register window target mapping " "0x%llx-0x%llx\n", (unsigned long long) addr, (unsigned long long) addr+size-1); } } /* Calculate the offset and return */ *offset = (addr - dwa->target) + dwa->win->win_offset; return (dwa); } /* * BHND_BUS_(READ|WRITE_* implementations */ /* bhndb_bus_(read|write) common implementation */ #define BHNDB_IO_COMMON_SETUP(_io_size) \ struct bhndb_softc *sc; \ struct bhndb_dw_alloc *dwa; \ struct resource *io_res; \ bus_size_t io_offset; \ \ sc = device_get_softc(dev); \ \ BHNDB_LOCK(sc); \ dwa = bhndb_io_resource(sc, rman_get_start(r->res) + \ offset, _io_size, &io_offset); \ io_res = dwa->parent_res; \ \ KASSERT(!r->direct, \ ("bhnd_bus slow path used for direct resource")); \ \ KASSERT(rman_get_flags(io_res) & RF_ACTIVE, \ ("i/o resource is not active")); #define BHNDB_IO_COMMON_TEARDOWN() \ BHNDB_UNLOCK(sc); /* Defines a bhndb_bus_read_* method implementation */ #define BHNDB_IO_READ(_type, _name) \ static _type \ bhndb_bus_read_ ## _name (device_t dev, device_t child, \ struct bhnd_resource *r, bus_size_t offset) \ { \ _type v; \ BHNDB_IO_COMMON_SETUP(sizeof(_type)); \ v = bus_read_ ## _name (io_res, io_offset); \ BHNDB_IO_COMMON_TEARDOWN(); \ \ return (v); \ } /* Defines a bhndb_bus_write_* method implementation */ #define BHNDB_IO_WRITE(_type, _name) \ static void \ bhndb_bus_write_ ## _name (device_t dev, device_t child, \ struct bhnd_resource *r, bus_size_t offset, _type value) \ { \ BHNDB_IO_COMMON_SETUP(sizeof(_type)); \ bus_write_ ## _name (io_res, io_offset, value); \ BHNDB_IO_COMMON_TEARDOWN(); \ } /* Defines a bhndb_bus_(read|write|set)_(multi|region)_* method */ #define BHNDB_IO_MISC(_type, _ptr, _op, _size) \ static void \ bhndb_bus_ ## _op ## _ ## _size (device_t dev, \ device_t child, struct bhnd_resource *r, bus_size_t offset, \ _type _ptr datap, bus_size_t count) \ { \ BHNDB_IO_COMMON_SETUP(sizeof(_type) * count); \ bus_ ## _op ## _ ## _size (io_res, io_offset, \ datap, count); \ BHNDB_IO_COMMON_TEARDOWN(); \ } /* Defines a complete set of read/write methods */ #define BHNDB_IO_METHODS(_type, _size) \ BHNDB_IO_READ(_type, _size) \ BHNDB_IO_WRITE(_type, _size) \ \ BHNDB_IO_READ(_type, stream_ ## _size) \ BHNDB_IO_WRITE(_type, stream_ ## _size) \ \ BHNDB_IO_MISC(_type, *, read_multi, _size) \ BHNDB_IO_MISC(_type, *, write_multi, _size) \ \ BHNDB_IO_MISC(_type, *, read_multi_stream, _size) \ BHNDB_IO_MISC(_type, *, write_multi_stream, _size) \ \ BHNDB_IO_MISC(_type, , set_multi, _size) \ BHNDB_IO_MISC(_type, , set_region, _size) \ BHNDB_IO_MISC(_type, *, read_region, _size) \ BHNDB_IO_MISC(_type, *, write_region, _size) \ \ BHNDB_IO_MISC(_type, *, read_region_stream, _size) \ BHNDB_IO_MISC(_type, *, write_region_stream, _size) BHNDB_IO_METHODS(uint8_t, 1); BHNDB_IO_METHODS(uint16_t, 2); BHNDB_IO_METHODS(uint32_t, 4); /** * Default bhndb(4) implementation of BHND_BUS_BARRIER(). */ static void bhndb_bus_barrier(device_t dev, device_t child, struct bhnd_resource *r, bus_size_t offset, bus_size_t length, int flags) { BHNDB_IO_COMMON_SETUP(length); bus_barrier(io_res, io_offset + offset, length, flags); BHNDB_IO_COMMON_TEARDOWN(); } /** + * 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 bhndb_get_dma_tag(device_t dev, device_t child) { // TODO return (NULL); } static device_method_t bhndb_methods[] = { /* Device interface */ \ DEVMETHOD(device_probe, bhndb_generic_probe), DEVMETHOD(device_detach, bhndb_generic_detach), DEVMETHOD(device_shutdown, bus_generic_shutdown), DEVMETHOD(device_suspend, bhndb_generic_suspend), DEVMETHOD(device_resume, bhndb_generic_resume), /* Bus interface */ DEVMETHOD(bus_probe_nomatch, bhndb_probe_nomatch), DEVMETHOD(bus_print_child, bhndb_print_child), DEVMETHOD(bus_child_pnpinfo_str, bhndb_child_pnpinfo_str), DEVMETHOD(bus_child_location_str, bhndb_child_location_str), DEVMETHOD(bus_add_child, bhndb_add_child), DEVMETHOD(bus_child_deleted, bhndb_child_deleted), DEVMETHOD(bus_alloc_resource, bhndb_alloc_resource), DEVMETHOD(bus_release_resource, bhndb_release_resource), 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), DEVMETHOD(bus_adjust_resource, bhndb_adjust_resource), DEVMETHOD(bus_set_resource, bus_generic_rl_set_resource), DEVMETHOD(bus_get_resource, bus_generic_rl_get_resource), DEVMETHOD(bus_delete_resource, bus_generic_rl_delete_resource), DEVMETHOD(bus_get_resource_list, bhndb_get_resource_list), DEVMETHOD(bus_read_ivar, bhndb_read_ivar), DEVMETHOD(bus_write_ivar, bhndb_write_ivar), /* BHNDB interface */ DEVMETHOD(bhndb_get_chipid, bhndb_get_chipid), DEVMETHOD(bhndb_is_core_disabled, bhndb_is_core_disabled), DEVMETHOD(bhndb_get_hostb_core, bhndb_get_hostb_core), DEVMETHOD(bhndb_suspend_resource, bhndb_suspend_resource), DEVMETHOD(bhndb_resume_resource, bhndb_resume_resource), /* BHND interface */ DEVMETHOD(bhnd_bus_get_chipid, bhndb_get_chipid), 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), DEVMETHOD(bhnd_bus_deregister_provider, bhnd_bus_generic_sr_deregister_provider), DEVMETHOD(bhnd_bus_retain_provider, bhnd_bus_generic_sr_retain_provider), DEVMETHOD(bhnd_bus_release_provider, bhnd_bus_generic_sr_release_provider), DEVMETHOD(bhnd_bus_read_1, bhndb_bus_read_1), DEVMETHOD(bhnd_bus_read_2, bhndb_bus_read_2), DEVMETHOD(bhnd_bus_read_4, bhndb_bus_read_4), DEVMETHOD(bhnd_bus_write_1, bhndb_bus_write_1), DEVMETHOD(bhnd_bus_write_2, bhndb_bus_write_2), DEVMETHOD(bhnd_bus_write_4, bhndb_bus_write_4), DEVMETHOD(bhnd_bus_read_stream_1, bhndb_bus_read_stream_1), DEVMETHOD(bhnd_bus_read_stream_2, bhndb_bus_read_stream_2), DEVMETHOD(bhnd_bus_read_stream_4, bhndb_bus_read_stream_4), DEVMETHOD(bhnd_bus_write_stream_1, bhndb_bus_write_stream_1), DEVMETHOD(bhnd_bus_write_stream_2, bhndb_bus_write_stream_2), DEVMETHOD(bhnd_bus_write_stream_4, bhndb_bus_write_stream_4), DEVMETHOD(bhnd_bus_read_multi_1, bhndb_bus_read_multi_1), DEVMETHOD(bhnd_bus_read_multi_2, bhndb_bus_read_multi_2), DEVMETHOD(bhnd_bus_read_multi_4, bhndb_bus_read_multi_4), DEVMETHOD(bhnd_bus_write_multi_1, bhndb_bus_write_multi_1), DEVMETHOD(bhnd_bus_write_multi_2, bhndb_bus_write_multi_2), DEVMETHOD(bhnd_bus_write_multi_4, bhndb_bus_write_multi_4), DEVMETHOD(bhnd_bus_read_multi_stream_1, bhndb_bus_read_multi_stream_1), DEVMETHOD(bhnd_bus_read_multi_stream_2, bhndb_bus_read_multi_stream_2), DEVMETHOD(bhnd_bus_read_multi_stream_4, bhndb_bus_read_multi_stream_4), DEVMETHOD(bhnd_bus_write_multi_stream_1,bhndb_bus_write_multi_stream_1), DEVMETHOD(bhnd_bus_write_multi_stream_2,bhndb_bus_write_multi_stream_2), DEVMETHOD(bhnd_bus_write_multi_stream_4,bhndb_bus_write_multi_stream_4), DEVMETHOD(bhnd_bus_set_multi_1, bhndb_bus_set_multi_1), DEVMETHOD(bhnd_bus_set_multi_2, bhndb_bus_set_multi_2), DEVMETHOD(bhnd_bus_set_multi_4, bhndb_bus_set_multi_4), DEVMETHOD(bhnd_bus_set_region_1, bhndb_bus_set_region_1), DEVMETHOD(bhnd_bus_set_region_2, bhndb_bus_set_region_2), DEVMETHOD(bhnd_bus_set_region_4, bhndb_bus_set_region_4), DEVMETHOD(bhnd_bus_read_region_1, bhndb_bus_read_region_1), DEVMETHOD(bhnd_bus_read_region_2, bhndb_bus_read_region_2), DEVMETHOD(bhnd_bus_read_region_4, bhndb_bus_read_region_4), DEVMETHOD(bhnd_bus_write_region_1, bhndb_bus_write_region_1), DEVMETHOD(bhnd_bus_write_region_2, bhndb_bus_write_region_2), DEVMETHOD(bhnd_bus_write_region_4, bhndb_bus_write_region_4), DEVMETHOD(bhnd_bus_read_region_stream_1,bhndb_bus_read_region_stream_1), DEVMETHOD(bhnd_bus_read_region_stream_2,bhndb_bus_read_region_stream_2), DEVMETHOD(bhnd_bus_read_region_stream_4,bhndb_bus_read_region_stream_4), DEVMETHOD(bhnd_bus_write_region_stream_1,bhndb_bus_write_region_stream_1), DEVMETHOD(bhnd_bus_write_region_stream_2,bhndb_bus_write_region_stream_2), DEVMETHOD(bhnd_bus_write_region_stream_4,bhndb_bus_write_region_stream_4), DEVMETHOD(bhnd_bus_barrier, bhndb_bus_barrier), DEVMETHOD_END }; devclass_t bhndb_devclass; DEFINE_CLASS_0(bhndb, bhndb_driver, bhndb_methods, sizeof(struct bhndb_softc)); MODULE_VERSION(bhndb, 1); MODULE_DEPEND(bhndb, bhnd, 1, 1, 1); Index: head/sys/dev/bhnd/bhndb/bhndb_if.m =================================================================== --- head/sys/dev/bhnd/bhndb/bhndb_if.m (revision 326078) +++ head/sys/dev/bhnd/bhndb/bhndb_if.m (revision 326079) @@ -1,226 +1,274 @@ #- -# 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: # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR # IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES # OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. # IN NO EVENT SHALL 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. # # $FreeBSD$ #include #include #include #include #include #include # # bhndb bridge device interface. # INTERFACE bhndb; HEADER { + struct bhndb_intr_isrc; struct bhndb_regwin; struct bhndb_hw; struct bhndb_hw_priority; } CODE { #include #include static const struct bhnd_chipid * bhndb_null_get_chipid(device_t dev, device_t child) { panic("bhndb_get_chipid unimplemented"); } static int bhndb_null_populate_board_info(device_t dev, device_t child, struct bhnd_board_info *info) { panic("bhndb_populate_board_info unimplemented"); } static int bhndb_null_is_core_disabled(device_t dev, device_t child, struct bhnd_core_info *core) { panic("bhndb_is_core_disabled unimplemented"); } static int bhndb_null_get_hostb_core(device_t dev, device_t child, struct bhnd_core_info *core) { panic("bhndb_get_hostb_core unimplemented"); } static void bhndb_null_suspend_resource(device_t dev, device_t child, int type, struct resource *r) { panic("bhndb_suspend_resource unimplemented"); } static int bhndb_null_resume_resource(device_t dev, device_t child, int type, struct resource *r) { panic("bhndb_resume_resource unimplemented"); } 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"); + } } /** * Return the chip identification information for @p child. * * @param dev The parent device of @p child. * @param child The bhndb-attached device. */ METHOD const struct bhnd_chipid * get_chipid { device_t dev; device_t child; } DEFAULT bhndb_null_get_chipid; /** * Populate @p info with board info known only to the bridge, * deferring to any existing initialized fields in @p info. * * @param dev The parent device of @p child. * @param child The bhndb-attached device. * @param[in,out] info A board info structure previously initialized with any * information available from NVRAM. */ METHOD int populate_board_info { device_t dev; device_t child; struct bhnd_board_info *info; } DEFAULT bhndb_null_populate_board_info; /** * Return true if the hardware required by @p core is unpopulated or * otherwise unusable. * * In some cases, the core's pins may be left floating, or the hardware * may otherwise be non-functional; this method allows the parent device * to explicitly specify whether @p core should be disabled. * * @param dev The parent device of @p child. * @param child The attached bhnd device. * @param core A core discovered on @p child. */ METHOD bool is_core_disabled { device_t dev; device_t child; struct bhnd_core_info *core; } DEFAULT bhndb_null_is_core_disabled; /** * Get the host bridge core info for the attached bhnd bus. * * @param dev The bridge device. * @param child The bhnd bus device attached to @p dev. * @param[out] core Will be populated with the host bridge core info, if * found. * * @retval 0 success * @retval ENOENT No host bridge core found. * @retval non-zero If locating the host bridge core otherwise fails, a * regular UNIX error code should be returned. */ METHOD int get_hostb_core { device_t dev; device_t child; struct bhnd_core_info *core; } DEFAULT bhndb_null_get_hostb_core; /** * Mark a resource as 'suspended', gauranteeing to the bridge that no * further use of the resource will be made until BHNDB_RESUME_RESOURCE() * is called. * * Bridge resources consumed by the reference may be released; these will * be reacquired if BHNDB_RESUME_RESOURCE() completes successfully. * * Requests to suspend a suspended resource will be ignored. * * @param dev The bridge device. * @param child The child device requesting resource suspension. This does * not need to be the owner of @p r. * @param type The resource type. * @param r The resource to be suspended. */ METHOD void suspend_resource { device_t dev; device_t child; int type; struct resource *r; } DEFAULT bhndb_null_suspend_resource; /** * Attempt to re-enable a resource previously suspended by * BHNDB_SUSPEND_RESOURCE(). * * Bridge resources required by the reference may not be available, in which * case an error will be returned and the resource mapped by @p r must not be * used in any capacity. * * Requests to resume a non-suspended resource will be ignored. * * @param dev The bridge device. * @param child The child device requesting resource suspension. This does * not need to be the owner of @p r. * @param type The resource type. * @param r The resource to be suspended. */ METHOD int resume_resource { device_t dev; device_t child; int type; struct resource *r; } 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. * @param win The register window. * @param addr The address to be configured for @p win. * * @retval 0 success * @retval ENODEV The provided @p win is not memory-mapped on the bus or does * not support setting a base address. * @retval non-zero failure */ METHOD int set_window_addr { device_t dev; 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 (revision 326078) +++ head/sys/dev/bhnd/bhndb/bhndb_pci.c (revision 326079) @@ -1,1179 +1,1481 @@ /*- * 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: * 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. */ #include __FBSDID("$FreeBSD$"); /* * PCI-specific implementation for the BHNDB bridge driver. * * Provides support for bridging from a PCI parent bus to a BHND-compatible * bus (e.g. bcma or siba) via a Broadcom PCI core configured in end-point * mode. * * This driver handles all initial generic host-level PCI interactions with a * PCI/PCIe bridge core operating in endpoint mode. Once the bridged bhnd(4) * bus has been enumerated, this driver works in tandem with a core-specific * bhnd_pci_hostb driver to manage the PCI core. */ #include #include #include #include #include #include #include #include #include #include #include #include #include +#include + #include #include "bhndb_pcireg.h" #include "bhndb_pcivar.h" #include "bhndb_private.h" 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); static int bhndb_disable_pci_clocks(device_t dev); static int bhndb_pci_compat_setregwin(device_t dev, device_t pci_dev, const struct bhndb_regwin *, bhnd_addr_t); 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); static bus_addr_t bhndb_pci_sprom_addr(struct bhndb_pci_softc *sc); static bus_size_t bhndb_pci_sprom_size(struct bhndb_pci_softc *sc); static int bhndb_pci_eio_init(struct bhndb_pci_eio *pio, device_t dev, device_t pci_dev, struct bhndb_host_resources *hr); static int bhndb_pci_eio_map(struct bhnd_erom_io *eio, bhnd_addr_t addr, bhnd_size_t size); static uint32_t bhndb_pci_eio_read(struct bhnd_erom_io *eio, bhnd_size_t offset, u_int width); #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 */ device_t pci_dev; /**< parent PCI device */ struct bhndb_host_resources *hr; /**< borrowed reference to host resources */ const struct bhndb_regwin *win; /**< mapped register window, or NULL */ struct resource *res; /**< resource containing the register window, or NULL if no window mapped */ bhnd_addr_t res_target; /**< current target address (if mapped) */ bool mapped; /**< true if a valid mapping exists, false otherwise */ bhnd_addr_t addr; /**< mapped address */ 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(). * * Verifies that the parent is a PCI/PCIe device. */ 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); parent_bus = device_get_devclass(device_get_parent(parent)); 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); } static int bhndb_pci_attach(device_t dev) { struct bhndb_pci_softc *sc; struct bhnd_chipid cid; 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))) goto cleanup; /* Identify the chip and enumerate the bridged cores */ error = bhndb_pci_read_core_table(dev, &cid, &cores, &ncores, &erom_class); if (error) goto cleanup; /* Select the appropriate register window handler */ if (cid.chip_type == BHND_CHIPTYPE_SIBA) { sc->set_regwin = bhndb_pci_compat_setregwin; } else { 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) goto cleanup; /* Fix-up power on defaults for SROM-less devices. */ bhndb_init_sromless_pci_config(sc); /* Add any additional child devices */ if ((error = bhndb_pci_add_children(sc))) goto cleanup; /* Probe and attach our children */ if ((error = bus_generic_attach(dev))) goto cleanup; free(cores, M_BHND); return (0); cleanup: 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) free(cores, M_BHND); pci_disable_busmaster(sc->parent); + BHNDB_PCI_LOCK_DESTROY(sc); + return (error); } static int bhndb_pci_detach(device_t dev) { struct bhndb_pci_softc *sc; int error; sc = device_get_softc(dev); /* Attempt to detach our children */ if ((error = bus_generic_detach(dev))) return (error); /* Perform generic bridge detach */ if ((error = bhndb_generic_detach(dev))) return (error); /* Disable clocks (if required by this hardware) */ 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); } /** * Use the generic PCI bridge hardware configuration to enumerate the bridged * bhnd(4) bus' core table. * * @note This function may be safely called prior to device attach, (e.g. * from DEVICE_PROBE). * @note This function requires exclusive ownership over allocating and * configuring host bridge resources, and should only be called prior to * completion of device attach and full configuration of the bridge. * * @param dev The bhndb_pci bridge device. * @param[out] chipid On success, the parsed chip identification. * @param[out] cores On success, the enumerated core table. The * caller is responsible for freeing this table via * bhndb_pci_free_core_table(). * @param[out] ncores On success, the number of cores found in * @p cores. * @param[out] eromcls On success, a pointer to the erom class used to * parse the device enumeration table. This * argument may be NULL if the class is not * desired. * * @retval 0 success * @retval non-zero if enumerating the bridged bhnd(4) bus fails, a regular * unix error code will be returned. */ 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) { const struct bhndb_hwcfg *cfg; struct bhndb_host_resources *hr; struct bhndb_pci_eio pio; struct bhnd_core_info *erom_cores; const struct bhnd_chipid *hint; struct bhnd_chipid cid; bhnd_erom_class_t *erom_class; bhnd_erom_t *erom; device_t parent_dev; u_int erom_ncores; int error; parent_dev = device_get_parent(dev); erom = NULL; erom_cores = NULL; /* Fetch our chipid hint (if any) and generic hardware configuration */ cfg = BHNDB_BUS_GET_GENERIC_HWCFG(parent_dev, dev); hint = BHNDB_BUS_GET_CHIPID(parent_dev, dev); /* Allocate our host resources */ if ((error = bhndb_alloc_host_resources(parent_dev, cfg, &hr))) return (error); /* Initialize our erom I/O state */ if ((error = bhndb_pci_eio_init(&pio, dev, parent_dev, hr))) goto failed; /* Map the first bus core from our bridged bhnd(4) bus */ error = bhndb_pci_eio_map(&pio.eio, BHND_DEFAULT_CHIPC_ADDR, BHND_DEFAULT_CORE_SIZE); if (error) goto failed; /* Probe for a usable EROM class, and read the chip identifier */ erom_class = bhnd_erom_probe_driver_classes(device_get_devclass(dev), &pio.eio, hint, &cid); if (erom_class == NULL) { device_printf(dev, "device enumeration unsupported; no " "compatible driver found\n"); error = ENXIO; goto failed; } /* Allocate EROM parser */ if ((erom = bhnd_erom_alloc(erom_class, &cid, &pio.eio)) == NULL) { device_printf(dev, "failed to allocate device enumeration " "table parser\n"); error = ENXIO; goto failed; } /* Read the full core table */ error = bhnd_erom_get_core_table(erom, &erom_cores, &erom_ncores); if (error) { device_printf(dev, "error fetching core table: %d\n", error); goto failed; } /* Provide the results to our caller */ *cores = malloc(sizeof(erom_cores[0]) * erom_ncores, M_BHND, M_WAITOK); memcpy(*cores, erom_cores, sizeof(erom_cores[0]) * erom_ncores); *ncores = erom_ncores; *chipid = cid; if (eromcls != NULL) *eromcls = erom_class; /* Clean up */ bhnd_erom_free_core_table(erom, erom_cores); bhnd_erom_free(erom); bhndb_release_host_resources(hr); return (0); failed: if (erom_cores != NULL) bhnd_erom_free_core_table(erom, erom_cores); if (erom != NULL) bhnd_erom_free(erom); bhndb_release_host_resources(hr); return (error); } static int bhndb_pci_add_children(struct bhndb_pci_softc *sc) { bus_size_t nv_sz; int error; /** * If SPROM is mapped directly into BAR0, add child NVRAM * device. */ nv_sz = bhndb_pci_sprom_size(sc); if (nv_sz > 0) { struct bhndb_devinfo *dinfo; device_t child; if (bootverbose) { device_printf(sc->dev, "found SPROM (%ju bytes)\n", (uintmax_t)nv_sz); } /* Add sprom device, ordered early enough to be available * before the bridged bhnd(4) bus is attached. */ child = BUS_ADD_CHILD(sc->dev, BHND_PROBE_ROOT + BHND_PROBE_ORDER_EARLY, "bhnd_nvram", -1); if (child == NULL) { device_printf(sc->dev, "failed to add sprom device\n"); return (ENXIO); } /* Initialize device address space and resource covering the * BAR0 SPROM shadow. */ dinfo = device_get_ivars(child); dinfo->addrspace = BHNDB_ADDRSPACE_NATIVE; error = bus_set_resource(child, SYS_RES_MEMORY, 0, bhndb_pci_sprom_addr(sc), nv_sz); if (error) { device_printf(sc->dev, "failed to register sprom resources\n"); return (error); } } return (0); } static const struct bhndb_regwin * bhndb_pci_sprom_regwin(struct bhndb_pci_softc *sc) { struct bhndb_resources *bres; const struct bhndb_hwcfg *cfg; const struct bhndb_regwin *sprom_win; bres = sc->bhndb.bus_res; cfg = bres->cfg; sprom_win = bhndb_regwin_find_type(cfg->register_windows, BHNDB_REGWIN_T_SPROM, BHNDB_PCI_V0_BAR0_SPROM_SIZE); return (sprom_win); } static bus_addr_t bhndb_pci_sprom_addr(struct bhndb_pci_softc *sc) { const struct bhndb_regwin *sprom_win; struct resource *r; /* Fetch the SPROM register window */ sprom_win = bhndb_pci_sprom_regwin(sc); KASSERT(sprom_win != NULL, ("requested sprom address on PCI_V2+")); /* Fetch the associated resource */ r = bhndb_host_resource_for_regwin(sc->bhndb.bus_res->res, sprom_win); KASSERT(r != NULL, ("missing resource for sprom window\n")); return (rman_get_start(r) + sprom_win->win_offset); } static bus_size_t bhndb_pci_sprom_size(struct bhndb_pci_softc *sc) { const struct bhndb_regwin *sprom_win; uint32_t sctl; bus_size_t sprom_sz; sprom_win = bhndb_pci_sprom_regwin(sc); /* PCI_V2 and later devices map SPROM/OTP via ChipCommon */ if (sprom_win == NULL) return (0); /* Determine SPROM size */ sctl = pci_read_config(sc->parent, BHNDB_PCI_SPROM_CONTROL, 4); if (sctl & BHNDB_PCI_SPROM_BLANK) return (0); switch (sctl & BHNDB_PCI_SPROM_SZ_MASK) { case BHNDB_PCI_SPROM_SZ_1KB: sprom_sz = (1 * 1024); break; case BHNDB_PCI_SPROM_SZ_4KB: sprom_sz = (4 * 1024); break; case BHNDB_PCI_SPROM_SZ_16KB: sprom_sz = (16 * 1024); break; case BHNDB_PCI_SPROM_SZ_RESERVED: default: device_printf(sc->dev, "invalid PCI sprom size 0x%x\n", sctl); return (0); } if (sprom_sz > sprom_win->win_size) { device_printf(sc->dev, "PCI sprom size (0x%x) overruns defined register window\n", sctl); return (0); } 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 * mapped to the wrong core. * * This function updates the SROM shadow to point the BAR0 windows at the * current PCI core. * * Applies to all PCI/PCIe revisions. */ 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)); } } static int bhndb_pci_resume(device_t dev) { struct bhndb_pci_softc *sc; int error; sc = device_get_softc(dev); /* Enable clocks (if supported by this hardware) */ if ((error = bhndb_enable_pci_clocks(sc->dev))) return (error); /* Perform resume */ return (bhndb_generic_resume(dev)); } static int bhndb_pci_suspend(device_t dev) { struct bhndb_pci_softc *sc; int error; sc = device_get_softc(dev); /* Disable clocks (if supported by this hardware) */ if ((error = bhndb_disable_pci_clocks(sc->dev))) return (error); /* Perform suspend */ return (bhndb_generic_suspend(dev)); } static int bhndb_pci_set_window_addr(device_t dev, const struct bhndb_regwin *rw, bhnd_addr_t addr) { struct bhndb_pci_softc *sc = device_get_softc(dev); return (sc->set_regwin(sc->dev, sc->parent, rw, addr)); } /** * A siba(4) and bcma(4)-compatible bhndb_set_window_addr implementation. * * On siba(4) devices, it's possible that writing a PCI window register may * not succeed; it's necessary to immediately read the configuration register * and retry if not set to the desired value. * * This is not necessary on bcma(4) devices, but other than the overhead of * validating the register, there's no harm in performing the verification. */ static int bhndb_pci_compat_setregwin(device_t dev, device_t pci_dev, const struct bhndb_regwin *rw, bhnd_addr_t addr) { int error; int reg; if (rw->win_type != BHNDB_REGWIN_T_DYN) return (ENODEV); reg = rw->d.dyn.cfg_offset; for (u_int i = 0; i < BHNDB_PCI_BARCTRL_WRITE_RETRY; i++) { if ((error = bhndb_pci_fast_setregwin(dev, pci_dev, rw, addr))) return (error); if (pci_read_config(pci_dev, reg, 4) == addr) return (0); DELAY(10); } /* Unable to set window */ return (ENODEV); } /** * A bcma(4)-only bhndb_set_window_addr implementation. */ static int bhndb_pci_fast_setregwin(device_t dev, device_t pci_dev, const struct bhndb_regwin *rw, bhnd_addr_t addr) { /* The PCI bridge core only supports 32-bit addressing, regardless * of the bus' support for 64-bit addressing */ if (addr > UINT32_MAX) return (ERANGE); switch (rw->win_type) { case BHNDB_REGWIN_T_DYN: /* Addresses must be page aligned */ if (addr % rw->win_size != 0) return (EINVAL); pci_write_config(pci_dev, rw->d.dyn.cfg_offset, addr, 4); break; default: return (ENODEV); } return (0); } static int bhndb_pci_populate_board_info(device_t dev, device_t child, struct bhnd_board_info *info) { struct bhndb_pci_softc *sc; sc = device_get_softc(dev); /* * On a subset of Apple BCM4360 modules, always prefer the * PCI subdevice to the SPROM-supplied boardtype. * * TODO: * * Broadcom's own drivers implement this override, and then later use * the remapped BCM4360 board type to determine the required * board-specific workarounds. * * Without access to this hardware, it's unclear why this mapping * is done, and we must do the same. If we can survey the hardware * in question, it may be possible to replace this behavior with * explicit references to the SPROM-supplied boardtype(s) in our * quirk definitions. */ if (pci_get_subvendor(sc->parent) == PCI_VENDOR_APPLE) { switch (info->board_type) { case BHND_BOARD_BCM94360X29C: case BHND_BOARD_BCM94360X29CP2: case BHND_BOARD_BCM94360X51: case BHND_BOARD_BCM94360X51P2: info->board_type = 0; /* allow override below */ break; default: break; } } /* If NVRAM did not supply vendor/type info, provide the PCI * subvendor/subdevice values. */ if (info->board_vendor == 0) info->board_vendor = pci_get_subvendor(sc->parent); if (info->board_type == 0) info->board_type = pci_get_subdevice(sc->parent); return (0); } /** - * 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 */ static bool bhndb_is_pcie_attached(device_t dev) { int reg; if (pci_find_cap(device_get_parent(dev), PCIY_EXPRESS, ®) == 0) return (true); return (false); } /** * Enable externally managed clocks, if required. * * Some PCI chipsets (BCM4306, possibly others) chips do not support * the idle low-power clock. Clocking must be bootstrapped at * attach/resume by directly adjusting GPIO registers exposed in the * PCI config space, and correspondingly, explicitly shutdown at * detach/suspend. * * @note This function may be safely called prior to device attach, (e.g. * from DEVICE_PROBE). * * @param dev The bhndb bridge device */ static int bhndb_enable_pci_clocks(device_t dev) { device_t pci_dev; uint32_t gpio_in, gpio_out, gpio_en; uint32_t gpio_flags; uint16_t pci_status; pci_dev = device_get_parent(dev); /* Only supported and required on PCI devices */ if (!bhndb_is_pcie_attached(dev)) return (0); /* Read state of XTAL pin */ gpio_in = pci_read_config(pci_dev, BHNDB_PCI_GPIO_IN, 4); if (gpio_in & BHNDB_PCI_GPIO_XTAL_ON) return (0); /* already enabled */ /* Fetch current config */ gpio_out = pci_read_config(pci_dev, BHNDB_PCI_GPIO_OUT, 4); gpio_en = pci_read_config(pci_dev, BHNDB_PCI_GPIO_OUTEN, 4); /* Set PLL_OFF/XTAL_ON pins to HIGH and enable both pins */ gpio_flags = (BHNDB_PCI_GPIO_PLL_OFF|BHNDB_PCI_GPIO_XTAL_ON); gpio_out |= gpio_flags; gpio_en |= gpio_flags; pci_write_config(pci_dev, BHNDB_PCI_GPIO_OUT, gpio_out, 4); pci_write_config(pci_dev, BHNDB_PCI_GPIO_OUTEN, gpio_en, 4); DELAY(1000); /* Reset PLL_OFF */ gpio_out &= ~BHNDB_PCI_GPIO_PLL_OFF; pci_write_config(pci_dev, BHNDB_PCI_GPIO_OUT, gpio_out, 4); DELAY(5000); /* Clear any PCI 'sent target-abort' flag. */ pci_status = pci_read_config(pci_dev, PCIR_STATUS, 2); pci_status &= ~PCIM_STATUS_STABORT; pci_write_config(pci_dev, PCIR_STATUS, pci_status, 2); return (0); } /** * Disable externally managed clocks, if required. * * This function may be safely called prior to device attach, (e.g. * from DEVICE_PROBE). * * @param dev The bhndb bridge device */ static int bhndb_disable_pci_clocks(device_t dev) { device_t pci_dev; uint32_t gpio_out, gpio_en; pci_dev = device_get_parent(dev); /* Only supported and required on PCI devices */ if (bhndb_is_pcie_attached(dev)) return (0); /* Fetch current config */ gpio_out = pci_read_config(pci_dev, BHNDB_PCI_GPIO_OUT, 4); gpio_en = pci_read_config(pci_dev, BHNDB_PCI_GPIO_OUTEN, 4); /* Set PLL_OFF to HIGH, XTAL_ON to LOW. */ gpio_out &= ~BHNDB_PCI_GPIO_XTAL_ON; gpio_out |= BHNDB_PCI_GPIO_PLL_OFF; pci_write_config(pci_dev, BHNDB_PCI_GPIO_OUT, gpio_out, 4); /* Enable both output pins */ gpio_en |= (BHNDB_PCI_GPIO_PLL_OFF|BHNDB_PCI_GPIO_XTAL_ON); pci_write_config(pci_dev, BHNDB_PCI_GPIO_OUTEN, gpio_en, 4); return (0); } static bhnd_clksrc bhndb_pci_pwrctl_get_clksrc(device_t dev, device_t child, bhnd_clock clock) { struct bhndb_pci_softc *sc; uint32_t gpio_out; sc = device_get_softc(dev); /* Only supported on PCI devices */ if (bhndb_is_pcie_attached(sc->dev)) return (ENODEV); /* Only ILP is supported */ if (clock != BHND_CLOCK_ILP) return (ENXIO); gpio_out = pci_read_config(sc->parent, BHNDB_PCI_GPIO_OUT, 4); if (gpio_out & BHNDB_PCI_GPIO_SCS) return (BHND_CLKSRC_PCI); else return (BHND_CLKSRC_XTAL); } static int bhndb_pci_pwrctl_gate_clock(device_t dev, device_t child, bhnd_clock clock) { struct bhndb_pci_softc *sc = device_get_softc(dev); /* Only supported on PCI devices */ if (bhndb_is_pcie_attached(sc->dev)) return (ENODEV); /* Only HT is supported */ if (clock != BHND_CLOCK_HT) return (ENXIO); return (bhndb_disable_pci_clocks(sc->dev)); } static int bhndb_pci_pwrctl_ungate_clock(device_t dev, device_t child, bhnd_clock clock) { struct bhndb_pci_softc *sc = device_get_softc(dev); /* Only supported on PCI devices */ if (bhndb_is_pcie_attached(sc->dev)) return (ENODEV); /* Only HT is supported */ if (clock != BHND_CLOCK_HT) return (ENXIO); 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); } /** * Initialize a new bhndb PCI bridge EROM I/O instance. This EROM I/O * implementation supports mapping of the device enumeration table via the * @p hr host resources. * * @param pio The instance to be initialized. * @param dev The bridge device. * @param pci_dev The bridge's parent PCI device. * @param hr The host resources to be used to map the device * enumeration table. */ static int bhndb_pci_eio_init(struct bhndb_pci_eio *pio, device_t dev, device_t pci_dev, struct bhndb_host_resources *hr) { memset(&pio->eio, sizeof(pio->eio), 0); pio->eio.map = bhndb_pci_eio_map; pio->eio.read = bhndb_pci_eio_read; pio->eio.fini = NULL; pio->dev = dev; pio->pci_dev = pci_dev; pio->hr = hr; pio->win = NULL; pio->res = NULL; return (0); } /** * Attempt to adjust the dynamic register window backing @p pio to permit * reading @p size bytes at @p addr. * * If @p addr or @p size fall outside the existing mapped range, or if * @p pio is not backed by a dynamic register window, ENXIO will be returned. * * @param pio The bhndb PCI erom I/O state to be modified. * @param addr The address to be include */ static int bhndb_pci_eio_adjust_mapping(struct bhndb_pci_eio *pio, bhnd_addr_t addr, bhnd_size_t size) { bhnd_addr_t target; bhnd_size_t offset; int error; KASSERT(pio->win != NULL, ("missing register window")); KASSERT(pio->res != NULL, ("missing regwin resource")); KASSERT(pio->win->win_type == BHNDB_REGWIN_T_DYN, ("unexpected window type %d", pio->win->win_type)); /* The requested subrange must fall within the total mapped range */ if (addr < pio->addr || (addr - pio->addr) > pio->size || size > pio->size || (addr - pio->addr) - pio->size < size) { return (ENXIO); } /* Do we already have a useable mapping? */ if (addr >= pio->res_target && addr <= pio->res_target + pio->win->win_size && (pio->res_target + pio->win->win_size) - addr >= size) { return (0); } /* Page-align the target address */ offset = addr % pio->win->win_size; target = addr - offset; /* Configure the register window */ error = bhndb_pci_compat_setregwin(pio->dev, pio->pci_dev, pio->win, target); if (error) { device_printf(pio->dev, "failed to configure dynamic register " "window: %d\n", error); return (error); } pio->res_target = target; return (0); } /* bhnd_erom_io_map() implementation */ static int bhndb_pci_eio_map(struct bhnd_erom_io *eio, bhnd_addr_t addr, bhnd_size_t size) { struct bhndb_pci_eio *pio; const struct bhndb_regwin *regwin; struct resource *r; bhnd_addr_t target; bhnd_size_t offset; int error; pio = (struct bhndb_pci_eio *)eio; /* Locate a useable dynamic register window */ regwin = bhndb_regwin_find_type(pio->hr->cfg->register_windows, BHNDB_REGWIN_T_DYN, MIN(size, BHND_DEFAULT_CORE_SIZE)); if (regwin == NULL) { device_printf(pio->dev, "unable to map %#jx+%#jx; no " "usable dynamic register window found\n", addr, size); return (ENXIO); } /* Locate the host resource mapping our register window */ if ((r = bhndb_host_resource_for_regwin(pio->hr, regwin)) == NULL) { device_printf(pio->dev, "unable to map %#jx+%#jx; no " "usable register resource found\n", addr, size); return (ENXIO); } /* Page-align the target address */ offset = addr % regwin->win_size; target = addr - offset; /* Configure the register window */ error = bhndb_pci_compat_setregwin(pio->dev, pio->pci_dev, regwin, target); if (error) { device_printf(pio->dev, "failed to configure dynamic register " "window: %d\n", error); return (error); } /* Update our mapping state */ pio->win = regwin; pio->res = r; pio->addr = addr; pio->size = size; pio->res_target = target; return (0); } /* bhnd_erom_io_read() implementation */ static uint32_t bhndb_pci_eio_read(struct bhnd_erom_io *eio, bhnd_size_t offset, u_int width) { struct bhndb_pci_eio *pio; bhnd_addr_t addr; bus_size_t res_offset; int error; pio = (struct bhndb_pci_eio *)eio; /* Calculate absolute address */ if (BHND_SIZE_MAX - offset < pio->addr) { device_printf(pio->dev, "invalid offset %#jx+%#jx\n", pio->addr, offset); return (UINT32_MAX); } addr = pio->addr + offset; /* Adjust the mapping for our read */ if ((error = bhndb_pci_eio_adjust_mapping(pio, addr, width))) { device_printf(pio->dev, "failed to adjust register mapping: " "%d\n", error); return (UINT32_MAX); } KASSERT(pio->res_target <= addr, ("invalid mapping (%#jx vs. %#jx)", pio->res_target, addr)); /* Determine the actual read offset within our register window * resource */ res_offset = (addr - pio->res_target) + pio->win->win_offset; /* Perform our read */ switch (width) { case 1: return (bus_read_1(pio->res, res_offset)); case 2: return (bus_read_2(pio->res, res_offset)); case 4: return (bus_read_4(pio->res, res_offset)); default: panic("unsupported width: %u", width); } } static device_method_t bhndb_pci_methods[] = { /* Device interface */ DEVMETHOD(device_probe, bhndb_pci_probe), DEVMETHOD(device_attach, bhndb_pci_attach), DEVMETHOD(device_resume, bhndb_pci_resume), DEVMETHOD(device_suspend, bhndb_pci_suspend), 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), /* 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 }; DEFINE_CLASS_1(bhndb, bhndb_pci_driver, bhndb_pci_methods, sizeof(struct bhndb_pci_softc), bhndb_driver); MODULE_VERSION(bhndb_pci, 1); MODULE_DEPEND(bhndb_pci, bhnd_pci_hostb, 1, 1, 1); MODULE_DEPEND(bhndb_pci, pci, 1, 1, 1); MODULE_DEPEND(bhndb_pci, bhndb, 1, 1, 1); MODULE_DEPEND(bhndb_pci, bhnd, 1, 1, 1); Index: head/sys/dev/bhnd/bhndb/bhndb_pcireg.h =================================================================== --- head/sys/dev/bhnd/bhndb/bhndb_pcireg.h (revision 326078) +++ head/sys/dev/bhnd/bhndb/bhndb_pcireg.h (revision 326079) @@ -1,210 +1,211 @@ /*- * Copyright (c) 2015 Landon Fuller * Copyright (c) 2010 Broadcom Corporation * * Portions of this file were derived from the bcmdevs.h header contributed by * Broadcom to Android's bcmdhd driver module, and the pcicfg.h header * distributed with Broadcom's initial brcm80211 Linux driver release. * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * * $FreeBSD$ */ #ifndef _BHND_BHNDB_PCIREG_H_ #define _BHND_BHNDB_PCIREG_H_ /* * Common PCI/PCIE Bridge Configuration Registers. * * = MAJOR CORE REVISIONS = * * There have been four revisions to the BAR0 memory mappings used * in BHND PCI/PCIE bridge cores: * * == PCI_V0 == * Applies to: * - PCI (cid=0x804, revision <= 12) * BAR0 size: 8KB * Address Map: * [offset+ size] type description * [0x0000+0x1000] dynamic mapped backplane address space (window 0). * [0x1000+0x0800] fixed SPROM shadow * [0x1800+0x0800] fixed pci core registers * * == PCI_V1 == * Applies to: * - PCI (cid=0x804, revision >= 13) * - PCIE (cid=0x820) with ChipCommon (revision <= 31) * BAR0 size: 16KB * Address Map: * [offset+ size] type description * [0x0000+0x1000] dynamic mapped backplane address space (window 0). * [0x1000+0x1000] fixed SPROM shadow * [0x2000+0x1000] fixed pci/pcie core registers * [0x3000+0x1000] fixed chipcommon core registers * * == PCI_V2 == * Applies to: * - PCIE (cid=0x820) with ChipCommon (revision >= 32) * BAR0 size: 16KB * Address Map: * [offset+ size] type description * [0x0000+0x1000] dynamic mapped backplane address space (window 0). * [0x1000+0x1000] dynamic mapped backplane address space (window 1). * [0x2000+0x1000] fixed pci/pcie core registers * [0x3000+0x1000] fixed chipcommon core registers * * == PCI_V3 == * Applies to: * - PCIE Gen 2 (cid=0x83c) * BAR0 size: 32KB * Address Map: * [offset+ size] type description * [0x0000+0x1000] dynamic mapped backplane address space (window 0). * [0x1000+0x1000] dynamic mapped backplane address space (window 1). * [0x2000+0x1000] fixed pci/pcie core registers * [0x3000+0x1000] fixed chipcommon core registers * [???] * BAR1 size: varies * Address Map: * [offset+ size] type description * [0x0000+0x????] fixed ARM tightly-coupled memory (TCM). * While fullmac chipsets provided a fixed * 4KB mapping, newer devices will vary. * * = MINOR CORE REVISIONS = * * == PCI Cores Revision >= 3 == * - Mapped GPIO CSRs into the PCI config space. Refer to * BHND_PCI_GPIO_*. * * == PCI/PCIE Cores Revision >= 14 == * - Mapped the clock CSR into the PCI config space. Refer to * BHND_PCI_CLK_CTL_ST */ /* Common PCI/PCIE Config Registers */ #define BHNDB_PCI_SPROM_CONTROL 0x88 /* sprom property control */ #define BHNDB_PCI_BAR1_CONTROL 0x8c /* BAR1 region prefetch/burst control */ #define BHNDB_PCI_INT_STATUS 0x90 /* PCI and other cores interrupts */ #define BHNDB_PCI_INT_MASK 0x94 /* mask of PCI and other cores interrupts */ #define BHNDB_PCI_TO_SB_MB 0x98 /* signal backplane interrupts */ #define BHNDB_PCI_BACKPLANE_ADDR 0xa0 /* address an arbitrary location on the system backplane */ #define BHNDB_PCI_BACKPLANE_DATA 0xa4 /* data at the location specified by above address */ /* PCI (non-PCIe) GPIO/Clock Config Registers */ #define BHNDB_PCI_CLK_CTL 0xa8 /* clock control/status (pci >=rev14) */ #define BHNDB_PCI_GPIO_IN 0xb0 /* gpio input (pci >=rev3) */ #define BHNDB_PCI_GPIO_OUT 0xb4 /* gpio output (pci >=rev3) */ #define BHNDB_PCI_GPIO_OUTEN 0xb8 /* gpio output enable (pci >=rev3) */ /* Hardware revisions used to determine PCI revision */ #define BHNDB_PCI_V0_MAX_PCI_HWREV 12 #define BHNDB_PCI_V1_MIN_PCI_HWREV 13 #define BHNDB_PCI_V1_MAX_CHIPC_HWREV 31 #define BHNDB_PCI_V2_MIN_CHIPC_HWREV 32 /** * Number of times to retry writing to a PCI window address register. * * On siba(4) devices, it's possible that writing a PCI window register may * not succeed; it's necessary to immediately read the configuration register * and retry if not set to the desired value. */ #define BHNDB_PCI_BARCTRL_WRITE_RETRY 50 /* PCI_V0 */ #define BHNDB_PCI_V0_BAR0_WIN0_CONTROL 0x80 /* backplane address space accessed by BAR0/WIN0 */ #define BHNDB_PCI_V0_BAR1_WIN0_CONTROL 0x84 /* backplane address space accessed by BAR1/WIN0. */ #define BHNDB_PCI_V0_BAR0_SIZE 0x2000 /* 8KB BAR0 */ #define BHNDB_PCI_V0_BAR0_WIN0_OFFSET 0x0 /* bar0 + 0x0 accesses configurable 4K region of backplane address space */ #define BHNDB_PCI_V0_BAR0_WIN0_SIZE 0x1000 #define BHNDB_PCI_V0_BAR0_SPROM_OFFSET 0x1000 /* bar0 + 4K accesses sprom shadow (in pci core) */ #define BHNDB_PCI_V0_BAR0_SPROM_SIZE 0x0800 #define BHNDB_PCI_V0_BAR0_PCIREG_OFFSET 0x1800 /* bar0 + 6K accesses pci core registers */ #define BHNDB_PCI_V0_BAR0_PCIREG_SIZE 0x0800 /* PCI_V1 */ #define BHNDB_PCI_V1_BAR0_WIN0_CONTROL 0x80 /* backplane address space accessed by BAR0/WIN0 */ #define BHNDB_PCI_V1_BAR1_WIN0_CONTROL 0x84 /* backplane address space accessed by BAR1/WIN0. */ #define BHNDB_PCI_V1_BAR0_SIZE 0x4000 /* 16KB BAR0 */ #define BHNDB_PCI_V1_BAR0_WIN0_OFFSET 0x0 /* bar0 + 0x0 accesses configurable 4K region of backplane address space */ #define BHNDB_PCI_V1_BAR0_WIN0_SIZE 0x1000 #define BHNDB_PCI_V1_BAR0_SPROM_OFFSET 0x1000 /* bar0 + 4K accesses sprom shadow (in pci core) */ #define BHNDB_PCI_V1_BAR0_SPROM_SIZE 0x1000 #define BHNDB_PCI_V1_BAR0_PCIREG_OFFSET 0x2000 /* bar0 + 8K accesses pci/pcie core registers */ #define BHNDB_PCI_V1_BAR0_PCIREG_SIZE 0x1000 #define BHNDB_PCI_V1_BAR0_CCREGS_OFFSET 0x3000 /* bar0 + 12K accesses chipc core registers */ #define BHNDB_PCI_V1_BAR0_CCREGS_SIZE 0x1000 /* PCI_V2 */ #define BHNDB_PCI_V2_BAR0_WIN0_CONTROL 0x80 /* backplane address space accessed by BAR0/WIN0 */ #define BHNDB_PCI_V2_BAR1_WIN0_CONTROL 0x84 /* backplane address space accessed by BAR1/WIN0. */ #define BHNDB_PCI_V2_BAR0_WIN1_CONTROL 0xAC /* backplane address space accessed by BAR0/WIN1 */ #define BHNDB_PCI_V2_BAR0_SIZE 0x4000 /* 16KB BAR0 */ #define BHNDB_PCI_V2_BAR0_WIN0_OFFSET 0x0 /* bar0 + 0x0 accesses configurable 4K region of backplane address space */ #define BHNDB_PCI_V2_BAR0_WIN0_SIZE 0x1000 #define BHNDB_PCI_V2_BAR0_WIN1_OFFSET 0x1000 /* bar0 + 4K accesses second 4K window */ #define BHNDB_PCI_V2_BAR0_WIN1_SIZE 0x1000 #define BHNDB_PCI_V2_BAR0_PCIREG_OFFSET 0x2000 /* bar0 + 8K accesses pci/pcie core registers */ #define BHNDB_PCI_V2_BAR0_PCIREG_SIZE 0x1000 #define BHNDB_PCI_V2_BAR0_CCREGS_OFFSET 0x3000 /* bar0 + 12K accesses chipc core registers */ #define BHNDB_PCI_V2_BAR0_CCREGS_SIZE 0x1000 /* PCI_V3 (PCIe-G2) */ #define BHNDB_PCI_V3_BAR0_WIN0_CONTROL 0x80 /* backplane address space accessed by BAR0/WIN0 */ #define BHNDB_PCI_V3_BAR0_WIN1_CONTROL 0x70 /* backplane address space accessed by BAR0/WIN1 */ #define BHNDB_PCI_V3_BAR0_SIZE 0x8000 /* 32KB BAR0 */ #define BHNDB_PCI_V3_BAR0_WIN0_OFFSET 0x0 /* bar0 + 0x0 accesses configurable 4K region of backplane address space */ #define BHNDB_PCI_V3_BAR0_WIN0_SIZE 0x1000 #define BHNDB_PCI_V3_BAR0_WIN1_OFFSET 0x1000 /* bar0 + 4K accesses second 4K window */ #define BHNDB_PCI_V3_BAR0_WIN1_SIZE 0x1000 #define BHNDB_PCI_V3_BAR0_PCIREG_OFFSET 0x2000 /* bar0 + 8K accesses pci/pcie core registers */ #define BHNDB_PCI_V3_BAR0_PCIREG_SIZE 0x1000 #define BHNDB_PCI_V3_BAR0_CCREGS_OFFSET 0x3000 /* bar0 + 12K accesses chipc core registers */ #define BHNDB_PCI_V3_BAR0_CCREGS_SIZE 0x1000 /* BHNDB_PCI_INT_STATUS */ #define BHNDB_PCI_SBIM_STATUS_SERR 0x4 /* backplane SBErr interrupt status */ /* 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 */ /* BHNDB_PCI_SPROM_CONTROL */ #define BHNDB_PCI_SPROM_SZ_MASK 0x03 /**< sprom size mask */ #define BHNDB_PCI_SPROM_SZ_1KB 0x00 /**< 1KB sprom size */ #define BHNDB_PCI_SPROM_SZ_4KB 0x01 /**< 4KB sprom size */ #define BHNDB_PCI_SPROM_SZ_16KB 0x02 /**< 16KB sprom size */ #define BHNDB_PCI_SPROM_SZ_RESERVED 0x03 /**< unsupported sprom size */ #define BHNDB_PCI_SPROM_LOCKED 0x08 /**< sprom locked */ #define BHNDB_PCI_SPROM_BLANK 0x04 /**< sprom blank */ #define BHNDB_PCI_SPROM_WRITEEN 0x10 /**< sprom write enable */ #define BHNDB_PCI_SPROM_BOOTROM_WE 0x20 /**< external bootrom write enable */ #define BHNDB_PCI_SPROM_BACKPLANE_EN 0x40 /**< enable indirect backplane access (BHNDB_PCI_BACKPLANE_*) */ #define BHNDB_PCI_SPROM_OTPIN_USE 0x80 /**< device OTP in use */ /* PCI (non-PCIe) BHNDB_PCI_GPIO_OUTEN */ #define BHNDB_PCI_GPIO_SCS 0x10 /* PCI config space bit 4 for 4306c0 slow clock source */ #define BHNDB_PCI_GPIO_HWRAD_OFF 0x20 /* PCI config space GPIO 13 for hw radio disable */ #define BHNDB_PCI_GPIO_XTAL_ON 0x40 /* PCI config space GPIO 14 for Xtal power-up */ #define BHNDB_PCI_GPIO_PLL_OFF 0x80 /* PCI config space GPIO 15 for PLL power-down */ #endif /* _BHND_BHNDB_PCIREG_H_ */ Index: head/sys/dev/bhnd/bhndb/bhndb_pcivar.h =================================================================== --- head/sys/dev/bhnd/bhndb/bhndb_pcivar.h (revision 326078) +++ head/sys/dev/bhnd/bhndb/bhndb_pcivar.h (revision 326079) @@ -1,67 +1,133 @@ /*- - * 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: * 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 _BHND_BHNDB_PCIVAR_H_ #define _BHND_BHNDB_PCIVAR_H_ #include "bhndbvar.h" /* * bhndb(4) PCI driver subclass. */ DECLARE_CLASS(bhndb_pci_driver); struct bhndb_pci_softc; /* * An interconnect-specific function implementing BHNDB_SET_WINDOW_ADDR */ 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 (revision 326078) +++ head/sys/dev/bhnd/bhndb/bhndb_private.h (revision 326079) @@ -1,223 +1,260 @@ /*- * 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: * 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 _BHND_BHNDB_PRIVATE_H_ #define _BHND_BHNDB_PRIVATE_H_ #include #include #include #include #include #include #include #include "bhndbvar.h" /* * Private bhndb(4) driver definitions. */ struct bhndb_dw_alloc; +struct bhndb_intr_handler; struct bhndb_region; struct bhndb_resources; struct bhndb_resources *bhndb_alloc_resources(device_t dev, device_t parent_dev, const struct bhndb_hwcfg *cfg); void bhndb_free_resources( struct bhndb_resources *br); int bhndb_add_resource_region( struct bhndb_resources *br, bhnd_addr_t addr, bhnd_size_t size, bhndb_priority_t priority, 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); struct bhndb_dw_alloc *bhndb_dw_find_resource( struct bhndb_resources *dr, struct resource *r); struct bhndb_dw_alloc *bhndb_dw_find_mapping( struct bhndb_resources *br, bhnd_addr_t addr, bhnd_size_t size); int bhndb_dw_retain( struct bhndb_resources *br, struct bhndb_dw_alloc *dwa, struct resource *res); void bhndb_dw_release( struct bhndb_resources *br, struct bhndb_dw_alloc *dwa, struct resource *res); int bhndb_dw_set_addr(device_t dev, struct bhndb_resources *br, struct bhndb_dw_alloc *dwa, bus_addr_t addr, bus_size_t size); const struct bhndb_hw_priority *bhndb_hw_priority_find_core( const struct bhndb_hw_priority *table, struct bhnd_core_info *core); /** * Dynamic register window allocation reference. */ struct bhndb_dw_rentry { struct resource *dw_res; /**< child resource */ LIST_ENTRY(bhndb_dw_rentry) dw_link; }; /** * A dynamic register window allocation record. */ struct bhndb_dw_alloc { const struct bhndb_regwin *win; /**< window definition */ struct resource *parent_res; /**< enclosing resource */ u_int rnid; /**< region identifier */ rman_res_t target; /**< the current window address, or 0x0 if unknown */ LIST_HEAD(, bhndb_dw_rentry) refs; /**< references */ }; /** * A bus address region description. */ struct bhndb_region { bhnd_addr_t addr; /**< start of mapped range */ bhnd_size_t size; /**< size of mapped range */ bhndb_priority_t priority; /**< direct resource allocation priority */ const struct bhndb_regwin *static_regwin; /**< fixed mapping regwin, if any */ STAILQ_ENTRY(bhndb_region) link; }; /** + * 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 { device_t dev; /**< bridge device */ const struct bhndb_hwcfg *cfg; /**< hardware configuration */ struct bhndb_host_resources *res; /**< host resources, or NULL if not allocated */ 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 */ struct bhndb_dw_alloc *dw_alloc; /**< dynamic window allocation records */ size_t dwa_count; /**< number of dynamic windows available. */ 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 */ }; /** * Returns true if the all dynamic windows are marked free, false * otherwise. * * @param br The resource state to check. */ static inline bool bhndb_dw_all_free(struct bhndb_resources *br) { int bit; bit_ffs(br->dwa_freelist, br->dwa_count, &bit); return (bit == -1); } /** * Find the next free dynamic window region in @p br. * * @param br The resource state to search. */ static inline struct bhndb_dw_alloc * bhndb_dw_next_free(struct bhndb_resources *br) { struct bhndb_dw_alloc *dw_free; int bit; bit_ffc(br->dwa_freelist, br->dwa_count, &bit); if (bit == -1) return (NULL); dw_free = &br->dw_alloc[bit]; KASSERT(LIST_EMPTY(&dw_free->refs), ("free list out of sync with refs")); return (dw_free); } /** * Returns true if a dynamic window allocation is marked as free. * * @param br The resource state owning @p dwa. * @param dwa The dynamic window allocation record to be checked. */ static inline bool bhndb_dw_is_free(struct bhndb_resources *br, struct bhndb_dw_alloc *dwa) { bool is_free = LIST_EMPTY(&dwa->refs); KASSERT(is_free == !bit_test(br->dwa_freelist, dwa->rnid), ("refs out of sync with free list")); return (is_free); } #define BHNDB_LOCK_INIT(sc) \ mtx_init(&(sc)->sc_mtx, device_get_nameunit((sc)->dev), \ "bhndb resource allocator lock", MTX_DEF) #define BHNDB_LOCK(sc) mtx_lock(&(sc)->sc_mtx) #define BHNDB_UNLOCK(sc) mtx_unlock(&(sc)->sc_mtx) #define BHNDB_LOCK_ASSERT(sc, what) mtx_assert(&(sc)->sc_mtx, what) #define BHNDB_LOCK_DESTROY(sc) mtx_destroy(&(sc)->sc_mtx) #endif /* _BHND_BHNDB_PRIVATE_H_ */ Index: head/sys/dev/bhnd/bhndb/bhndb_subr.c =================================================================== --- head/sys/dev/bhnd/bhndb/bhndb_subr.c (revision 326078) +++ head/sys/dev/bhnd/bhndb/bhndb_subr.c (revision 326079) @@ -1,1164 +1,1379 @@ /*- * 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: * 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. */ #include __FBSDID("$FreeBSD$"); #include #include #include #include "bhndb_private.h" #include "bhndbvar.h" /** * Attach a BHND bridge device to @p parent. * * @param parent A parent PCI device. * @param[out] bhndb On success, the probed and attached bhndb bridge device. * @param unit The device unit number, or -1 to select the next available unit * number. * * @retval 0 success * @retval non-zero Failed to attach the bhndb device. */ int bhndb_attach_bridge(device_t parent, device_t *bhndb, int unit) { int error; *bhndb = device_add_child(parent, "bhndb", unit); if (*bhndb == NULL) return (ENXIO); if (!(error = device_probe_and_attach(*bhndb))) return (0); if ((device_delete_child(parent, *bhndb))) device_printf(parent, "failed to detach bhndb child\n"); return (error); } /* * Call BHNDB_SUSPEND_RESOURCE() for all resources in @p rl. */ static void bhndb_do_suspend_resources(device_t dev, struct resource_list *rl) { struct resource_list_entry *rle; /* Suspend all child resources. */ STAILQ_FOREACH(rle, rl, link) { /* Skip non-allocated resources */ if (rle->res == NULL) continue; BHNDB_SUSPEND_RESOURCE(device_get_parent(dev), dev, rle->type, rle->res); } } /** * Helper function for implementing BUS_RESUME_CHILD() on bridged * bhnd(4) buses. * * This implementation of BUS_RESUME_CHILD() uses BUS_GET_RESOURCE_LIST() * to find the child's resources and call BHNDB_SUSPEND_RESOURCE() for all * child resources, ensuring that the device's allocated bridge resources * will be available to other devices during bus resumption. * * Before suspending any resources, @p child is suspended by * calling bhnd_generic_suspend_child(). * * If @p child is not a direct child of @p dev, suspension is delegated to * the @p dev parent. */ int bhnd_generic_br_suspend_child(device_t dev, device_t child) { struct resource_list *rl; int error; if (device_get_parent(child) != dev) BUS_SUSPEND_CHILD(device_get_parent(dev), child); if (device_is_suspended(child)) return (EBUSY); /* Suspend the child device */ if ((error = bhnd_generic_suspend_child(dev, child))) return (error); /* Fetch the resource list. If none, there's nothing else to do */ rl = BUS_GET_RESOURCE_LIST(device_get_parent(child), child); if (rl == NULL) return (0); /* Suspend all child resources. */ bhndb_do_suspend_resources(dev, rl); return (0); } /** * Helper function for implementing BUS_RESUME_CHILD() on bridged * bhnd(4) bus devices. * * This implementation of BUS_RESUME_CHILD() uses BUS_GET_RESOURCE_LIST() * to find the child's resources and call BHNDB_RESUME_RESOURCE() for all * child resources, before delegating to bhnd_generic_resume_child(). * * If resource resumption fails, @p child will not be resumed. * * If @p child is not a direct child of @p dev, suspension is delegated to * the @p dev parent. */ int bhnd_generic_br_resume_child(device_t dev, device_t child) { struct resource_list *rl; struct resource_list_entry *rle; int error; if (device_get_parent(child) != dev) BUS_RESUME_CHILD(device_get_parent(dev), child); if (!device_is_suspended(child)) return (EBUSY); /* Fetch the resource list. If none, there's nothing else to do */ rl = BUS_GET_RESOURCE_LIST(device_get_parent(child), child); if (rl == NULL) return (bhnd_generic_resume_child(dev, child)); /* Resume all resources */ STAILQ_FOREACH(rle, rl, link) { /* Skip non-allocated resources */ if (rle->res == NULL) continue; error = BHNDB_RESUME_RESOURCE(device_get_parent(dev), dev, rle->type, rle->res); if (error) { /* Put all resources back into a suspend state */ bhndb_do_suspend_resources(dev, rl); return (error); } } /* Now that all resources are resumed, resume child */ if ((error = bhnd_generic_resume_child(dev, child))) { /* Put all resources back into a suspend state */ bhndb_do_suspend_resources(dev, rl); } return (error); } /** * Find a host resource of @p type that maps the given range. * * @param hr The resource state to search. * @param type The resource type to search for (see SYS_RES_*). * @param start The start address of the range to search for. * @param count The size of the range to search for. * * @retval resource the host resource containing the requested range. * @retval NULL if no resource containing the requested range can be found. */ struct resource * bhndb_host_resource_for_range(struct bhndb_host_resources *hr, int type, rman_res_t start, rman_res_t count) { for (u_int i = 0; hr->resource_specs[i].type != -1; i++) { struct resource *r = hr->resources[i]; if (hr->resource_specs[i].type != type) continue; /* Verify range */ if (rman_get_start(r) > start) continue; if (rman_get_end(r) < (start + count - 1)) continue; return (r); } return (NULL); } /** * Find a host resource of that matches the given register window definition. * * @param hr The resource state to search. * @param win A register window definition. * * @retval resource the host resource corresponding to @p win. * @retval NULL if no resource corresponding to @p win can be found. */ struct resource * bhndb_host_resource_for_regwin(struct bhndb_host_resources *hr, const struct bhndb_regwin *win) { const struct resource_spec *rspecs; rspecs = hr->resource_specs; for (u_int i = 0; rspecs[i].type != -1; i++) { if (win->res.type != rspecs[i].type) continue; if (win->res.rid != rspecs[i].rid) continue; /* Found declared resource */ return (hr->resources[i]); } device_printf(hr->owner, "missing regwin resource spec " "(type=%d, rid=%d)\n", win->res.type, win->res.rid); return (NULL); } /** * Allocate and initialize a new resource state structure. * * @param dev The bridge device. * @param parent_dev The parent device from which host resources should be * allocated. * @param cfg The hardware configuration to be used. */ struct bhndb_resources * bhndb_alloc_resources(device_t dev, device_t parent_dev, const struct bhndb_hwcfg *cfg) { struct bhndb_resources *r; const struct bhndb_regwin *win; 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) return (NULL); /* Basic initialization */ r->dev = dev; r->cfg = cfg; 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; r->ht_mem_rman.rm_end = ~0; r->ht_mem_rman.rm_type = RMAN_ARRAY; r->ht_mem_rman.rm_descr = "BHNDB host memory"; if ((error = rman_init(&r->ht_mem_rman))) { device_printf(r->dev, "could not initialize ht_mem_rman\n"); goto failed; } free_ht_mem = true; /* Initialize resource manager for the bridged address space. */ r->br_mem_rman.rm_start = 0; r->br_mem_rman.rm_end = BUS_SPACE_MAXADDR_32BIT; r->br_mem_rman.rm_type = RMAN_ARRAY; r->br_mem_rman.rm_descr = "BHNDB bridged memory"; if ((error = rman_init(&r->br_mem_rman))) { device_printf(r->dev, "could not initialize br_mem_rman\n"); goto failed; } free_br_mem = true; error = rman_manage_region(&r->br_mem_rman, 0, BUS_SPACE_MAXADDR_32BIT); if (error) { device_printf(r->dev, "could not configure br_mem_rman\n"); 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, BHNDB_REGWIN_T_DYN); if (r->dwa_count >= INT_MAX) { device_printf(r->dev, "max dynamic regwin count exceeded\n"); goto failed; } /* Allocate the dynamic window allocation table. */ r->dw_alloc = malloc(sizeof(r->dw_alloc[0]) * r->dwa_count, M_BHND, M_NOWAIT); if (r->dw_alloc == NULL) goto failed; /* Allocate the dynamic window allocation freelist */ r->dwa_freelist = bit_alloc(r->dwa_count, M_BHND, M_NOWAIT); if (r->dwa_freelist == NULL) goto failed; /* Initialize the dynamic window table */ rnid = 0; last_window_size = 0; for (win = cfg->register_windows; win->win_type != BHNDB_REGWIN_T_INVALID; win++) { struct bhndb_dw_alloc *dwa; /* Skip non-DYN windows */ if (win->win_type != BHNDB_REGWIN_T_DYN) continue; /* Validate the window size */ if (win->win_size == 0) { device_printf(r->dev, "ignoring zero-length dynamic " "register window\n"); continue; } else if (last_window_size == 0) { last_window_size = win->win_size; } else if (last_window_size != win->win_size) { /* * No existing hardware should trigger this. * * If you run into this in the future, the dynamic * window allocator and the resource priority system * will need to be extended to support multiple register * window allocation pools. */ device_printf(r->dev, "devices that vend multiple " "dynamic register window sizes are not currently " "supported\n"); goto failed; } dwa = &r->dw_alloc[rnid]; dwa->win = win; dwa->parent_res = NULL; dwa->rnid = rnid; dwa->target = 0x0; LIST_INIT(&dwa->refs); rnid++; } /* Allocate host resources */ error = bhndb_alloc_host_resources(parent_dev, r->cfg, &r->res); if (error) { device_printf(r->dev, "could not allocate host resources on %s: %d\n", device_get_nameunit(parent_dev), error); goto failed; } /* Populate (and validate) parent resource references for all * dynamic windows */ for (size_t i = 0; i < r->dwa_count; i++) { struct bhndb_dw_alloc *dwa; const struct bhndb_regwin *win; dwa = &r->dw_alloc[i]; win = dwa->win; /* Find and validate corresponding resource. */ dwa->parent_res = bhndb_host_resource_for_regwin(r->res, win); if (dwa->parent_res == NULL) { device_printf(r->dev, "no host resource found for %u " "register window with offset %#jx and " "size %#jx\n", win->win_type, (uintmax_t)win->win_offset, (uintmax_t)win->win_size); error = ENXIO; goto failed; } if (rman_get_size(dwa->parent_res) < win->win_offset + win->win_size) { device_printf(r->dev, "resource %d too small for " "register window with offset %llx and size %llx\n", rman_get_rid(dwa->parent_res), (unsigned long long) win->win_offset, (unsigned long long) win->win_size); error = EINVAL; goto failed; } } /* Add allocated memory resources to our host memory resource manager */ for (u_int i = 0; r->res->resource_specs[i].type != -1; i++) { struct resource *res; /* skip non-memory resources */ if (r->res->resource_specs[i].type != SYS_RES_MEMORY) continue; /* add host resource to set of managed regions */ res = r->res->resources[i]; error = rman_manage_region(&r->ht_mem_rman, rman_get_start(res), rman_get_end(res)); if (error) { device_printf(r->dev, "could not register host memory region with " "ht_mem_rman: %d\n", error); goto failed; } } return (r); failed: if (free_ht_mem) rman_fini(&r->ht_mem_rman); 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); if (r->dwa_freelist != NULL) free(r->dwa_freelist, M_BHND); if (r->res != NULL) bhndb_release_host_resources(r->res); free(r, M_BHND); return (NULL); } /** * Deallocate the given bridge resource structure and any associated resources. * * @param br Resource state to be deallocated. */ 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++) { dwa = &br->dw_alloc[i]; /* Skip free dynamic windows */ if (bhndb_dw_is_free(br, dwa)) continue; 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); /* Clean up resource reservations */ for (size_t i = 0; i < br->dwa_count; i++) { dwa = &br->dw_alloc[i]; LIST_FOREACH_SAFE(dwr, &dwa->refs, dw_link, dwr_next) { LIST_REMOVE(dwr, dw_link); free(dwr, M_BHND); } } /* Release bus regions */ STAILQ_FOREACH_SAFE(region, &br->bus_regions, link, r_next) { STAILQ_REMOVE(&br->bus_regions, region, bhndb_region, link); free(region, M_BHND); } /* 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); } /** * Allocate host bus resources defined by @p hwcfg. * * On success, the caller assumes ownership of the allocated host resources, * which must be freed via bhndb_release_host_resources(). * * @param dev The device to be used when allocating resources * (e.g. via bus_alloc_resources()). * @param hwcfg The hardware configuration defining the host * resources to be allocated * @param[out] resources On success, the allocated host resources. */ int bhndb_alloc_host_resources(device_t dev, const struct bhndb_hwcfg *hwcfg, struct bhndb_host_resources **resources) { struct bhndb_host_resources *hr; size_t nres; int error; hr = malloc(sizeof(*hr), M_BHND, M_WAITOK); hr->owner = dev; hr->cfg = hwcfg; hr->resource_specs = NULL; hr->resources = NULL; /* Determine our bridge resource count from the hardware config. */ nres = 0; for (size_t i = 0; hwcfg->resource_specs[i].type != -1; i++) nres++; /* Allocate space for a non-const copy of our resource_spec * table; this will be updated with the RIDs assigned by * bus_alloc_resources. */ hr->resource_specs = malloc(sizeof(hr->resource_specs[0]) * (nres + 1), M_BHND, M_WAITOK); /* Initialize and terminate the table */ for (size_t i = 0; i < nres; i++) hr->resource_specs[i] = hwcfg->resource_specs[i]; hr->resource_specs[nres].type = -1; /* Allocate space for our resource references */ hr->resources = malloc(sizeof(hr->resources[0]) * nres, M_BHND, M_WAITOK); /* Allocate host resources */ error = bus_alloc_resources(hr->owner, hr->resource_specs, hr->resources); if (error) { device_printf(dev, "could not allocate bridge resources via " "%s: %d\n", device_get_nameunit(dev), error); goto failed; } *resources = hr; return (0); failed: if (hr->resource_specs != NULL) free(hr->resource_specs, M_BHND); if (hr->resources != NULL) free(hr->resources, M_BHND); free(hr, M_BHND); return (error); } /** * Deallocate a set of bridge host resources. * * @param hr The resources to be freed. */ void bhndb_release_host_resources(struct bhndb_host_resources *hr) { bus_release_resources(hr->owner, hr->resource_specs, hr->resources); free(hr->resources, M_BHND); free(hr->resource_specs, M_BHND); free(hr, M_BHND); } /** * Search @p cores for the core serving as the bhnd host bridge. * * This function uses a heuristic valid on all known PCI/PCIe/PCMCIA-bridged * bhnd(4) devices to determine the hostb core: * * - The core must have a Broadcom vendor ID. * - The core devclass must match the bridge type. * - The core must be the first device on the bus with the bridged device * class. * * @param cores The core table to search. * @param ncores The number of cores in @p cores. * @param bridge_devclass The expected device class of the bridge core. * @param[out] core If found, the matching host bridge core info. * * @retval 0 success * @retval ENOENT not found */ int bhndb_find_hostb_core(struct bhnd_core_info *cores, u_int ncores, bhnd_devclass_t bridge_devclass, struct bhnd_core_info *core) { struct bhnd_core_match md; struct bhnd_core_info *match; u_int match_core_idx; /* Set up a match descriptor for the required device class. */ md = (struct bhnd_core_match) { BHND_MATCH_CORE_CLASS(bridge_devclass), BHND_MATCH_CORE_UNIT(0) }; /* Find the matching core with the lowest core index */ match = NULL; match_core_idx = UINT_MAX; for (u_int i = 0; i < ncores; i++) { if (!bhnd_core_matches(&cores[i], &md)) continue; /* Lower core indices take precedence */ if (match != NULL && match_core_idx < match->core_idx) continue; match = &cores[i]; match_core_idx = match->core_idx; } if (match == NULL) return (ENOENT); *core = *match; return (0); } /** + * 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. * @param addr The base address of this region. * @param size The size of this region. * @param priority The resource priority to be assigned to allocations * made within this bus region. * @param static_regwin If available, a static register window mapping this * bus region entry. If not available, NULL. * * @retval 0 success * @retval non-zero if adding the bus region fails. */ int bhndb_add_resource_region(struct bhndb_resources *br, bhnd_addr_t addr, bhnd_size_t size, bhndb_priority_t priority, const struct bhndb_regwin *static_regwin) { struct bhndb_region *reg; /* Insert in the bus resource list */ reg = malloc(sizeof(*reg), M_BHND, M_NOWAIT); if (reg == NULL) return (ENOMEM); *reg = (struct bhndb_region) { .addr = addr, .size = size, .priority = priority, .static_regwin = static_regwin }; STAILQ_INSERT_HEAD(&br->bus_regions, reg, link); 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. * * @param br The resource state to search. * @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. */ struct bhndb_region * bhndb_find_resource_region(struct bhndb_resources *br, bhnd_addr_t addr, bhnd_size_t size) { struct bhndb_region *region; STAILQ_FOREACH(region, &br->bus_regions, link) { /* Request must fit within the region's mapping */ if (addr < region->addr) continue; if (addr + size > region->addr + region->size) continue; return (region); } /* Not found */ return (NULL); } /** * Find the entry matching @p r in @p dwa's references, if any. * * @param dwa The dynamic window allocation to search * @param r The resource to search for in @p dwa. */ static struct bhndb_dw_rentry * bhndb_dw_find_resource_entry(struct bhndb_dw_alloc *dwa, struct resource *r) { struct bhndb_dw_rentry *rentry; LIST_FOREACH(rentry, &dwa->refs, dw_link) { struct resource *dw_res = rentry->dw_res; /* Match dev/rid/addr/size */ if (rman_get_device(dw_res) != rman_get_device(r) || rman_get_rid(dw_res) != rman_get_rid(r) || rman_get_start(dw_res) != rman_get_start(r) || rman_get_size(dw_res) != rman_get_size(r)) { continue; } /* Matching allocation found */ return (rentry); } return (NULL); } /** * Find the dynamic region allocated for @p r, if any. * * @param br The resource state to search. * @param r The resource to search for. * * @retval bhndb_dw_alloc The allocation record for @p r. * @retval NULL if no dynamic window is allocated for @p r. */ struct bhndb_dw_alloc * bhndb_dw_find_resource(struct bhndb_resources *br, struct resource *r) { struct bhndb_dw_alloc *dwa; for (size_t i = 0; i < br->dwa_count; i++) { dwa = &br->dw_alloc[i]; /* Skip free dynamic windows */ if (bhndb_dw_is_free(br, dwa)) continue; /* Matching allocation found? */ if (bhndb_dw_find_resource_entry(dwa, r) != NULL) return (dwa); } return (NULL); } /** * Find an existing dynamic window mapping @p size bytes * at @p addr. The window may or may not be free. * * @param br The resource state to search. * @param addr The requested starting address. * @param size The requested size. * * @retval bhndb_dw_alloc A window allocation that fully contains the requested * range. * @retval NULL If no mapping region can be found. */ struct bhndb_dw_alloc * bhndb_dw_find_mapping(struct bhndb_resources *br, bhnd_addr_t addr, bhnd_size_t size) { struct bhndb_dw_alloc *dwr; const struct bhndb_regwin *win; /* Search for an existing dynamic mapping of this address range. */ for (size_t i = 0; i < br->dwa_count; i++) { dwr = &br->dw_alloc[i]; win = dwr->win; /* Verify the range */ if (addr < dwr->target) continue; if (addr + size > dwr->target + win->win_size) continue; /* Found a usable mapping */ return (dwr); } /* not found */ return (NULL); } /** * Retain a reference to @p dwa for use by @p res. * * @param br The resource state owning @p dwa. * @param dwa The allocation record to be retained. * @param res The resource that will own a reference to @p dwa. * * @retval 0 success * @retval ENOMEM Failed to allocate a new reference structure. */ int bhndb_dw_retain(struct bhndb_resources *br, struct bhndb_dw_alloc *dwa, struct resource *res) { struct bhndb_dw_rentry *rentry; KASSERT(bhndb_dw_find_resource_entry(dwa, res) == NULL, ("double-retain of dynamic window for same resource")); /* Insert a reference entry; we use M_NOWAIT to allow use from * within a non-sleepable lock */ rentry = malloc(sizeof(*rentry), M_BHND, M_NOWAIT); if (rentry == NULL) return (ENOMEM); rentry->dw_res = res; LIST_INSERT_HEAD(&dwa->refs, rentry, dw_link); /* Update the free list */ bit_set(br->dwa_freelist, dwa->rnid); return (0); } /** * Release a reference to @p dwa previously retained by @p res. If the * reference count of @p dwa reaches zero, it will be added to the * free list. * * @param br The resource state owning @p dwa. * @param dwa The allocation record to be released. * @param res The resource that currently owns a reference to @p dwa. */ void bhndb_dw_release(struct bhndb_resources *br, struct bhndb_dw_alloc *dwa, struct resource *r) { struct bhndb_dw_rentry *rentry; /* Find the rentry */ rentry = bhndb_dw_find_resource_entry(dwa, r); KASSERT(rentry != NULL, ("over release of resource entry")); LIST_REMOVE(rentry, dw_link); free(rentry, M_BHND); /* If this was the last reference, update the free list */ if (LIST_EMPTY(&dwa->refs)) bit_clear(br->dwa_freelist, dwa->rnid); } /** * Attempt to set (or reset) the target address of @p dwa to map @p size bytes * at @p addr. * * This will apply any necessary window alignment and verify that * the window is capable of mapping the requested range prior to modifying * therecord. * * @param dev The device on which to issue the BHNDB_SET_WINDOW_ADDR() request. * @param br The resource state owning @p dwa. * @param dwa The allocation record to be configured. * @param addr The address to be mapped via @p dwa. * @param size The number of bytes to be mapped at @p addr. * * @retval 0 success * @retval non-zero no usable register window available. */ int bhndb_dw_set_addr(device_t dev, struct bhndb_resources *br, struct bhndb_dw_alloc *dwa, bus_addr_t addr, bus_size_t size) { const struct bhndb_regwin *rw; bus_addr_t offset; int error; rw = dwa->win; KASSERT(bhndb_dw_is_free(br, dwa), ("attempting to set the target address on an in-use window")); /* Page-align the target address */ offset = addr % rw->win_size; dwa->target = addr - offset; /* Verify that the window is large enough for the full target */ if (rw->win_size - offset < size) return (ENOMEM); /* Update the window target */ error = BHNDB_SET_WINDOW_ADDR(dev, dwa->win, dwa->target); if (error) { dwa->target = 0x0; return (error); } return (0); } /** * Return the count of @p type register windows in @p table. * * @param table The table to search. * @param type The required window type, or BHNDB_REGWIN_T_INVALID to * count all register window types. */ size_t bhndb_regwin_count(const struct bhndb_regwin *table, bhndb_regwin_type_t type) { const struct bhndb_regwin *rw; size_t count; count = 0; for (rw = table; rw->win_type != BHNDB_REGWIN_T_INVALID; rw++) { if (type == BHNDB_REGWIN_T_INVALID || rw->win_type == type) count++; } return (count); } /** * Search @p table for the first window with the given @p type. * * @param table The table to search. * @param type The required window type. * @param min_size The minimum window size. * * @retval bhndb_regwin The first matching window. * @retval NULL If no window of the requested type could be found. */ const struct bhndb_regwin * bhndb_regwin_find_type(const struct bhndb_regwin *table, bhndb_regwin_type_t type, bus_size_t min_size) { const struct bhndb_regwin *rw; for (rw = table; rw->win_type != BHNDB_REGWIN_T_INVALID; rw++) { if (rw->win_type == type && rw->win_size >= min_size) return (rw); } return (NULL); } /** * Search @p windows for the first matching core window. * * @param table The table to search. * @param class The required core class. * @param unit The required core unit, or -1. * @param port_type The required port type. * @param port The required port. * @param region The required region. * * @retval bhndb_regwin The first matching window. * @retval NULL If no matching window was found. */ const struct bhndb_regwin * bhndb_regwin_find_core(const struct bhndb_regwin *table, bhnd_devclass_t class, int unit, bhnd_port_type port_type, u_int port, u_int region) { const struct bhndb_regwin *rw; for (rw = table; rw->win_type != BHNDB_REGWIN_T_INVALID; rw++) { if (rw->win_type != BHNDB_REGWIN_T_CORE) continue; if (rw->d.core.class != class) continue; if (unit != -1 && rw->d.core.unit != unit) continue; if (rw->d.core.port_type != port_type) continue; if (rw->d.core.port != port) continue; if (rw->d.core.region != region) continue; return (rw); } return (NULL); } /** * Search @p windows for the best available window of at least @p min_size. * * Search order: * - BHND_REGWIN_T_CORE * - BHND_REGWIN_T_DYN * * @param table The table to search. * @param class The required core class. * @param unit The required core unit, or -1. * @param port_type The required port type. * @param port The required port. * @param region The required region. * @param min_size The minimum window size. * * @retval bhndb_regwin The first matching window. * @retval NULL If no matching window was found. */ const struct bhndb_regwin * bhndb_regwin_find_best(const struct bhndb_regwin *table, bhnd_devclass_t class, int unit, bhnd_port_type port_type, u_int port, u_int region, bus_size_t min_size) { const struct bhndb_regwin *rw; /* Prefer a fixed core mapping */ rw = bhndb_regwin_find_core(table, class, unit, port_type, port, region); if (rw != NULL) return (rw); /* Fall back on a generic dynamic window */ return (bhndb_regwin_find_type(table, BHNDB_REGWIN_T_DYN, min_size)); } /** * Return true if @p regw defines a BHNDB_REGWIN_T_CORE register window * that matches against @p core. * * @param regw A register window to match against. * @param core The bhnd(4) core info to match against @p regw. */ bool bhndb_regwin_match_core(const struct bhndb_regwin *regw, struct bhnd_core_info *core) { /* Only core windows are supported */ if (regw->win_type != BHNDB_REGWIN_T_CORE) return (false); /* Device class must match */ if (bhnd_core_class(core) != regw->d.core.class) return (false); /* Device unit must match */ if (core->unit != regw->d.core.unit) return (false); /* Matches */ return (true); } /** * Search for a core resource priority descriptor in @p table that matches * @p core. * * @param table The table to search. * @param core The core to match against @p table. */ const struct bhndb_hw_priority * bhndb_hw_priority_find_core(const struct bhndb_hw_priority *table, struct bhnd_core_info *core) { const struct bhndb_hw_priority *hp; for (hp = table; hp->ports != NULL; hp++) { if (bhnd_core_matches(core, &hp->match)) return (hp); } /* not found */ return (NULL); } Index: head/sys/dev/bhnd/bhndb/bhndbvar.h =================================================================== --- head/sys/dev/bhnd/bhndb/bhndbvar.h (revision 326078) +++ head/sys/dev/bhnd/bhndb/bhndbvar.h (revision 326079) @@ -1,167 +1,185 @@ /*- * 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: * 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 _BHND_BHNDBVAR_H_ #define _BHND_BHNDBVAR_H_ #include #include #include #include #include #include #include #include #include "bhndb.h" #include "bhndb_if.h" /* * Definitions shared by bhndb(4) driver implementations. */ DECLARE_CLASS(bhndb_driver); +/* forward declarations */ +struct bhndb_intr_isrc; struct bhndb_resources; struct bhndb_host_resources; int bhndb_attach(device_t dev, struct bhnd_chipid *cid, struct bhnd_core_info *cores, u_int ncores, struct bhnd_core_info *bridge_core, bhnd_erom_class_t *erom_class); int bhndb_generic_probe(device_t dev); int bhndb_generic_detach(device_t dev); int bhndb_generic_suspend(device_t dev); int bhndb_generic_resume(device_t dev); int bhndb_generic_init_full_config(device_t dev, device_t child, const struct bhndb_hw_priority *hw_prio_table); int bhnd_generic_br_suspend_child(device_t dev, device_t child); int bhnd_generic_br_resume_child(device_t dev, device_t child); int bhndb_find_hostb_core( struct bhnd_core_info *cores, u_int ncores, 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); void bhndb_release_host_resources( struct bhndb_host_resources *resources); struct resource *bhndb_host_resource_for_range( struct bhndb_host_resources *resources, int type, rman_res_t start, rman_res_t count); struct resource *bhndb_host_resource_for_regwin( struct bhndb_host_resources *resources, const struct bhndb_regwin *win); size_t bhndb_regwin_count( const struct bhndb_regwin *table, bhndb_regwin_type_t type); const struct bhndb_regwin *bhndb_regwin_find_type( const struct bhndb_regwin *table, bhndb_regwin_type_t type, bus_size_t min_size); const struct bhndb_regwin *bhndb_regwin_find_core( const struct bhndb_regwin *table, bhnd_devclass_t class, int unit, bhnd_port_type port_type, u_int port, u_int region); const struct bhndb_regwin *bhndb_regwin_find_best( const struct bhndb_regwin *table, bhnd_devclass_t class, int unit, bhnd_port_type port_type, u_int port, u_int region, bus_size_t min_size); bool bhndb_regwin_match_core( const struct bhndb_regwin *regw, struct bhnd_core_info *core); /** * bhndb child address space. Children either operate in the bridged * SoC address space, or within the address space mapped to the host * device (e.g. the PCI BAR(s)). */ typedef enum { BHNDB_ADDRSPACE_BRIDGED, /**< bridged (SoC) address space */ BHNDB_ADDRSPACE_NATIVE /**< host address space */ } bhndb_addrspace; /** bhndb child instance state */ struct bhndb_devinfo { bhndb_addrspace addrspace; /**< child address space. */ struct resource_list resources; /**< child resources. */ }; /** + * 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 { device_t owner; /**< device owning the allocated resources */ const struct bhndb_hwcfg *cfg; /**< bridge hardware configuration */ struct resource_spec *resource_specs; /**< resource specification table */ struct resource **resources; /**< allocated resource table */ }; /** * bhndb driver instance state. Must be first member of all subclass * softc structures. */ struct bhndb_softc { device_t dev; /**< bridge device */ struct bhnd_chipid chipid; /**< chip identification */ struct bhnd_core_info bridge_core; /**< bridge core info */ device_t parent_dev; /**< parent device */ device_t bus_dev; /**< child bhnd(4) bus */ struct bhnd_service_registry services; /**< local service registry */ 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 (revision 326078) +++ head/sys/dev/bhnd/bhndvar.h (revision 326079) @@ -1,102 +1,108 @@ /*- * 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: * 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 _BHND_BHNDVAR_H_ #define _BHND_BHNDVAR_H_ #include #include #include #include #include #include #include "bhnd.h" /* * Definitions shared by bhnd(4) bus and bhndb(4) bridge driver implementations. */ MALLOC_DECLARE(M_BHND); DECLARE_CLASS(bhnd_driver); int bhnd_generic_attach(device_t dev); int bhnd_generic_detach(device_t dev); int bhnd_generic_shutdown(device_t dev); int bhnd_generic_resume(device_t dev); int bhnd_generic_suspend(device_t dev); int bhnd_generic_get_probe_order(device_t dev, device_t child); int bhnd_generic_alloc_pmu(device_t dev, device_t child); int bhnd_generic_release_pmu(device_t dev, device_t child); int bhnd_generic_request_clock(device_t dev, device_t child, bhnd_clock clock); int bhnd_generic_enable_clocks(device_t dev, device_t child, uint32_t clocks); int bhnd_generic_request_ext_rsrc(device_t dev, device_t child, u_int rsrc); int bhnd_generic_release_ext_rsrc(device_t dev, device_t child, u_int rsrc); int bhnd_generic_print_child(device_t dev, device_t child); void bhnd_generic_probe_nomatch(device_t dev, device_t child); void bhnd_generic_child_deleted(device_t dev, device_t child); int bhnd_generic_suspend_child(device_t dev, device_t child); 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, bhnd_nvram_type type); /** * bhnd driver instance state. Must be first member of all subclass * softc structures. */ struct bhnd_softc { device_t dev; /**< bus device */ }; #endif /* _BHND_BHNDVAR_H_ */ Index: head/sys/dev/bhnd/cores/chipc/chipc.c =================================================================== --- head/sys/dev/bhnd/cores/chipc/chipc.c (revision 326078) +++ head/sys/dev/bhnd/cores/chipc/chipc.c (revision 326079) @@ -1,1383 +1,1408 @@ /*- * Copyright (c) 2015-2016 Landon Fuller * Copyright (c) 2016 Michael Zhilin * 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. */ #include __FBSDID("$FreeBSD$"); /* * Broadcom ChipCommon driver. * * With the exception of some very early chipsets, the ChipCommon core * has been included in all HND SoCs and chipsets based on the siba(4) * and bcma(4) interconnects, providing a common interface to chipset * identification, bus enumeration, UARTs, clocks, watchdog interrupts, * GPIO, flash, etc. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "chipcreg.h" #include "chipcvar.h" #include "chipc_private.h" devclass_t bhnd_chipc_devclass; /**< bhnd(4) chipcommon device class */ static struct bhnd_device_quirk chipc_quirks[]; /* Supported device identifiers */ static const struct bhnd_device chipc_devices[] = { BHND_DEVICE(BCM, CC, NULL, chipc_quirks), BHND_DEVICE(BCM, 4706_CC, NULL, chipc_quirks), BHND_DEVICE_END }; /* Device quirks table */ static struct bhnd_device_quirk chipc_quirks[] = { /* HND OTP controller revisions */ BHND_CORE_QUIRK (HWREV_EQ (12), CHIPC_QUIRK_OTP_HND), /* (?) */ BHND_CORE_QUIRK (HWREV_EQ (17), CHIPC_QUIRK_OTP_HND), /* BCM4311 */ BHND_CORE_QUIRK (HWREV_EQ (22), CHIPC_QUIRK_OTP_HND), /* BCM4312 */ /* IPX OTP controller revisions */ BHND_CORE_QUIRK (HWREV_EQ (21), CHIPC_QUIRK_OTP_IPX), BHND_CORE_QUIRK (HWREV_GTE(23), CHIPC_QUIRK_OTP_IPX), BHND_CORE_QUIRK (HWREV_GTE(32), CHIPC_QUIRK_SUPPORTS_SPROM), BHND_CORE_QUIRK (HWREV_GTE(35), CHIPC_QUIRK_SUPPORTS_CAP_EXT), BHND_CORE_QUIRK (HWREV_GTE(49), CHIPC_QUIRK_IPX_OTPL_SIZE), /* 4706 variant quirks */ BHND_CORE_QUIRK (HWREV_EQ (38), CHIPC_QUIRK_4706_NFLASH), /* BCM5357? */ BHND_CHIP_QUIRK (4706, HWREV_ANY, CHIPC_QUIRK_4706_NFLASH), /* 4331 quirks*/ BHND_CHIP_QUIRK (4331, HWREV_ANY, CHIPC_QUIRK_4331_EXTPA_MUX_SPROM), BHND_PKG_QUIRK (4331, TN, CHIPC_QUIRK_4331_GPIO2_5_MUX_SPROM), BHND_PKG_QUIRK (4331, TNA0, CHIPC_QUIRK_4331_GPIO2_5_MUX_SPROM), BHND_PKG_QUIRK (4331, TT, CHIPC_QUIRK_4331_EXTPA2_MUX_SPROM), /* 4360 quirks */ BHND_CHIP_QUIRK (4352, HWREV_LTE(2), CHIPC_QUIRK_4360_FEM_MUX_SPROM), BHND_CHIP_QUIRK (43460, HWREV_LTE(2), CHIPC_QUIRK_4360_FEM_MUX_SPROM), BHND_CHIP_QUIRK (43462, HWREV_LTE(2), CHIPC_QUIRK_4360_FEM_MUX_SPROM), BHND_CHIP_QUIRK (43602, HWREV_LTE(2), CHIPC_QUIRK_4360_FEM_MUX_SPROM), 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, struct chipc_caps *caps); static int chipc_read_caps(struct chipc_softc *sc, struct chipc_caps *caps); static bool chipc_should_enable_muxed_sprom( struct chipc_softc *sc); static int chipc_enable_otp_power(struct chipc_softc *sc); static void chipc_disable_otp_power(struct chipc_softc *sc); static int chipc_enable_sprom_pins(struct chipc_softc *sc); static void chipc_disable_sprom_pins(struct chipc_softc *sc); static int chipc_try_activate_resource(struct chipc_softc *sc, device_t child, int type, int rid, struct resource *r, bool req_direct); static int chipc_init_rman(struct chipc_softc *sc); static void chipc_free_rman(struct chipc_softc *sc); static struct rman *chipc_get_rman(struct chipc_softc *sc, int type); /* quirk and capability flag convenience macros */ #define CHIPC_QUIRK(_sc, _name) \ ((_sc)->quirks & CHIPC_QUIRK_ ## _name) #define CHIPC_CAP(_sc, _name) \ ((_sc)->caps._name) #define CHIPC_ASSERT_QUIRK(_sc, name) \ KASSERT(CHIPC_QUIRK((_sc), name), ("quirk " __STRING(_name) " not set")) #define CHIPC_ASSERT_CAP(_sc, name) \ KASSERT(CHIPC_CAP((_sc), name), ("capability " __STRING(_name) " not set")) static int chipc_probe(device_t dev) { const struct bhnd_device *id; id = bhnd_device_lookup(dev, chipc_devices, sizeof(chipc_devices[0])); if (id == NULL) return (ENXIO); bhnd_set_default_core_desc(dev); return (BUS_PROBE_DEFAULT); } static int chipc_attach(device_t dev) { struct chipc_softc *sc; int error; sc = device_get_softc(dev); sc->dev = dev; sc->quirks = bhnd_device_quirks(dev, chipc_devices, sizeof(chipc_devices[0])); sc->sprom_refcnt = 0; CHIPC_LOCK_INIT(sc); STAILQ_INIT(&sc->mem_regions); /* Set up resource management */ if ((error = chipc_init_rman(sc))) { device_printf(sc->dev, "failed to initialize chipc resource state: %d\n", error); goto failed; } /* Allocate the region containing the chipc register block */ if ((sc->core_region = chipc_find_region_by_rid(sc, 0)) == NULL) { error = ENXIO; goto failed; } error = chipc_retain_region(sc, sc->core_region, RF_ALLOCATED|RF_ACTIVE); if (error) { sc->core_region = NULL; goto failed; } /* Save a direct reference to our chipc registers */ sc->core = sc->core_region->cr_res; /* Fetch and parse capability register(s) */ if ((error = chipc_read_caps(sc, &sc->caps))) goto failed; if (bootverbose) chipc_print_caps(sc->dev, &sc->caps); /* Attach all supported child devices */ if ((error = chipc_add_children(sc))) goto failed; if ((error = bus_generic_attach(dev))) goto failed; /* Register ourselves with the bus */ if ((error = bhnd_register_provider(dev, BHND_SERVICE_CHIPC))) goto failed; return (0); failed: device_delete_children(sc->dev); if (sc->core_region != NULL) { chipc_release_region(sc, sc->core_region, RF_ALLOCATED|RF_ACTIVE); } chipc_free_rman(sc); CHIPC_LOCK_DESTROY(sc); return (error); } static int chipc_detach(device_t dev) { struct chipc_softc *sc; int error; sc = device_get_softc(dev); if ((error = bhnd_deregister_provider(dev, BHND_SERVICE_ANY))) return (error); if ((error = bus_generic_detach(dev))) return (error); chipc_release_region(sc, sc->core_region, RF_ALLOCATED|RF_ACTIVE); chipc_free_rman(sc); CHIPC_LOCK_DESTROY(sc); return (0); } static int chipc_add_children(struct chipc_softc *sc) { device_t child; const char *flash_bus; int error; /* SPROM/OTP */ if (sc->caps.nvram_src == BHND_NVRAM_SRC_SPROM || sc->caps.nvram_src == BHND_NVRAM_SRC_OTP) { child = BUS_ADD_CHILD(sc->dev, 0, "bhnd_nvram", -1); if (child == NULL) { device_printf(sc->dev, "failed to add nvram device\n"); return (ENXIO); } /* 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); + } } /* * PMU/PWR_CTRL * * On AOB ("Always on Bus") devices, the PMU core (if it exists) is * attached directly to the bhnd(4) bus -- not chipc. */ if (sc->caps.pwr_ctrl || (sc->caps.pmu && !sc->caps.aob)) { child = BUS_ADD_CHILD(sc->dev, 0, "bhnd_pmu", -1); if (child == NULL) { device_printf(sc->dev, "failed to add pmu\n"); return (ENXIO); } } /* All remaining devices are SoC-only */ if (bhnd_get_attach_type(sc->dev) != BHND_ATTACH_NATIVE) return (0); /* 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); return (ENXIO); } /* 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", flash_bus); return (ENXIO); } /* 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); } /** * Determine the NVRAM data source for this device. * * The SPROM, OTP, and flash capability flags must be fully populated in * @p caps. * * @param sc chipc driver state. * @param caps capability flags to be used to derive NVRAM configuration. */ static bhnd_nvram_src chipc_find_nvram_src(struct chipc_softc *sc, struct chipc_caps *caps) { uint32_t otp_st, srom_ctrl; /* * We check for hardware presence in order of precedence. For example, * SPROM is is always used in preference to internal OTP if found. */ if (CHIPC_QUIRK(sc, SUPPORTS_SPROM) && caps->sprom) { srom_ctrl = bhnd_bus_read_4(sc->core, CHIPC_SPROM_CTRL); if (srom_ctrl & CHIPC_SRC_PRESENT) return (BHND_NVRAM_SRC_SPROM); } /* Check for programmed OTP H/W subregion (contains SROM data) */ if (CHIPC_QUIRK(sc, SUPPORTS_OTP) && caps->otp_size > 0) { /* TODO: need access to HND-OTP device */ if (!CHIPC_QUIRK(sc, OTP_HND)) { device_printf(sc->dev, "NVRAM unavailable: unsupported OTP controller.\n"); return (BHND_NVRAM_SRC_UNKNOWN); } otp_st = bhnd_bus_read_4(sc->core, CHIPC_OTPST); if (otp_st & CHIPC_OTPS_GUP_HW) return (BHND_NVRAM_SRC_OTP); } /* Check for flash */ if (caps->flash_type != CHIPC_FLASH_NONE) return (BHND_NVRAM_SRC_FLASH); /* No NVRAM hardware capability declared */ return (BHND_NVRAM_SRC_UNKNOWN); } /* Read and parse chipc capabilities */ static int chipc_read_caps(struct chipc_softc *sc, struct chipc_caps *caps) { uint32_t cap_reg; uint32_t cap_ext_reg; uint32_t regval; /* Fetch cap registers */ cap_reg = bhnd_bus_read_4(sc->core, CHIPC_CAPABILITIES); cap_ext_reg = 0; if (CHIPC_QUIRK(sc, SUPPORTS_CAP_EXT)) cap_ext_reg = bhnd_bus_read_4(sc->core, CHIPC_CAPABILITIES_EXT); /* Extract values */ caps->num_uarts = CHIPC_GET_BITS(cap_reg, CHIPC_CAP_NUM_UART); caps->mipseb = CHIPC_GET_FLAG(cap_reg, CHIPC_CAP_MIPSEB); caps->uart_gpio = CHIPC_GET_FLAG(cap_reg, CHIPC_CAP_UARTGPIO); caps->uart_clock = CHIPC_GET_BITS(cap_reg, CHIPC_CAP_UCLKSEL); caps->extbus_type = CHIPC_GET_BITS(cap_reg, CHIPC_CAP_EXTBUS); caps->pwr_ctrl = CHIPC_GET_FLAG(cap_reg, CHIPC_CAP_PWR_CTL); caps->jtag_master = CHIPC_GET_FLAG(cap_reg, CHIPC_CAP_JTAGP); caps->pll_type = CHIPC_GET_BITS(cap_reg, CHIPC_CAP_PLL); caps->backplane_64 = CHIPC_GET_FLAG(cap_reg, CHIPC_CAP_BKPLN64); caps->boot_rom = CHIPC_GET_FLAG(cap_reg, CHIPC_CAP_ROM); caps->pmu = CHIPC_GET_FLAG(cap_reg, CHIPC_CAP_PMU); caps->eci = CHIPC_GET_FLAG(cap_reg, CHIPC_CAP_ECI); caps->sprom = CHIPC_GET_FLAG(cap_reg, CHIPC_CAP_SPROM); caps->otp_size = CHIPC_GET_BITS(cap_reg, CHIPC_CAP_OTP_SIZE); caps->seci = CHIPC_GET_FLAG(cap_ext_reg, CHIPC_CAP2_SECI); caps->gsio = CHIPC_GET_FLAG(cap_ext_reg, CHIPC_CAP2_GSIO); caps->aob = CHIPC_GET_FLAG(cap_ext_reg, CHIPC_CAP2_AOB); /* Fetch OTP size for later IPX controller revisions */ if (CHIPC_QUIRK(sc, IPX_OTPL_SIZE)) { regval = bhnd_bus_read_4(sc->core, CHIPC_OTPLAYOUT); caps->otp_size = CHIPC_GET_BITS(regval, CHIPC_OTPL_SIZE); } /* Determine flash type and parameters */ caps->cfi_width = 0; switch (CHIPC_GET_BITS(cap_reg, CHIPC_CAP_FLASH)) { case CHIPC_CAP_SFLASH_ST: caps->flash_type = CHIPC_SFLASH_ST; break; case CHIPC_CAP_SFLASH_AT: caps->flash_type = CHIPC_SFLASH_AT; break; case CHIPC_CAP_NFLASH: /* unimplemented */ caps->flash_type = CHIPC_NFLASH; break; case CHIPC_CAP_PFLASH: caps->flash_type = CHIPC_PFLASH_CFI; /* determine cfi width */ regval = bhnd_bus_read_4(sc->core, CHIPC_FLASH_CFG); if (CHIPC_GET_FLAG(regval, CHIPC_FLASH_CFG_DS)) caps->cfi_width = 2; else caps->cfi_width = 1; break; case CHIPC_CAP_FLASH_NONE: caps->flash_type = CHIPC_FLASH_NONE; break; } /* Handle 4706_NFLASH fallback */ if (CHIPC_QUIRK(sc, 4706_NFLASH) && CHIPC_GET_FLAG(cap_reg, CHIPC_CAP_4706_NFLASH)) { caps->flash_type = CHIPC_NFLASH_4706; } /* Determine NVRAM source. Must occur after the SPROM/OTP/flash * capability flags have been populated. */ caps->nvram_src = chipc_find_nvram_src(sc, caps); /* Determine the SPROM offset within OTP (if any). SPROM-formatted * data is placed within the OTP general use region. */ caps->sprom_offset = 0; if (caps->nvram_src == BHND_NVRAM_SRC_OTP) { CHIPC_ASSERT_QUIRK(sc, OTP_IPX); /* Bit offset to GUP HW subregion containing SPROM data */ regval = bhnd_bus_read_4(sc->core, CHIPC_OTPLAYOUT); caps->sprom_offset = CHIPC_GET_BITS(regval, CHIPC_OTPL_GUP); /* Convert to bytes */ caps->sprom_offset /= 8; } return (0); } static int chipc_suspend(device_t dev) { return (bus_generic_suspend(dev)); } static int chipc_resume(device_t dev) { return (bus_generic_resume(dev)); } static void chipc_probe_nomatch(device_t dev, device_t child) { struct resource_list *rl; const char *name; name = device_get_name(child); if (name == NULL) name = "unknown device"; device_printf(dev, "<%s> at", name); rl = BUS_GET_RESOURCE_LIST(dev, child); if (rl != NULL) { resource_list_print_type(rl, "mem", SYS_RES_MEMORY, "%#jx"); resource_list_print_type(rl, "irq", SYS_RES_IRQ, "%jd"); } printf(" (no driver attached)\n"); } static int chipc_print_child(device_t dev, device_t child) { struct resource_list *rl; int retval = 0; retval += bus_print_child_header(dev, child); rl = BUS_GET_RESOURCE_LIST(dev, child); if (rl != NULL) { retval += resource_list_print_type(rl, "mem", SYS_RES_MEMORY, "%#jx"); retval += resource_list_print_type(rl, "irq", SYS_RES_IRQ, "%jd"); } retval += bus_print_child_domain(dev, child); retval += bus_print_child_footer(dev, child); return (retval); } static int chipc_child_pnpinfo_str(device_t dev, device_t child, char *buf, size_t buflen) { if (buflen == 0) return (EOVERFLOW); *buf = '\0'; return (0); } static int chipc_child_location_str(device_t dev, device_t child, char *buf, size_t buflen) { if (buflen == 0) return (EOVERFLOW); *buf = '\0'; return (ENXIO); } static device_t chipc_add_child(device_t dev, u_int order, const char *name, int unit) { struct chipc_softc *sc; struct chipc_devinfo *dinfo; device_t child; sc = device_get_softc(dev); child = device_add_child_ordered(dev, order, name, unit); if (child == NULL) return (NULL); dinfo = malloc(sizeof(struct chipc_devinfo), M_BHND, M_NOWAIT); if (dinfo == NULL) { device_delete_child(dev, child); return (NULL); } resource_list_init(&dinfo->resources); + dinfo->irq_mapped = false; device_set_ivars(child, dinfo); return (child); } static void chipc_child_deleted(device_t dev, device_t child) { 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); } device_set_ivars(child, NULL); } static struct resource_list * chipc_get_resource_list(device_t dev, device_t child) { struct chipc_devinfo *dinfo = device_get_ivars(child); return (&dinfo->resources); } /* Allocate region records for the given port, and add the port's memory * range to the mem_rman */ static int chipc_rman_init_regions (struct chipc_softc *sc, bhnd_port_type type, u_int port) { struct chipc_region *cr; rman_res_t start, end; u_int num_regions; int error; num_regions = bhnd_get_region_count(sc->dev, type, port); for (u_int region = 0; region < num_regions; region++) { /* Allocate new region record */ cr = chipc_alloc_region(sc, type, port, region); if (cr == NULL) return (ENODEV); /* Can't manage regions that cannot be allocated */ if (cr->cr_rid < 0) { BHND_DEBUG_DEV(sc->dev, "no rid for chipc region " "%s%u.%u", bhnd_port_type_name(type), port, region); chipc_free_region(sc, cr); continue; } /* Add to rman's managed range */ start = cr->cr_addr; end = cr->cr_end; if ((error = rman_manage_region(&sc->mem_rman, start, end))) { chipc_free_region(sc, cr); return (error); } /* Add to region list */ STAILQ_INSERT_TAIL(&sc->mem_regions, cr, cr_link); } return (0); } /* Initialize memory state for all chipc port regions */ static int chipc_init_rman(struct chipc_softc *sc) { u_int num_ports; int error; /* Port types for which we'll register chipc_region mappings */ bhnd_port_type types[] = { BHND_PORT_DEVICE }; /* Initialize resource manager */ sc->mem_rman.rm_start = 0; sc->mem_rman.rm_end = BUS_SPACE_MAXADDR; sc->mem_rman.rm_type = RMAN_ARRAY; sc->mem_rman.rm_descr = "ChipCommon Device Memory"; if ((error = rman_init(&sc->mem_rman))) { device_printf(sc->dev, "could not initialize mem_rman: %d\n", error); return (error); } /* Populate per-port-region state */ for (u_int i = 0; i < nitems(types); i++) { num_ports = bhnd_get_port_count(sc->dev, types[i]); for (u_int port = 0; port < num_ports; port++) { error = chipc_rman_init_regions(sc, types[i], port); if (error) { device_printf(sc->dev, "region init failed for %s%u: %d\n", bhnd_port_type_name(types[i]), port, error); goto failed; } } } return (0); failed: chipc_free_rman(sc); return (error); } /* Free memory management state */ static void chipc_free_rman(struct chipc_softc *sc) { struct chipc_region *cr, *cr_next; STAILQ_FOREACH_SAFE(cr, &sc->mem_regions, cr_link, cr_next) chipc_free_region(sc, cr); rman_fini(&sc->mem_rman); } /** * Return the rman instance for a given resource @p type, if any. * * @param sc The chipc device state. * @param type The resource type (e.g. SYS_RES_MEMORY, SYS_RES_IRQ, ...) */ static struct rman * chipc_get_rman(struct chipc_softc *sc, int type) { switch (type) { case SYS_RES_MEMORY: 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: return (NULL); }; } static struct resource * chipc_alloc_resource(device_t dev, device_t child, int type, int *rid, rman_res_t start, rman_res_t end, rman_res_t count, u_int flags) { struct chipc_softc *sc; struct chipc_region *cr; struct resource_list_entry *rle; struct resource *rv; struct rman *rm; int error; bool passthrough, isdefault; sc = device_get_softc(dev); passthrough = (device_get_parent(child) != dev); isdefault = RMAN_IS_DEFAULT_RANGE(start, end); rle = NULL; /* Fetch the resource manager, delegate request if necessary */ rm = chipc_get_rman(sc, type); if (rm == NULL) { /* Requested resource type is delegated to our parent */ rv = bus_generic_rl_alloc_resource(dev, child, type, rid, start, end, count, flags); return (rv); } /* Populate defaults */ if (!passthrough && isdefault) { /* Fetch the resource list entry. */ rle = resource_list_find(BUS_GET_RESOURCE_LIST(dev, child), type, *rid); if (rle == NULL) { device_printf(dev, "default resource %#x type %d for child %s " "not found\n", *rid, type, device_get_nameunit(child)); return (NULL); } if (rle->res != NULL) { device_printf(dev, "resource entry %#x type %d for child %s is busy " "[%d]\n", *rid, type, device_get_nameunit(child), rman_get_flags(rle->res)); return (NULL); } start = rle->start; end = rle->end; count = ulmax(count, rle->count); } /* Locate a mapping region */ if ((cr = chipc_find_region(sc, start, end)) == NULL) { /* Resource requests outside our shared port regions can be * delegated to our parent. */ rv = bus_generic_rl_alloc_resource(dev, child, type, rid, start, end, count, flags); return (rv); } /* Try to retain a region reference */ if ((error = chipc_retain_region(sc, cr, RF_ALLOCATED))) return (NULL); /* Make our rman reservation */ rv = rman_reserve_resource(rm, start, end, count, flags & ~RF_ACTIVE, child); if (rv == NULL) { chipc_release_region(sc, cr, RF_ALLOCATED); return (NULL); } rman_set_rid(rv, *rid); /* Activate */ if (flags & RF_ACTIVE) { error = bus_activate_resource(child, type, *rid, rv); if (error) { device_printf(dev, "failed to activate entry %#x type %d for " "child %s: %d\n", *rid, type, device_get_nameunit(child), error); chipc_release_region(sc, cr, RF_ALLOCATED); rman_release_resource(rv); return (NULL); } } /* Update child's resource list entry */ if (rle != NULL) { rle->res = rv; rle->start = rman_get_start(rv); rle->end = rman_get_end(rv); rle->count = rman_get_size(rv); } return (rv); } static int chipc_release_resource(device_t dev, device_t child, int type, int rid, struct resource *r) { struct chipc_softc *sc; struct chipc_region *cr; struct rman *rm; struct resource_list_entry *rle; int error; sc = device_get_softc(dev); /* Handled by parent bus? */ rm = chipc_get_rman(sc, type); if (rm == NULL || !rman_is_region_manager(r, rm)) { return (bus_generic_rl_release_resource(dev, child, type, rid, r)); } /* Locate the mapping region */ cr = chipc_find_region(sc, rman_get_start(r), rman_get_end(r)); if (cr == NULL) return (EINVAL); /* 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); /* Drop allocation reference */ chipc_release_region(sc, cr, RF_ALLOCATED); /* Clear reference from the resource list entry if exists */ rle = resource_list_find(BUS_GET_RESOURCE_LIST(dev, child), type, rid); if (rle != NULL) rle->res = NULL; return (0); } static int chipc_adjust_resource(device_t dev, device_t child, int type, struct resource *r, rman_res_t start, rman_res_t end) { struct chipc_softc *sc; struct chipc_region *cr; struct rman *rm; sc = device_get_softc(dev); /* Handled by parent bus? */ rm = chipc_get_rman(sc, type); if (rm == NULL || !rman_is_region_manager(r, rm)) { return (bus_generic_adjust_resource(dev, child, type, r, start, end)); } /* The range is limited to the existing region mapping */ cr = chipc_find_region(sc, rman_get_start(r), rman_get_end(r)); if (cr == NULL) return (EINVAL); if (end <= start) return (EINVAL); if (start < cr->cr_addr || end > cr->cr_end) return (EINVAL); /* Range falls within the existing region */ return (rman_adjust_resource(r, start, end)); } /** * Retain an RF_ACTIVE reference to the region mapping @p r, and * configure @p r with its subregion values. * * @param sc Driver instance state. * @param child Requesting child device. * @param type resource type of @p r. * @param rid resource id of @p r * @param r resource to be activated. * @param req_direct If true, failure to allocate a direct bhnd resource * will be treated as an error. If false, the resource will not be marked * as RF_ACTIVE if bhnd direct resource allocation fails. */ static int chipc_try_activate_resource(struct chipc_softc *sc, device_t child, int type, int rid, struct resource *r, bool req_direct) { struct rman *rm; struct chipc_region *cr; bhnd_size_t cr_offset; rman_res_t r_start, r_end, r_size; int error; rm = chipc_get_rman(sc, type); if (rm == NULL || !rman_is_region_manager(r, rm)) return (EINVAL); r_start = rman_get_start(r); r_end = rman_get_end(r); r_size = rman_get_size(r); /* Find the corresponding chipc region */ cr = chipc_find_region(sc, r_start, r_end); if (cr == NULL) return (EINVAL); /* Calculate subregion offset within the chipc region */ cr_offset = r_start - cr->cr_addr; /* Retain (and activate, if necessary) the chipc region */ if ((error = chipc_retain_region(sc, cr, RF_ACTIVE))) return (error); /* Configure child resource with its subregion values. */ if (cr->cr_res->direct) { error = chipc_init_child_resource(r, cr->cr_res->res, cr_offset, r_size); if (error) goto cleanup; /* Mark active */ if ((error = rman_activate_resource(r))) goto cleanup; } else if (req_direct) { error = ENOMEM; goto cleanup; } return (0); cleanup: chipc_release_region(sc, cr, RF_ACTIVE); return (error); } static int chipc_activate_bhnd_resource(device_t dev, device_t child, int type, int rid, struct bhnd_resource *r) { struct chipc_softc *sc; struct rman *rm; int error; sc = device_get_softc(dev); /* Delegate non-locally managed resources to parent */ rm = chipc_get_rman(sc, type); if (rm == NULL || !rman_is_region_manager(r->res, rm)) { return (bhnd_bus_generic_activate_resource(dev, child, type, rid, r)); } /* Try activating the chipc region resource */ error = chipc_try_activate_resource(sc, child, type, rid, r->res, false); if (error) return (error); /* Mark the child resource as direct according to the returned resource * state */ if (rman_get_flags(r->res) & RF_ACTIVE) r->direct = true; return (0); } static int chipc_activate_resource(device_t dev, device_t child, int type, int rid, struct resource *r) { struct chipc_softc *sc; struct rman *rm; sc = device_get_softc(dev); /* Delegate non-locally managed resources to parent */ rm = chipc_get_rman(sc, type); if (rm == NULL || !rman_is_region_manager(r, rm)) { return (bus_generic_activate_resource(dev, child, type, rid, r)); } /* Try activating the chipc region-based resource */ return (chipc_try_activate_resource(sc, child, type, rid, r, true)); } /** * Default bhndb(4) implementation of BUS_DEACTIVATE_RESOURCE(). */ static int chipc_deactivate_resource(device_t dev, device_t child, int type, int rid, struct resource *r) { struct chipc_softc *sc; struct chipc_region *cr; struct rman *rm; int error; sc = device_get_softc(dev); /* Handled by parent bus? */ rm = chipc_get_rman(sc, type); if (rm == NULL || !rman_is_region_manager(r, rm)) { return (bus_generic_deactivate_resource(dev, child, type, rid, r)); } /* Find the corresponding chipc region */ cr = chipc_find_region(sc, rman_get_start(r), rman_get_end(r)); if (cr == NULL) return (EINVAL); /* Mark inactive */ if ((error = rman_deactivate_resource(r))) return (error); /* Drop associated RF_ACTIVE reference */ chipc_release_region(sc, cr, RF_ACTIVE); return (0); } /** * Examine bus state and make a best effort determination of whether it's * likely safe to enable the muxed SPROM pins. * * On devices that do not use SPROM pin muxing, always returns true. * * @param sc chipc driver state. */ static bool chipc_should_enable_muxed_sprom(struct chipc_softc *sc) { device_t *devs; device_t hostb; device_t parent; int devcount; int error; bool result; /* Nothing to do? */ if (!CHIPC_QUIRK(sc, MUX_SPROM)) return (true); mtx_lock(&Giant); /* for newbus */ parent = device_get_parent(sc->dev); hostb = bhnd_bus_find_hostb_device(parent); if ((error = device_get_children(parent, &devs, &devcount))) { mtx_unlock(&Giant); return (false); } /* Reject any active devices other than ChipCommon, or the * host bridge (if any). */ result = true; for (int i = 0; i < devcount; i++) { if (devs[i] == hostb || devs[i] == sc->dev) continue; if (!device_is_attached(devs[i])) continue; if (device_is_suspended(devs[i])) continue; /* Active device; assume SPROM is busy */ result = false; break; } free(devs, M_TEMP); mtx_unlock(&Giant); return (result); } static int chipc_enable_sprom(device_t dev) { struct chipc_softc *sc; int error; sc = device_get_softc(dev); CHIPC_LOCK(sc); /* Already enabled? */ if (sc->sprom_refcnt >= 1) { sc->sprom_refcnt++; CHIPC_UNLOCK(sc); return (0); } switch (sc->caps.nvram_src) { case BHND_NVRAM_SRC_SPROM: error = chipc_enable_sprom_pins(sc); break; case BHND_NVRAM_SRC_OTP: error = chipc_enable_otp_power(sc); break; default: error = 0; break; } /* Bump the reference count */ if (error == 0) sc->sprom_refcnt++; CHIPC_UNLOCK(sc); return (error); } static void chipc_disable_sprom(device_t dev) { struct chipc_softc *sc; sc = device_get_softc(dev); CHIPC_LOCK(sc); /* Check reference count, skip disable if in-use. */ KASSERT(sc->sprom_refcnt > 0, ("sprom refcnt overrelease")); sc->sprom_refcnt--; if (sc->sprom_refcnt > 0) { CHIPC_UNLOCK(sc); return; } switch (sc->caps.nvram_src) { case BHND_NVRAM_SRC_SPROM: chipc_disable_sprom_pins(sc); break; case BHND_NVRAM_SRC_OTP: chipc_disable_otp_power(sc); break; default: break; } CHIPC_UNLOCK(sc); } static int chipc_enable_otp_power(struct chipc_softc *sc) { // TODO: Enable OTP resource via PMU, and wait up to 100 usec for // OTPS_READY to be set in `optstatus`. return (0); } static void chipc_disable_otp_power(struct chipc_softc *sc) { // TODO: Disable OTP resource via PMU } /** * If required by this device, enable access to the SPROM. * * @param sc chipc driver state. */ static int chipc_enable_sprom_pins(struct chipc_softc *sc) { uint32_t cctrl; CHIPC_LOCK_ASSERT(sc, MA_OWNED); KASSERT(sc->sprom_refcnt == 0, ("sprom pins already enabled")); /* Nothing to do? */ if (!CHIPC_QUIRK(sc, MUX_SPROM)) return (0); /* Check whether bus is busy */ if (!chipc_should_enable_muxed_sprom(sc)) return (EBUSY); cctrl = bhnd_bus_read_4(sc->core, CHIPC_CHIPCTRL); /* 4331 devices */ if (CHIPC_QUIRK(sc, 4331_EXTPA_MUX_SPROM)) { cctrl &= ~CHIPC_CCTRL4331_EXTPA_EN; if (CHIPC_QUIRK(sc, 4331_GPIO2_5_MUX_SPROM)) cctrl &= ~CHIPC_CCTRL4331_EXTPA_ON_GPIO2_5; if (CHIPC_QUIRK(sc, 4331_EXTPA2_MUX_SPROM)) cctrl &= ~CHIPC_CCTRL4331_EXTPA_EN2; bhnd_bus_write_4(sc->core, CHIPC_CHIPCTRL, cctrl); return (0); } /* 4360 devices */ if (CHIPC_QUIRK(sc, 4360_FEM_MUX_SPROM)) { /* Unimplemented */ } /* Refuse to proceed on unsupported devices with muxed SPROM pins */ device_printf(sc->dev, "muxed sprom lines on unrecognized device\n"); return (ENXIO); } /** * If required by this device, revert any GPIO/pin configuration applied * to allow SPROM access. * * @param sc chipc driver state. */ static void chipc_disable_sprom_pins(struct chipc_softc *sc) { uint32_t cctrl; /* Nothing to do? */ if (!CHIPC_QUIRK(sc, MUX_SPROM)) return; CHIPC_LOCK_ASSERT(sc, MA_OWNED); KASSERT(sc->sprom_refcnt == 0, ("sprom pins in use")); cctrl = bhnd_bus_read_4(sc->core, CHIPC_CHIPCTRL); /* 4331 devices */ if (CHIPC_QUIRK(sc, 4331_EXTPA_MUX_SPROM)) { cctrl |= CHIPC_CCTRL4331_EXTPA_EN; if (CHIPC_QUIRK(sc, 4331_GPIO2_5_MUX_SPROM)) cctrl |= CHIPC_CCTRL4331_EXTPA_ON_GPIO2_5; if (CHIPC_QUIRK(sc, 4331_EXTPA2_MUX_SPROM)) cctrl |= CHIPC_CCTRL4331_EXTPA_EN2; bhnd_bus_write_4(sc->core, CHIPC_CHIPCTRL, cctrl); return; } /* 4360 devices */ if (CHIPC_QUIRK(sc, 4360_FEM_MUX_SPROM)) { /* Unimplemented */ } } static uint32_t chipc_read_chipst(device_t dev) { struct chipc_softc *sc = device_get_softc(dev); return (bhnd_bus_read_4(sc->core, CHIPC_CHIPST)); } static void chipc_write_chipctrl(device_t dev, uint32_t value, uint32_t mask) { struct chipc_softc *sc; uint32_t cctrl; sc = device_get_softc(dev); CHIPC_LOCK(sc); cctrl = bhnd_bus_read_4(sc->core, CHIPC_CHIPCTRL); cctrl = (cctrl & ~mask) | (value | mask); bhnd_bus_write_4(sc->core, CHIPC_CHIPCTRL, cctrl); CHIPC_UNLOCK(sc); } static struct chipc_caps * chipc_get_caps(device_t dev) { struct chipc_softc *sc; sc = device_get_softc(dev); return (&sc->caps); } static device_method_t chipc_methods[] = { /* Device interface */ DEVMETHOD(device_probe, chipc_probe), DEVMETHOD(device_attach, chipc_attach), DEVMETHOD(device_detach, chipc_detach), DEVMETHOD(device_suspend, chipc_suspend), DEVMETHOD(device_resume, chipc_resume), /* Bus interface */ DEVMETHOD(bus_probe_nomatch, chipc_probe_nomatch), DEVMETHOD(bus_print_child, chipc_print_child), DEVMETHOD(bus_child_pnpinfo_str, chipc_child_pnpinfo_str), DEVMETHOD(bus_child_location_str, chipc_child_location_str), DEVMETHOD(bus_add_child, chipc_add_child), DEVMETHOD(bus_child_deleted, chipc_child_deleted), DEVMETHOD(bus_set_resource, bus_generic_rl_set_resource), DEVMETHOD(bus_get_resource, bus_generic_rl_get_resource), DEVMETHOD(bus_delete_resource, bus_generic_rl_delete_resource), DEVMETHOD(bus_alloc_resource, chipc_alloc_resource), DEVMETHOD(bus_release_resource, chipc_release_resource), DEVMETHOD(bus_adjust_resource, chipc_adjust_resource), DEVMETHOD(bus_activate_resource, chipc_activate_resource), DEVMETHOD(bus_deactivate_resource, chipc_deactivate_resource), DEVMETHOD(bus_get_resource_list, chipc_get_resource_list), 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), /* BHND bus inteface */ DEVMETHOD(bhnd_bus_activate_resource, chipc_activate_bhnd_resource), /* ChipCommon interface */ DEVMETHOD(bhnd_chipc_read_chipst, chipc_read_chipst), DEVMETHOD(bhnd_chipc_write_chipctrl, chipc_write_chipctrl), DEVMETHOD(bhnd_chipc_enable_sprom, chipc_enable_sprom), DEVMETHOD(bhnd_chipc_disable_sprom, chipc_disable_sprom), DEVMETHOD(bhnd_chipc_get_caps, chipc_get_caps), DEVMETHOD_END }; DEFINE_CLASS_0(bhnd_chipc, bhnd_chipc_driver, chipc_methods, sizeof(struct chipc_softc)); EARLY_DRIVER_MODULE(bhnd_chipc, bhnd, bhnd_chipc_driver, bhnd_chipc_devclass, 0, 0, BUS_PASS_BUS + BUS_PASS_ORDER_MIDDLE); MODULE_DEPEND(bhnd_chipc, bhnd, 1, 1, 1); MODULE_VERSION(bhnd_chipc, 1); Index: head/sys/dev/bhnd/cores/chipc/chipc_private.h =================================================================== --- head/sys/dev/bhnd/cores/chipc/chipc_private.h (revision 326078) +++ head/sys/dev/bhnd/cores/chipc/chipc_private.h (revision 326079) @@ -1,105 +1,110 @@ /*- * 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: * 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 _BHND_CORES_CHIPC_CHIPC_PRIVATE_H_ #define _BHND_CORES_CHIPC_CHIPC_PRIVATE_H_ #include #include #include #include #include #include #include #include /* * Private bhnd_chipc(4) driver definitions. */ struct chipc_caps; struct chipc_region; struct chipc_softc; int chipc_init_child_resource(struct resource *r, 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, u_int region); void chipc_free_region(struct chipc_softc *sc, struct chipc_region *cr); struct chipc_region *chipc_find_region(struct chipc_softc *sc, rman_res_t start, rman_res_t end); struct chipc_region *chipc_find_region_by_rid(struct chipc_softc *sc, int rid); int chipc_retain_region(struct chipc_softc *sc, struct chipc_region *cr, int flags); int chipc_release_region(struct chipc_softc *sc, struct chipc_region *cr, int flags); void chipc_print_caps(device_t dev, struct chipc_caps *caps); /** * chipc SYS_RES_MEMORY region allocation record. */ struct chipc_region { bhnd_port_type cr_port_type; /**< bhnd port type */ u_int cr_port_num; /**< bhnd port number */ u_int cr_region_num; /**< bhnd region number */ bhnd_addr_t cr_addr; /**< region base address */ bhnd_addr_t cr_end; /**< region end address */ bhnd_size_t cr_count; /**< region count */ int cr_rid; /**< rid to use when performing resource allocation, or -1 if region has no assigned resource ID */ struct bhnd_resource *cr_res; /**< bus resource, or NULL */ int cr_res_rid; /**< cr_res RID, if any. */ u_int cr_refs; /**< RF_ALLOCATED refcount */ u_int cr_act_refs; /**< RF_ACTIVE refcount */ STAILQ_ENTRY(chipc_region) cr_link; }; #endif /* _BHND_CORES_CHIPC_CHIPC_PRIVATE_H_ */ Index: head/sys/dev/bhnd/cores/chipc/chipc_subr.c =================================================================== --- head/sys/dev/bhnd/cores/chipc/chipc_subr.c (revision 326078) +++ head/sys/dev/bhnd/cores/chipc/chipc_subr.c (revision 326079) @@ -1,525 +1,571 @@ /*- * Copyright (c) 2016 Michael Zhilin * Copyright (c) 2015-2016 Landon Fuller * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer, * 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. */ #include __FBSDID("$FreeBSD$"); #include #include #include "chipc_private.h" #include "chipcvar.h" /** * Return a human-readable name for the given flash @p type. */ const char * chipc_flash_name(chipc_flash type) { switch (type) { case CHIPC_PFLASH_CFI: return ("CFI Flash"); case CHIPC_SFLASH_ST: case CHIPC_SFLASH_AT: return ("SPI Flash"); case CHIPC_QSFLASH_ST: case CHIPC_QSFLASH_AT: return ("QSPI Flash"); case CHIPC_NFLASH: case CHIPC_NFLASH_4706: return ("NAND"); case CHIPC_FLASH_NONE: default: return ("unknown"); } } /** * Return the name of the bus device class used by flash @p type, * or NULL if @p type is unsupported. */ const char * chipc_flash_bus_name(chipc_flash type) { switch (type) { case CHIPC_PFLASH_CFI: return ("cfi"); case CHIPC_SFLASH_ST: case CHIPC_SFLASH_AT: return ("spi"); case CHIPC_QSFLASH_ST: case CHIPC_QSFLASH_AT: /* unimplemented; spi? */ return (NULL); case CHIPC_NFLASH: case CHIPC_NFLASH_4706: /* unimplemented; nandbus? */ return (NULL); case CHIPC_FLASH_NONE: default: return (NULL); } } /** * Return the name of the flash device class for SPI flash @p type, * or NULL if @p type does not use SPI, or is unsupported. */ const char * chipc_sflash_device_name(chipc_flash type) { switch (type) { case CHIPC_SFLASH_ST: return ("mx25l"); case CHIPC_SFLASH_AT: return ("at45d"); case CHIPC_QSFLASH_ST: case CHIPC_QSFLASH_AT: /* unimplemented */ return (NULL); case CHIPC_PFLASH_CFI: case CHIPC_NFLASH: case CHIPC_NFLASH_4706: case CHIPC_FLASH_NONE: default: return (NULL); } } /** * Initialize child resource @p r with a virtual address, tag, and handle * copied from @p parent, adjusted to contain only the range defined by * @p offsize and @p size. * * @param r The register to be initialized. * @param parent The parent bus resource that fully contains the subregion. * @param offset The subregion offset within @p parent. * @param size The subregion size. */ int chipc_init_child_resource(struct resource *r, struct resource *parent, bhnd_size_t offset, bhnd_size_t size) { bus_space_handle_t bh, child_bh; bus_space_tag_t bt; uintptr_t vaddr; int error; /* Fetch the parent resource's bus values */ vaddr = (uintptr_t) rman_get_virtual(parent); bt = rman_get_bustag(parent); bh = rman_get_bushandle(parent); /* Configure child resource with offset-adjusted values */ vaddr += offset; error = bus_space_subregion(bt, bh, offset, size, &child_bh); if (error) return (error); rman_set_virtual(r, (void *) vaddr); rman_set_bustag(r, bt); rman_set_bushandle(r, child_bh); return (0); } /** - * 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). * @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_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; bhnd_size_t region_size; 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 */ error = bhnd_get_region_addr(sc->dev, BHND_PORT_DEVICE, port, region, ®ion_addr, ®ion_size); if (error) { device_printf(sc->dev, "lookup of %s%u.%u failed: %d\n", bhnd_port_type_name(BHND_PORT_DEVICE), port, region, error); return (error); } /* Populate defaults */ if (isdefault) { start = 0; count = region_size; } /* Verify requested range is mappable */ if (start > region_size || region_size - start < count) { device_printf(sc->dev, "%s%u.%u region cannot map requested range %#jx+%#jx\n", bhnd_port_type_name(BHND_PORT_DEVICE), port, region, start, count); 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)); } /* * Print a capability structure. */ void chipc_print_caps(device_t dev, struct chipc_caps *caps) { #define CC_TFS(_flag) (caps->_flag ? "yes" : "no") device_printf(dev, "MIPSEB: %-3s | BP64: %s\n", CC_TFS(mipseb), CC_TFS(backplane_64)); device_printf(dev, "UARTs: %-3hhu | UGPIO: %s\n", caps->num_uarts, CC_TFS(uart_gpio)); // XXX: hitting a kvprintf bug with '%#02x' not prefixing '0x' in // some cases, and not apply the field width in others device_printf(dev, "UARTClk: 0x%02x | Flash: %u\n", caps->uart_clock, caps->flash_type); device_printf(dev, "SPROM: %-3s | OTP: %s\n", CC_TFS(sprom), CC_TFS(otp_size)); device_printf(dev, "CFIsz: 0x%02x | OTPsz: 0x%02x\n", caps->cfi_width, caps->otp_size); device_printf(dev, "ExtBus: 0x%02x | PwrCtrl: %s\n", caps->extbus_type, CC_TFS(pwr_ctrl)); device_printf(dev, "PLL: 0x%02x | JTAGM: %s\n", caps->pll_type, CC_TFS(jtag_master)); device_printf(dev, "PMU: %-3s | ECI: %s\n", CC_TFS(pmu), CC_TFS(eci)); device_printf(dev, "SECI: %-3s | GSIO: %s\n", CC_TFS(seci), CC_TFS(gsio)); device_printf(dev, "AOB: %-3s | BootROM: %s\n", CC_TFS(aob), CC_TFS(boot_rom)); #undef CC_TFS } /** * Allocate and initialize new region record. * * @param sc Driver instance state. * @param type The port type to query. * @param port The port number to query. * @param region The region number to query. */ struct chipc_region * chipc_alloc_region(struct chipc_softc *sc, bhnd_port_type type, u_int port, u_int region) { struct chipc_region *cr; int error; /* Don't bother allocating a chipc_region if init will fail */ if (!bhnd_is_region_valid(sc->dev, type, port, region)) return (NULL); /* Allocate and initialize region info */ cr = malloc(sizeof(*cr), M_BHND, M_NOWAIT); if (cr == NULL) return (NULL); cr->cr_port_type = type; cr->cr_port_num = port; cr->cr_region_num = region; cr->cr_res = NULL; cr->cr_refs = 0; cr->cr_act_refs = 0; error = bhnd_get_region_addr(sc->dev, type, port, region, &cr->cr_addr, &cr->cr_count); if (error) { device_printf(sc->dev, "fetching chipc region address failed: %d\n", error); goto failed; } cr->cr_end = cr->cr_addr + cr->cr_count - 1; /* Fetch default resource ID for this region. Not all regions have an * assigned rid, in which case this will return -1 */ cr->cr_rid = bhnd_get_port_rid(sc->dev, type, port, region); return (cr); failed: device_printf(sc->dev, "chipc region alloc failed for %s%u.%u\n", bhnd_port_type_name(type), port, region); free(cr, M_BHND); return (NULL); } /** * Deallocate the given region record and its associated resource, if any. * * @param sc Driver instance state. * @param cr Region record to be deallocated. */ void chipc_free_region(struct chipc_softc *sc, struct chipc_region *cr) { KASSERT(cr->cr_refs == 0, ("chipc %s%u.%u region has %u active references", bhnd_port_type_name(cr->cr_port_type), cr->cr_port_num, cr->cr_region_num, cr->cr_refs)); if (cr->cr_res != NULL) { bhnd_release_resource(sc->dev, SYS_RES_MEMORY, cr->cr_res_rid, cr->cr_res); } free(cr, M_BHND); } /** * Locate the region mapping the given range, if any. Returns NULL if no * valid region is found. * * @param sc Driver instance state. * @param start start of address range. * @param end end of address range. */ struct chipc_region * chipc_find_region(struct chipc_softc *sc, rman_res_t start, rman_res_t end) { struct chipc_region *cr; if (start > end) return (NULL); STAILQ_FOREACH(cr, &sc->mem_regions, cr_link) { if (start < cr->cr_addr || end > cr->cr_end) continue; /* Found */ return (cr); } /* Not found */ return (NULL); } /** * Locate a region mapping by its bhnd-assigned resource id (as returned by * bhnd_get_port_rid). * * @param sc Driver instance state. * @param rid Resource ID to query for. */ struct chipc_region * chipc_find_region_by_rid(struct chipc_softc *sc, int rid) { struct chipc_region *cr; int port_rid; STAILQ_FOREACH(cr, &sc->mem_regions, cr_link) { port_rid = bhnd_get_port_rid(sc->dev, cr->cr_port_type, cr->cr_port_num, cr->cr_region_num); if (port_rid == -1 || port_rid != rid) continue; /* Found */ return (cr); } /* Not found */ return (NULL); } /** * Retain a reference to a chipc_region, allocating and activating the * backing resource as required. * * @param sc chipc driver instance state * @param cr region to retain. * @param flags specify RF_ALLOCATED to retain an allocation reference, * RF_ACTIVE to retain an activation reference. */ int chipc_retain_region(struct chipc_softc *sc, struct chipc_region *cr, int flags) { int error; KASSERT(!(flags &~ (RF_ACTIVE|RF_ALLOCATED)), ("unsupported flags")); CHIPC_LOCK(sc); /* Handle allocation */ if (flags & RF_ALLOCATED) { /* If this is the first reference, allocate the resource */ if (cr->cr_refs == 0) { KASSERT(cr->cr_res == NULL, ("non-NULL resource has refcount")); /* Fetch initial resource ID */ if ((cr->cr_res_rid = cr->cr_rid) == -1) { CHIPC_UNLOCK(sc); return (EINVAL); } /* Allocate resource */ cr->cr_res = bhnd_alloc_resource(sc->dev, SYS_RES_MEMORY, &cr->cr_res_rid, cr->cr_addr, cr->cr_end, cr->cr_count, 0); if (cr->cr_res == NULL) { CHIPC_UNLOCK(sc); return (ENXIO); } } /* Increment allocation refcount */ cr->cr_refs++; } /* Handle activation */ if (flags & RF_ACTIVE) { KASSERT(cr->cr_refs > 0, ("cannot activate unallocated resource")); /* If this is the first reference, activate the resource */ if (cr->cr_act_refs == 0) { error = bhnd_activate_resource(sc->dev, SYS_RES_MEMORY, cr->cr_res_rid, cr->cr_res); if (error) { /* Drop any allocation reference acquired * above */ CHIPC_UNLOCK(sc); chipc_release_region(sc, cr, flags &~ RF_ACTIVE); return (error); } } /* Increment activation refcount */ cr->cr_act_refs++; } CHIPC_UNLOCK(sc); return (0); } /** * Release a reference to a chipc_region, deactivating and releasing the * backing resource if the reference count hits zero. * * @param sc chipc driver instance state * @param cr region to retain. * @param flags specify RF_ALLOCATED to release an allocation reference, * RF_ACTIVE to release an activation reference. */ int chipc_release_region(struct chipc_softc *sc, struct chipc_region *cr, int flags) { int error; CHIPC_LOCK(sc); error = 0; KASSERT(cr->cr_res != NULL, ("release on NULL region resource")); if (flags & RF_ACTIVE) { KASSERT(cr->cr_act_refs > 0, ("RF_ACTIVE over-released")); KASSERT(cr->cr_act_refs <= cr->cr_refs, ("RF_ALLOCATED released with RF_ACTIVE held")); /* If this is the last reference, deactivate the resource */ if (cr->cr_act_refs == 1) { error = bhnd_deactivate_resource(sc->dev, SYS_RES_MEMORY, cr->cr_res_rid, cr->cr_res); if (error) goto done; } /* Drop our activation refcount */ cr->cr_act_refs--; } if (flags & RF_ALLOCATED) { KASSERT(cr->cr_refs > 0, ("overrelease of refs")); /* If this is the last reference, release the resource */ if (cr->cr_refs == 1) { error = bhnd_release_resource(sc->dev, SYS_RES_MEMORY, cr->cr_res_rid, cr->cr_res); if (error) goto done; cr->cr_res = NULL; } /* Drop our allocation refcount */ cr->cr_refs--; } done: CHIPC_UNLOCK(sc); return (error); } Index: head/sys/dev/bhnd/cores/chipc/chipcvar.h =================================================================== --- head/sys/dev/bhnd/cores/chipc/chipcvar.h (revision 326078) +++ head/sys/dev/bhnd/cores/chipc/chipcvar.h (revision 326079) @@ -1,169 +1,175 @@ /*- - * 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: * 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 _BHND_CORES_CHIPC_CHIPCVAR_H_ #define _BHND_CORES_CHIPC_CHIPCVAR_H_ #include #include #include #include "chipc.h" DECLARE_CLASS(bhnd_chipc_driver); extern devclass_t bhnd_chipc_devclass; struct chipc_region; const char *chipc_flash_name(chipc_flash type); const char *chipc_flash_bus_name(chipc_flash type); const char *chipc_sflash_device_name(chipc_flash type); /* * ChipCommon device quirks / features */ enum { /** No quirks */ CHIPC_QUIRK_NONE = 0, /** * ChipCommon-controlled SPROM/OTP is supported, along with the * CHIPC_CAP_SPROM capability flag. */ CHIPC_QUIRK_SUPPORTS_SPROM = (1<<1), /** * The BCM4706 NAND flash interface is supported, along with the * CHIPC_CAP_4706_NFLASH capability flag. */ CHIPC_QUIRK_4706_NFLASH = (1<<2), /** * The SPROM is attached via muxed pins. The pins must be switched * to allow reading/writing. */ CHIPC_QUIRK_MUX_SPROM = (1<<3), /** * Access to the SPROM uses pins shared with the 802.11a external PA. * * On modules using these 4331 packages, the CCTRL4331_EXTPA_EN flag * must be cleared to allow SPROM access. */ CHIPC_QUIRK_4331_EXTPA_MUX_SPROM = (1<<4) | CHIPC_QUIRK_MUX_SPROM, /** * Access to the SPROM uses pins shared with the 802.11a external PA. * * On modules using these 4331 chip packages, the external PA is * attached via GPIO 2, 5, and sprom_dout pins. * * When enabling and disabling EXTPA to allow SPROM access, the * CCTRL4331_EXTPA_ON_GPIO2_5 flag must also be set or cleared, * respectively. */ CHIPC_QUIRK_4331_GPIO2_5_MUX_SPROM = (1<<5) | CHIPC_QUIRK_4331_EXTPA_MUX_SPROM, /** * Access to the SPROM uses pins shared with two 802.11a external PAs. * * When enabling and disabling EXTPA, the CCTRL4331_EXTPA_EN2 must also * be cleared to allow SPROM access. */ CHIPC_QUIRK_4331_EXTPA2_MUX_SPROM = (1<<6) | CHIPC_QUIRK_4331_EXTPA_MUX_SPROM, /** * SPROM pins are muxed with the FEM control lines on this 4360-family * device. The muxed pins must be switched to allow reading/writing * the SPROM. */ CHIPC_QUIRK_4360_FEM_MUX_SPROM = (1<<5) | CHIPC_QUIRK_MUX_SPROM, /** Supports CHIPC_CAPABILITIES_EXT register */ CHIPC_QUIRK_SUPPORTS_CAP_EXT = (1<<6), /** Supports HND or IPX OTP registers (CHIPC_OTPST, CHIPC_OTPCTRL, * CHIPC_OTPPROG) */ CHIPC_QUIRK_SUPPORTS_OTP = (1<<7), /** Supports HND OTP registers. */ CHIPC_QUIRK_OTP_HND = (1<<8) | CHIPC_QUIRK_SUPPORTS_OTP, /** Supports IPX OTP registers. */ CHIPC_QUIRK_OTP_IPX = (1<<9) | CHIPC_QUIRK_SUPPORTS_OTP, /** OTP size is defined via CHIPC_OTPLAYOUT register in later * ChipCommon revisions using the 'IPX' OTP controller. */ CHIPC_QUIRK_IPX_OTPL_SIZE = (1<<10) }; /** * chipc child device info. */ 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 */ }; /** * chipc driver instance state. */ struct chipc_softc { device_t dev; struct bhnd_resource *core; /**< core registers. */ struct chipc_region *core_region; /**< region containing core registers */ uint32_t quirks; /**< chipc quirk flags */ struct chipc_caps caps; /**< chipc capabilities */ struct mtx mtx; /**< state mutex. */ size_t sprom_refcnt; /**< SPROM pin enable refcount */ struct rman mem_rman; /**< port memory manager */ STAILQ_HEAD(, chipc_region) mem_regions;/**< memory allocation records */ }; #define CHIPC_LOCK_INIT(sc) \ mtx_init(&(sc)->mtx, device_get_nameunit((sc)->dev), \ "BHND chipc driver lock", MTX_DEF) #define CHIPC_LOCK(sc) mtx_lock(&(sc)->mtx) #define CHIPC_UNLOCK(sc) mtx_unlock(&(sc)->mtx) #define CHIPC_LOCK_ASSERT(sc, what) mtx_assert(&(sc)->mtx, what) #define CHIPC_LOCK_DESTROY(sc) mtx_destroy(&(sc)->mtx) #endif /* _BHND_CORES_CHIPC_CHIPCVAR_H_ */ Index: head/sys/dev/bhnd/cores/pci/bhnd_pci_hostb.c =================================================================== --- head/sys/dev/bhnd/cores/pci/bhnd_pci_hostb.c (revision 326078) +++ head/sys/dev/bhnd/cores/pci/bhnd_pci_hostb.c (revision 326079) @@ -1,662 +1,662 @@ /*- * 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: * 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. */ #include __FBSDID("$FreeBSD$"); /* * Broadcom BHND PCI/PCIe-Gen1 PCI-Host Bridge. * * This driver handles all interactions with PCI bridge cores operating in * endpoint mode. * * Host-level PCI operations are handled at the bhndb bridge level by the * bhndb_pci driver. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "bhnd_pcireg.h" #include "bhnd_pci_hostbvar.h" static const struct bhnd_device_quirk bhnd_pci_quirks[]; static const struct bhnd_device_quirk bhnd_pcie_quirks[]; /* Device driver work-around variations */ typedef enum { BHND_PCI_WAR_ATTACH, /**< apply attach workarounds */ BHND_PCI_WAR_RESUME, /**< apply resume workarounds */ BHND_PCI_WAR_SUSPEND, /**< apply suspend workarounds */ BHND_PCI_WAR_DETACH /**< apply detach workarounds */ } bhnd_pci_war_state; static int bhnd_pci_wars_early_once(struct bhnd_pcihb_softc *sc); static int bhnd_pci_wars_hwup(struct bhnd_pcihb_softc *sc, bhnd_pci_war_state state); static int bhnd_pci_wars_hwdown(struct bhnd_pcihb_softc *sc, bhnd_pci_war_state state); /* * device/quirk tables */ #define BHND_PCI_DEV(_core, _quirks) \ BHND_DEVICE(BCM, _core, NULL, _quirks, BHND_DF_HOSTB) static const struct bhnd_device bhnd_pci_devs[] = { BHND_PCI_DEV(PCI, bhnd_pci_quirks), BHND_PCI_DEV(PCIE, bhnd_pcie_quirks), BHND_DEVICE_END }; static const struct bhnd_device_quirk bhnd_pci_quirks[] = { /* core revision quirks */ BHND_CORE_QUIRK (HWREV_ANY, BHND_PCI_QUIRK_SBTOPCI2_PREF_BURST), BHND_CORE_QUIRK (HWREV_GTE(11), BHND_PCI_QUIRK_SBTOPCI2_READMULTI | BHND_PCI_QUIRK_CLKRUN_DSBL), /* BCM4321CB2 boards that require 960ns latency timer override */ BHND_BOARD_QUIRK(BCM4321CB2, BHND_PCI_QUIRK_960NS_LATTIM_OVR), BHND_BOARD_QUIRK(BCM4321CB2_AG, BHND_PCI_QUIRK_960NS_LATTIM_OVR), BHND_DEVICE_QUIRK_END }; static const struct bhnd_device_quirk bhnd_pcie_quirks[] = { /* core revision quirks */ BHND_CORE_QUIRK (HWREV_EQ (0), BHND_PCIE_QUIRK_SDR9_L0s_HANG), BHND_CORE_QUIRK (HWREV_RANGE(0,1), BHND_PCIE_QUIRK_UR_STATUS_FIX), BHND_CORE_QUIRK (HWREV_EQ (1), BHND_PCIE_QUIRK_PCIPM_REQEN), BHND_CORE_QUIRK (HWREV_RANGE(3,5), BHND_PCIE_QUIRK_ASPM_OVR | BHND_PCIE_QUIRK_SDR9_POLARITY | BHND_PCIE_QUIRK_SDR9_NO_FREQRETRY), BHND_CORE_QUIRK (HWREV_LTE(6), BHND_PCIE_QUIRK_L1_IDLE_THRESH), BHND_CORE_QUIRK (HWREV_GTE(6), BHND_PCIE_QUIRK_SPROM_L23_PCI_RESET), BHND_CORE_QUIRK (HWREV_EQ (7), BHND_PCIE_QUIRK_SERDES_NOPLLDOWN), BHND_CORE_QUIRK (HWREV_GTE(8), BHND_PCIE_QUIRK_L1_TIMER_PERF), BHND_CORE_QUIRK (HWREV_LTE(17), BHND_PCIE_QUIRK_MAX_MRRS_128), /* Apple boards on which BHND_BFL2_PCIEWAR_OVR should be assumed * to be set. */ {{ BHND_MATCH_BOARD_VENDOR (PCI_VENDOR_APPLE), BHND_MATCH_BOARD_REV (HWREV_LTE(0x71)), BHND_MATCH_SROMREV (EQ(4)) }, 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 | BHND_PCIE_QUIRK_DEFAULT_MRRS_512), BHND_A4331_QUIRK(BCM94331X28, BHND_PCIE_QUIRK_SERDES_TXDRV_MAX | BHND_PCIE_QUIRK_DEFAULT_MRRS_512), BHND_A4331_QUIRK(BCM94331X28B, BHND_PCIE_QUIRK_DEFAULT_MRRS_512), BHND_A4331_QUIRK(BCM94331X29B, BHND_PCIE_QUIRK_SERDES_TXDRV_MAX | BHND_PCIE_QUIRK_DEFAULT_MRRS_512), BHND_A4331_QUIRK(BCM94331X19C, BHND_PCIE_QUIRK_SERDES_TXDRV_MAX | BHND_PCIE_QUIRK_DEFAULT_MRRS_512), BHND_A4331_QUIRK(BCM94331X29D, BHND_PCIE_QUIRK_DEFAULT_MRRS_512), BHND_A4331_QUIRK(BCM94331X33, BHND_PCIE_QUIRK_DEFAULT_MRRS_512), #undef BHND_A4331_QUIRK BHND_DEVICE_QUIRK_END }; #define BHND_PCI_SOFTC(_sc) (&((_sc)->common)) #define BHND_PCI_READ_2(_sc, _reg) \ bhnd_bus_read_2(BHND_PCI_SOFTC(_sc)->mem_res, (_reg)) #define BHND_PCI_READ_4(_sc, _reg) \ bhnd_bus_read_4(BHND_PCI_SOFTC(_sc)->mem_res, (_reg)) #define BHND_PCI_WRITE_2(_sc, _reg, _val) \ bhnd_bus_write_2(BHND_PCI_SOFTC(_sc)->mem_res, (_reg), (_val)) #define BHND_PCI_WRITE_4(_sc, _reg, _val) \ bhnd_bus_write_4(BHND_PCI_SOFTC(_sc)->mem_res, (_reg), (_val)) #define BHND_PCI_PROTO_READ_4(_sc, _reg) \ bhnd_pcie_read_proto_reg(BHND_PCI_SOFTC(_sc), (_reg)) #define BHND_PCI_PROTO_WRITE_4(_sc, _reg, _val) \ bhnd_pcie_write_proto_reg(BHND_PCI_SOFTC(_sc), (_reg), (_val)) #define BHND_PCI_MDIO_READ(_sc, _phy, _reg) \ bhnd_pcie_mdio_read(BHND_PCI_SOFTC(_sc), (_phy), (_reg)) #define BHND_PCI_MDIO_WRITE(_sc, _phy, _reg, _val) \ bhnd_pcie_mdio_write(BHND_PCI_SOFTC(_sc), (_phy), (_reg), (_val)) #define BHND_PCI_MDIO_READ_EXT(_sc, _phy, _devaddr, _reg) \ bhnd_pcie_mdio_read_ext(BHND_PCI_SOFTC(_sc), (_phy), (_devaddr), (_reg)) #define BHND_PCI_MDIO_WRITE_EXT(_sc, _phy, _devaddr, _reg, _val) \ bhnd_pcie_mdio_write_ext(BHND_PCI_SOFTC(_sc), (_phy), \ (_devaddr), (_reg), (_val)) #define BPCI_REG_SET(_regv, _attr, _val) \ BHND_PCI_REG_SET((_regv), BHND_ ## _attr, (_val)) #define BPCI_REG_GET(_regv, _attr) \ BHND_PCI_REG_GET((_regv), BHND_ ## _attr) #define BPCI_CMN_REG_SET(_regv, _attr, _val) \ BHND_PCI_CMN_REG_SET(BHND_PCI_SOFTC(_sc)->regfmt, (_regv), \ BHND_ ## _attr, (_val)) #define BPCI_CMN_REG_GET(_regv, _attr) \ BHND_PCI_CMN_REG_GET(BHND_PCI_SOFTC(_sc)->regfmt, (_regv), \ BHND_ ## _attr) static int bhnd_pci_hostb_attach(device_t dev) { struct bhnd_pcihb_softc *sc; int error; sc = device_get_softc(dev); sc->dev = dev; sc->quirks = bhnd_device_quirks(dev, bhnd_pci_devs, sizeof(bhnd_pci_devs[0])); /* Find the host PCI bridge device */ sc->pci_dev = bhnd_find_bridge_root(dev, devclass_find("pci")); if (sc->pci_dev == NULL) { device_printf(dev, "parent pci bridge device not found\n"); return (ENXIO); } /* Common setup */ if ((error = bhnd_pci_generic_attach(dev))) return (error); /* Apply early single-shot work-arounds */ if ((error = bhnd_pci_wars_early_once(sc))) goto failed; /* Apply attach/resume work-arounds */ if ((error = bhnd_pci_wars_hwup(sc, BHND_PCI_WAR_ATTACH))) goto failed; return (0); failed: bhnd_pci_generic_detach(dev); return (error); } static int bhnd_pci_hostb_detach(device_t dev) { struct bhnd_pcihb_softc *sc; int error; sc = device_get_softc(dev); /* Apply suspend/detach work-arounds */ if ((error = bhnd_pci_wars_hwdown(sc, BHND_PCI_WAR_DETACH))) return (error); return (bhnd_pci_generic_detach(dev)); } static int bhnd_pci_hostb_suspend(device_t dev) { struct bhnd_pcihb_softc *sc; int error; sc = device_get_softc(dev); /* Apply suspend/detach work-arounds */ if ((error = bhnd_pci_wars_hwdown(sc, BHND_PCI_WAR_SUSPEND))) return (error); return (bhnd_pci_generic_suspend(dev)); } static int bhnd_pci_hostb_resume(device_t dev) { struct bhnd_pcihb_softc *sc; int error; sc = device_get_softc(dev); if ((error = bhnd_pci_generic_resume(dev))) return (error); /* Apply attach/resume work-arounds */ if ((error = bhnd_pci_wars_hwup(sc, BHND_PCI_WAR_RESUME))) { bhnd_pci_generic_detach(dev); return (error); } return (0); } /** * Apply any hardware work-arounds that must be executed exactly once, early in * the attach process. * * This must be called after core enumeration and discovery of all applicable * quirks, but prior to probe/attach of any cores, parsing of * SPROM, etc. */ static int bhnd_pci_wars_early_once(struct bhnd_pcihb_softc *sc) { int error; /* Set PCI latency timer */ if (sc->quirks & BHND_PCI_QUIRK_960NS_LATTIM_OVR) { pci_write_config(sc->pci_dev, PCIR_LATTIMER, 0x20 /* 960ns */, 1); } /* Determine whether ASPM/CLKREQ should be forced on, or forced off. */ if (sc->quirks & BHND_PCIE_QUIRK_ASPM_OVR) { struct bhnd_board_info board; bool aspm_en; /* Fetch board info */ if ((error = bhnd_read_board_info(sc->dev, &board))) return (error); /* Check board flags */ aspm_en = true; if (board.board_flags2 & BHND_BFL2_PCIEWAR_OVR) aspm_en = false; /* Early Apple devices did not (but should have) set * BHND_BFL2_PCIEWAR_OVR in SPROM. */ if (sc->quirks & BHND_PCIE_QUIRK_BFL2_PCIEWAR_EN) aspm_en = false; sc->aspm_quirk_override.aspm_en = aspm_en; } /* Determine correct polarity by observing the attach-time PCIe PHY * link status. This is used later to reset/force the SerDes * polarity */ if (sc->quirks & BHND_PCIE_QUIRK_SDR9_POLARITY) { uint32_t st; bool inv; st = BHND_PCI_PROTO_READ_4(sc, BHND_PCIE_PLP_STATUSREG); inv = ((st & BHND_PCIE_PLP_POLARITY_INV) != 0); sc->sdr9_quirk_polarity.inv = inv; } /* Override maximum read request size */ if (bhnd_get_class(sc->dev) == BHND_DEVCLASS_PCIE) { int msize; msize = 128; /* compatible with all PCIe-G1 core revisions */ if (sc->quirks & BHND_PCIE_QUIRK_DEFAULT_MRRS_512) msize = 512; if (pci_set_max_read_req(sc->pci_dev, msize) == 0) panic("set mrrs on non-PCIe device"); } return (0); } /** * Apply any hardware workarounds that are required upon attach or resume * of the bridge device. */ static int bhnd_pci_wars_hwup(struct bhnd_pcihb_softc *sc, bhnd_pci_war_state state) { /* Note that the order here matters; these work-arounds * should not be re-ordered without careful review of their * interdependencies */ /* Enable PCI prefetch/burst/readmulti flags */ if (sc->quirks & BHND_PCI_QUIRK_SBTOPCI2_PREF_BURST || sc->quirks & BHND_PCI_QUIRK_SBTOPCI2_READMULTI) { uint32_t sbp2; sbp2 = BHND_PCI_READ_4(sc, BHND_PCI_SBTOPCI2); if (sc->quirks & BHND_PCI_QUIRK_SBTOPCI2_PREF_BURST) sbp2 |= (BHND_PCI_SBTOPCI_PREF|BHND_PCI_SBTOPCI_BURST); if (sc->quirks & BHND_PCI_QUIRK_SBTOPCI2_READMULTI) sbp2 |= BHND_PCI_SBTOPCI_RC_READMULTI; BHND_PCI_WRITE_4(sc, BHND_PCI_SBTOPCI2, sbp2); } /* Disable PCI CLKRUN# */ if (sc->quirks & BHND_PCI_QUIRK_CLKRUN_DSBL) { uint32_t ctl; ctl = BHND_PCI_READ_4(sc, BHND_PCI_CLKRUN_CTL); ctl |= BHND_PCI_CLKRUN_DSBL; BHND_PCI_WRITE_4(sc, BHND_PCI_CLKRUN_CTL, ctl); } /* Enable TLP unmatched address handling work-around */ if (sc->quirks & BHND_PCIE_QUIRK_UR_STATUS_FIX) { uint32_t wrs; wrs = BHND_PCI_PROTO_READ_4(sc, BHND_PCIE_TLP_WORKAROUNDSREG); wrs |= BHND_PCIE_TLP_WORKAROUND_URBIT; BHND_PCI_PROTO_WRITE_4(sc, BHND_PCIE_TLP_WORKAROUNDSREG, wrs); } /* Adjust SerDes CDR tuning to ensure that CDR is stable before sending * data during L0s to L0 exit transitions. */ if (sc->quirks & BHND_PCIE_QUIRK_SDR9_L0s_HANG) { uint16_t sdv; /* Set RX track/acquire timers to 2.064us/40.96us */ sdv = BPCI_REG_SET(0, PCIE_SDR9_RX_TIMER1_LKTRK, (2064/16)); sdv = BPCI_REG_SET(sdv, PCIE_SDR9_RX_TIMER1_LKACQ, (40960/1024)); BHND_PCI_MDIO_WRITE(sc, BHND_PCIE_PHY_SDR9_TXRX, BHND_PCIE_SDR9_RX_TIMER1, sdv); /* Apply CDR frequency workaround */ sdv = BHND_PCIE_SDR9_RX_CDR_FREQ_OVR_EN; sdv = BPCI_REG_SET(sdv, PCIE_SDR9_RX_CDR_FREQ_OVR, 0x0); BHND_PCI_MDIO_WRITE(sc, BHND_PCIE_PHY_SDR9_TXRX, BHND_PCIE_SDR9_RX_CDR, sdv); /* Apply CDR BW tunings */ sdv = 0; sdv = BPCI_REG_SET(sdv, PCIE_SDR9_RX_CDRBW_INTGTRK, 0x2); sdv = BPCI_REG_SET(sdv, PCIE_SDR9_RX_CDRBW_INTGACQ, 0x4); sdv = BPCI_REG_SET(sdv, PCIE_SDR9_RX_CDRBW_PROPTRK, 0x6); sdv = BPCI_REG_SET(sdv, PCIE_SDR9_RX_CDRBW_PROPACQ, 0x6); BHND_PCI_MDIO_WRITE(sc, BHND_PCIE_PHY_SDR9_TXRX, BHND_PCIE_SDR9_RX_CDRBW, sdv); } /* Force correct SerDes polarity */ if (sc->quirks & BHND_PCIE_QUIRK_SDR9_POLARITY) { uint16_t rxctl; rxctl = BHND_PCI_MDIO_READ(sc, BHND_PCIE_PHY_SDR9_TXRX, BHND_PCIE_SDR9_RX_CTRL); rxctl |= BHND_PCIE_SDR9_RX_CTRL_FORCE; if (sc->sdr9_quirk_polarity.inv) rxctl |= BHND_PCIE_SDR9_RX_CTRL_POLARITY_INV; else rxctl &= ~BHND_PCIE_SDR9_RX_CTRL_POLARITY_INV; BHND_PCI_MDIO_WRITE(sc, BHND_PCIE_PHY_SDR9_TXRX, BHND_PCIE_SDR9_RX_CTRL, rxctl); } /* Disable startup retry on PLL frequency detection failure */ if (sc->quirks & BHND_PCIE_QUIRK_SDR9_NO_FREQRETRY) { uint16_t pctl; pctl = BHND_PCI_MDIO_READ(sc, BHND_PCIE_PHY_SDR9_PLL, BHND_PCIE_SDR9_PLL_CTRL); pctl &= ~BHND_PCIE_SDR9_PLL_CTRL_FREQDET_EN; BHND_PCI_MDIO_WRITE(sc, BHND_PCIE_PHY_SDR9_PLL, BHND_PCIE_SDR9_PLL_CTRL, pctl); } /* Explicitly enable PCI-PM */ if (sc->quirks & BHND_PCIE_QUIRK_PCIPM_REQEN) { uint32_t lcreg; lcreg = BHND_PCI_PROTO_READ_4(sc, BHND_PCIE_DLLP_LCREG); lcreg |= BHND_PCIE_DLLP_LCREG_PCIPM_EN; BHND_PCI_PROTO_WRITE_4(sc, BHND_PCIE_DLLP_LCREG, lcreg); } /* Adjust L1 timer to fix slow L1->L0 transitions */ if (sc->quirks & BHND_PCIE_QUIRK_L1_IDLE_THRESH) { uint32_t pmt; pmt = BHND_PCI_PROTO_READ_4(sc, BHND_PCIE_DLLP_PMTHRESHREG); pmt = BPCI_REG_SET(pmt, PCIE_L1THRESHOLDTIME, BHND_PCIE_L1THRESHOLD_WARVAL); BHND_PCI_PROTO_WRITE_4(sc, BHND_PCIE_DLLP_PMTHRESHREG, pmt); } /* Extend L1 timer for better performance. * TODO: We could enable/disable this on demand for better power * savings if we tie this to HT clock request handling */ if (sc->quirks & BHND_PCIE_QUIRK_L1_TIMER_PERF) { uint32_t pmt; pmt = BHND_PCI_PROTO_READ_4(sc, BHND_PCIE_DLLP_PMTHRESHREG); pmt |= BHND_PCIE_ASPMTIMER_EXTEND; BHND_PCI_PROTO_WRITE_4(sc, BHND_PCIE_DLLP_PMTHRESHREG, pmt); } /* Override ASPM/ECPM settings in SPROM shadow and PCIER_LINK_CTL */ if (sc->quirks & BHND_PCIE_QUIRK_ASPM_OVR) { bus_size_t reg; uint16_t cfg; /* Set ASPM L1/L0s flags in SPROM shadow */ reg = BHND_PCIE_SPROM_SHADOW + BHND_PCIE_SRSH_ASPM_OFFSET; cfg = BHND_PCI_READ_2(sc, reg); if (sc->aspm_quirk_override.aspm_en) cfg |= BHND_PCIE_SRSH_ASPM_ENB; else cfg &= ~BHND_PCIE_SRSH_ASPM_ENB; BHND_PCI_WRITE_2(sc, reg, cfg); /* Set ASPM/ECPM (CLKREQ) flags in PCIe link control register */ cfg = pcie_read_config(sc->pci_dev, PCIER_LINK_CTL, 2); if (sc->aspm_quirk_override.aspm_en) cfg |= PCIEM_LINK_CTL_ASPMC; else cfg &= ~PCIEM_LINK_CTL_ASPMC; cfg &= ~PCIEM_LINK_CTL_ECPM; /* CLKREQ# */ pcie_write_config(sc->pci_dev, PCIER_LINK_CTL, cfg, 2); /* Set CLKREQ (ECPM) flags in SPROM shadow */ reg = BHND_PCIE_SPROM_SHADOW + BHND_PCIE_SRSH_CLKREQ_OFFSET_R5; cfg = BHND_PCI_READ_2(sc, reg); if (sc->aspm_quirk_override.aspm_en) cfg |= BHND_PCIE_SRSH_CLKREQ_ENB; else cfg &= ~BHND_PCIE_SRSH_CLKREQ_ENB; BHND_PCI_WRITE_2(sc, reg, cfg); } /* Enable L23READY_EXIT_NOPRST if not already set in SPROM. */ if (sc->quirks & BHND_PCIE_QUIRK_SPROM_L23_PCI_RESET) { bus_size_t reg; uint16_t cfg; /* Fetch the misc cfg flags from SPROM */ reg = BHND_PCIE_SPROM_SHADOW + BHND_PCIE_SRSH_PCIE_MISC_CONFIG; cfg = BHND_PCI_READ_2(sc, reg); /* Write EXIT_NOPRST flag if not already set in SPROM */ if (!(cfg & BHND_PCIE_SRSH_L23READY_EXIT_NOPRST)) { cfg |= BHND_PCIE_SRSH_L23READY_EXIT_NOPRST; BHND_PCI_WRITE_2(sc, reg, cfg); } } /* Disable SerDes PLL down */ if (sc->quirks & BHND_PCIE_QUIRK_SERDES_NOPLLDOWN) { device_t bhnd, chipc; bus_size_t reg; bhnd = device_get_parent(sc->dev); chipc = bhnd_bus_find_child(bhnd, BHND_DEVCLASS_CC, 0); KASSERT(chipc != NULL, ("missing chipcommon device")); /* Write SerDes PLL disable flag to the ChipCommon core */ BHND_CHIPC_WRITE_CHIPCTRL(chipc, CHIPCTRL_4321_PLL_DOWN, CHIPCTRL_4321_PLL_DOWN); /* Clear SPROM shadow backdoor register */ reg = BHND_PCIE_SPROM_SHADOW + BHND_PCIE_SRSH_BD_OFFSET; BHND_PCI_WRITE_2(sc, reg, 0); } /* Adjust TX drive strength and pre-emphasis coefficient */ if (sc->quirks & BHND_PCIE_QUIRK_SERDES_TXDRV_ADJUST) { uint16_t txdrv; /* Fetch current TX driver parameters */ txdrv = BHND_PCI_MDIO_READ_EXT(sc, BHND_PCIE_PHYADDR_SD, BHND_PCIE_SD_REGS_TX0, BHND_PCIE_SD_TX_DRIVER); /* Set 700mV drive strength */ if (sc->quirks & BHND_PCIE_QUIRK_SERDES_TXDRV_700MV) { txdrv = BPCI_REG_SET(txdrv, PCIE_SD_TX_DRIVER_P2_COEFF, BHND_PCIE_APPLE_TX_P2_COEFF_700MV); txdrv = BPCI_REG_SET(txdrv, PCIE_SD_TX_DRIVER_IDRIVER, BHND_PCIE_APPLE_TX_IDRIVER_700MV); } /* ... or, set max drive strength */ if (sc->quirks & BHND_PCIE_QUIRK_SERDES_TXDRV_MAX) { txdrv = BPCI_REG_SET(txdrv, PCIE_SD_TX_DRIVER_P2_COEFF, BHND_PCIE_APPLE_TX_P2_COEFF_MAX); txdrv = BPCI_REG_SET(txdrv, PCIE_SD_TX_DRIVER_IDRIVER, BHND_PCIE_APPLE_TX_IDRIVER_MAX); } BHND_PCI_MDIO_WRITE_EXT(sc, BHND_PCIE_PHYADDR_SD, BHND_PCIE_SD_REGS_TX0, BHND_PCIE_SD_TX_DRIVER, txdrv); } return (0); } /** * Apply any hardware workarounds that are required upon detach or suspend * of the bridge device. */ static int bhnd_pci_wars_hwdown(struct bhnd_pcihb_softc *sc, bhnd_pci_war_state state) { /* Reduce L1 timer for better power savings. * TODO: We could enable/disable this on demand for better power * savings if we tie this to HT clock request handling */ if (sc->quirks & BHND_PCIE_QUIRK_L1_TIMER_PERF) { uint32_t pmt; pmt = BHND_PCI_PROTO_READ_4(sc, BHND_PCIE_DLLP_PMTHRESHREG); pmt &= ~BHND_PCIE_ASPMTIMER_EXTEND; BHND_PCI_PROTO_WRITE_4(sc, BHND_PCIE_DLLP_PMTHRESHREG, pmt); } /* Enable CLKREQ (ECPM). If suspending, also disable ASPM L1 entry */ if (sc->quirks & BHND_PCIE_QUIRK_ASPM_OVR) { uint16_t lcreg; lcreg = pcie_read_config(sc->pci_dev, PCIER_LINK_CTL, 2); lcreg |= PCIEM_LINK_CTL_ECPM; /* CLKREQ# */ if (state == BHND_PCI_WAR_SUSPEND) lcreg &= ~PCIEM_LINK_CTL_ASPMC_L1; pcie_write_config(sc->pci_dev, PCIER_LINK_CTL, lcreg, 2); } return (0); } static device_method_t bhnd_pci_hostb_methods[] = { /* Device interface */ DEVMETHOD(device_attach, bhnd_pci_hostb_attach), DEVMETHOD(device_detach, bhnd_pci_hostb_detach), DEVMETHOD(device_suspend, bhnd_pci_hostb_suspend), DEVMETHOD(device_resume, bhnd_pci_hostb_resume), DEVMETHOD_END }; DEFINE_CLASS_1(bhnd_hostb, bhnd_pci_hostb_driver, bhnd_pci_hostb_methods, sizeof(struct bhnd_pcihb_softc), bhnd_pci_driver); DRIVER_MODULE(bhnd_pci_hostb, bhnd, bhnd_pci_hostb_driver, bhnd_hostb_devclass, 0, 0); MODULE_VERSION(bhnd_pci_hostb, 1); MODULE_DEPEND(bhnd_pci_hostb, bhnd, 1, 1, 1); MODULE_DEPEND(bhnd_pci_hostb, bhnd_pci, 1, 1, 1); Index: head/sys/dev/bhnd/cores/usb/bhnd_usb.c =================================================================== --- head/sys/dev/bhnd/cores/usb/bhnd_usb.c (revision 326078) +++ head/sys/dev/bhnd/cores/usb/bhnd_usb.c (revision 326079) @@ -1,481 +1,500 @@ /*- * Copyright (c) 2010, Aleksandr Rybalko * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice unmodified, this list of conditions, and the following * disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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$"); /* * Ported version of BroadCom USB core driver from ZRouter project */ #include #include #include #include #include #include #include #include #include #include #include #include #include "bhnd_usbvar.h" /****************************** Variables ************************************/ static const struct bhnd_device bhnd_usb_devs[] = { BHND_DEVICE(BCM, USB20H, "USB2.0 Host core", NULL), BHND_DEVICE_END }; /****************************** Prototypes ***********************************/ static int bhnd_usb_attach(device_t); static int bhnd_usb_probe(device_t); static device_t bhnd_usb_add_child(device_t dev, u_int order, const char *name, int unit); static int bhnd_usb_print_all_resources(device_t dev); static int bhnd_usb_print_child(device_t bus, device_t child); static struct resource * bhnd_usb_alloc_resource(device_t bus, device_t child, int type, int *rid, rman_res_t start, rman_res_t end, rman_res_t count, u_int flags); static int bhnd_usb_release_resource(device_t dev, device_t child, int type, int rid, struct resource *r); static struct resource_list * bhnd_usb_get_reslist(device_t dev, device_t child); static int bhnd_usb_probe(device_t dev) { const struct bhnd_device *id; id = bhnd_device_lookup(dev, bhnd_usb_devs, sizeof(bhnd_usb_devs[0])); if (id == NULL) return (ENXIO); device_set_desc(dev, id->desc); return (BUS_PROBE_DEFAULT); } static int bhnd_usb_attach(device_t dev) { struct bhnd_usb_softc *sc; int rid; uint32_t tmp; int tries, err; sc = device_get_softc(dev); bhnd_reset_hw(dev, 0); /* * Allocate the resources which the parent bus has already * determined for us. * XXX: There are few windows (usually 2), RID should be chip-specific */ rid = 0; sc->sc_mem = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid, RF_ACTIVE); if (sc->sc_mem == NULL) { BHND_ERROR_DEV(dev, "unable to allocate memory"); return (ENXIO); } sc->sc_bt = rman_get_bustag(sc->sc_mem); sc->sc_bh = rman_get_bushandle(sc->sc_mem); sc->sc_maddr = rman_get_start(sc->sc_mem); sc->sc_msize = rman_get_size(sc->sc_mem); rid = 0; sc->sc_irq = bus_alloc_resource_any(dev, SYS_RES_IRQ, &rid, RF_SHAREABLE | RF_ACTIVE); if (sc->sc_irq == NULL) { BHND_ERROR_DEV(dev, "unable to allocate IRQ"); return (ENXIO); } sc->sc_irqn = rman_get_start(sc->sc_irq); sc->mem_rman.rm_start = sc->sc_maddr; sc->mem_rman.rm_end = sc->sc_maddr + sc->sc_msize - 1; sc->mem_rman.rm_type = RMAN_ARRAY; sc->mem_rman.rm_descr = "BHND USB core I/O memory addresses"; if (rman_init(&sc->mem_rman) != 0 || rman_manage_region(&sc->mem_rman, sc->mem_rman.rm_start, sc->mem_rman.rm_end) != 0) { 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); #define OHCI_CONTROL 0x04 bus_write_4(sc->sc_mem, OHCI_CONTROL, 0); if ( bhnd_get_device(dev) == BHND_COREID_USB20H) { uint32_t rev = bhnd_get_hwrev(dev); BHND_INFO_DEV(dev, "USB HOST 2.0 setup for rev %d", rev); if (rev == 1/* ? == 2 */) { /* SiBa code */ /* Change Flush control reg */ tmp = bus_read_4(sc->sc_mem, 0x400) & ~0x8; bus_write_4(sc->sc_mem, 0x400, tmp); tmp = bus_read_4(sc->sc_mem, 0x400); BHND_DEBUG_DEV(dev, "USB20H fcr: 0x%x", tmp); /* Change Shim control reg */ tmp = bus_read_4(sc->sc_mem, 0x304) & ~0x100; bus_write_4(sc->sc_mem, 0x304, tmp); tmp = bus_read_4(sc->sc_mem, 0x304); BHND_DEBUG_DEV(dev, "USB20H shim: 0x%x", tmp); } else if (rev >= 5) { /* BCMA code */ err = bhnd_alloc_pmu(dev); if(err) { BHND_ERROR_DEV(dev, "can't alloc pmu: %d", err); return (err); } err = bhnd_request_ext_rsrc(dev, 1); if(err) { BHND_ERROR_DEV(dev, "can't req ext: %d", err); return (err); } /* Take out of resets */ bus_write_4(sc->sc_mem, 0x200, 0x4ff); DELAY(25); bus_write_4(sc->sc_mem, 0x200, 0x6ff); DELAY(25); /* Make sure digital and AFE are locked in USB PHY */ bus_write_4(sc->sc_mem, 0x524, 0x6b); DELAY(50); bus_read_4(sc->sc_mem, 0x524); DELAY(50); bus_write_4(sc->sc_mem, 0x524, 0xab); DELAY(50); bus_read_4(sc->sc_mem, 0x524); DELAY(50); bus_write_4(sc->sc_mem, 0x524, 0x2b); DELAY(50); bus_read_4(sc->sc_mem, 0x524); DELAY(50); bus_write_4(sc->sc_mem, 0x524, 0x10ab); DELAY(50); bus_read_4(sc->sc_mem, 0x524); tries = 10000; for (;;) { DELAY(10); tmp = bus_read_4(sc->sc_mem, 0x528); if (tmp & 0xc000) break; if (--tries != 0) continue; tmp = bus_read_4(sc->sc_mem, 0x528); BHND_ERROR_DEV(dev, "USB20H mdio_rddata 0x%08x", tmp); } /* XXX: Puzzle code */ bus_write_4(sc->sc_mem, 0x528, 0x80000000); bus_read_4(sc->sc_mem, 0x314); DELAY(265); bus_write_4(sc->sc_mem, 0x200, 0x7ff); DELAY(10); /* Take USB and HSIC out of non-driving modes */ bus_write_4(sc->sc_mem, 0x510, 0); } } bus_generic_probe(dev); if (bhnd_get_device(dev) == BHND_COREID_USB20H && ( bhnd_get_hwrev(dev) > 0)) bhnd_usb_add_child(dev, 0, "ehci", -1); bhnd_usb_add_child(dev, 1, "ohci", -1); bus_generic_attach(dev); return (0); } static struct resource * bhnd_usb_alloc_resource(device_t bus, device_t child, int type, int *rid, rman_res_t start, rman_res_t end, rman_res_t count, u_int flags) { 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); if (rle->res != NULL) panic("%s: resource entry is busy", __func__); start = rle->start; end = rle->end; count = rle->count; } else { BHND_INFO_DEV(bus, "trying allocate %d - %d (%jx-%jx) for %s", type, *rid, start, end, device_get_nameunit(child) ); } /* * If the request is for a resource which we manage, * attempt to satisfy the allocation ourselves. */ if (type == SYS_RES_MEMORY) { rv = rman_reserve_resource(&sc->mem_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); } - 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 * bhnd_usb_get_reslist(device_t dev, device_t child) { struct bhnd_usb_devinfo *sdi; sdi = device_get_ivars(child); return (&sdi->sdi_rl); } static int 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); } static int bhnd_usb_print_all_resources(device_t dev) { struct bhnd_usb_devinfo *sdi; struct resource_list *rl; int retval; retval = 0; sdi = device_get_ivars(dev); rl = &sdi->sdi_rl; if (STAILQ_FIRST(rl)) retval += printf(" at"); retval += resource_list_print_type(rl, "mem", SYS_RES_MEMORY, "%jx"); retval += resource_list_print_type(rl, "irq", SYS_RES_IRQ, "%ld"); return (retval); } static int bhnd_usb_print_child(device_t bus, device_t child) { int retval = 0; retval += bus_print_child_header(bus, child); retval += bhnd_usb_print_all_resources(child); if (device_get_flags(child)) retval += printf(" flags %#x", device_get_flags(child)); retval += printf(" on %s\n", device_get_nameunit(bus)); return (retval); } static device_t bhnd_usb_add_child(device_t dev, u_int order, const char *name, int unit) { 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), DEVMETHOD(device_probe, bhnd_usb_probe), /* 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), DEVMETHOD(bus_release_resource, bhnd_usb_release_resource), /* Bus interface: generic part */ 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_teardown_intr, bus_generic_teardown_intr), DEVMETHOD_END }; static devclass_t bhnd_usb_devclass; DEFINE_CLASS_0(bhnd_usb, bhnd_usb_driver, bhnd_usb_methods, sizeof(struct bhnd_usb_softc)); DRIVER_MODULE(bhnd_usb, bhnd, bhnd_usb_driver, bhnd_usb_devclass, 0, 0); MODULE_VERSION(bhnd_usb, 1); Index: head/sys/dev/bhnd/cores/usb/bhnd_usbvar.h =================================================================== --- head/sys/dev/bhnd/cores/usb/bhnd_usbvar.h (revision 326078) +++ head/sys/dev/bhnd/cores/usb/bhnd_usbvar.h (revision 326079) @@ -1,59 +1,60 @@ /*- * Copyright (c) 2010, Aleksandr Rybalko * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice unmodified, this list of conditions, and the following * disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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. * * $FreeBSD$ * */ #ifndef _BHND_USBVAR_H_ #define _BHND_USBVAR_H_ struct bhnd_usb_softc { bus_space_tag_t sc_bt; bus_space_handle_t sc_bh; bus_addr_t sc_maddr; bus_size_t sc_msize; bus_addr_t sc_irqn; struct intr_event *sc_events; /* IRQ events structs */ struct resource *sc_mem; struct resource *sc_irq; struct rman mem_rman; struct rman irq_rman; int devid; }; 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; }; #endif /* _BHND_USBVAR_H_ */ Index: head/sys/dev/bhnd/siba/siba.c =================================================================== --- head/sys/dev/bhnd/siba/siba.c (revision 326078) +++ head/sys/dev/bhnd/siba/siba.c (revision 326079) @@ -1,1004 +1,1086 @@ /*- - * 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: * 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. */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include "sibareg.h" #include "sibavar.h" static bhnd_erom_class_t * siba_get_erom_class(driver_t *driver) { return (&siba_erom_parser); } int siba_probe(device_t dev) { device_set_desc(dev, "SIBA BHND bus"); return (BUS_PROBE_DEFAULT); } /** * Default siba(4) bus driver implementation of DEVICE_ATTACH(). * * This implementation initializes internal siba(4) state and performs * bus enumeration, and must be called by subclassing drivers in * DEVICE_ATTACH() before any other bus methods. */ int siba_attach(device_t dev) { struct siba_softc *sc; int error; sc = device_get_softc(dev); sc->dev = dev; /* Enumerate children */ if ((error = siba_add_children(dev))) { device_delete_children(dev); return (error); } return (0); } int siba_detach(device_t dev) { return (bhnd_generic_detach(dev)); } int siba_resume(device_t dev) { return (bhnd_generic_resume(dev)); } int siba_suspend(device_t dev) { return (bhnd_generic_suspend(dev)); } static int siba_read_ivar(device_t dev, device_t child, int index, uintptr_t *result) { const struct siba_devinfo *dinfo; const struct bhnd_core_info *cfg; dinfo = device_get_ivars(child); cfg = &dinfo->core_id.core_info; switch (index) { case BHND_IVAR_VENDOR: *result = cfg->vendor; return (0); case BHND_IVAR_DEVICE: *result = cfg->device; return (0); case BHND_IVAR_HWREV: *result = cfg->hwrev; return (0); case BHND_IVAR_DEVICE_CLASS: *result = bhnd_core_class(cfg); return (0); case BHND_IVAR_VENDOR_NAME: *result = (uintptr_t) bhnd_vendor_name(cfg->vendor); return (0); case BHND_IVAR_DEVICE_NAME: *result = (uintptr_t) bhnd_core_name(cfg); return (0); case BHND_IVAR_CORE_INDEX: *result = cfg->core_idx; return (0); case BHND_IVAR_CORE_UNIT: *result = cfg->unit; return (0); case BHND_IVAR_PMU_INFO: *result = (uintptr_t) dinfo->pmu_info; return (0); default: return (ENOENT); } } static int siba_write_ivar(device_t dev, device_t child, int index, uintptr_t value) { struct siba_devinfo *dinfo; dinfo = device_get_ivars(child); switch (index) { case BHND_IVAR_VENDOR: case BHND_IVAR_DEVICE: case BHND_IVAR_HWREV: case BHND_IVAR_DEVICE_CLASS: case BHND_IVAR_VENDOR_NAME: case BHND_IVAR_DEVICE_NAME: case BHND_IVAR_CORE_INDEX: case BHND_IVAR_CORE_UNIT: return (EINVAL); case BHND_IVAR_PMU_INFO: dinfo->pmu_info = (struct bhnd_core_pmu_info *) value; return (0); default: return (ENOENT); } } static struct resource_list * siba_get_resource_list(device_t dev, device_t child) { struct siba_devinfo *dinfo = device_get_ivars(child); return (&dinfo->resources); } static int siba_read_iost(device_t dev, device_t child, uint16_t *iost) { uint32_t tmhigh; int error; error = bhnd_read_config(child, SIBA_CFG0_TMSTATEHIGH, &tmhigh, 4); if (error) return (error); *iost = (SIBA_REG_GET(tmhigh, TMH_SISF)); return (0); } static int siba_read_ioctl(device_t dev, device_t child, uint16_t *ioctl) { uint32_t ts_low; int error; if ((error = bhnd_read_config(child, SIBA_CFG0_TMSTATELOW, &ts_low, 4))) return (error); *ioctl = (SIBA_REG_GET(ts_low, TML_SICF)); return (0); } static int siba_write_ioctl(device_t dev, device_t child, uint16_t value, uint16_t mask) { struct siba_devinfo *dinfo; struct bhnd_resource *r; uint32_t ts_low, ts_mask; if (device_get_parent(child) != dev) return (EINVAL); /* 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 */ ts_mask = (mask << SIBA_TML_SICF_SHIFT) & SIBA_TML_SICF_MASK; ts_low = (value << SIBA_TML_SICF_SHIFT) & ts_mask; return (siba_write_target_state(child, dinfo, SIBA_CFG0_TMSTATELOW, ts_low, ts_mask)); } static bool siba_is_hw_suspended(device_t dev, device_t child) { uint32_t ts_low; uint16_t ioctl; int error; /* Fetch target state */ error = bhnd_read_config(child, SIBA_CFG0_TMSTATELOW, &ts_low, 4); if (error) { device_printf(child, "error reading HW reset state: %d\n", error); return (true); } /* Is core held in RESET? */ if (ts_low & SIBA_TML_RESET) return (true); /* Is core clocked? */ ioctl = SIBA_REG_GET(ts_low, TML_SICF); if (!(ioctl & BHND_IOCTL_CLK_EN)) return (true); return (false); } static int siba_reset_hw(device_t dev, device_t child, uint16_t ioctl) { struct siba_devinfo *dinfo; struct bhnd_resource *r; uint32_t ts_low, imstate; int error; if (device_get_parent(child) != dev) return (EINVAL); 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 * BHND_IOCTL_CLK_FORCE. */ if (ioctl & (BHND_IOCTL_CLK_EN | BHND_IOCTL_CLK_FORCE)) return (EINVAL); /* Place core into known RESET state */ if ((error = BHND_BUS_SUSPEND_HW(dev, child))) return (error); /* Leaving the core in reset, set the caller's IOCTL flags and * enable the core's clocks. */ ts_low = (ioctl | BHND_IOCTL_CLK_EN | BHND_IOCTL_CLK_FORCE) << SIBA_TML_SICF_SHIFT; error = siba_write_target_state(child, dinfo, SIBA_CFG0_TMSTATELOW, ts_low, SIBA_TML_SICF_MASK); if (error) return (error); /* Clear any target errors */ if (bhnd_bus_read_4(r, SIBA_CFG0_TMSTATEHIGH) & SIBA_TMH_SERR) { error = siba_write_target_state(child, dinfo, SIBA_CFG0_TMSTATEHIGH, 0, SIBA_TMH_SERR); if (error) return (error); } /* Clear any initiator errors */ imstate = bhnd_bus_read_4(r, SIBA_CFG0_IMSTATE); if (imstate & (SIBA_IM_IBE|SIBA_IM_TO)) { error = siba_write_target_state(child, dinfo, SIBA_CFG0_IMSTATE, 0, SIBA_IM_IBE|SIBA_IM_TO); if (error) return (error); } /* Release from RESET while leaving clocks forced, ensuring the * signal propagates throughout the core */ error = siba_write_target_state(child, dinfo, SIBA_CFG0_TMSTATELOW, 0x0, SIBA_TML_RESET); if (error) return (error); /* The core should now be active; we can clear the BHND_IOCTL_CLK_FORCE * bit and allow the core to manage clock gating. */ error = siba_write_target_state(child, dinfo, SIBA_CFG0_TMSTATELOW, 0x0, (BHND_IOCTL_CLK_FORCE << SIBA_TML_SICF_SHIFT)); if (error) return (error); return (0); } static int siba_suspend_hw(device_t dev, device_t child) { struct siba_devinfo *dinfo; struct bhnd_core_pmu_info *pm; struct bhnd_resource *r; uint32_t idl, ts_low; uint16_t ioctl; int error; if (device_get_parent(child) != dev) return (EINVAL); dinfo = device_get_ivars(child); 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? */ ts_low = bhnd_bus_read_4(r, SIBA_CFG0_TMSTATELOW); if (ts_low & SIBA_TML_RESET) { /* Clear IOCTL flags, ensuring the clock is disabled */ return (siba_write_target_state(child, dinfo, SIBA_CFG0_TMSTATELOW, 0x0, SIBA_TML_SICF_MASK)); return (0); } /* If clocks are already disabled, we can put the core directly * into RESET */ ioctl = SIBA_REG_GET(ts_low, TML_SICF); if (!(ioctl & BHND_IOCTL_CLK_EN)) { /* Set RESET and clear IOCTL flags */ return (siba_write_target_state(child, dinfo, SIBA_CFG0_TMSTATELOW, SIBA_TML_RESET, SIBA_TML_RESET | SIBA_TML_SICF_MASK)); } /* Reject any further target backplane transactions */ error = siba_write_target_state(child, dinfo, SIBA_CFG0_TMSTATELOW, SIBA_TML_REJ, SIBA_TML_REJ); if (error) return (error); /* If this is an initiator core, we need to reject initiator * transactions too. */ idl = bhnd_bus_read_4(r, SIBA_CFG0_IDLOW); if (idl & SIBA_IDL_INIT) { error = siba_write_target_state(child, dinfo, SIBA_CFG0_IMSTATE, SIBA_IM_RJ, SIBA_IM_RJ); if (error) return (error); } /* Put the core into RESET|REJECT, forcing clocks to ensure the RESET * signal propagates throughout the core, leaving REJECT asserted. */ ts_low = SIBA_TML_RESET; ts_low |= (BHND_IOCTL_CLK_EN | BHND_IOCTL_CLK_FORCE) << SIBA_TML_SICF_SHIFT; error = siba_write_target_state(child, dinfo, SIBA_CFG0_TMSTATELOW, ts_low, ts_low); if (error) return (error); /* Give RESET ample time */ DELAY(10); /* Leaving core in reset, disable all clocks, clear REJ flags and * IOCTL state */ error = siba_write_target_state(child, dinfo, SIBA_CFG0_TMSTATELOW, SIBA_TML_RESET, SIBA_TML_RESET | SIBA_TML_REJ | SIBA_TML_SICF_MASK); if (error) return (error); /* Clear previously asserted initiator reject */ if (idl & SIBA_IDL_INIT) { error = siba_write_target_state(child, dinfo, SIBA_CFG0_IMSTATE, 0, SIBA_IM_RJ); if (error) return (error); } /* Core is now in RESET, with clocks disabled and REJ not asserted. * * We lastly need to inform the PMU, releasing any outstanding per-core * PMU requests */ if (pm != NULL) { if ((error = BHND_PMU_CORE_RELEASE(pm->pm_pmu, pm))) return (error); } return (0); } static int siba_read_config(device_t dev, device_t child, bus_size_t offset, void *value, u_int width) { struct siba_devinfo *dinfo; rman_res_t r_size; /* Must be directly attached */ if (device_get_parent(child) != dev) return (EINVAL); /* 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); } } static int siba_write_config(device_t dev, device_t child, bus_size_t offset, const void *value, u_int width) { struct siba_devinfo *dinfo; struct bhnd_resource *r; rman_res_t r_size; /* Must be directly attached */ if (device_get_parent(child) != dev) return (EINVAL); /* 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 */ r_size = rman_get_size(r->res); if (r_size < offset || r_size - offset < width) return (EFAULT); switch (width) { case 1: bhnd_bus_write_1(r, offset, *(const uint8_t *)value); return (0); case 2: bhnd_bus_write_2(r, offset, *(const uint8_t *)value); return (0); case 4: bhnd_bus_write_4(r, offset, *(const uint8_t *)value); return (0); default: return (EINVAL); } } static u_int siba_get_port_count(device_t dev, device_t child, bhnd_port_type type) { struct siba_devinfo *dinfo; /* delegate non-bus-attached devices to our parent */ if (device_get_parent(child) != dev) return (BHND_BUS_GET_PORT_COUNT(device_get_parent(dev), child, 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 siba_get_region_count(device_t dev, device_t child, bhnd_port_type type, u_int port) { struct siba_devinfo *dinfo; /* delegate non-bus-attached devices to our parent */ if (device_get_parent(child) != dev) return (BHND_BUS_GET_REGION_COUNT(device_get_parent(dev), child, 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 siba_get_port_rid(device_t dev, device_t child, bhnd_port_type port_type, u_int port_num, u_int region_num) { 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) return (BHND_BUS_GET_PORT_RID(device_get_parent(dev), child, 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 siba_decode_port_rid(device_t dev, device_t child, int type, int rid, bhnd_port_type *port_type, u_int *port_num, u_int *region_num) { struct siba_devinfo *dinfo; /* delegate non-bus-attached devices to our parent */ if (device_get_parent(child) != dev) return (BHND_BUS_DECODE_PORT_RID(device_get_parent(dev), child, type, rid, port_type, port_num, region_num)); dinfo = device_get_ivars(child); /* Ports are always memory mapped */ 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); } static int siba_get_region_addr(device_t dev, device_t child, bhnd_port_type port_type, u_int port_num, u_int region_num, bhnd_addr_t *addr, bhnd_size_t *size) { 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) { return (BHND_BUS_GET_REGION_ADDR(device_get_parent(dev), child, port_type, port_num, region_num, addr, size)); } 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); } /** * Register all address space mappings for @p di. * * @param dev The siba bus device. * @param di The device info instance on which to register all address * space entries. * @param r A resource mapping the enumeration table block for @p di. */ static int siba_register_addrspaces(device_t dev, struct siba_devinfo *di, struct bhnd_resource *r) { struct siba_core_id *cid; uint32_t addr; uint32_t size; int error; cid = &di->core_id; /* Register the device address space entries */ for (uint8_t i = 0; i < di->core_id.num_addrspace; i++) { uint32_t adm; u_int adm_offset; uint32_t bus_reserved; /* Determine the register offset */ adm_offset = siba_admatch_offset(i); if (adm_offset == 0) { device_printf(dev, "addrspace %hhu is unsupported", i); return (ENODEV); } /* Fetch the address match register value */ adm = bhnd_bus_read_4(r, adm_offset); /* Parse the value */ if ((error = siba_parse_admatch(adm, &addr, &size))) { device_printf(dev, "failed to decode address " " match register value 0x%x\n", adm); return (error); } /* If this is the device's core/enumeration addrespace, * reserve the Sonics configuration register blocks for the * use of our bus. */ bus_reserved = 0; if (i == SIBA_CORE_ADDRSPACE) bus_reserved = cid->num_cfg_blocks * SIBA_CFG_SIZE; /* Append the region info */ error = siba_append_dinfo_region(di, i, addr, size, bus_reserved); if (error) return (error); } 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. * @param dinfo The device info instance on which to map all per-core * configuration blocks. */ static int siba_map_cfg_resources(device_t dev, struct siba_devinfo *dinfo) { 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) { device_printf(dev, "config block count %hhu out of range\n", num_cfg); return (ENXIO); } /* Fetch the core register address space */ addrspace = siba_find_addrspace(dinfo, BHND_PORT_DEVICE, 0, 0); if (addrspace == NULL) { device_printf(dev, "missing device registers\n"); return (ENXIO); } /* * 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); } } return (0); } static device_t siba_add_child(device_t dev, u_int order, const char *name, int unit) { struct siba_devinfo *dinfo; device_t child; child = device_add_child_ordered(dev, order, name, unit); if (child == NULL) return (NULL); if ((dinfo = siba_alloc_dinfo(dev)) == NULL) { device_delete_child(dev, child); return (NULL); } device_set_ivars(child, dinfo); return (child); } static void siba_child_deleted(device_t dev, device_t child) { struct bhnd_softc *sc; struct siba_devinfo *dinfo; sc = device_get_softc(dev); /* Call required bhnd(4) implementation */ bhnd_generic_child_deleted(dev, child); /* 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); } /** * Scan the core table and add all valid discovered cores to * the bus. * * @param dev The siba bus device. */ int siba_add_children(device_t dev) { const struct bhnd_chipid *chipid; struct siba_core_id *cores; struct bhnd_resource *r; device_t *children; int rid; int error; cores = NULL; r = NULL; chipid = BHND_BUS_GET_CHIPID(dev, dev); /* Allocate our temporary core and device table */ cores = malloc(sizeof(*cores) * chipid->ncores, M_BHND, M_WAITOK); children = malloc(sizeof(*children) * chipid->ncores, M_BHND, M_WAITOK | M_ZERO); /* * Add child devices for all discovered cores. * * On bridged devices, we'll exhaust our available register windows if * we map config blocks on unpopulated/disabled cores. To avoid this, we * defer mapping of the per-core siba(4) config blocks until all cores * have been enumerated and otherwise configured. */ 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; /* Map the core's register block */ rid = 0; r_start = SIBA_CORE_ADDR(i); r_count = SIBA_CORE_SIZE; r_end = r_start + SIBA_CORE_SIZE - 1; r = bhnd_alloc_resource(dev, SYS_RES_MEMORY, &rid, r_start, r_end, r_count, RF_ACTIVE); if (r == NULL) { error = ENXIO; goto failed; } /* Read the core info */ idhigh = bhnd_bus_read_4(r, SB0_REG_ABS(SIBA_CFG0_IDHIGH)); idlow = bhnd_bus_read_4(r, SB0_REG_ABS(SIBA_CFG0_IDLOW)); cores[i] = siba_parse_core_id(idhigh, idlow, i, 0); /* Determine and set unit number */ for (u_int j = 0; j < i; j++) { struct bhnd_core_info *cur = &cores[i].core_info; struct bhnd_core_info *prev = &cores[j].core_info; if (prev->vendor == cur->vendor && prev->device == cur->device) cur->unit++; } /* 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; } if ((error = siba_init_dinfo(dev, dinfo, &cores[i]))) goto failed; /* Register the core's address space(s). */ 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 * assignment */ for (u_int i = 0; i < chipid->ncores; i++) { struct siba_devinfo *dinfo; device_t child; - int nintr; child = children[i]; /* Skip if core is disabled */ if (bhnd_is_hw_disabled(child)) continue; dinfo = device_get_ivars(child); /* Map the core's config blocks */ 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); } free(cores, M_BHND); free(children, M_BHND); return (0); failed: for (u_int i = 0; i < chipid->ncores; i++) { if (children[i] == NULL) continue; device_delete_child(dev, children[i]); } free(cores, M_BHND); free(children, M_BHND); if (r != NULL) bhnd_release_resource(dev, SYS_RES_MEMORY, rid, r); return (error); } static device_method_t siba_methods[] = { /* Device interface */ DEVMETHOD(device_probe, siba_probe), DEVMETHOD(device_attach, siba_attach), DEVMETHOD(device_detach, siba_detach), DEVMETHOD(device_resume, siba_resume), DEVMETHOD(device_suspend, siba_suspend), /* Bus interface */ DEVMETHOD(bus_add_child, siba_add_child), DEVMETHOD(bus_child_deleted, siba_child_deleted), DEVMETHOD(bus_read_ivar, siba_read_ivar), DEVMETHOD(bus_write_ivar, siba_write_ivar), DEVMETHOD(bus_get_resource_list, siba_get_resource_list), /* BHND interface */ DEVMETHOD(bhnd_bus_get_erom_class, siba_get_erom_class), DEVMETHOD(bhnd_bus_read_ioctl, siba_read_ioctl), DEVMETHOD(bhnd_bus_write_ioctl, siba_write_ioctl), DEVMETHOD(bhnd_bus_read_iost, siba_read_iost), DEVMETHOD(bhnd_bus_is_hw_suspended, siba_is_hw_suspended), DEVMETHOD(bhnd_bus_reset_hw, siba_reset_hw), DEVMETHOD(bhnd_bus_suspend_hw, siba_suspend_hw), DEVMETHOD(bhnd_bus_read_config, siba_read_config), DEVMETHOD(bhnd_bus_write_config, siba_write_config), DEVMETHOD(bhnd_bus_get_port_count, siba_get_port_count), DEVMETHOD(bhnd_bus_get_region_count, siba_get_region_count), DEVMETHOD(bhnd_bus_get_port_rid, siba_get_port_rid), 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 }; DEFINE_CLASS_1(bhnd, siba_driver, siba_methods, sizeof(struct siba_softc), bhnd_driver); MODULE_VERSION(siba, 1); MODULE_DEPEND(siba, bhnd, 1, 1, 1); Index: head/sys/dev/bhnd/siba/siba_bhndb.c =================================================================== --- head/sys/dev/bhnd/siba/siba_bhndb.c (revision 326078) +++ head/sys/dev/bhnd/siba/siba_bhndb.c (revision 326079) @@ -1,301 +1,298 @@ /*- * 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: * 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. */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include "sibareg.h" #include "sibavar.h" /* * 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 * RESP_TIMEOUT error state. */ 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 }; static int siba_bhndb_probe(device_t dev) { const struct bhnd_chipid *cid; int error; /* Defer to default probe implementation */ if ((error = siba_probe(dev)) > 0) return (error); /* Check bus type */ cid = BHNDB_GET_CHIPID(device_get_parent(dev), dev); if (cid->chip_type != BHND_CHIPTYPE_SIBA) return (ENXIO); /* Set device description */ bhnd_set_default_bus_desc(dev, cid); return (error); } 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; /* Delegate remainder to standard bhnd method implementation */ if ((error = bhnd_generic_attach(dev))) goto failed; return (0); failed: device_delete_children(dev); return (error); } static int siba_bhndb_resume(device_t dev) { - struct siba_softc *sc; + struct siba_bhndb_softc *sc; int error; sc = device_get_softc(dev); /* Apply attach/resume work-arounds */ if ((error = siba_bhndb_wars_hwup(sc))) return (error); /* Call our superclass' implementation */ return (siba_resume(dev)); } /* Suspend all references to the device's cfg register blocks */ 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); } } static int siba_bhndb_suspend_child(device_t dev, device_t child) { struct siba_devinfo *dinfo; int error; if (device_get_parent(child) != dev) BUS_SUSPEND_CHILD(device_get_parent(dev), child); dinfo = device_get_ivars(child); /* Suspend the child */ if ((error = bhnd_generic_br_suspend_child(dev, child))) return (error); /* Suspend resource references to the child's config registers */ siba_bhndb_suspend_cfgblocks(dev, dinfo); return (0); } static int siba_bhndb_resume_child(device_t dev, device_t child) { struct siba_devinfo *dinfo; int error; if (device_get_parent(child) != dev) BUS_SUSPEND_CHILD(device_get_parent(dev), child); if (!device_is_suspended(child)) return (EBUSY); dinfo = device_get_ivars(child); /* 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); } } /* Resume the child */ if ((error = bhnd_generic_br_resume_child(dev, child))) { siba_bhndb_suspend_cfgblocks(dev, dinfo); return (error); } return (0); } /* 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) }); if (d11 == NULL) return (0); /* 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); } /** * Apply any hardware workarounds that are required upon attach or resume * 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); } return (0); } static device_method_t siba_bhndb_methods[] = { /* Device interface */ DEVMETHOD(device_probe, siba_bhndb_probe), DEVMETHOD(device_attach, siba_bhndb_attach), DEVMETHOD(device_resume, siba_bhndb_resume), /* Bus interface */ DEVMETHOD(bus_suspend_child, siba_bhndb_suspend_child), DEVMETHOD(bus_resume_child, siba_bhndb_resume_child), DEVMETHOD_END }; DEFINE_CLASS_2(bhnd, siba_bhndb_driver, siba_bhndb_methods, sizeof(struct siba_softc), bhnd_bhndb_driver, siba_driver); DRIVER_MODULE(siba_bhndb, bhndb, siba_bhndb_driver, bhnd_devclass, NULL, NULL); MODULE_VERSION(siba_bhndb, 1); MODULE_DEPEND(siba_bhndb, siba, 1, 1, 1); MODULE_DEPEND(siba_bhndb, bhnd, 1, 1, 1); MODULE_DEPEND(siba_bhndb, bhndb, 1, 1, 1); Index: head/sys/dev/bhnd/siba/siba_erom.c =================================================================== --- head/sys/dev/bhnd/siba/siba_erom.c (revision 326078) +++ head/sys/dev/bhnd/siba/siba_erom.c (revision 326079) @@ -1,504 +1,553 @@ /*- * 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: * 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. */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include "sibareg.h" #include "sibavar.h" struct siba_erom; struct siba_erom_io; static int siba_eio_init(struct siba_erom_io *io, struct bhnd_erom_io *eio, u_int ncores); static uint32_t siba_eio_read_4(struct siba_erom_io *io, u_int core_idx, bus_size_t offset); static struct siba_core_id siba_eio_read_core_id(struct siba_erom_io *io, u_int core_idx, int unit); static int siba_eio_read_chipid(struct siba_erom_io *io, bus_addr_t enum_addr, struct bhnd_chipid *cid); /** * SIBA EROM generic I/O context */ struct siba_erom_io { struct bhnd_erom_io *eio; /**< erom I/O callbacks */ bhnd_addr_t base_addr; /**< address of first core */ u_int ncores; /**< core count */ }; /** * SIBA EROM per-instance state. */ struct siba_erom { struct bhnd_erom obj; struct siba_erom_io io; /**< i/o context */ }; #define EROM_LOG(io, fmt, ...) do { \ printf("%s: " fmt, __FUNCTION__, ##__VA_ARGS__); \ } while(0) /* SIBA implementation of BHND_EROM_PROBE() */ static int siba_erom_probe(bhnd_erom_class_t *cls, struct bhnd_erom_io *eio, const struct bhnd_chipid *hint, struct bhnd_chipid *cid) { struct siba_erom_io io; uint32_t idreg; int error; /* Initialize I/O context, assuming at least the first core is mapped */ if ((error = siba_eio_init(&io, eio, 1))) return (error); /* Try using the provided hint. */ if (hint != NULL) { struct siba_core_id sid; /* Validate bus type */ if (hint->chip_type != BHND_CHIPTYPE_SIBA) return (ENXIO); /* * Verify the first core's IDHIGH/IDLOW identification. * * The core must be a Broadcom core, but must *not* be * a chipcommon core; those shouldn't be hinted. * * The first core on EXTIF-equipped devices varies, but on the * BCM4710, it's a SDRAM core (0x803). */ sid = siba_eio_read_core_id(&io, 0, 0); if (sid.core_info.vendor != BHND_MFGID_BCM) return (ENXIO); if (sid.core_info.device == BHND_COREID_CC) return (EINVAL); *cid = *hint; } else { /* Validate bus type */ idreg = siba_eio_read_4(&io, 0, CHIPC_ID); if (CHIPC_GET_BITS(idreg, CHIPC_ID_BUS) != BHND_CHIPTYPE_SIBA) return (ENXIO); /* Identify the chipset */ if ((error = siba_eio_read_chipid(&io, SIBA_ENUM_ADDR, cid))) return (error); /* Verify the chip type */ if (cid->chip_type != BHND_CHIPTYPE_SIBA) return (ENXIO); } /* * gcc hack: ensure bhnd_chipid.ncores cannot exceed SIBA_MAX_CORES * without triggering build failure due to -Wtype-limits * * if (cid.ncores > SIBA_MAX_CORES) * return (EINVAL) */ _Static_assert((2^sizeof(cid->ncores)) <= SIBA_MAX_CORES, "ncores could result in over-read of backing resource"); return (0); } /* SIBA implementation of BHND_EROM_INIT() */ static int siba_erom_init(bhnd_erom_t *erom, const struct bhnd_chipid *cid, struct bhnd_erom_io *eio) { struct siba_erom *sc; int error; sc = (struct siba_erom *)erom; /* Attempt to map the full core enumeration space */ error = bhnd_erom_io_map(eio, cid->enum_addr, cid->ncores * SIBA_CORE_SIZE); if (error) { printf("%s: failed to map %u cores: %d\n", __FUNCTION__, cid->ncores, error); return (error); } /* Initialize I/O context */ return (siba_eio_init(&sc->io, eio, cid->ncores)); } /* SIBA implementation of BHND_EROM_FINI() */ static void siba_erom_fini(bhnd_erom_t *erom) { struct siba_erom *sc = (struct siba_erom *)erom; bhnd_erom_io_fini(sc->io.eio); } /* Initialize siba_erom resource I/O context */ static int siba_eio_init(struct siba_erom_io *io, struct bhnd_erom_io *eio, u_int ncores) { io->eio = eio; io->ncores = ncores; return (0); } /** * Read a 32-bit value from @p offset relative to the base address of * the given @p core_idx. * * @param io EROM I/O context. * @param core_idx Core index. * @param offset Core register offset. */ static uint32_t siba_eio_read_4(struct siba_erom_io *io, u_int core_idx, bus_size_t offset) { /* Sanity check core index and offset */ if (core_idx >= io->ncores) panic("core index %u out of range (ncores=%u)", core_idx, io->ncores); if (offset > SIBA_CORE_SIZE - sizeof(uint32_t)) panic("invalid core offset %#jx", (uintmax_t)offset); /* Perform read */ return (bhnd_erom_io_read(io->eio, SIBA_CORE_OFFSET(core_idx) + offset, 4)); } /** * Read and parse identification registers for the given @p core_index. * * @param io EROM I/O context. * @param core_idx The core index. * @param unit The caller-specified unit number to be included in the return * value. */ static struct siba_core_id siba_eio_read_core_id(struct siba_erom_io *io, u_int core_idx, int unit) { uint32_t idhigh, idlow; idhigh = siba_eio_read_4(io, core_idx, SB0_REG_ABS(SIBA_CFG0_IDHIGH)); idlow = siba_eio_read_4(io, core_idx, SB0_REG_ABS(SIBA_CFG0_IDLOW)); return (siba_parse_core_id(idhigh, idlow, core_idx, unit)); } /** * Read and parse the chip identification register from the ChipCommon core. * * @param io EROM I/O context. * @param enum_addr The physical address mapped by @p io. * @param cid On success, the parsed chip identifier. */ static int siba_eio_read_chipid(struct siba_erom_io *io, bus_addr_t enum_addr, struct bhnd_chipid *cid) { struct siba_core_id ccid; uint32_t idreg; /* Identify the chipcommon core */ ccid = siba_eio_read_core_id(io, 0, 0); if (ccid.core_info.vendor != BHND_MFGID_BCM || ccid.core_info.device != BHND_COREID_CC) { if (bootverbose) { EROM_LOG(io, "first core not chipcommon " "(vendor=%#hx, core=%#hx)\n", ccid.core_info.vendor, ccid.core_info.device); } return (ENXIO); } /* Identify the chipset */ idreg = siba_eio_read_4(io, 0, CHIPC_ID); *cid = bhnd_parse_chipid(idreg, enum_addr); /* Fix up the core count in-place */ return (bhnd_chipid_fixed_ncores(cid, ccid.core_info.hwrev, &cid->ncores)); } static int siba_erom_lookup_core(bhnd_erom_t *erom, const struct bhnd_core_match *desc, struct bhnd_core_info *core) { struct siba_erom *sc; struct bhnd_core_match imatch; sc = (struct siba_erom *)erom; /* We can't determine a core's unit number during the initial scan. */ imatch = *desc; imatch.m.match.core_unit = 0; /* Locate the first matching core */ for (u_int i = 0; i < sc->io.ncores; i++) { struct siba_core_id sid; struct bhnd_core_info ci; /* Read the core info */ sid = siba_eio_read_core_id(&sc->io, i, 0); ci = sid.core_info; /* Check for initial match */ if (!bhnd_core_matches(&ci, &imatch)) continue; /* Re-scan preceding cores to determine the unit number. */ for (u_int j = 0; j < i; j++) { sid = siba_eio_read_core_id(&sc->io, j, 0); /* Bump the unit number? */ if (sid.core_info.vendor == ci.vendor && sid.core_info.device == ci.device) ci.unit++; } /* Check for full match against now-valid unit number */ if (!bhnd_core_matches(&ci, desc)) continue; /* Matching core found */ *core = ci; return (0); } /* Not found */ return (ENOENT); } static int siba_erom_lookup_core_addr(bhnd_erom_t *erom, const struct bhnd_core_match *desc, bhnd_port_type type, u_int port, u_int region, struct bhnd_core_info *info, bhnd_addr_t *addr, bhnd_size_t *size) { struct siba_erom *sc; struct bhnd_core_info core; 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; /* Locate the requested core */ if ((error = siba_erom_lookup_core(erom, desc, &core))) return (error); /* Fetch full siba core ident */ 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); /* Determine the register offset */ am_offset = siba_admatch_offset(addrspace); if (am_offset == 0) { printf("addrspace %u is unsupported", addrspace); return (ENODEV); } /* Read and parse the address match register */ am = siba_eio_read_4(&sc->io, core.core_idx, am_offset); if ((error = siba_parse_admatch(am, &am_addr, &am_size))) { printf("failed to decode address match register value 0x%x\n", am); return (error); } if (info != NULL) *info = core; *addr = am_addr; *size = am_size; return (0); } /* BHND_EROM_GET_CORE_TABLE() */ static int siba_erom_get_core_table(bhnd_erom_t *erom, struct bhnd_core_info **cores, u_int *num_cores) { struct siba_erom *sc; struct bhnd_core_info *out; sc = (struct siba_erom *)erom; /* Allocate our core array */ out = malloc(sizeof(*out) * sc->io.ncores, M_BHND, M_NOWAIT); if (out == NULL) return (ENOMEM); *cores = out; *num_cores = sc->io.ncores; /* Enumerate all cores. */ for (u_int i = 0; i < sc->io.ncores; i++) { struct siba_core_id sid; /* Read the core info */ sid = siba_eio_read_core_id(&sc->io, i, 0); out[i] = sid.core_info; /* Determine unit number */ for (u_int j = 0; j < i; j++) { if (out[j].vendor == out[i].vendor && out[j].device == out[i].device) out[i].unit++; } } return (0); } /* BHND_EROM_FREE_CORE_TABLE() */ static void siba_erom_free_core_table(bhnd_erom_t *erom, struct bhnd_core_info *cores) { free(cores, M_BHND); } /* BHND_EROM_DUMP() */ static int siba_erom_dump(bhnd_erom_t *erom) { struct siba_erom *sc; int error; sc = (struct siba_erom *)erom; /* Enumerate all cores. */ for (u_int i = 0; i < sc->io.ncores; i++) { uint32_t idhigh, idlow; uint32_t nraddr; idhigh = siba_eio_read_4(&sc->io, i, SB0_REG_ABS(SIBA_CFG0_IDHIGH)); idlow = siba_eio_read_4(&sc->io, i, SB0_REG_ABS(SIBA_CFG0_IDLOW)); printf("siba core %u:\n", i); printf("\tvendor:\t0x%04x\n", SIBA_REG_GET(idhigh, IDH_VENDOR)); printf("\tdevice:\t0x%04x\n", SIBA_REG_GET(idhigh, IDH_DEVICE)); printf("\trev:\t0x%04x\n", SIBA_IDH_CORE_REV(idhigh)); printf("\tsbrev:\t0x%02x\n", SIBA_REG_GET(idlow, IDL_SBREV)); /* Enumerate the address match registers */ nraddr = SIBA_REG_GET(idlow, IDL_NRADDR); printf("\tnraddr\t0x%04x\n", nraddr); for (size_t addrspace = 0; addrspace < nraddr; addrspace++) { uint32_t am, am_addr, am_size; u_int am_offset; /* Determine the register offset */ am_offset = siba_admatch_offset(addrspace); if (am_offset == 0) { printf("addrspace %zu unsupported", addrspace); break; } /* Read and parse the address match register */ am = siba_eio_read_4(&sc->io, i, am_offset); error = siba_parse_admatch(am, &am_addr, &am_size); if (error) { printf("failed to decode address match " "register value 0x%x\n", am); continue; } printf("\taddrspace %zu\n", addrspace); printf("\t\taddr: 0x%08x\n", am_addr); printf("\t\tsize: 0x%08x\n", am_size); } } return (0); } static kobj_method_t siba_erom_methods[] = { KOBJMETHOD(bhnd_erom_probe, siba_erom_probe), KOBJMETHOD(bhnd_erom_init, siba_erom_init), KOBJMETHOD(bhnd_erom_fini, siba_erom_fini), KOBJMETHOD(bhnd_erom_get_core_table, siba_erom_get_core_table), KOBJMETHOD(bhnd_erom_free_core_table, siba_erom_free_core_table), KOBJMETHOD(bhnd_erom_lookup_core, siba_erom_lookup_core), KOBJMETHOD(bhnd_erom_lookup_core_addr, siba_erom_lookup_core_addr), KOBJMETHOD(bhnd_erom_dump, siba_erom_dump), KOBJMETHOD_END }; BHND_EROM_DEFINE_CLASS(siba_erom, siba_erom_parser, siba_erom_methods, sizeof(struct siba_erom)); Index: head/sys/dev/bhnd/siba/siba_subr.c =================================================================== --- head/sys/dev/bhnd/siba/siba_subr.c (revision 326078) +++ head/sys/dev/bhnd/siba/siba_subr.c (revision 326079) @@ -1,551 +1,705 @@ /*- - * 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: * 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. */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include "sibareg.h" #include "sibavar.h" /** * Map a siba(4) OCP vendor code to its corresponding JEDEC JEP-106 vendor * code. * * @param ocp_vendor An OCP vendor code. * @return The BHND_MFGID constant corresponding to @p ocp_vendor, or * BHND_MFGID_INVALID if the OCP vendor is unknown. */ uint16_t siba_get_bhnd_mfgid(uint16_t ocp_vendor) { switch (ocp_vendor) { case OCP_VENDOR_BCM: return (BHND_MFGID_BCM); default: return (BHND_MFGID_INVALID); } } /** * Parse the SIBA_IDH_* fields from the per-core identification * registers, returning a siba_core_id representation. * * @param idhigh The SIBA_R0_IDHIGH register. * @param idlow The SIBA_R0_IDLOW register. * @param core_id The core id (index) to include in the result. * @param unit The unit number to include in the result. */ struct siba_core_id siba_parse_core_id(uint32_t idhigh, uint32_t idlow, u_int core_idx, int unit) { uint16_t ocp_vendor; uint8_t sonics_rev; uint8_t num_addrspace; uint8_t num_cfg; ocp_vendor = SIBA_REG_GET(idhigh, IDH_VENDOR); sonics_rev = SIBA_REG_GET(idlow, IDL_SBREV); num_addrspace = SIBA_REG_GET(idlow, IDL_NRADDR) + 1 /* + enum block */; /* Determine the number of sonics config register blocks */ num_cfg = SIBA_CFG_NUM_2_2; if (sonics_rev >= SIBA_IDL_SBREV_2_3) num_cfg = SIBA_CFG_NUM_2_3; return (struct siba_core_id) { .core_info = { .vendor = siba_get_bhnd_mfgid(ocp_vendor), .device = SIBA_REG_GET(idhigh, IDH_DEVICE), .hwrev = SIBA_IDH_CORE_REV(idhigh), .core_idx = core_idx, .unit = unit }, .sonics_vendor = ocp_vendor, .sonics_rev = sonics_rev, .num_addrspace = num_addrspace, .num_cfg_blocks = num_cfg }; } /** * Allocate and return a new empty device info structure. * * @param bus The requesting bus device. * * @retval NULL if allocation failed. */ struct siba_devinfo * siba_alloc_dinfo(device_t bus) { struct siba_devinfo *dinfo; dinfo = malloc(sizeof(struct siba_devinfo), M_BHND, M_NOWAIT|M_ZERO); if (dinfo == NULL) 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; } /** * Initialize a device info structure previously allocated via * siba_alloc_dinfo, copying the provided core id. * * @param dev The requesting bus device. * @param dinfo The device info instance. * @param core Device core info. * * @retval 0 success * @retval non-zero initialization failed. */ int siba_init_dinfo(device_t dev, struct siba_devinfo *dinfo, const struct siba_core_id *core_id) { dinfo->core_id = *core_id; return (0); } /** - * 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 */ if (addrspace == 0) return (0); else return (1); } /** - * 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) */ if (addrspace == 0) return (0); else return (addrspace - 1); } /** + * 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]); } /** * Map a bhnd(4) type/port/region triplet to its associated address space * index, if any. * * For compatibility with bcma(4), we map address spaces to port/region * identifiers as follows: * * [port] [addrspace] * device0.0 0 * device1.0 1 * 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. * * @retval 0 success * @retval ENOENT if the given type/port/region cannot be mapped to a * 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; else if (port == 1) idx = region + 1; else return (ENOENT); - if (idx >= num_addrspace) + if (idx >= core_id->num_addrspace) return (ENOENT); /* Found */ *addridx = idx; return (0); } /** * Map an bhnd(4) type/port/region triplet to its associated address space * entry, if any. * * The only supported port type is BHND_PORT_DEVICE. * * @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. */ struct siba_addrspace * siba_find_addrspace(struct siba_devinfo *dinfo, bhnd_port_type type, u_int port, u_int region) { u_int addridx; 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); /* Found */ if (addridx >= SIBA_MAX_ADDRSPACE) return (NULL); return (&dinfo->addrspace[addridx]); } /** * Append an address space entry to @p dinfo. * * @param dinfo The device info entry to update. * @param addridx The address space index. * @param base The mapping's base address. * @param size The mapping size. * @param bus_reserved Number of bytes to reserve in @p size for bus use * when registering the resource list entry. This is used to reserve bus * access to the core's SIBA_CFG* register blocks. * * @retval 0 success * @retval non-zero An error occurred appending the entry. */ int siba_append_dinfo_region(struct siba_devinfo *dinfo, uint8_t addridx, uint32_t base, uint32_t size, uint32_t bus_reserved) { struct siba_addrspace *sa; rman_res_t r_size; /* Verify that base + size will not overflow */ if (size > 0 && UINT32_MAX - (size - 1) < base) return (ERANGE); /* Verify that size - bus_reserved will not underflow */ if (size < bus_reserved) return (ERANGE); /* Must not be 0-length */ if (size == 0) return (EINVAL); /* Must not exceed addrspace array size */ if (addridx >= nitems(dinfo->addrspace)) return (EINVAL); /* Initialize new addrspace entry */ sa = &dinfo->addrspace[addridx]; sa->sa_base = base; sa->sa_size = size; sa->sa_bus_reserved = bus_reserved; /* Populate the resource list */ r_size = size - bus_reserved; sa->sa_rid = resource_list_add_next(&dinfo->resources, SYS_RES_MEMORY, base, base + (r_size - 1), r_size); return (0); } /** * 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); } /** * Return the core-enumeration-relative offset for the @p addrspace * SIBA_R0_ADMATCH* register. * * @param addrspace The address space index. * * @retval non-zero success * @retval 0 the given @p addrspace index is not supported. */ u_int siba_admatch_offset(uint8_t addrspace) { switch (addrspace) { case 0: return SB0_REG_ABS(SIBA_CFG0_ADMATCH0); case 1: return SB0_REG_ABS(SIBA_CFG0_ADMATCH1); case 2: return SB0_REG_ABS(SIBA_CFG0_ADMATCH2); case 3: return SB0_REG_ABS(SIBA_CFG0_ADMATCH3); default: return (0); } } /** * Parse a SIBA_R0_ADMATCH* register. * * @param addrspace The address space index. * @param am The address match register value to be parsed. * @param[out] addr The parsed address. * @param[out] size The parsed size. * * @retval 0 success * @retval non-zero a parse error occurred. */ int siba_parse_admatch(uint32_t am, uint32_t *addr, uint32_t *size) { u_int am_type; /* Negative encoding is not supported. This is not used on any * currently known devices*/ if (am & SIBA_AM_ADNEG) return (EINVAL); /* Extract the base address and size */ am_type = SIBA_REG_GET(am, AM_TYPE); switch (am_type) { case 0: *addr = am & SIBA_AM_BASE0_MASK; *size = 1 << (SIBA_REG_GET(am, AM_ADINT0) + 1); break; case 1: *addr = am & SIBA_AM_BASE1_MASK; *size = 1 << (SIBA_REG_GET(am, AM_ADINT1) + 1); break; case 2: *addr = am & SIBA_AM_BASE2_MASK; *size = 1 << (SIBA_REG_GET(am, AM_ADINT2) + 1); break; default: return (EINVAL); } return (0); } /** * Write @p value to @p dev's CFG0 target/initiator state register and * wait for completion. * * @param dev The siba(4) child device. * @param reg The state register to write (e.g. SIBA_CFG0_TMSTATELOW, * SIBA_CFG0_IMSTATE) * @param value The value to write to @p reg. * @param mask The mask of bits to be included from @p value. * * @retval 0 success. * @retval ENODEV if SIBA_CFG0 is not mapped by @p dinfo. * @retval ETIMEDOUT if a timeout occurs prior to SIBA_TMH_BUSY clearing. */ int siba_write_target_state(device_t dev, struct siba_devinfo *dinfo, bus_size_t reg, uint32_t value, uint32_t mask) { struct bhnd_resource *r; 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 */ if (reg > SIBA_CFG_SIZE-4) return (EFAULT); for (int i = 0; i < 300; i += 10) { rval = bhnd_bus_read_4(r, reg); rval &= ~mask; rval |= (value & mask); bhnd_bus_write_4(r, reg, rval); bhnd_bus_read_4(r, reg); /* read-back */ DELAY(1); /* If the write has completed, wait for target busy state * to clear */ rval = bhnd_bus_read_4(r, reg); if ((rval & mask) == (value & mask)) return (siba_wait_target_busy(dev, dinfo, 100000)); DELAY(10); } return (ETIMEDOUT); } /** * Spin for up to @p usec waiting for SIBA_TMH_BUSY to clear in * @p dev's SIBA_CFG0_TMSTATEHIGH register. * * @param dev The siba(4) child device to wait on. * @param dinfo The @p dev's device info * * @retval 0 if SIBA_TMH_BUSY is cleared prior to the @p usec timeout. * @retval ENODEV if SIBA_CFG0 is not mapped by @p dinfo. * @retval ETIMEDOUT if a timeout occurs prior to SIBA_TMH_BUSY clearing. */ int siba_wait_target_busy(device_t dev, struct siba_devinfo *dinfo, int usec) { 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) { ts_high = bhnd_bus_read_4(r, SIBA_CFG0_TMSTATEHIGH); if (!(ts_high & SIBA_TMH_BUSY)) return (0); DELAY(10); } device_printf(dev, "SIBA_TMH_BUSY wait timeout\n"); return (ETIMEDOUT); } Index: head/sys/dev/bhnd/siba/sibareg.h =================================================================== --- head/sys/dev/bhnd/siba/sibareg.h (revision 326078) +++ head/sys/dev/bhnd/siba/sibareg.h (revision 326079) @@ -1,267 +1,274 @@ /*- * Copyright (c) 2015 Landon Fuller * Copyright (c) 2010 Broadcom Corporation * * This file was derived from the sbconfig.h header distributed with * Broadcom's initial brcm80211 Linux driver release, as * contributed to the Linux staging repository. * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * * $FreeBSD$ */ #ifndef _BHND_SIBA_SIBAREG_ #define _BHND_SIBA_SIBAREG_ #include /* * Broadcom SIBA Configuration Space Registers. * * Backplane configuration registers common to siba(4) core register * blocks. */ /** * Extract a config attribute by applying _MASK and _SHIFT defines. * * @param _reg The register value containing the desired attribute * @param _attr The BCMA EROM attribute name (e.g. ENTRY_ISVALID), to be * concatenated with the `SB` prefix and `_MASK`/`_SHIFT` suffixes. */ #define SIBA_REG_GET(_entry, _attr) \ ((_entry & SIBA_ ## _attr ## _MASK) \ >> SIBA_ ## _attr ## _SHIFT) #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 */ /** Evaluates to the bus address offset of the @p idx core register block */ #define SIBA_CORE_OFFSET(idx) ((idx) * SIBA_CORE_SIZE) /** Evaluates to the bus address of the @p idx core register block */ #define SIBA_CORE_ADDR(idx) (SIBA_ENUM_ADDR + SIBA_CORE_OFFSET(idx)) /* * Sonics configuration registers are mapped to each core's enumeration * space, at the end of the 4kb device register block, in reverse * order: * * [0x0000-0x0dff] core registers * [0x0e00-0x0eff] SIBA_R1 registers (sonics >= 2.3) * [0x0f00-0x0fff] SIBA_R0 registers */ #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) /* SIBA_CFG0 registers */ #define SIBA_CFG0_IPSFLAG 0x08 /**< initiator port ocp slave flag */ #define SIBA_CFG0_TPSFLAG 0x18 /**< target port ocp slave flag */ #define SIBA_CFG0_TMERRLOGA 0x48 /**< sonics >= 2.3 */ #define SIBA_CFG0_TMERRLOG 0x50 /**< sonics >= 2.3 */ #define SIBA_CFG0_ADMATCH3 0x60 /**< address match3 */ #define SIBA_CFG0_ADMATCH2 0x68 /**< address match2 */ #define SIBA_CFG0_ADMATCH1 0x70 /**< address match1 */ #define SIBA_CFG0_IMSTATE 0x90 /**< initiator agent state */ #define SIBA_CFG0_INTVEC 0x94 /**< interrupt mask */ #define SIBA_CFG0_TMSTATELOW 0x98 /**< target state */ #define SIBA_CFG0_TMSTATEHIGH 0x9c /**< target state */ #define SIBA_CFG0_BWA0 0xa0 /**< bandwidth allocation table0 */ #define SIBA_CFG0_IMCONFIGLOW 0xa8 /**< initiator configuration */ #define SIBA_CFG0_IMCONFIGHIGH 0xac /**< initiator configuration */ #define SIBA_CFG0_ADMATCH0 0xb0 /**< address match0 */ #define SIBA_CFG0_TMCONFIGLOW 0xb8 /**< target configuration */ #define SIBA_CFG0_TMCONFIGHIGH 0xbc /**< target configuration */ #define SIBA_CFG0_BCONFIG 0xc0 /**< broadcast configuration */ #define SIBA_CFG0_BSTATE 0xc8 /**< broadcast state */ #define SIBA_CFG0_ACTCNFG 0xd8 /**< activate configuration */ #define SIBA_CFG0_FLAGST 0xe8 /**< current sbflags */ #define SIBA_CFG0_IDLOW 0xf8 /**< identification */ #define SIBA_CFG0_IDHIGH 0xfc /**< identification */ /* SIBA_CFG1 registers (sonics >= 2.3) */ #define SIBA_CFG1_IMERRLOGA 0xa8 /**< (sonics >= 2.3) */ #define SIBA_CFG1_IMERRLOG 0xb0 /**< sbtmerrlog (sonics >= 2.3) */ #define SIBA_CFG1_TMPORTCONNID0 0xd8 /**< sonics >= 2.3 */ #define SIBA_CFG1_TMPORTLOCK0 0xf8 /**< sonics >= 2.3 */ /* sbipsflag */ #define SIBA_IPS_INT1_MASK 0x3f /* which sbflags get routed to mips interrupt 1 */ #define SIBA_IPS_INT1_SHIFT 0 #define SIBA_IPS_INT2_MASK 0x3f00 /* which sbflags get routed to mips interrupt 2 */ #define SIBA_IPS_INT2_SHIFT 8 #define SIBA_IPS_INT3_MASK 0x3f0000 /* which sbflags get routed to mips interrupt 3 */ #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 */ #define SIBA_TPS_NUM0_SHIFT 0 #define SIBA_TPS_F0EN0 0x40 /* interrupt is always sent on the backplane */ /* sbtmerrlog */ #define SIBA_TMEL_CM 0x00000007 /* command */ #define SIBA_TMEL_CI 0x0000ff00 /* connection id */ #define SIBA_TMEL_EC 0x0f000000 /* error code */ #define SIBA_TMEL_ME 0x80000000 /* multiple error */ /* sbimstate */ #define SIBA_IM_PC 0xf /* pipecount */ #define SIBA_IM_AP_MASK 0x30 /* arbitration policy */ #define SIBA_IM_AP_BOTH 0x00 /* use both timeslaces and token */ #define SIBA_IM_AP_TS 0x10 /* use timesliaces only */ #define SIBA_IM_AP_TK 0x20 /* use token only */ #define SIBA_IM_AP_RSV 0x30 /* reserved */ #define SIBA_IM_IBE 0x20000 /* inbanderror */ #define SIBA_IM_TO 0x40000 /* timeout */ #define SIBA_IM_BY 0x01800000 /* busy (sonics >= 2.3) */ #define SIBA_IM_RJ 0x02000000 /* reject (sonics >= 2.3) */ /* sbtmstatelow */ #define SIBA_TML_RESET 0x0001 /* reset */ #define SIBA_TML_REJ_MASK 0x0006 /* reject field */ #define SIBA_TML_REJ 0x0002 /* reject */ #define SIBA_TML_TMPREJ 0x0004 /* temporary reject, for error recovery */ #define SIBA_TML_SICF_MASK 0xFFFF0000 /* core IOCTL flags */ #define SIBA_TML_SICF_SHIFT 16 /* sbtmstatehigh */ #define SIBA_TMH_SERR 0x0001 /* serror */ #define SIBA_TMH_INT 0x0002 /* interrupt */ #define SIBA_TMH_BUSY 0x0004 /* busy */ #define SIBA_TMH_TO 0x0020 /* timeout (sonics >= 2.3) */ #define SIBA_TMH_SISF_MASK 0xFFFF0000 /* core IOST flags */ #define SIBA_TMH_SISF_SHIFT 16 /* sbbwa0 */ #define SIBA_BWA_TAB0_MASK 0xffff /* lookup table 0 */ #define SIBA_BWA_TAB1_MASK 0xffff /* lookup table 1 */ #define SIBA_BWA_TAB1_SHIFT 16 /* sbimconfiglow */ #define SIBA_IMCL_STO_MASK 0x7 /* service timeout */ #define SIBA_IMCL_RTO_MASK 0x70 /* request timeout */ #define SIBA_IMCL_RTO_SHIFT 4 #define SIBA_IMCL_CID_MASK 0xff0000 /* connection id */ #define SIBA_IMCL_CID_SHIFT 16 /* sbimconfighigh */ #define SIBA_IMCH_IEM_MASK 0xc /* inband error mode */ #define SIBA_IMCH_TEM_MASK 0x30 /* timeout error mode */ #define SIBA_IMCH_TEM_SHIFT 4 #define SIBA_IMCH_BEM_MASK 0xc0 /* bus error mode */ #define SIBA_IMCH_BEM_SHIFT 6 /* sbadmatch0-4 */ #define SIBA_AM_TYPE_MASK 0x3 /* address type */ #define SIBA_AM_TYPE_SHIFT 0x0 #define SIBA_AM_AD64 0x4 /* reserved */ #define SIBA_AM_ADINT0_MASK 0xf8 /* type0 size */ #define SIBA_AM_ADINT0_SHIFT 3 #define SIBA_AM_ADINT1_MASK 0x1f8 /* type1 size */ #define SIBA_AM_ADINT1_SHIFT 3 #define SIBA_AM_ADINT2_MASK 0x1f8 /* type2 size */ #define SIBA_AM_ADINT2_SHIFT 3 #define SIBA_AM_ADEN 0x400 /* enable */ #define SIBA_AM_ADNEG 0x800 /* negative decode */ #define SIBA_AM_BASE0_MASK 0xffffff00 /* type0 base address */ #define SIBA_AM_BASE0_SHIFT 8 #define SIBA_AM_BASE1_MASK 0xfffff000 /* type1 base address for the core */ #define SIBA_AM_BASE1_SHIFT 12 #define SIBA_AM_BASE2_MASK 0xffff0000 /* type2 base address for the core */ #define SIBA_AM_BASE2_SHIFT 16 /* sbtmconfiglow */ #define SIBA_TMCL_CD_MASK 0xff /* clock divide */ #define SIBA_TMCL_CO_MASK 0xf800 /* clock offset */ #define SIBA_TMCL_CO_SHIFT 11 #define SIBA_TMCL_IF_MASK 0xfc0000 /* interrupt flags */ #define SIBA_TMCL_IF_SHIFT 18 #define SIBA_TMCL_IM_MASK 0x3000000 /* interrupt mode */ #define SIBA_TMCL_IM_SHIFT 24 /* sbtmconfighigh */ #define SIBA_TMCH_BM_MASK 0x3 /* busy mode */ #define SIBA_TMCH_RM_MASK 0x3 /* retry mode */ #define SIBA_TMCH_RM_SHIFT 2 #define SIBA_TMCH_SM_MASK 0x30 /* stop mode */ #define SIBA_TMCH_SM_SHIFT 4 #define SIBA_TMCH_EM_MASK 0x300 /* sb error mode */ #define SIBA_TMCH_EM_SHIFT 8 #define SIBA_TMCH_IM_MASK 0xc00 /* int mode */ #define SIBA_TMCH_IM_SHIFT 10 /* sbbconfig */ #define SIBA_BC_LAT_MASK 0x3 /* sb latency */ #define SIBA_BC_MAX0_MASK 0xf0000 /* maxccntr0 */ #define SIBA_BC_MAX0_SHIFT 16 #define SIBA_BC_MAX1_MASK 0xf00000 /* maxccntr1 */ #define SIBA_BC_MAX1_SHIFT 20 /* sbbstate */ #define SIBA_BS_SRD 0x1 /* st reg disable */ #define SIBA_BS_HRD 0x2 /* hold reg disable */ /* sbidlow */ #define SIBA_IDL_CS_MASK 0x3 /* config space */ #define SIBA_IDL_CS_SHIFT 0 #define SIBA_IDL_NRADDR_MASK 0x38 /* # address ranges supported */ #define SIBA_IDL_NRADDR_SHIFT 3 #define SIBA_IDL_SYNCH 0x40 /* sync */ #define SIBA_IDL_INIT 0x80 /* initiator */ #define SIBA_IDL_MINLAT_MASK 0xf00 /* minimum backplane latency */ #define SIBA_IDL_MINLAT_SHIFT 8 #define SIBA_IDL_MAXLAT_MASK 0xf000 /* maximum backplane latency */ #define SIBA_IDL_MAXLAT_SHIFT 12 #define SIBA_IDL_FIRST_MASK 0x10000 /* this initiator is first */ #define SIBA_IDL_FIRST_SHIFT 16 #define SIBA_IDL_CW_MASK 0xc0000 /* cycle counter width */ #define SIBA_IDL_CW_SHIFT 18 #define SIBA_IDL_TP_MASK 0xf00000 /* target ports */ #define SIBA_IDL_TP_SHIFT 20 #define SIBA_IDL_IP_MASK 0xf000000 /* initiator ports */ #define SIBA_IDL_IP_SHIFT 24 #define SIBA_IDL_SBREV_MASK 0xf0000000 /* sonics backplane revision code */ #define SIBA_IDL_SBREV_SHIFT 28 #define SIBA_IDL_SBREV_2_2 0x0 /* version 2.2 or earlier */ #define SIBA_IDL_SBREV_2_3 0x1 /* version 2.3 */ /* sbidhigh */ #define SIBA_IDH_RC_MASK 0x000f /* revision code */ #define SIBA_IDH_RCE_MASK 0x7000 /* revision code extension field */ #define SIBA_IDH_RCE_SHIFT 8 #define SIBA_IDH_DEVICE_MASK 0x8ff0 /* core code */ #define SIBA_IDH_DEVICE_SHIFT 4 #define SIBA_IDH_VENDOR_MASK 0xffff0000 /* vendor code */ #define SIBA_IDH_VENDOR_SHIFT 16 #define SIBA_IDH_CORE_REV(sbidh) \ (SIBA_REG_GET((sbidh), IDH_RCE) | ((sbidh) & SIBA_IDH_RC_MASK)) #define SIBA_COMMIT 0xfd8 /* update buffered registers value */ #endif /* _BHND_SIBA_SIBAREG_ */ Index: head/sys/dev/bhnd/siba/sibavar.h =================================================================== --- head/sys/dev/bhnd/siba/sibavar.h (revision 326078) +++ head/sys/dev/bhnd/siba/sibavar.h (revision 326079) @@ -1,172 +1,209 @@ /*- - * 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: * 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 _SIBA_SIBAVAR_H_ #define _SIBA_SIBAVAR_H_ #include #include #include #include #include #include "siba.h" /* * Internal definitions shared by siba(4) driver implementations. */ struct siba_addrspace; +struct siba_cfg_block; struct siba_devinfo; struct siba_core_id; int siba_probe(device_t dev); int siba_attach(device_t dev); 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); struct siba_core_id siba_parse_core_id(uint32_t idhigh, uint32_t idlow, u_int core_idx, int unit); int siba_add_children(device_t bus); struct siba_devinfo *siba_alloc_dinfo(device_t dev); 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); u_int siba_admatch_offset(uint8_t addrspace); int siba_parse_admatch(uint32_t am, uint32_t *addr, uint32_t *size); int siba_write_target_state(device_t dev, struct siba_devinfo *dinfo, bus_size_t reg, uint32_t value, uint32_t mask); int siba_wait_target_busy(device_t child, struct siba_devinfo *dinfo, int usec); /* Sonics configuration register blocks */ #define SIBA_CFG_NUM_2_2 1 /**< sonics <= 2.2 maps SIBA_CFG0. */ #define SIBA_CFG_NUM_2_3 2 /**< sonics <= 2.3 maps SIBA_CFG0 and SIBA_CFG1 */ #define SIBA_MAX_CFG SIBA_CFG_NUM_2_3 /**< maximum number of supported config register blocks */ #define SIBA_CFG_RID_BASE 100 /**< base resource ID for SIBA_CFG* register allocations */ #define SIBA_CFG_RID(_dinfo, _cfg) \ (SIBA_CFG_RID_BASE + (_cfg) + \ (_dinfo->core_id.core_info.core_idx * SIBA_MAX_CFG)) /* Sonics/OCP address space mappings */ #define SIBA_CORE_ADDRSPACE 0 /**< Address space mapping the primary device registers */ #define SIBA_MAX_ADDRSPACE 4 /**< Maximum number of Sonics/OCP * address space mappings for a * single core. */ /* bhnd(4) (port,region) representation of siba address space mappings */ #define SIBA_MAX_PORT 2 /**< maximum number of advertised * bhnd(4) ports */ /** siba(4) address space descriptor */ struct siba_addrspace { uint32_t sa_base; /**< base address */ uint32_t sa_size; /**< size */ int sa_rid; /**< bus resource id */ uint32_t sa_bus_reserved;/**< number of bytes at high end of * 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. */ struct siba_core_id { struct bhnd_core_info core_info; /**< standard bhnd(4) core info */ uint16_t sonics_vendor; /**< OCP vendor identifier used to generate * the JEDEC-106 bhnd(4) vendor identifier. */ uint8_t sonics_rev; /**< sonics backplane revision code */ uint8_t num_addrspace; /**< number of address ranges mapped to this core. */ uint8_t num_cfg_blocks; /**< number of Sonics configuration register blocks mapped to the core's enumeration space */ }; /** * siba(4) per-device info */ struct siba_devinfo { 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 */ }; /** siba(4) per-instance state */ struct siba_softc { struct bhnd_softc bhnd_sc; /**< bhnd state */ device_t dev; /**< siba device */ }; #endif /* _SIBA_SIBAVAR_H_ */ Index: head/sys/mips/broadcom/bcm_bmips.c =================================================================== --- head/sys/mips/broadcom/bcm_bmips.c (revision 326078) +++ head/sys/mips/broadcom/bcm_bmips.c (revision 326079) @@ -1,123 +1,420 @@ /*- * 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: * 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. */ #include __FBSDID("$FreeBSD$"); #include #include #include #include +#include #include #include + +#include #include #include +#include +#include "pic_if.h" + +#include "bcm_mipsvar.h" #include "bcm_bmipsreg.h" /* * BMIPS32 and BMIPS3300 core driver. * * These cores are only found on siba(4) chipsets, allowing * 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.c =================================================================== --- head/sys/mips/broadcom/bcm_machdep.c (revision 326078) +++ head/sys/mips/broadcom/bcm_machdep.c (revision 326079) @@ -1,636 +1,651 @@ /*- * Copyright (c) 2007 Bruce M. Simpson. * 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: * 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 "opt_ddb.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "bcm_machdep.h" #include "bcm_bmips_exts.h" #ifdef CFE #include #include #endif #if 0 #define BCM_TRACE(_fmt, ...) printf(_fmt, ##__VA_ARGS__) #else #define BCM_TRACE(_fmt, ...) #endif static int bcm_init_platform_data(struct bcm_platform *bp); static int bcm_find_core(struct bcm_platform *bp, const struct bhnd_core_match *descs, size_t num_descs, struct bhnd_core_info *info, uintptr_t *addr); static int bcm_erom_probe_and_attach(bhnd_erom_class_t **erom_cls, kobj_ops_t erom_ops, bhnd_erom_t *erom, size_t esize, struct bhnd_erom_io *eio, struct bhnd_chipid *cid); extern int *edata; extern int *end; static struct bcm_platform bcm_platform_data; static bool bcm_platform_data_avail = false; #ifdef CFE static struct bcm_nvram_iocfe bcm_cfe_nvram; #endif static const struct bhnd_core_match bcm_chipc_cores[] = { { BHND_MATCH_CORE(BHND_MFGID_BCM, BHND_COREID_CC) }, { 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) }, }; struct bcm_platform * bcm_get_platform(void) { if (!bcm_platform_data_avail) panic("platform data not available"); return (&bcm_platform_data); } static bus_addr_t bcm_get_bus_addr(void) { long maddr; if (resource_long_value("bhnd", 0, "maddr", &maddr) == 0) return ((u_long)maddr); return (BHND_DEFAULT_CHIPC_ADDR); } static bus_size_t bcm_get_bus_size(void) { long msize; if (resource_long_value("bhnd", 0, "msize", &msize) == 0) return ((u_long)msize); return (BHND_DEFAULT_ENUM_SIZE); } /** * Search the device enumeration table for a core matching @p descs, * * @param bp Platform state containing a valid EROM parser. * @param descs The core match descriptor table. * @param num_descs The number of match descriptors in @p descs. * @param[out] info If non-NULL, will be populated with the core * info. * @param[out] addr If non-NULL, will be populated with the core's * physical register address. */ static int bcm_find_core(struct bcm_platform *bp, const struct bhnd_core_match *descs, size_t num_descs, struct bhnd_core_info *info, uintptr_t *addr) { bhnd_addr_t b_addr; bhnd_size_t b_size; int error; /* Fetch core info */ for (size_t i = 0; i < num_descs; i++) { error = bhnd_erom_lookup_core_addr(&bp->erom.obj, &descs[i], BHND_PORT_DEVICE, 0, 0, info, &b_addr, &b_size); /* Terminate search on first match */ if (error == 0) break; /* Terminate on first error (other than core not found) */ if (error != ENOENT) return (error); /* Continue search ... */ } /* Provide the core's base address */ if (addr != NULL && b_addr > UINTPTR_MAX) { BCM_ERR("core address %#jx overflows native address width\n", (uintmax_t)b_addr); return (ERANGE); } if (addr != NULL) *addr = b_addr; return (0); } /** * Read a variable directly from NVRAM, decoding as @p type. * * @param bp Platform state. * @param name The raw name of the variable to be fetched, * including any device path (/pci/1/1/varname) or * alias prefix (0:varname). * @param[out] buf On success, the requested value will be written * to this buffer. This argment may be NULL if * the value is not desired. * @param[in,out] len The capacity of @p buf. On success, will be set * to the actual size of the requested value. * @param type The data type to be written to @p buf. * * @retval 0 success * @retval ENOMEM If @p buf is non-NULL and a buffer of @p len is too * small to hold the requested value. * @retval ENOENT If @p name is not found. * @retval EFTYPE If the variable data cannot be coerced to @p type. * @retval ERANGE If value coercion would overflow @p type. * @retval non-zero If parsing NVRAM otherwise fails, a regular unix error * code will be returned. */ int bcm_get_nvram(struct bcm_platform *bp, const char *name, void *buf, size_t *len, bhnd_nvram_type type) { if (bp->nvram_io == NULL || bp->nvram_cls == NULL) return (ENOENT); return (bhnd_nvram_data_getvar_direct(bp->nvram_cls, bp->nvram_io, name, buf, len, type)); } /** * Probe and attach a bhnd_erom parser instance for the bhnd bus. * * @param[out] erom_cls The probed EROM class. * @param[out] erom_ops The storage to be used when compiling * @p erom_cls. * @param[out] erom The storage to be used when initializing the * static instance of @p erom_cls. * @param esize The total available number of bytes allocated * for @p erom. If this is less than is required * by @p erom_cls ENOMEM will be returned. * @param eio EROM I/O callbacks to be used. * @param[out] cid On success, the probed chip identification. */ static int bcm_erom_probe_and_attach(bhnd_erom_class_t **erom_cls, kobj_ops_t erom_ops, bhnd_erom_t *erom, size_t esize, struct bhnd_erom_io *eio, struct bhnd_chipid *cid) { bhnd_erom_class_t **clsp; bus_addr_t bus_addr; int error, prio, result; *erom_cls = NULL; prio = 0; /* Map our first bus core for the erom probe */ bus_addr = bcm_get_bus_addr(); if ((error = bhnd_erom_io_map(eio, bus_addr, BHND_DEFAULT_CORE_SIZE))) { BCM_ERR("failed to map first core at %#jx+%#jx: %d\n", (uintmax_t)bus_addr, (uintmax_t)BHND_DEFAULT_CORE_SIZE, error); return (error); } SET_FOREACH(clsp, bhnd_erom_class_set) { struct bhnd_chipid pcid; bhnd_erom_class_t *cls; struct kobj_ops kops; cls = *clsp; /* Compile the class' ops table */ kobj_class_compile_static(cls, &kops); /* Probe the bus address */ result = bhnd_erom_probe(cls, eio, NULL, &pcid); /* Drop pointer to stack allocated ops table */ cls->ops = NULL; /* The parser did not match if an error was returned */ if (result > 0) continue; /* Check for a new highest priority match */ if (*erom_cls == NULL || result > prio) { prio = result; *cid = pcid; *erom_cls = cls; } /* Terminate immediately on BUS_PROBE_SPECIFIC */ if (result == BUS_PROBE_SPECIFIC) break; } /* Valid EROM class probed? */ if (*erom_cls == NULL) { BCM_ERR("no erom parser found for root bus at %#jx\n", (uintmax_t)bus_addr); return (ENOENT); } /* Using the provided storage, recompile the erom class ... */ kobj_class_compile_static(*erom_cls, erom_ops); /* ... and initialize the erom parser instance */ error = bhnd_erom_init_static(*erom_cls, erom, esize, cid, eio); return (error); } /** * Populate platform configuration data. */ static int bcm_init_platform_data(struct bcm_platform *bp) { bus_addr_t bus_addr, bus_size; bus_space_tag_t erom_bst; bus_space_handle_t erom_bsh; bool aob, pmu; int error; bus_addr = bcm_get_bus_addr(); bus_size = bcm_get_bus_size(); #ifdef CFE /* Fetch CFE console handle (if any). Must be initialized before * any calls to printf/early_putc. */ if ((bp->cfe_console = cfe_getstdhandle(CFE_STDHANDLE_CONSOLE)) < 0) bp->cfe_console = -1; /* Probe CFE NVRAM sources */ bp->nvram_io = &bcm_cfe_nvram.io; error = bcm_nvram_find_cfedev(&bcm_cfe_nvram, &bp->nvram_cls); if (error) { bp->nvram_io = NULL; bp->nvram_cls = NULL; } #endif /* CFE */ /* Probe and attach device table provider, populating our * chip identification */ erom_bst = mips_bus_space_generic; erom_bsh = BCM_SOC_BSH(bus_addr, 0); error = bhnd_erom_iobus_init(&bp->erom_io, bus_addr, bus_size, erom_bst, erom_bsh); if (error) { BCM_ERR("failed to initialize erom I/O callbacks: %d\n", error); return (error); } error = bcm_erom_probe_and_attach(&bp->erom_impl, &bp->erom_ops, &bp->erom.obj, sizeof(bp->erom), &bp->erom_io.eio, &bp->cid); if (error) { BCM_ERR("error attaching erom parser: %d\n", error); bhnd_erom_io_fini(&bp->erom_io.eio); return (error); } if (bootverbose) bhnd_erom_dump(&bp->erom.obj); /* Fetch chipcommon core info */ error = bcm_find_core(bp, bcm_chipc_cores, nitems(bcm_chipc_cores), &bp->cc_id, &bp->cc_addr); if (error) { BCM_ERR("error locating chipc core: %d\n", error); return (error); } /* Fetch chipc capability flags */ bp->cc_caps = BCM_SOC_READ_4(bp->cc_addr, CHIPC_CAPABILITIES); bp->cc_caps_ext = 0x0; if (CHIPC_HWREV_HAS_CAP_EXT(bp->cc_id.hwrev)) bp->cc_caps_ext = BCM_CHIPC_READ_4(bp, CHIPC_CAPABILITIES_EXT); /* Fetch PMU info */ pmu = CHIPC_GET_FLAG(bp->cc_caps, CHIPC_CAP_PMU); aob = CHIPC_GET_FLAG(bp->cc_caps_ext, CHIPC_CAP2_AOB); if (pmu && aob) { /* PMU block mapped to a PMU core on the Always-on-Bus (aob) */ error = bcm_find_core(bp, bcm_pmu_cores, nitems(bcm_pmu_cores), &bp->pmu_id, &bp->pmu_addr); if (error) { BCM_ERR("error locating pmu core: %d\n", error); return (error); } } else if (pmu) { /* PMU block mapped to chipc */ bp->pmu_addr = bp->cc_addr; bp->pmu_id = bp->cc_id; } else { /* No PMU */ bp->pmu_addr = 0x0; memset(&bp->pmu_id, 0, sizeof(bp->pmu_id)); } /* Initialize PMU query state */ if (pmu) { error = bhnd_pmu_query_init(&bp->pmu, NULL, bp->cid, &bcm_pmu_soc_io, bp); if (error) { 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 */ if ((error = bhnd_service_registry_init(&bp->services))) { BCM_ERR("error initializing service registry: %d\n", error); return (error); } bcm_platform_data_avail = true; return (0); } void platform_cpu_init() { /* Nothing special */ } static void mips_init(void) { int i, j; printf("entry: mips_init()\n"); #ifdef CFE /* * Query DRAM memory map from CFE. */ physmem = 0; for (i = 0; i < 10; i += 2) { int result; uint64_t addr, len, type; result = cfe_enummem(i / 2, 0, &addr, &len, &type); if (result < 0) { BCM_TRACE("There is no phys memory for: %d\n", i); phys_avail[i] = phys_avail[i + 1] = 0; break; } if (type != CFE_MI_AVAILABLE) { BCM_TRACE("phys memory is not available: %d\n", i); continue; } phys_avail[i] = addr; if (i == 0 && addr == 0) { /* * If this is the first physical memory segment probed * from CFE, omit the region at the start of physical * memory where the kernel has been loaded. */ phys_avail[i] += MIPS_KSEG0_TO_PHYS(kernel_kseg0_end); } BCM_TRACE("phys memory is available for: %d\n", i); BCM_TRACE(" => addr = %jx\n", addr); BCM_TRACE(" => len = %jd\n", len); phys_avail[i + 1] = addr + len; physmem += len; } BCM_TRACE("Total phys memory is : %ld\n", physmem); realmem = btoc(physmem); #endif for (j = 0; j < i; j++) dump_avail[j] = phys_avail[j]; physmem = realmem; init_param1(); init_param2(physmem); mips_cpu_init(); pmap_bootstrap(); mips_proc0_init(); mutex_init(); kdb_init(); #ifdef KDB if (boothowto & RB_KDB) kdb_enter(KDB_WHY_BOOTFLAGS, "Boot flags requested debugger"); #endif } void platform_reset(void) { struct bcm_platform *bp; bool bcm4785war; printf("bcm::platform_reset()\n"); intr_disable(); #ifdef CFE /* Fall back on CFE if reset requested during platform * data initialization */ if (!bcm_platform_data_avail) { cfe_exit(0, 0); while (1); } #endif bp = bcm_get_platform(); bcm4785war = false; /* Handle BCM4785-specific behavior */ if (bp->cid.chip_id == BHND_CHIPID_BCM4785) { bcm4785war = true; /* Switch to async mode */ bcm_bmips_wr_pllcfg3(BMIPS_BCMCFG_PLLCFG3_SM); } /* Set watchdog (PMU or ChipCommon) */ if (bp->pmu_addr != 0x0) { BCM_PMU_WRITE_4(bp, BHND_PMU_WATCHDOG, 1); } else BCM_CHIPC_WRITE_4(bp, CHIPC_WATCHDOG, 1); /* BCM4785 */ if (bcm4785war) { mips_sync(); __asm __volatile("wait"); } while (1); } void platform_start(__register_t a0, __register_t a1, __register_t a2, __register_t a3) { vm_offset_t kernend; uint64_t platform_counter_freq; int error; /* clear the BSS and SBSS segments */ kernend = (vm_offset_t)&end; memset(&edata, 0, kernend - (vm_offset_t)(&edata)); mips_postboot_fixup(); /* Initialize pcpu stuff */ mips_pcpu0_init(); #ifdef CFE /* * Initialize CFE firmware trampolines. This must be done * before any CFE APIs are called, including writing * to the CFE console. * * CFE passes the following values in registers: * a0: firmware handle * a2: firmware entry point * a3: entry point seal */ if (a3 == CFE_EPTSEAL) cfe_init(a0, a2); #endif /* Init BCM platform data */ if ((error = bcm_init_platform_data(&bcm_platform_data))) panic("bcm_init_platform_data() failed: %d", error); platform_counter_freq = bcm_get_cpufreq(bcm_get_platform()); /* CP0 ticks every two cycles */ mips_timer_early_init(platform_counter_freq / 2); cninit(); mips_init(); mips_timer_init_params(platform_counter_freq, 1); } /* * CFE-based EARLY_PRINTF support. To use, add the following to the kernel * config: * option EARLY_PRINTF * option CFE * device cfe */ #if defined(EARLY_PRINTF) && defined(CFE) static void bcm_cfe_eputc(int c) { unsigned char ch; int handle; ch = (unsigned char) c; /* bcm_get_platform() cannot be used here, as we may be called * from bcm_init_platform_data(). */ if ((handle = bcm_platform_data.cfe_console) < 0) return; if (ch == '\n') early_putc('\r'); while ((cfe_write(handle, &ch, 1)) == 0) continue; } early_putc_t *early_putc = bcm_cfe_eputc; #endif /* EARLY_PRINTF */ Index: head/sys/mips/broadcom/bcm_machdep.h =================================================================== --- head/sys/mips/broadcom/bcm_machdep.h (revision 326078) +++ head/sys/mips/broadcom/bcm_machdep.h (revision 326079) @@ -1,130 +1,138 @@ /*- * 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: * 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_MACHDEP_H_ #define _MIPS_BROADCOM_BCM_MACHDEP_H_ #include #include #include #include #include #include "bcm_nvram_cfevar.h" extern const struct bhnd_pmu_io bcm_pmu_soc_io; struct bcm_platform { struct bhnd_chipid cid; /**< chip id */ struct bhnd_core_info cc_id; /**< chipc core info */ uintptr_t cc_addr; /**< chipc core phys address */ 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 */ bhnd_erom_class_t *erom_impl; /**< erom parser class */ struct kobj_ops erom_ops; /**< compiled kobj opcache */ struct bhnd_erom_iobus erom_io; /**< erom I/O callbacks */ union { bhnd_erom_static_t data; bhnd_erom_t obj; } erom; struct bhnd_nvram_io *nvram_io; /**< NVRAM I/O context, or NULL if unavailable */ bhnd_nvram_data_class *nvram_cls; /**< NVRAM data class, or NULL if unavailable */ struct bhnd_service_registry services; /**< platform service providers */ #ifdef CFE int cfe_console; /**< Console handle, or -1 */ #endif }; struct bcm_platform *bcm_get_platform(void); uint64_t bcm_get_cpufreq(struct bcm_platform *bp); uint64_t bcm_get_sifreq(struct bcm_platform *bp); uint64_t bcm_get_alpfreq(struct bcm_platform *bp); uint64_t bcm_get_ilpfreq(struct bcm_platform *bp); u_int bcm_get_uart_rclk(struct bcm_platform *bp); int bcm_get_nvram(struct bcm_platform *bp, const char *name, void *outp, size_t *olen, bhnd_nvram_type type); #define BCM_ERR(fmt, ...) \ printf("%s: " fmt, __FUNCTION__, ##__VA_ARGS__) #define BCM_SOC_BSH(_addr, _offset) \ ((bus_space_handle_t)BCM_SOC_ADDR((_addr), (_offset))) #define BCM_SOC_ADDR(_addr, _offset) \ MIPS_PHYS_TO_KSEG1((_addr) + (_offset)) #define BCM_SOC_READ_4(_addr, _offset) \ readl(BCM_SOC_ADDR((_addr), (_offset))) #define BCM_SOC_WRITE_4(_addr, _reg, _val) \ writel(BCM_SOC_ADDR((_addr), (_offset)), (_val)) #define BCM_CORE_ADDR(_bp, _name, _reg) \ BCM_SOC_ADDR(_bp->_name, (_reg)) #define BCM_CORE_READ_4(_bp, _name, _reg) \ readl(BCM_CORE_ADDR(_bp, _name, (_reg))) #define BCM_CORE_WRITE_4(_bp, _name, _reg, _val) \ writel(BCM_CORE_ADDR(_bp, _name, (_reg)), (_val)) #define BCM_CHIPC_READ_4(_bp, _reg) \ 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)) #define BCM_PMU_WRITE_4(_bp, _reg, _val) \ BCM_CORE_WRITE_4(_bp, pmu_addr, (_reg), (_val)) #endif /* _MIPS_BROADCOM_BCM_MACHDEP_H_ */ Index: head/sys/mips/broadcom/bcm_mips.c =================================================================== --- head/sys/mips/broadcom/bcm_mips.c (nonexistent) +++ head/sys/mips/broadcom/bcm_mips.c (revision 326079) @@ -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); Property changes on: head/sys/mips/broadcom/bcm_mips.c ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Added: svn:keywords ## -0,0 +1 ## +FreeBSD=%H \ No newline at end of property Added: svn:mime-type ## -0,0 +1 ## +text/plain \ No newline at end of property Index: head/sys/mips/broadcom/bcm_mips74k.c =================================================================== --- head/sys/mips/broadcom/bcm_mips74k.c (revision 326078) +++ head/sys/mips/broadcom/bcm_mips74k.c (revision 326079) @@ -1,127 +1,392 @@ /*- * 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: * 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. */ #include __FBSDID("$FreeBSD$"); #include #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); } 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); } static int 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 (revision 326078) +++ head/sys/mips/broadcom/bcm_mips74kreg.h (revision 326079) @@ -1,62 +1,68 @@ /*- * Copyright (c) 2016 Landon Fuller * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer, * 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_MIPS74KREG_H_ #define _MIPS_BROADCOM_MIPS74KREG_H_ #define BCM_MIPS74K_CORECTL 0x00 /**< core control */ #define BCM_MIPS74K_EXCBASE 0x04 /**< exception base */ #define BCM_MIPS74K_BIST_STATUS 0x0C /**< built-in self-test status */ #define BCM_MIPS74K_INTR_STATUS 0x10 /**< interrupt status */ /* INTR(0-5)_MASK map bcma(4) OOB interrupt bus lines to MIPS hardware * interrupts. */ #define BCM_MIPS74K_INTR0_SEL 0x14 /**< IRQ0 OOBSEL mask */ #define BCM_MIPS74K_INTR1_SEL 0x18 /**< IRQ1 OOBSEL mask */ #define BCM_MIPS74K_INTR2_SEL 0x1C /**< IRQ2 OOBSEL mask */ #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 (nonexistent) +++ head/sys/mips/broadcom/bcm_mipsvar.h (revision 326079) @@ -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_ */ Property changes on: head/sys/mips/broadcom/bcm_mipsvar.h ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Added: svn:keywords ## -0,0 +1 ## +FreeBSD=%H \ No newline at end of property Added: svn:mime-type ## -0,0 +1 ## +text/plain \ No newline at end of property Index: head/sys/mips/broadcom/bcma_nexus.c =================================================================== --- head/sys/mips/broadcom/bcma_nexus.c (revision 326078) +++ head/sys/mips/broadcom/bcma_nexus.c (revision 326079) @@ -1,114 +1,119 @@ /*- * Copyright (c) 2016 Michael Zhilin * Copyright (c) 2015-2016 Landon Fuller * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer, * 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$ */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include +#include +#include "bcm_mipsvar.h" #include "bcm_machdep.h" #include "bhnd_nexusvar.h" /* * Supports bcma(4) attachment to a MIPS nexus bus. */ 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) { int error; switch (bcm_get_platform()->cid.chip_type) { case BHND_CHIPTYPE_BCMA: case BHND_CHIPTYPE_BCMA_ALT: case BHND_CHIPTYPE_UBUS: break; default: return (ENXIO); } if ((error = bcma_probe(dev)) > 0) return (error); /* Set device description */ bhnd_set_default_bus_desc(dev, &bcm_get_platform()->cid); return (BUS_PROBE_SPECIFIC); } static int bcma_nexus_attach(device_t dev) { int error; /* Perform initial attach and enumerate our children. */ if ((error = bcma_attach(dev))) goto failed; /* Delegate remainder to standard bhnd method implementation */ if ((error = bhnd_generic_attach(dev))) goto failed; return (0); failed: device_delete_children(dev); return (error); } static device_method_t bcma_nexus_methods[] = { DEVMETHOD(device_probe, bcma_nexus_probe), DEVMETHOD(device_attach, bcma_nexus_attach), DEVMETHOD_END }; DEFINE_CLASS_2(bhnd, bcma_nexus_driver, bcma_nexus_methods, sizeof(struct bcma_softc), bhnd_nexus_driver, bcma_driver); EARLY_DRIVER_MODULE(bcma_nexus, nexus, bcma_nexus_driver, bhnd_devclass, 0, 0, BUS_PASS_BUS + BUS_PASS_ORDER_MIDDLE); Index: head/sys/mips/broadcom/bhnd_nexus.c =================================================================== --- head/sys/mips/broadcom/bhnd_nexus.c (revision 326078) +++ head/sys/mips/broadcom/bhnd_nexus.c (revision 326079) @@ -1,195 +1,217 @@ /*- * 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: * 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$ */ #include __FBSDID("$FreeBSD$"); /* * bhnd(4) driver mix-in providing shared common methods for * bhnd bus devices attached via a MIPS root nexus. */ #include #include #include +#include #include #include #include #include #include #include #include #include "bcm_machdep.h" +#include "bcm_mipsvar.h" #include "bhnd_nexusvar.h" /** * Default bhnd_nexus implementation of BHND_BUS_GET_SERVICE_REGISTRY(). */ static struct bhnd_service_registry * bhnd_nexus_get_service_registry(device_t dev, device_t child) { struct bcm_platform *bp = bcm_get_platform(); return (&bp->services); } /** * Default bhnd_nexus implementation of BHND_BUS_ACTIVATE_RESOURCE(). */ static int bhnd_nexus_activate_resource(device_t dev, device_t child, int type, int rid, struct bhnd_resource *r) { int error; /* Always direct */ if ((error = bus_activate_resource(child, type, rid, r->res))) return (error); r->direct = true; return (0); } /** * Default bhnd_nexus implementation of BHND_BUS_DEACTIVATE_RESOURCE(). */ static int bhnd_nexus_deactivate_resource(device_t dev, device_t child, int type, int rid, struct bhnd_resource *r) { int error; /* Always direct */ KASSERT(r->direct, ("indirect resource delegated to bhnd_nexus\n")); if ((error = bus_deactivate_resource(child, type, rid, r->res))) return (error); r->direct = false; return (0); } /** * Default bhnd_nexus implementation of BHND_BUS_IS_HW_DISABLED(). */ static bool bhnd_nexus_is_hw_disabled(device_t dev, device_t child) { struct bcm_platform *bp; struct bhnd_chipid *cid; bp = bcm_get_platform(); cid = &bp->cid; /* The BCM4706 low-cost package leaves secondary GMAC cores * floating */ if (cid->chip_id == BHND_CHIPID_BCM4706 && cid->chip_pkg == BHND_PKGID_BCM4706L && bhnd_get_device(child) == BHND_COREID_4706_GMAC && bhnd_get_core_unit(child) != 0) { return (true); } return (false); } /** * Default bhnd_nexus implementation of BHND_BUS_AGET_ATTACH_TYPE(). */ static bhnd_attach_type bhnd_nexus_get_attach_type(device_t dev, device_t child) { return (BHND_ATTACH_NATIVE); } /** * Default bhnd_nexus implementation of BHND_BUS_GET_CHIPID(). */ static const struct bhnd_chipid * bhnd_nexus_get_chipid(device_t dev, device_t child) { return (&bcm_get_platform()->cid); } /** - * 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[] = { /* bhnd interface */ DEVMETHOD(bhnd_bus_get_service_registry,bhnd_nexus_get_service_registry), DEVMETHOD(bhnd_bus_register_provider, bhnd_bus_generic_sr_register_provider), DEVMETHOD(bhnd_bus_deregister_provider, bhnd_bus_generic_sr_deregister_provider), DEVMETHOD(bhnd_bus_retain_provider, bhnd_bus_generic_sr_retain_provider), DEVMETHOD(bhnd_bus_release_provider, bhnd_bus_generic_sr_release_provider), DEVMETHOD(bhnd_bus_activate_resource, bhnd_nexus_activate_resource), DEVMETHOD(bhnd_bus_deactivate_resource, bhnd_nexus_deactivate_resource), 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 }; DEFINE_CLASS_0(bhnd, bhnd_nexus_driver, bhnd_nexus_methods, sizeof(struct bhnd_softc)); Index: head/sys/mips/broadcom/files.broadcom =================================================================== --- head/sys/mips/broadcom/files.broadcom (revision 326078) +++ head/sys/mips/broadcom/files.broadcom (revision 326079) @@ -1,30 +1,32 @@ # $FreeBSD$ # TODO: Add attachment elsewhere in the tree # for USB 1.1 OHCI, Ethernet and IPSEC cores # which are believed to be devices we have drivers for # which just need to be tweaked for attachment to an BHND system bus. 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 mips/broadcom/bhnd_nexus.c optional bhnd siba_nexus | \ bhnd bcma_nexus mips/broadcom/bcma_nexus.c optional bcma_nexus bcma bhnd mips/broadcom/siba_nexus.c optional siba_nexus siba bhnd mips/mips/tick.c standard mips/broadcom/uart_cpu_chipc.c optional uart mips/broadcom/uart_bus_chipc.c optional uart # TODO: Replace with BCM47xx/57xx/etc-aware geom_map geom/geom_flashmap.c standard # USB bits dev/bhnd/cores/usb/bhnd_usb.c optional usb dev/bhnd/cores/usb/bhnd_ehci.c optional ehci dev/bhnd/cores/usb/bhnd_ohci.c optional ohci Index: head/sys/mips/broadcom/siba_nexus.c =================================================================== --- head/sys/mips/broadcom/siba_nexus.c (revision 326078) +++ head/sys/mips/broadcom/siba_nexus.c (revision 326079) @@ -1,103 +1,108 @@ /*- * Copyright (c) 2015-2016 Landon Fuller * Copyright (c) 2007 Bruce M. Simpson. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 "bcm_machdep.h" +#include "bcm_mipsvar.h" #include "bhnd_nexusvar.h" /* * Supports siba(4) attachment to a MIPS nexus bus. * * 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) { int error; if (bcm_get_platform()->cid.chip_type != BHND_CHIPTYPE_SIBA) return (ENXIO); if ((error = siba_probe(dev)) > 0) return (error); /* Set device description */ bhnd_set_default_bus_desc(dev, &bcm_get_platform()->cid); return (BUS_PROBE_SPECIFIC); } static int siba_nexus_attach(device_t dev) { int error; /* Perform initial attach and enumerate our children. */ if ((error = siba_attach(dev))) goto failed; /* Delegate remainder to standard bhnd method implementation */ if ((error = bhnd_generic_attach(dev))) goto failed; return (0); failed: device_delete_children(dev); return (error); } static device_method_t siba_nexus_methods[] = { /* Device interface */ DEVMETHOD(device_probe, siba_nexus_probe), DEVMETHOD(device_attach, siba_nexus_attach), DEVMETHOD_END }; DEFINE_CLASS_2(bhnd, siba_nexus_driver, siba_nexus_methods, sizeof(struct siba_softc), bhnd_nexus_driver, siba_driver); EARLY_DRIVER_MODULE(siba_nexus, nexus, siba_nexus_driver, bhnd_devclass, 0, 0, BUS_PASS_BUS + BUS_PASS_ORDER_MIDDLE); Index: head/sys/mips/include/intr.h =================================================================== --- head/sys/mips/include/intr.h (revision 326078) +++ head/sys/mips/include/intr.h (revision 326079) @@ -1,80 +1,84 @@ /* $NetBSD: intr.h,v 1.7 2003/06/16 20:01:00 thorpej Exp $ */ /*- * Copyright (c) 1997 Mark Brinicombe. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by Mark Brinicombe * for the NetBSD Project. * 4. The name of the company nor the name of the author may be used to * endorse or promote products derived from this software without specific * prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR 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. * * $FreeBSD$ * */ #ifndef _MACHINE_INTR_H_ #define _MACHINE_INTR_H_ #ifdef INTRNG #ifdef FDT #include #endif #include #ifndef MIPS_NIRQ #define MIPS_NIRQ 128 #endif #ifndef NIRQ #define NIRQ MIPS_NIRQ #endif #ifndef FDT #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 */ int mips_pic_map_fixed_intrs(void); int mips_pic_activate_intr(device_t child, struct resource *r); int mips_pic_deactivate_intr(device_t child, struct resource *r); /* MIPS compatibility for legacy mips code */ void cpu_init_interrupts(void); void cpu_establish_hardintr(const char *, driver_filter_t *, driver_intr_t *, void *, int, int, void **); void cpu_establish_softintr(const char *, driver_filter_t *, void (*)(void*), void *, int, int, void **); /* MIPS interrupt C entry point */ void cpu_intr(struct trapframe *); #endif /* INTRNG */ #endif /* _MACHINE_INTR_H */ Index: head/sys/mips/mips/mips_pic.c =================================================================== --- head/sys/mips/mips/mips_pic.c (revision 326078) +++ head/sys/mips/mips/mips_pic.c (revision 326079) @@ -1,718 +1,714 @@ /*- * Copyright (c) 2015 Alexander Kabaev * Copyright (c) 2006 Oleksandr Tymoshenko * Copyright (c) 2002-2004 Juli Mallett * Copyright (c) 2017 The FreeBSD Foundation * All rights reserved. * * Portions of this software were developed by Landon Fuller * under sponsorship from the FreeBSD Foundation. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions, and the following disclaimer, * without modification, immediately at the beginning of the file. * 2. The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * * 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 "opt_platform.h" #include "opt_hwpmc_hooks.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef FDT #include #include #include #include #endif #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 *); static struct mips_pic_intr *mips_pic_find_intr(struct resource *r); static int mips_pic_map_fixed_intr(u_int irq, struct mips_pic_intr **mapping); static void cpu_establish_intr(struct mips_pic_softc *sc, const char *name, driver_filter_t *filt, void (*handler)(void*), void *arg, int irq, int flags, void **cookiep); #define INTR_MAP_DATA_MIPS INTR_MAP_DATA_PLAT_1 struct intr_map_data_mips_pic { struct intr_map_data hdr; u_int irq; }; /** * MIPS interrupt state; available prior to MIPS PIC device attachment. */ static struct mips_pic_intr { u_int mips_irq; /**< MIPS IRQ# 0-7 */ u_int intr_irq; /**< INTRNG IRQ#, or INTR_IRQ_INVALID if unmapped */ u_int consumers; /**< INTRNG activation refcount */ struct resource *res; /**< resource shared by all interrupt handlers registered via cpu_establish_hardintr() or cpu_establish_softintr(); NULL if no interrupt handlers are yet registered. */ } mips_pic_intrs[] = { { 0, INTR_IRQ_INVALID, 0, NULL }, { 1, INTR_IRQ_INVALID, 0, NULL }, { 2, INTR_IRQ_INVALID, 0, NULL }, { 3, INTR_IRQ_INVALID, 0, NULL }, { 4, INTR_IRQ_INVALID, 0, NULL }, { 5, INTR_IRQ_INVALID, 0, NULL }, { 6, INTR_IRQ_INVALID, 0, NULL }, { 7, INTR_IRQ_INVALID, 0, NULL }, }; struct mtx mips_pic_mtx; MTX_SYSINIT(mips_pic_mtx, &mips_pic_mtx, "mips intr controller mutex", MTX_DEF); struct mips_pic_irqsrc { struct intr_irqsrc isrc; u_int irq; }; struct mips_pic_softc { device_t pic_dev; struct mips_pic_irqsrc pic_irqs[NREAL_IRQS]; uint32_t nirqs; }; static struct mips_pic_softc *pic_sc; #define PIC_INTR_ISRC(sc, irq) (&(sc)->pic_irqs[(irq)].isrc) #ifdef FDT static struct ofw_compat_data compat_data[] = { {"mti,cpu-interrupt-controller", true}, {NULL, false} }; #endif #ifndef FDT static void mips_pic_identify(driver_t *drv, device_t parent) { BUS_ADD_CHILD(parent, 0, "cpupic", 0); } #endif static int mips_pic_probe(device_t dev) { #ifdef FDT if (!ofw_bus_status_okay(dev)) return (ENXIO); if (!ofw_bus_search_compatible(dev, compat_data)->ocd_data) return (ENXIO); #endif device_set_desc(dev, "MIPS32 Interrupt Controller"); return (BUS_PROBE_DEFAULT); } static inline void pic_irq_unmask(struct mips_pic_softc *sc, u_int irq) { mips_wr_status(mips_rd_status() | ((1 << irq) << 8)); } static inline void pic_irq_mask(struct mips_pic_softc *sc, u_int irq) { mips_wr_status(mips_rd_status() & ~((1 << irq) << 8)); } static inline intptr_t pic_xref(device_t dev) { #ifdef FDT return (OF_xref_from_node(ofw_bus_get_node(dev))); #else return (MIPS_PIC_XREF); #endif } static int mips_pic_register_isrcs(struct mips_pic_softc *sc) { int error; uint32_t irq, i, tmpirq; struct intr_irqsrc *isrc; char *name; for (irq = 0; irq < sc->nirqs; irq++) { sc->pic_irqs[irq].irq = irq; isrc = PIC_INTR_ISRC(sc, irq); if (irq < NSOFT_IRQS) { name = "sint"; tmpirq = irq; } else { name = "int"; tmpirq = irq - NSOFT_IRQS; } error = intr_isrc_register(isrc, sc->pic_dev, 0, "%s%u", name, tmpirq); if (error != 0) { for (i = 0; i < irq; i++) { intr_isrc_deregister(PIC_INTR_ISRC(sc, i)); } device_printf(sc->pic_dev, "%s failed", __func__); return (error); } } return (0); } static int mips_pic_attach(device_t dev) { struct mips_pic_softc *sc; intptr_t xref = pic_xref(dev); if (pic_sc) return (ENXIO); sc = device_get_softc(dev); sc->pic_dev = dev; pic_sc = sc; /* Set the number of interrupts */ sc->nirqs = nitems(sc->pic_irqs); /* Register the interrupts */ if (mips_pic_register_isrcs(sc) != 0) { device_printf(dev, "could not register PIC ISRCs\n"); goto cleanup; } /* * Now, when everything is initialized, it's right time to * register interrupt controller to interrupt framefork. */ if (intr_pic_register(dev, xref) == NULL) { device_printf(dev, "could not register PIC\n"); goto cleanup; } /* Claim our root controller role */ if (intr_pic_claim_root(dev, xref, mips_pic_intr, sc, 0) != 0) { device_printf(dev, "could not set PIC as a root\n"); intr_pic_deregister(dev, xref); goto cleanup; } return (0); cleanup: return(ENXIO); } int mips_pic_intr(void *arg) { struct mips_pic_softc *sc = arg; register_t cause, status; int i, intr; cause = mips_rd_cause(); status = mips_rd_status(); intr = (cause & MIPS_INT_MASK) >> 8; /* * Do not handle masked interrupts. They were masked by * pre_ithread function (mips_mask_XXX_intr) and will be * unmasked once ithread is through with handler */ intr &= (status & MIPS_INT_MASK) >> 8; while ((i = fls(intr)) != 0) { i--; /* Get a 0-offset interrupt. */ intr &= ~(1 << i); if (intr_isrc_dispatch(PIC_INTR_ISRC(sc, i), curthread->td_intr_frame) != 0) { device_printf(sc->pic_dev, "Stray interrupt %u detected\n", i); pic_irq_mask(sc, i); continue; } } KASSERT(i == 0, ("all interrupts handled")); #ifdef HWPMC_HOOKS if (pmc_hook && (PCPU_GET(curthread)->td_pflags & TDP_CALLCHAIN)) { struct trapframe *tf = PCPU_GET(curthread)->td_intr_frame; pmc_hook(PCPU_GET(curthread), PMC_FN_USER_CALLCHAIN, tf); } #endif return (FILTER_HANDLED); } static void mips_pic_disable_intr(device_t dev, struct intr_irqsrc *isrc) { u_int irq; irq = ((struct mips_pic_irqsrc *)isrc)->irq; pic_irq_mask(device_get_softc(dev), irq); } static void mips_pic_enable_intr(device_t dev, struct intr_irqsrc *isrc) { u_int irq; irq = ((struct mips_pic_irqsrc *)isrc)->irq; pic_irq_unmask(device_get_softc(dev), irq); } static int mips_pic_map_intr(device_t dev, struct intr_map_data *data, struct intr_irqsrc **isrcp) { struct mips_pic_softc *sc; int res; sc = device_get_softc(dev); res = 0; #ifdef FDT if (data->type == INTR_MAP_DATA_FDT) { struct intr_map_data_fdt *daf; daf = (struct intr_map_data_fdt *)data; if (daf->ncells != 1 || daf->cells[0] >= sc->nirqs) return (EINVAL); *isrcp = PIC_INTR_ISRC(sc, daf->cells[0]); } else #endif if (data->type == INTR_MAP_DATA_MIPS) { struct intr_map_data_mips_pic *mpd; mpd = (struct intr_map_data_mips_pic *)data; if (mpd->irq < 0 || mpd->irq >= sc->nirqs) return (EINVAL); *isrcp = PIC_INTR_ISRC(sc, mpd->irq); } else { res = ENOTSUP; } return (res); } static void mips_pic_pre_ithread(device_t dev, struct intr_irqsrc *isrc) { mips_pic_disable_intr(dev, isrc); } static void mips_pic_post_ithread(device_t dev, struct intr_irqsrc *isrc) { mips_pic_enable_intr(dev, isrc); } static void mips_pic_post_filter(device_t dev, struct intr_irqsrc *isrc) { } static device_method_t mips_pic_methods[] = { /* Device interface */ #ifndef FDT DEVMETHOD(device_identify, mips_pic_identify), #endif DEVMETHOD(device_probe, mips_pic_probe), DEVMETHOD(device_attach, mips_pic_attach), /* Interrupt controller interface */ DEVMETHOD(pic_disable_intr, mips_pic_disable_intr), DEVMETHOD(pic_enable_intr, mips_pic_enable_intr), DEVMETHOD(pic_map_intr, mips_pic_map_intr), DEVMETHOD(pic_pre_ithread, mips_pic_pre_ithread), DEVMETHOD(pic_post_ithread, mips_pic_post_ithread), DEVMETHOD(pic_post_filter, mips_pic_post_filter), { 0, 0 } }; static driver_t mips_pic_driver = { "cpupic", mips_pic_methods, sizeof(struct mips_pic_softc), }; static devclass_t mips_pic_devclass; #ifdef FDT EARLY_DRIVER_MODULE(cpupic, ofwbus, mips_pic_driver, mips_pic_devclass, 0, 0, BUS_PASS_INTERRUPT); #else EARLY_DRIVER_MODULE(cpupic, nexus, mips_pic_driver, mips_pic_devclass, 0, 0, BUS_PASS_INTERRUPT); #endif /** * Return the MIPS interrupt map entry for @p r, or NULL if no such entry has * been created. */ static struct mips_pic_intr * mips_pic_find_intr(struct resource *r) { struct mips_pic_intr *intr; rman_res_t irq; irq = rman_get_start(r); if (irq != rman_get_end(r) || rman_get_size(r) != 1) return (NULL); mtx_lock(&mips_pic_mtx); for (size_t i = 0; i < nitems(mips_pic_intrs); i++) { intr = &mips_pic_intrs[i]; if (intr->intr_irq != irq) continue; mtx_unlock(&mips_pic_mtx); return (intr); } mtx_unlock(&mips_pic_mtx); /* Not found */ return (NULL); } /** * Allocate a fixed IRQ mapping for the given MIPS @p irq, or return the * existing mapping if @p irq was previously mapped. * * @param irq The MIPS IRQ to be mapped. * @param[out] mapping On success, will be populated with the interrupt * mapping. * * @retval 0 success * @retval EINVAL if @p irq is not a valid MIPS IRQ#. * @retval non-zero If allocating the MIPS IRQ mapping otherwise fails, a * regular unix error code will be returned. */ static int mips_pic_map_fixed_intr(u_int irq, struct mips_pic_intr **mapping) { struct mips_pic_intr *intr; struct intr_map_data_mips_pic *data; device_t pic_dev; uintptr_t xref; if (irq < 0 || irq >= nitems(mips_pic_intrs)) return (EINVAL); mtx_lock(&mips_pic_mtx); /* Fetch corresponding interrupt entry */ intr = &mips_pic_intrs[irq]; KASSERT(intr->mips_irq == irq, ("intr %u found at index %u", intr->mips_irq, irq)); /* Already mapped? */ if (intr->intr_irq != INTR_IRQ_INVALID) { mtx_unlock(&mips_pic_mtx); *mapping = intr; return (0); } /* Map the interrupt */ data = (struct intr_map_data_mips_pic *)intr_alloc_map_data( INTR_MAP_DATA_MIPS, sizeof(*data), M_WAITOK | M_ZERO); data->irq = intr->mips_irq; #ifdef FDT /* PIC must be attached on FDT devices */ KASSERT(pic_sc != NULL, ("%s: no pic", __func__)); pic_dev = pic_sc->pic_dev; xref = pic_xref(pic_dev); #else /* !FDT */ /* PIC has a fixed xref, and may not have been attached yet */ pic_dev = NULL; if (pic_sc != NULL) pic_dev = pic_sc->pic_dev; xref = MIPS_PIC_XREF; #endif /* FDT */ KASSERT(intr->intr_irq == INTR_IRQ_INVALID, ("duplicate map")); intr->intr_irq = intr_map_irq(pic_dev, xref, &data->hdr); *mapping = intr; mtx_unlock(&mips_pic_mtx); return (0); } /** * * Produce fixed IRQ mappings for all MIPS IRQs. * * Non-FDT/OFW MIPS targets do not provide an equivalent to OFW_BUS_MAP_INTR(); * it is instead necessary to reserve INTRNG IRQ# 0-7 for use by MIPS device * drivers that assume INTRNG IRQs 0-7 are directly mapped to MIPS IRQs 0-7. * * XXX: There is no support in INTRNG for reserving a fixed IRQ range. However, * we should be called prior to any other interrupt mapping requests, and work * around this by iteratively allocating the required 0-7 MIP IRQ# range. * * @retval 0 success * @retval non-zero If allocating the MIPS IRQ mappings otherwise fails, a * regular unix error code will be returned. */ int mips_pic_map_fixed_intrs(void) { int error; for (u_int i = 0; i < nitems(mips_pic_intrs); i++) { struct mips_pic_intr *intr; if ((error = mips_pic_map_fixed_intr(i, &intr))) return (error); /* INTRNG IRQs 0-7 must be directly mapped to MIPS IRQs 0-7 */ if (intr->intr_irq != intr->mips_irq) { panic("invalid IRQ mapping: %u->%u", intr->intr_irq, intr->mips_irq); } } return (0); } /** * If @p r references a MIPS interrupt mapped by the MIPS32 interrupt * controller, handle interrupt activation internally. * * Otherwise, delegate directly to intr_activate_irq(). */ int mips_pic_activate_intr(device_t child, struct resource *r) { struct mips_pic_intr *intr; int error; /* Is this one of our shared MIPS interrupts? */ if ((intr = mips_pic_find_intr(r)) == NULL) { /* Delegate to standard INTRNG activation */ return (intr_activate_irq(child, r)); } /* Bump consumer count and request activation if required */ mtx_lock(&mips_pic_mtx); if (intr->consumers == UINT_MAX) { mtx_unlock(&mips_pic_mtx); return (ENOMEM); } if (intr->consumers == 0) { if ((error = intr_activate_irq(child, r))) { mtx_unlock(&mips_pic_mtx); return (error); } } intr->consumers++; mtx_unlock(&mips_pic_mtx); return (0); } /** * If @p r references a MIPS interrupt mapped by the MIPS32 interrupt * controller, handle interrupt deactivation internally. * * Otherwise, delegate directly to intr_deactivate_irq(). */ int mips_pic_deactivate_intr(device_t child, struct resource *r) { struct mips_pic_intr *intr; int error; /* Is this one of our shared MIPS interrupts? */ if ((intr = mips_pic_find_intr(r)) == NULL) { /* Delegate to standard INTRNG deactivation */ return (intr_deactivate_irq(child, r)); } /* Decrement consumer count and request deactivation if required */ mtx_lock(&mips_pic_mtx); KASSERT(intr->consumers > 0, ("refcount overrelease")); if (intr->consumers == 1) { if ((error = intr_deactivate_irq(child, r))) { mtx_unlock(&mips_pic_mtx); return (error); } } intr->consumers--; mtx_unlock(&mips_pic_mtx); return (0); } void cpu_init_interrupts(void) { } /** * Provide backwards-compatible support for registering a MIPS interrupt handler * directly, without allocating a bus resource. */ static void cpu_establish_intr(struct mips_pic_softc *sc, const char *name, driver_filter_t *filt, void (*handler)(void*), void *arg, int irq, int flags, void **cookiep) { struct mips_pic_intr *intr; struct resource *res; int rid; int error; rid = -1; /* Fetch (or create) a fixed mapping */ if ((error = mips_pic_map_fixed_intr(irq, &intr))) panic("Unable to map IRQ %d: %d", irq, error); /* Fetch the backing resource, if any */ mtx_lock(&mips_pic_mtx); res = intr->res; mtx_unlock(&mips_pic_mtx); /* Allocate our IRQ resource */ if (res == NULL) { /* Optimistically perform resource allocation */ rid = intr->intr_irq; res = bus_alloc_resource(sc->pic_dev, SYS_RES_IRQ, &rid, intr->intr_irq, intr->intr_irq, 1, RF_SHAREABLE|RF_ACTIVE); if (res != NULL) { /* Try to update intr->res */ mtx_lock(&mips_pic_mtx); if (intr->res == NULL) { intr->res = res; } mtx_unlock(&mips_pic_mtx); /* If intr->res was updated concurrently, free our local * resource allocation */ if (intr->res != res) { bus_release_resource(sc->pic_dev, SYS_RES_IRQ, rid, res); } } else { /* Maybe someone else allocated it? */ mtx_lock(&mips_pic_mtx); res = intr->res; mtx_unlock(&mips_pic_mtx); } if (res == NULL) { panic("Unable to allocate IRQ %d->%u resource", irq, intr->intr_irq); } } error = bus_setup_intr(sc->pic_dev, res, flags, filt, handler, arg, cookiep); if (error) panic("Unable to add IRQ %d handler: %d", irq, error); } void cpu_establish_hardintr(const char *name, driver_filter_t *filt, void (*handler)(void*), void *arg, int irq, int flags, void **cookiep) { KASSERT(pic_sc != NULL, ("%s: no pic", __func__)); if (irq < 0 || irq >= NHARD_IRQS) panic("%s called for unknown hard intr %d", __func__, irq); cpu_establish_intr(pic_sc, name, filt, handler, arg, irq+NSOFT_IRQS, flags, cookiep); } void cpu_establish_softintr(const char *name, driver_filter_t *filt, void (*handler)(void*), void *arg, int irq, int flags, void **cookiep) { KASSERT(pic_sc != NULL, ("%s: no pic", __func__)); if (irq < 0 || irq >= NSOFT_IRQS) panic("%s called for unknown soft intr %d", __func__, irq); cpu_establish_intr(pic_sc, name, filt, handler, arg, irq, flags, cookiep); }