Index: head/sys/dev/bhnd/bhnd.c =================================================================== --- head/sys/dev/bhnd/bhnd.c (revision 324069) +++ head/sys/dev/bhnd/bhnd.c (revision 324070) @@ -1,1284 +1,1006 @@ /*- * 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"); -/* Bus pass at which all bus-required children must be available, and - * attachment may be finalized. */ -#define BHND_FINISH_ATTACH_PASS BUS_PASS_DEFAULT - /** * 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); -static int bhnd_finish_attach(struct bhnd_softc *sc); - -static device_t bhnd_find_chipc(struct bhnd_softc *sc); -static struct chipc_caps *bhnd_find_chipc_caps(struct bhnd_softc *sc); -static device_t bhnd_find_platform_dev(struct bhnd_softc *sc, - const char *classname); -static device_t bhnd_find_pmu(struct bhnd_softc *sc); -static device_t bhnd_find_nvram(struct bhnd_softc *sc); - -static int compare_ascending_probe_order(const void *lhs, - const void *rhs); -static int compare_descending_probe_order(const void *lhs, - const void *rhs); - /** * 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; - device_t *devs; - int ndevs; int error; if (device_is_attached(dev)) return (EBUSY); sc = device_get_softc(dev); sc->dev = dev; - if ((error = device_get_children(dev, &devs, &ndevs))) - return (error); - /* Probe and attach all children */ - qsort(devs, ndevs, sizeof(*devs), compare_ascending_probe_order); - for (int i = 0; i < ndevs; i++) { - device_t child = devs[i]; - device_probe_and_attach(child); + if ((error = bhnd_bus_probe_children(dev))) { + bhnd_delete_children(sc); + return (error); } - /* Try to finalize attachment */ - if (bus_current_pass >= BHND_FINISH_ATTACH_PASS) { - if ((error = bhnd_finish_attach(sc))) - goto cleanup; - } - -cleanup: - free(devs, M_TEMP); - - if (error) - 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; - if ((error = device_get_children(sc->dev, &devs, &ndevs))) + /* Fetch children in detach order */ + error = bhnd_bus_get_children(sc->dev, &devs, &ndevs, + BHND_DEVICE_ORDER_DETACH); + if (error) return (error); - /* Detach in the reverse of attach order */ - qsort(devs, ndevs, sizeof(*devs), compare_descending_probe_order); + /* 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: - free(devs, M_TEMP); + 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); - return (bhnd_delete_children(sc)); + + 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); - if ((error = device_get_children(dev, &devs, &ndevs))) + /* Fetch children in detach order */ + error = bhnd_bus_get_children(dev, &devs, &ndevs, + BHND_DEVICE_ORDER_DETACH); + if (error) return (error); - /* Shutdown in the reverse of attach order */ - qsort(devs, ndevs, sizeof(*devs), compare_descending_probe_order); + /* 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: - free(devs, M_TEMP); + 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); - if ((error = device_get_children(dev, &devs, &ndevs))) + /* Fetch children in attach order */ + error = bhnd_bus_get_children(dev, &devs, &ndevs, + BHND_DEVICE_ORDER_ATTACH); + if (error) return (error); - qsort(devs, ndevs, sizeof(*devs), compare_ascending_probe_order); + /* 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: - free(devs, M_TEMP); + 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); - if ((error = device_get_children(dev, &devs, &ndevs))) + /* Fetch children in detach order */ + error = bhnd_bus_get_children(dev, &devs, &ndevs, + BHND_DEVICE_ORDER_DETACH); + if (error) return (error); - /* Suspend in the reverse of attach order */ - qsort(devs, ndevs, sizeof(*devs), compare_descending_probe_order); + /* 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: - free(devs, M_TEMP); + bhnd_bus_free_children(devs); return (error); } -static void -bhnd_new_pass(device_t dev) -{ - struct bhnd_softc *sc; - int error; - - sc = device_get_softc(dev); - - /* Attach any permissible children */ - bus_generic_new_pass(dev); - - /* Finalize attachment */ - if (!sc->attach_done && bus_current_pass >= BHND_FINISH_ATTACH_PASS) { - if ((error = bhnd_finish_attach(sc))) { - panic("bhnd_finish_attach() failed: %d", error); - } - } -} - -/* - * Finish any pending bus attachment operations. - * - * When attached as a SoC bus (as opposed to a bridged WiFi device), our - * platform devices may not be attached until later bus passes, necessitating - * delayed initialization on our part. - */ -static int -bhnd_finish_attach(struct bhnd_softc *sc) -{ - struct chipc_caps *ccaps; - - GIANT_REQUIRED; /* for newbus */ - - KASSERT(bus_current_pass >= BHND_FINISH_ATTACH_PASS, - ("bhnd_finish_attach() called in pass %d", bus_current_pass)); - - KASSERT(!sc->attach_done, ("duplicate call to bhnd_finish_attach()")); - - /* Locate chipc device */ - if ((sc->chipc_dev = bhnd_find_chipc(sc)) == NULL) { - device_printf(sc->dev, "error: ChipCommon device not found\n"); - return (ENXIO); - } - - ccaps = BHND_CHIPC_GET_CAPS(sc->chipc_dev); - - /* Look for NVRAM device */ - if (ccaps->nvram_src != BHND_NVRAM_SRC_UNKNOWN) { - if ((sc->nvram_dev = bhnd_find_nvram(sc)) == NULL) { - device_printf(sc->dev, - "warning: NVRAM %s device not found\n", - bhnd_nvram_src_name(ccaps->nvram_src)); - } - } - - /* Look for a PMU */ - if (ccaps->pmu || ccaps->pwr_ctrl) { - if ((sc->pmu_dev = bhnd_find_pmu(sc)) == NULL) { - device_printf(sc->dev, - "attach failed: supported PMU not found\n"); - return (ENXIO); - } - } - - /* Mark attach as completed */ - sc->attach_done = true; - - return (0); -} - -/* Locate the ChipCommon core. */ -static device_t -bhnd_find_chipc(struct bhnd_softc *sc) -{ - device_t chipc; - - /* Make sure we're holding Giant for newbus */ - GIANT_REQUIRED; - - /* chipc_dev is initialized during attachment */ - if (sc->attach_done) { - if ((chipc = sc->chipc_dev) == NULL) - return (NULL); - - goto found; - } - - /* Locate chipc core with a core unit of 0 */ - chipc = bhnd_find_child(sc->dev, BHND_DEVCLASS_CC, 0); - if (chipc == NULL) - return (NULL); - -found: - if (device_get_state(chipc) < DS_ATTACHING) { - device_printf(sc->dev, "chipc found, but did not attach\n"); - return (NULL); - } - - return (chipc); -} - -/* Locate the ChipCommon core and return the device capabilities */ -static struct chipc_caps * -bhnd_find_chipc_caps(struct bhnd_softc *sc) -{ - device_t chipc; - - if ((chipc = bhnd_find_chipc(sc)) == NULL) { - device_printf(sc->dev, - "chipc unavailable; cannot fetch capabilities\n"); - return (NULL); - } - - return (BHND_CHIPC_GET_CAPS(chipc)); -} - /** - * Find an attached platform device on @p dev, searching first for cores - * matching @p classname, and if not found, searching the children of the first - * bhnd_chipc device on the bus. - * - * @param sc Driver state. - * @param chipc Attached ChipCommon device. - * @param classname Device class to search for. - * - * @retval device_t A matching device. - * @retval NULL If no matching device is found. - */ -static device_t -bhnd_find_platform_dev(struct bhnd_softc *sc, const char *classname) -{ - device_t chipc, child; - - /* Make sure we're holding Giant for newbus */ - GIANT_REQUIRED; - - /* Look for a directly-attached child */ - child = device_find_child(sc->dev, classname, -1); - if (child != NULL) - goto found; - - /* Look for the first matching ChipCommon child */ - if ((chipc = bhnd_find_chipc(sc)) == NULL) { - device_printf(sc->dev, - "chipc unavailable; cannot locate %s\n", classname); - return (NULL); - } - - child = device_find_child(chipc, classname, -1); - if (child != NULL) - goto found; - - /* Look for a parent-attached device (e.g. nexus0 -> bhnd_nvram) */ - child = device_find_child(device_get_parent(sc->dev), classname, -1); - if (child == NULL) - return (NULL); - -found: - if (device_get_state(child) < DS_ATTACHING) - return (NULL); - - return (child); -} - -/* Locate the PMU device, if any */ -static device_t -bhnd_find_pmu(struct bhnd_softc *sc) -{ - /* Make sure we're holding Giant for newbus */ - GIANT_REQUIRED; - - /* pmu_dev is initialized during attachment */ - if (sc->attach_done) { - if (sc->pmu_dev == NULL) - return (NULL); - - if (device_get_state(sc->pmu_dev) < DS_ATTACHING) - return (NULL); - - return (sc->pmu_dev); - } - - - return (bhnd_find_platform_dev(sc, "bhnd_pmu")); -} - -/* Locate the NVRAM device, if any */ -static device_t -bhnd_find_nvram(struct bhnd_softc *sc) -{ - struct chipc_caps *ccaps; - - /* Make sure we're holding Giant for newbus */ - GIANT_REQUIRED; - - - /* nvram_dev is initialized during attachment */ - if (sc->attach_done) { - if (sc->nvram_dev == NULL) - return (NULL); - - if (device_get_state(sc->nvram_dev) < DS_ATTACHING) - return (NULL); - - return (sc->nvram_dev); - } - - if ((ccaps = bhnd_find_chipc_caps(sc)) == NULL) - return (NULL); - - if (ccaps->nvram_src == BHND_NVRAM_SRC_UNKNOWN) - return (NULL); - - return (bhnd_find_platform_dev(sc, "bhnd_nvram")); -} - -/* - * 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)); -} - -/** * 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_find_hostb_device(dev) == child) + 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 chipc_caps *ccaps; 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; - if ((ccaps = bhnd_find_chipc_caps(sc)) == NULL) { - device_printf(sc->dev, "alloc_pmu failed: chipc " - "capabilities unavailable\n"); - return (ENXIO); - } - - if ((pmu_dev = bhnd_find_pmu(sc)) == NULL) { - device_printf(sc->dev, - "pmu unavailable; cannot allocate request state\n"); - return (ENXIO); - } - /* 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) + 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_pmu = pmu_dev; 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; - device_t pmu; int error; GIANT_REQUIRED; /* for newbus */ sc = device_get_softc(dev); - if ((pmu = bhnd_find_pmu(sc)) == NULL) { - device_printf(sc->dev, - "pmu unavailable; cannot release request state\n"); - return (ENXIO); - } - /* 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(pmu, pm))) + 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 usable NVRAM child device. + * This implementation searches @p dev for a registered NVRAM child device. * - * If no usable child device is found on @p dev, the request is delegated to + * 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 */ - if ((nvram = bhnd_find_nvram(sc)) != NULL) - return BHND_NVRAM_GETVAR(nvram, name, buf, size, type); + 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)); } - - /* Clean up platform device references */ - if (sc->chipc_dev == child) { - sc->chipc_dev = NULL; - } else if (sc->nvram_dev == child) { - sc->nvram_dev = NULL; - } else if (sc->pmu_dev == child) { - sc->pmu_dev = NULL; - } } /** * 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); } /* * 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_new_pass, bhnd_new_pass), 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_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 324069) +++ head/sys/dev/bhnd/bhnd.h (revision 324070) @@ -1,1438 +1,1603 @@ /*- - * 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_BHND_H_ #define _BHND_BHND_H_ -#include +#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) } #define BHND_PKG_QUIRK(_chip, _pkg, _flags) \ {{ BHND_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_match_child(device_t dev, +device_t bhnd_bus_match_child(device_t bus, const struct bhnd_core_match *desc); -device_t bhnd_find_child(device_t dev, +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); /** * 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_find_hostb_device(device_t dev) { +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(). * * @param dev A bhnd bus child device. */ static inline 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. * * @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 * 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. */ static inline int bhnd_get_core_ivec(device_t dev, u_int intr, uint32_t *ivec) { return (BHND_BUS_GET_CORE_IVEC(device_get_parent(dev), dev, intr, ivec)); } /** * 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 324069) +++ head/sys/dev/bhnd/bhnd_bus_if.m (revision 324070) @@ -1,1470 +1,1574 @@ #- # 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 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 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_private.h =================================================================== --- head/sys/dev/bhnd/bhnd_private.h (nonexistent) +++ head/sys/dev/bhnd/bhnd_private.h (revision 324070) @@ -0,0 +1,57 @@ +/*- + * 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. + * + * $FreeBSD$ + */ + +#ifndef _BHND_BHND_PRIVATE_H_ +#define _BHND_BHND_PRIVATE_H_ + +#include +#include + +#include "bhnd_types.h" + +/* + * Private bhnd(4) driver definitions. + */ + +/** + * A bhnd(4) service registry entry. + */ +struct bhnd_service_entry { + device_t provider; /**< service provider */ + bhnd_service_t service; /**< service implemented */ + uint32_t flags; /**< entry flags (see BHND_SPF_*) */ + volatile u_int refs; /**< reference count; updated atomically + with only a shared lock held */ + + STAILQ_ENTRY(bhnd_service_entry) link; +}; + +#endif /* _BHND_BHND_PRIVATE_H_ */ Property changes on: head/sys/dev/bhnd/bhnd_private.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/dev/bhnd/bhnd_subr.c =================================================================== --- head/sys/dev/bhnd/bhnd_subr.c (revision 324069) +++ head/sys/dev/bhnd/bhnd_subr.c (revision 324070) @@ -1,1679 +1,2319 @@ /*- - * 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 "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 dev. + * Find a @p class child device with @p unit on @p bus. * - * @param parent The bhnd-compatible bus to be searched. + * @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_find_child(device_t dev, bhnd_devclass_t class, int unit) +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_match_child(dev, &md); + return bhnd_bus_match_child(bus, &md); } /** - * Find the first child device on @p dev that matches @p desc. + * Find the first child device on @p bus that matches @p desc. * - * @param parent The bhnd-compatible bus to be searched. + * @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_match_child(device_t dev, const struct bhnd_core_match *desc) +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(dev, &devlistp, &devcnt); + 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); 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_find_hostb_device(parent); + 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); } Index: head/sys/dev/bhnd/bhnd_types.h =================================================================== --- head/sys/dev/bhnd/bhnd_types.h (revision 324069) +++ head/sys/dev/bhnd/bhnd_types.h (revision 324070) @@ -1,171 +1,183 @@ /*- - * 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_BHND_TYPES_H_ #define _BHND_BHND_TYPES_H_ #include #include "nvram/bhnd_nvram.h" /** bhnd(4) device classes. */ typedef enum { BHND_DEVCLASS_CC, /**< chipcommon i/o controller */ BHND_DEVCLASS_CC_B, /**< chipcommon auxiliary controller */ BHND_DEVCLASS_PMU, /**< pmu controller */ BHND_DEVCLASS_PCI, /**< pci host/device bridge */ BHND_DEVCLASS_PCIE, /**< pcie host/device bridge */ BHND_DEVCLASS_PCCARD, /**< pcmcia host/device bridge */ BHND_DEVCLASS_RAM, /**< internal RAM/SRAM */ BHND_DEVCLASS_MEMC, /**< memory controller */ BHND_DEVCLASS_ENET, /**< 802.3 MAC/PHY */ BHND_DEVCLASS_ENET_MAC, /**< 802.3 MAC */ BHND_DEVCLASS_ENET_PHY, /**< 802.3 PHY */ BHND_DEVCLASS_WLAN, /**< 802.11 MAC/PHY/Radio */ BHND_DEVCLASS_WLAN_MAC, /**< 802.11 MAC */ BHND_DEVCLASS_WLAN_PHY, /**< 802.11 PHY */ BHND_DEVCLASS_CPU, /**< cpu core */ BHND_DEVCLASS_SOC_ROUTER, /**< interconnect router */ BHND_DEVCLASS_SOC_BRIDGE, /**< interconnect host bridge */ BHND_DEVCLASS_EROM, /**< bus device enumeration ROM */ BHND_DEVCLASS_NVRAM, /**< nvram/flash controller */ BHND_DEVCLASS_USB_HOST, /**< USB host controller */ BHND_DEVCLASS_USB_DEV, /**< USB device controller */ BHND_DEVCLASS_USB_DUAL, /**< USB host/device controller */ BHND_DEVCLASS_OTHER = 1000, /**< other / unknown */ BHND_DEVCLASS_INVALID /**< no/invalid class */ } bhnd_devclass_t; +/** bhnd(4) platform services. */ +typedef enum { + BHND_SERVICE_CHIPC, /**< chipcommon service; implements the bhnd_chipc interface */ + BHND_SERVICE_PMU, /**< pmu service; implements the bhnd_pmu interface */ + BHND_SERVICE_NVRAM, /**< nvram service; implements the bhnd_nvram interface */ + + BHND_SERVICE_ANY = 1000, /**< match on any service type */ +} bhnd_service_t; /** * bhnd(4) port types. * * Only BHND_PORT_DEVICE is guaranteed to be supported by all bhnd(4) bus * implementations. */ typedef enum { BHND_PORT_DEVICE = 0, /**< device memory */ BHND_PORT_BRIDGE = 1, /**< bridge memory */ BHND_PORT_AGENT = 2, /**< interconnect agent/wrapper */ } bhnd_port_type; /** * bhnd(4) attachment types. */ typedef enum { BHND_ATTACH_ADAPTER = 0, /**< A bridged card, such as a PCI WiFi chipset */ BHND_ATTACH_NATIVE = 1 /**< A bus resident on the native host, such as * the primary or secondary bus of an embedded * SoC */ } bhnd_attach_type; /** * bhnd(4) clock types. */ typedef enum { /** * Dynamically select an appropriate clock source based on all * outstanding clock requests. */ BHND_CLOCK_DYN = (1 << 0), /** * Idle Low-Power (ILP). * * No register access is required, or long request latency is * acceptable. */ BHND_CLOCK_ILP = (1 << 1), /** * Active Low-Power (ALP). * * Low-latency register access and low-rate DMA. */ BHND_CLOCK_ALP = (1 << 2), /** * High Throughput (HT). * * High bus throughput and lowest-latency register access. */ BHND_CLOCK_HT = (1 << 3) } bhnd_clock; /** * Given two clock types, return the type with the highest precedence. */ static inline bhnd_clock bhnd_clock_max(bhnd_clock a, bhnd_clock b) { return (a > b ? a : b); } /** * bhnd(4) clock sources. */ typedef enum { /** * Clock is provided by the PCI bus clock */ BHND_CLKSRC_PCI = 0, /** Clock is provided by a crystal. */ BHND_CLKSRC_XTAL = 1, /** Clock is provided by a low power oscillator. */ BHND_CLKSRC_LPO = 2, /** Clock source is unknown */ BHND_CLKSRC_UNKNOWN = 3 } bhnd_clksrc; /** Evaluates to true if @p cls is a device class that can be configured * as a host bridge device. */ #define BHND_DEVCLASS_SUPPORTS_HOSTB(cls) \ ((cls) == BHND_DEVCLASS_PCI || (cls) == BHND_DEVCLASS_PCIE || \ (cls) == BHND_DEVCLASS_PCCARD) /** * BHND bus address. * * @note While the interconnect may support 64-bit addressing, not * all bridges and SoC CPUs will. */ typedef uint64_t bhnd_addr_t; #define BHND_ADDR_MAX UINT64_MAX /**< Maximum bhnd_addr_t value */ /** BHND bus size. */ typedef uint64_t bhnd_size_t; #define BHND_SIZE_MAX UINT64_MAX /**< Maximum bhnd_size_t value */ #endif /* _BHND_BHND_TYPES_H_ */ Index: head/sys/dev/bhnd/bhndb/bhnd_bhndb.c =================================================================== --- head/sys/dev/bhnd/bhndb/bhnd_bhndb.c (revision 324069) +++ head/sys/dev/bhnd/bhndb/bhnd_bhndb.c (revision 324070) @@ -1,145 +1,149 @@ /*- * 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_match_child(dev, &md)); + return (bhnd_bus_match_child(dev, &md)); } static int bhnd_bhndb_assign_intr(device_t dev, device_t child, int rid) { /* Delegate to parent bridge */ return (BHND_BUS_ASSIGN_INTR(device_get_parent(dev), child, rid)); } 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 device_method_t bhnd_bhndb_methods[] = { /* 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_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 324069) +++ head/sys/dev/bhnd/bhndb/bhndb.c (revision 324070) @@ -1,2208 +1,2238 @@ /*- - * 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$"); /* * 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 int bhndb_find_hostb_core(struct bhndb_softc *sc, bhnd_erom_t *erom, struct bhnd_core_info *core); static bhnd_erom_class_t *bhndb_probe_erom_class(struct bhndb_softc *sc, struct bhnd_chipid *cid); static int bhndb_init_full_config(struct bhndb_softc *sc, bhnd_erom_class_t *eromcls); static struct bhnd_core_info *bhndb_get_bridge_core(struct bhndb_softc *sc); 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 the bridge core info. Will panic if the bridge core info has not yet * been populated during full bridge configuration. * * @param sc BHNDB device state. */ static struct bhnd_core_info * bhndb_get_bridge_core(struct bhndb_softc *sc) { if (!sc->have_br_core) panic("bridge not yet fully configured; no bridge core!"); return (&sc->bridge_core); } /** * 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 bridge_devclass The device class of the bridging core. This is used * to automatically detect the bridge core, and to disable additional bridge * cores (e.g. PCMCIA on a PCIe device). */ int bhndb_attach(device_t dev, bhnd_devclass_t bridge_devclass) { struct bhndb_devinfo *dinfo; struct bhndb_softc *sc; const struct bhndb_hwcfg *cfg; bhnd_erom_class_t *eromcls; int error; sc = device_get_softc(dev); sc->dev = dev; sc->parent_dev = device_get_parent(dev); sc->bridge_class = bridge_devclass; + if ((error = bhnd_service_registry_init(&sc->services))) + return (error); + BHNDB_LOCK_INIT(sc); /* Populate generic resource allocation state. */ cfg = BHNDB_BUS_GET_GENERIC_HWCFG(sc->parent_dev, sc->dev); sc->bus_res = bhndb_alloc_resources(dev, sc->parent_dev, cfg); - if (sc->bus_res == NULL) { - return (ENXIO); - } + if (sc->bus_res == NULL) + goto failed; /* Allocate our host resources */ if ((error = bhndb_alloc_host_resources(sc->bus_res))) goto failed; /* Probe for a usable EROM class for our bridged bhnd(4) bus and * populate our chip identifier. */ BHNDB_LOCK(sc); if ((eromcls = bhndb_probe_erom_class(sc, &sc->chipid)) == NULL) { BHNDB_UNLOCK(sc); device_printf(sc->dev, "device enumeration unsupported; no " "compatible driver found\n"); return (ENXIO); } BHNDB_UNLOCK(sc); /* 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; /* Enumerate the bridged device and fully initialize our bridged * resource configuration */ if ((error = bhndb_init_full_config(sc, eromcls))) { device_printf(sc->dev, "initializing full bridge " "configuration failed: %d\n", error); goto failed; } return (0); failed: BHNDB_LOCK_DESTROY(sc); if (sc->bus_res != NULL) bhndb_free_resources(sc->bus_res); + bhnd_service_registry_fini(&sc->services); + return (error); } /** * Return a borrowed reference to the host resource mapping at least * BHND_DEFAULT_CORE_SIZE bytes at the first bus core, for use with * bhnd_erom_probe(). * * This may return a borrowed reference to a bhndb_dw_alloc-managed * resource; any additional resource mapping requests may invalidate this * borrowed reference. * * @param sc BHNDB driver state. * @param[out] offset On success, the offset within the returned resource * at which the first bus core can be found. * * @retval non-NULL success. * @retval NULL If no usable mapping could be found. */ static struct resource * bhndb_erom_chipc_resource(struct bhndb_softc *sc, bus_size_t *offset) { const struct bhndb_hwcfg *cfg; struct bhndb_dw_alloc *dwa; struct resource *res; const struct bhndb_regwin *win; BHNDB_LOCK_ASSERT(sc, MA_OWNED); cfg = sc->bus_res->cfg; /* Find a static register window mapping ChipCommon. */ win = bhndb_regwin_find_core(cfg->register_windows, BHND_DEVCLASS_CC, 0, BHND_PORT_DEVICE, 0, 0); if (win != NULL) { if (win->win_size < BHND_DEFAULT_CORE_SIZE) { device_printf(sc->dev, "chipcommon register window too small\n"); return (NULL); } res = bhndb_find_regwin_resource(sc->bus_res, win); if (res == NULL) { device_printf(sc->dev, "chipcommon register window not allocated\n"); return (NULL); } *offset = win->win_offset; return (res); } /* We'll need to fetch and configure a dynamic window. We can assume a * device without a static ChipCommon mapping uses the default siba(4) * base address. */ dwa = bhndb_io_resource(sc, BHND_DEFAULT_CHIPC_ADDR, BHND_DEFAULT_CORE_SIZE, offset); if (dwa != NULL) return (dwa->parent_res); device_printf(sc->dev, "unable to map chipcommon registers; no usable " "register window found\n"); return (NULL); } /** * Probe all supported EROM classes, returning the best matching class * (or NULL if not found), writing the probed chip identifier to @p cid. * * @param sc BHNDB driver state. * @param cid On success, the bridged chipset's chip identifier. */ static bhnd_erom_class_t * bhndb_probe_erom_class(struct bhndb_softc *sc, struct bhnd_chipid *cid) { devclass_t bhndb_devclass; const struct bhnd_chipid *hint; struct resource *res; bus_size_t res_offset; driver_t **drivers; int drv_count; bhnd_erom_class_t *erom_cls; int prio, result; BHNDB_LOCK_ASSERT(sc, MA_OWNED); erom_cls = NULL; prio = 0; /* Let our parent device provide a chipid hint */ hint = BHNDB_BUS_GET_CHIPID(sc->parent_dev, sc->dev); /* Fetch a borrowed reference to the resource mapping ChipCommon. */ res = bhndb_erom_chipc_resource(sc, &res_offset); if (res == NULL) return (NULL); /* Fetch all available drivers */ bhndb_devclass = device_get_devclass(sc->dev); if (devclass_get_drivers(bhndb_devclass, &drivers, &drv_count) != 0) return (NULL); /* Enumerate the drivers looking for the best available EROM class */ for (int i = 0; i < drv_count; i++) { struct bhnd_chipid pcid; bhnd_erom_class_t *cls; cls = bhnd_driver_get_erom_class(drivers[i]); if (cls == NULL) continue; kobj_class_compile(cls); /* Probe the bus */ result = bhnd_erom_probe(cls, &BHND_DIRECT_RESOURCE(res), res_offset, hint, &pcid); /* 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; } return (erom_cls); } /* ascending core index comparison used by bhndb_find_hostb_core() */ static int compare_core_index(const void *lhs, const void *rhs) { u_int left = ((const struct bhnd_core_info *)lhs)->core_idx; u_int right = ((const struct bhnd_core_info *)rhs)->core_idx; if (left < right) return (-1); else if (left > right) return (1); else return (0); } /** * Search @p erom 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 sc BHNDB device state. * @param erom The device enumeration table parser to be used to fetch * core info. * @param[out] core If found, the matching core info. * * @retval 0 success * @retval ENOENT not found * @retval non-zero if an error occured fetching core info. */ static int bhndb_find_hostb_core(struct bhndb_softc *sc, bhnd_erom_t *erom, struct bhnd_core_info *core) { struct bhnd_core_match md; struct bhnd_core_info *cores; u_int ncores; int error; if ((error = bhnd_erom_get_core_table(erom, &cores, &ncores))) return (error); /* Set up a match descriptor for the required device class. */ md = (struct bhnd_core_match) { BHND_MATCH_CORE_CLASS(sc->bridge_class), BHND_MATCH_CORE_UNIT(0) }; /* Ensure the table is sorted by core index value, ascending; * the host bridge must be the absolute first matching device on the * bus. */ qsort(cores, ncores, sizeof(*cores), compare_core_index); /* Find the hostb core */ error = ENOENT; for (u_int i = 0; i < ncores; i++) { if (bhnd_core_matches(&cores[i], &md)) { /* Found! */ *core = cores[i]; error = 0; break; } } /* Clean up */ bhnd_erom_free_core_table(erom, cores); return (error); } /** * Identify the bridged device and perform final bridge resource configuration * based on capabilities of the enumerated device. * * Any bridged resources allocated using the generic brige hardware * configuration must be released prior to calling this function. */ static int bhndb_init_full_config(struct bhndb_softc *sc, bhnd_erom_class_t *eromcls) { struct bhnd_core_info *cores; struct bhndb_resources *br; const struct bhndb_hw_priority *hwprio; bhnd_erom_t *erom; const struct bhndb_hw *hw; u_int ncores; int error; erom = NULL; cores = NULL; br = NULL; /* Allocate EROM parser instance */ erom = bhnd_erom_alloc(eromcls, &sc->chipid, sc->bus_dev, 0); if (erom == NULL) { device_printf(sc->dev, "failed to allocate device enumeration " "table parser\n"); return (ENXIO); } /* Look for our host bridge core */ if ((error = bhndb_find_hostb_core(sc, erom, &sc->bridge_core))) { device_printf(sc->dev, "no host bridge core found\n"); goto cleanup; } else { sc->have_br_core = true; } /* Fetch the bridged device's core table */ if ((error = bhnd_erom_get_core_table(erom, &cores, &ncores))) { device_printf(sc->dev, "error fetching core table: %d\n", error); goto cleanup; } /* Find our full register window configuration */ if ((error = bhndb_find_hwspec(sc, cores, ncores, &hw))) { device_printf(sc->dev, "unable to identify device, " " using generic bridge resource definitions\n"); error = 0; goto cleanup; } if (bootverbose || BHNDB_DEBUG(PRIO)) device_printf(sc->dev, "%s resource configuration\n", hw->name); /* Allocate new bridge resource state using the discovered hardware * configuration */ br = bhndb_alloc_resources(sc->dev, sc->parent_dev, hw->cfg); if (br == NULL) { device_printf(sc->dev, "failed to allocate new resource state\n"); error = ENOMEM; goto cleanup; } /* Populate our resource priority configuration */ hwprio = BHNDB_BUS_GET_HARDWARE_PRIO(sc->parent_dev, sc->dev); error = bhndb_init_region_cfg(sc, erom, br, cores, ncores, hwprio); if (error) { device_printf(sc->dev, "failed to initialize resource " "priority configuration: %d\n", error); goto cleanup; } /* The EROM parser holds a reference to the resource state we're * about to invalidate */ bhnd_erom_free_core_table(erom, cores); bhnd_erom_free(erom); cores = NULL; erom = NULL; /* Replace existing resource state */ bhndb_free_resources(sc->bus_res); sc->bus_res = br; /* Pointer is now owned by sc->bus_res */ br = NULL; /* Re-allocate host resources */ if ((error = bhndb_alloc_host_resources(sc->bus_res))) { device_printf(sc->dev, "failed to reallocate bridge host " "resources: %d\n", error); goto cleanup; } return (0); cleanup: if (cores != NULL) bhnd_erom_free_core_table(erom, cores); if (erom != NULL) bhnd_erom_free(erom); if (br != NULL) bhndb_free_resources(br); 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); 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; struct bhnd_core_info *bridge_core; 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 */ bridge_core = bhndb_get_bridge_core(sc); if (BHND_DEVCLASS_SUPPORTS_HOSTB(bhnd_core_class(core))) return (!bhnd_cores_equal(core, 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 = *bhndb_get_bridge_core(sc); 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); 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_find_regwin_resource(sc->bus_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) return (ENXIO); 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_find_resource_range(sc->bus_res, 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); } return (0); } /** * 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_BRIDGED children, attempts to activate a static register * window, a dynamic register window, or configures @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; 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 * allocation if the priority is too low. */ if (bhndb_get_addrspace(sc, child) == BHNDB_ADDRSPACE_BRIDGED) { bhndb_priority_t r_prio; 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); } /* 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 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_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_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/bhndbvar.h =================================================================== --- head/sys/dev/bhnd/bhndb/bhndbvar.h (revision 324069) +++ head/sys/dev/bhnd/bhndb/bhndbvar.h (revision 324070) @@ -1,103 +1,109 @@ /*- - * 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_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); struct bhndb_resources; int bhndb_attach(device_t dev, bhnd_devclass_t bridge_devclass); 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); /** * 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. */ }; /** * 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 */ bhnd_devclass_t bridge_class; /**< bridge core type */ struct bhnd_core_info bridge_core; /**< bridge core. not populated until * full bridge config is initialized */ bool have_br_core; /**< false if not yet available */ 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 */ }; #endif /* _BHND_BHNDBVAR_H_ */ Index: head/sys/dev/bhnd/bhndvar.h =================================================================== --- head/sys/dev/bhnd/bhndvar.h (revision 324069) +++ head/sys/dev/bhnd/bhndvar.h (revision 324070) @@ -1,101 +1,102 @@ /*- * 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_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_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_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); +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); +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_get_nvram_var(device_t dev, - device_t child, const char *name, void *buf, - size_t *size, bhnd_nvram_type type); +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 */ - - bool attach_done; /**< true if initialization of - * all platform devices has - * been completed */ - device_t chipc_dev; /**< bhnd_chipc device */ - device_t nvram_dev; /**< bhnd_nvram device, if any */ - device_t pmu_dev; /**< bhnd_pmu device, if any */ + 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 324069) +++ head/sys/dev/bhnd/cores/chipc/chipc.c (revision 324070) @@ -1,1372 +1,1383 @@ /*- * 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) 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++) { 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); if (error) { device_printf(sc->dev, "failed to set uart%u irq %u\n", i, CHIPC_MIPS_IRQ); return (error); } /* UART registers are mapped sequentially */ error = chipc_set_resource(sc, child, SYS_RES_MEMORY, 0, CHIPC_UART(i), CHIPC_UART_SIZE, 0, 0); if (error) return (error); } /* Flash */ flash_bus = chipc_flash_bus_name(sc->caps.flash_type); if (flash_bus != NULL) { 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) return (error); /* flashctrl registers */ error = chipc_set_resource(sc, child, SYS_RES_MEMORY, 1, CHIPC_SFLASH_BASE, CHIPC_SFLASH_SIZE, 0, 0); if (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); 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) { resource_list_free(&dinfo->resources); 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. */ 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_find_hostb_device(parent); + 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/pwrctl/bhnd_pwrctl.c =================================================================== --- head/sys/dev/bhnd/cores/chipc/pwrctl/bhnd_pwrctl.c (revision 324069) +++ head/sys/dev/bhnd/cores/chipc/pwrctl/bhnd_pwrctl.c (revision 324070) @@ -1,479 +1,500 @@ /*- * Copyright (c) 2016 Landon Fuller - * Copyright (c) 2010, Broadcom Corporation. + * Copyright (c) 2010 Broadcom Corporation. + * Copyright (c) 2017 The FreeBSD Foundation * All rights reserved. + * + * This software was developed by Landon Fuller under sponsorship from + * the FreeBSD Foundation. * * This file is derived from the siutils.c source distributed with the * Asus RT-N16 firmware source code 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. * * $Id: siutils.c,v 1.821.2.48 2011-02-11 20:59:28 Exp $ */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include "bhnd_chipc_if.h" #include "bhnd_pwrctl_private.h" /* * ChipCommon Power Control. * * Provides a bhnd_pmu_if-compatible interface to device clocking and * power management on non-PMU chipsets. */ typedef enum { BHND_PWRCTL_WAR_UP, /**< apply attach/resume workarounds */ BHND_PWRCTL_WAR_RUN, /**< apply running workarounds */ BHND_PWRCTL_WAR_DOWN, /**< apply detach/suspend workarounds */ } bhnd_pwrctl_wars; static int bhnd_pwrctl_updateclk(struct bhnd_pwrctl_softc *sc, bhnd_pwrctl_wars wars); static struct bhnd_device_quirk pwrctl_quirks[]; /* Supported parent core device identifiers */ static const struct bhnd_device pwrctl_devices[] = { BHND_DEVICE(BCM, CC, "ChipCommon Power Control", pwrctl_quirks), BHND_DEVICE_END }; /* Device quirks table */ static struct bhnd_device_quirk pwrctl_quirks[] = { BHND_CORE_QUIRK (HWREV_LTE(5), PWRCTL_QUIRK_PCICLK_CTL), BHND_CORE_QUIRK (HWREV_RANGE(6, 9), PWRCTL_QUIRK_SLOWCLK_CTL), BHND_CORE_QUIRK (HWREV_RANGE(10, 19), PWRCTL_QUIRK_INSTACLK_CTL), BHND_DEVICE_QUIRK_END }; static int bhnd_pwrctl_probe(device_t dev) { const struct bhnd_device *id; struct chipc_caps *ccaps; device_t chipc; /* Look for compatible chipc parent */ chipc = device_get_parent(dev); if (device_get_devclass(chipc) != devclass_find("bhnd_chipc")) return (ENXIO); if (device_get_driver(chipc) != &bhnd_chipc_driver) return (ENXIO); /* Verify chipc capability flags */ ccaps = BHND_CHIPC_GET_CAPS(chipc); if (ccaps->pmu || !ccaps->pwr_ctrl) return (ENXIO); /* Check for chipc device match */ id = bhnd_device_lookup(chipc, pwrctl_devices, sizeof(pwrctl_devices[0])); if (id == NULL) return (ENXIO); device_set_desc(dev, id->desc); return (BUS_PROBE_NOWILDCARD); } static int bhnd_pwrctl_attach(device_t dev) { struct bhnd_pwrctl_softc *sc; const struct bhnd_chipid *cid; struct chipc_softc *chipc_sc; bhnd_devclass_t hostb_class; device_t hostb_dev; + device_t bus; int error; sc = device_get_softc(dev); /* TODO: Need further testing on actual PWRCTL hardware */ device_printf(dev, "WARNING: Using untested PWRCTL support\n"); sc->dev = dev; sc->chipc_dev = device_get_parent(dev); sc->quirks = bhnd_device_quirks(sc->chipc_dev, pwrctl_devices, sizeof(pwrctl_devices[0])); + bus = device_get_parent(sc->chipc_dev); + /* On devices that lack a slow clock source, HT must always be * enabled. */ hostb_class = BHND_DEVCLASS_INVALID; - hostb_dev = bhnd_find_hostb_device(device_get_parent(sc->chipc_dev)); + hostb_dev = bhnd_bus_find_hostb_device(device_get_parent(sc->chipc_dev)); if (hostb_dev != NULL) hostb_class = bhnd_get_class(hostb_dev); cid = bhnd_get_chipid(sc->chipc_dev); switch (cid->chip_id) { case BHND_CHIPID_BCM4311: if (cid->chip_rev <= 1 && hostb_class == BHND_DEVCLASS_PCI) sc->quirks |= PWRCTL_QUIRK_FORCE_HT; break; case BHND_CHIPID_BCM4321: if (hostb_class == BHND_DEVCLASS_PCIE || hostb_class == BHND_DEVCLASS_PCI) sc->quirks |= PWRCTL_QUIRK_FORCE_HT; break; case BHND_CHIPID_BCM4716: if (hostb_class == BHND_DEVCLASS_PCIE) sc->quirks |= PWRCTL_QUIRK_FORCE_HT; break; } /* Fetch core register block from ChipCommon parent */ chipc_sc = device_get_softc(sc->chipc_dev); sc->res = chipc_sc->core; PWRCTL_LOCK_INIT(sc); STAILQ_INIT(&sc->clkres_list); /* Initialize power control */ PWRCTL_LOCK(sc); if ((error = bhnd_pwrctl_init(sc))) { PWRCTL_UNLOCK(sc); goto cleanup; } /* Apply default clock transitions */ if ((error = bhnd_pwrctl_updateclk(sc, BHND_PWRCTL_WAR_UP))) { PWRCTL_UNLOCK(sc); goto cleanup; } PWRCTL_UNLOCK(sc); + /* Register as the bus PMU provider */ + if ((error = bhnd_register_provider(dev, BHND_SERVICE_PMU))) { + device_printf(sc->dev, "failed to register PMU with bus : %d\n", + error); + goto cleanup; + } + return (0); cleanup: PWRCTL_LOCK_DESTROY(sc); return (error); } static int bhnd_pwrctl_detach(device_t dev) { struct bhnd_pwrctl_softc *sc; struct bhnd_pwrctl_clkres *clkres, *crnext; int error; sc = device_get_softc(dev); + if ((error = bhnd_deregister_provider(dev, BHND_SERVICE_ANY))) + return (error); + + PWRCTL_LOCK(sc); + if ((error = bhnd_pwrctl_setclk(sc, BHND_CLOCK_DYN))) return (error); + + PWRCTL_UNLOCK(sc); STAILQ_FOREACH_SAFE(clkres, &sc->clkres_list, cr_link, crnext) free(clkres, M_DEVBUF); PWRCTL_LOCK_DESTROY(sc); return (0); } static int bhnd_pwrctl_suspend(device_t dev) { struct bhnd_pwrctl_softc *sc; int error; sc = device_get_softc(dev); /* Update clock state */ PWRCTL_LOCK(sc); error = bhnd_pwrctl_updateclk(sc, BHND_PWRCTL_WAR_DOWN); PWRCTL_UNLOCK(sc); return (error); } static int bhnd_pwrctl_resume(device_t dev) { struct bhnd_pwrctl_softc *sc; int error; sc = device_get_softc(dev); PWRCTL_LOCK(sc); /* Re-initialize power control registers */ if ((error = bhnd_pwrctl_init(sc))) { device_printf(sc->dev, "PWRCTL init failed: %d\n", error); goto cleanup; } /* Restore clock state */ if ((error = bhnd_pwrctl_updateclk(sc, BHND_PWRCTL_WAR_UP))) { device_printf(sc->dev, "clock state restore failed: %d\n", error); goto cleanup; } cleanup: PWRCTL_UNLOCK(sc); return (error); } /** * Find the clock reservation associated with @p pinfo, if any. * * @param sc Driver instance state. * @param pinfo PMU info for device. */ static struct bhnd_pwrctl_clkres * bhnd_pwrctl_find_res(struct bhnd_pwrctl_softc *sc, struct bhnd_core_pmu_info *pinfo) { struct bhnd_pwrctl_clkres *clkres; PWRCTL_LOCK_ASSERT(sc, MA_OWNED); STAILQ_FOREACH(clkres, &sc->clkres_list, cr_link) { if (clkres->owner == pinfo->pm_dev) return (clkres); } /* not found */ return (NULL); } /** * Enumerate all active clock requests, compute the minimum required clock, * and issue any required clock transition. * * @param sc Driver instance state. * @param wars Work-around state. */ static int bhnd_pwrctl_updateclk(struct bhnd_pwrctl_softc *sc, bhnd_pwrctl_wars wars) { struct bhnd_pwrctl_clkres *clkres; bhnd_clock clock; PWRCTL_LOCK_ASSERT(sc, MA_OWNED); /* Default clock target */ clock = BHND_CLOCK_DYN; /* Apply quirk-specific overrides to the clock target */ switch (wars) { case BHND_PWRCTL_WAR_UP: /* Force HT clock */ if (PWRCTL_QUIRK(sc, FORCE_HT)) clock = BHND_CLOCK_HT; break; case BHND_PWRCTL_WAR_RUN: /* Cannot transition clock if FORCE_HT */ if (PWRCTL_QUIRK(sc, FORCE_HT)) return (0); break; case BHND_PWRCTL_WAR_DOWN: /* Leave default clock unmodified to permit * transition back to BHND_CLOCK_DYN on FORCE_HT devices. */ break; } /* Determine required clock */ STAILQ_FOREACH(clkres, &sc->clkres_list, cr_link) clock = bhnd_clock_max(clock, clkres->clock); /* Map to supported clock setting */ switch (clock) { case BHND_CLOCK_DYN: case BHND_CLOCK_ILP: clock = BHND_CLOCK_DYN; break; case BHND_CLOCK_ALP: /* In theory FORCE_ALP is supported by the hardware, but * there are currently no known use-cases for it; mapping * to HT is still valid, and allows us to punt on determing * where FORCE_ALP is supported and functional */ clock = BHND_CLOCK_HT; break; case BHND_CLOCK_HT: break; default: device_printf(sc->dev, "unknown clock: %#x\n", clock); return (ENODEV); } /* Issue transition */ return (bhnd_pwrctl_setclk(sc, clock)); } static int bhnd_pwrctl_core_req_clock(device_t dev, struct bhnd_core_pmu_info *pinfo, bhnd_clock clock) { struct bhnd_pwrctl_softc *sc; struct bhnd_pwrctl_clkres *clkres; int error; sc = device_get_softc(dev); error = 0; PWRCTL_LOCK(sc); clkres = bhnd_pwrctl_find_res(sc, pinfo); /* BHND_CLOCK_DYN discards the clock reservation entirely */ if (clock == BHND_CLOCK_DYN) { /* nothing to clean up? */ if (clkres == NULL) { PWRCTL_UNLOCK(sc); return (0); } /* drop reservation and apply clock transition */ STAILQ_REMOVE(&sc->clkres_list, clkres, bhnd_pwrctl_clkres, cr_link); if ((error = bhnd_pwrctl_updateclk(sc, BHND_PWRCTL_WAR_RUN))) { device_printf(dev, "clock transition failed: %d\n", error); /* restore reservation */ STAILQ_INSERT_TAIL(&sc->clkres_list, clkres, cr_link); PWRCTL_UNLOCK(sc); return (error); } /* deallocate orphaned reservation */ free(clkres, M_DEVBUF); PWRCTL_UNLOCK(sc); return (0); } /* create (or update) reservation */ if (clkres == NULL) { clkres = malloc(sizeof(struct bhnd_pwrctl_clkres), M_DEVBUF, M_NOWAIT); if (clkres == NULL) return (ENOMEM); clkres->owner = pinfo->pm_dev; clkres->clock = clock; STAILQ_INSERT_TAIL(&sc->clkres_list, clkres, cr_link); } else { KASSERT(clkres->owner == pinfo->pm_dev, ("invalid owner")); clkres->clock = clock; } /* apply clock transition */ error = bhnd_pwrctl_updateclk(sc, BHND_PWRCTL_WAR_RUN); if (error) { STAILQ_REMOVE(&sc->clkres_list, clkres, bhnd_pwrctl_clkres, cr_link); free(clkres, M_DEVBUF); } PWRCTL_UNLOCK(sc); return (error); } static int bhnd_pwrctl_core_req_ext_rsrc(device_t dev, struct bhnd_core_pmu_info *pinfo, u_int rsrc) { /* HW does not support per-core external resources */ return (ENODEV); } static int bhnd_pwrctl_core_release_ext_rsrc(device_t dev, struct bhnd_core_pmu_info *pinfo, u_int rsrc) { /* HW does not support per-core external resources */ return (ENODEV); } static int bhnd_pwrctl_core_en_clocks(device_t dev, struct bhnd_core_pmu_info *pinfo, uint32_t clocks) { /* All supported clocks are already enabled by default (?) */ clocks &= ~(BHND_CLOCK_DYN | BHND_CLOCK_ILP | BHND_CLOCK_ALP | BHND_CLOCK_HT); if (clocks != 0) { device_printf(dev, "%s requested unknown clocks: %#x\n", device_get_nameunit(pinfo->pm_dev), clocks); return (ENODEV); } return (0); } static int bhnd_pwrctl_core_release(device_t dev, struct bhnd_core_pmu_info *pinfo) { /* Requesting BHND_CLOCK_DYN releases any outstanding clock * reservations */ return (bhnd_pwrctl_core_req_clock(dev, pinfo, BHND_CLOCK_DYN)); } static device_method_t bhnd_pwrctl_methods[] = { /* Device interface */ DEVMETHOD(device_probe, bhnd_pwrctl_probe), DEVMETHOD(device_attach, bhnd_pwrctl_attach), DEVMETHOD(device_detach, bhnd_pwrctl_detach), DEVMETHOD(device_suspend, bhnd_pwrctl_suspend), DEVMETHOD(device_resume, bhnd_pwrctl_resume), /* BHND PMU interface */ DEVMETHOD(bhnd_pmu_core_req_clock, bhnd_pwrctl_core_req_clock), DEVMETHOD(bhnd_pmu_core_en_clocks, bhnd_pwrctl_core_en_clocks), DEVMETHOD(bhnd_pmu_core_req_ext_rsrc, bhnd_pwrctl_core_req_ext_rsrc), DEVMETHOD(bhnd_pmu_core_release_ext_rsrc, bhnd_pwrctl_core_release_ext_rsrc), DEVMETHOD(bhnd_pmu_core_release, bhnd_pwrctl_core_release), DEVMETHOD_END }; DEFINE_CLASS_0(bhnd_pmu, bhnd_pwrctl_driver, bhnd_pwrctl_methods, sizeof(struct bhnd_pwrctl_softc)); EARLY_DRIVER_MODULE(bhnd_pwrctl, bhnd_chipc, bhnd_pwrctl_driver, bhnd_pmu_devclass, NULL, NULL, BUS_PASS_TIMER + BUS_PASS_ORDER_MIDDLE); MODULE_DEPEND(bhnd_pwrctl, bhnd, 1, 1, 1); MODULE_VERSION(bhnd_pwrctl, 1); Index: head/sys/dev/bhnd/cores/pci/bhnd_pci_hostb.c =================================================================== --- head/sys/dev/bhnd/cores/pci/bhnd_pci_hostb.c (revision 324069) +++ head/sys/dev/bhnd/cores/pci/bhnd_pci_hostb.c (revision 324070) @@ -1,658 +1,662 @@ /*- - * 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$"); /* * 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_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_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_find_child(bhnd, BHND_DEVCLASS_CC, 0); + 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/pmu/bhnd_pmu.c =================================================================== --- head/sys/dev/bhnd/cores/pmu/bhnd_pmu.c (revision 324069) +++ head/sys/dev/bhnd/cores/pmu/bhnd_pmu.c (revision 324070) @@ -1,525 +1,540 @@ /*- * 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 #include #include #include #include "bhnd_nvram_map.h" #include "bhnd_pmureg.h" #include "bhnd_pmuvar.h" #include "bhnd_pmu_private.h" /* * Broadcom PMU driver. * * On modern BHND chipsets, the PMU, GCI, and SRENG (Save/Restore Engine?) * register blocks are found within a dedicated PMU core (attached via * the AHB 'always on bus'). * * On earlier chipsets, these register blocks are found at the same * offsets within the ChipCommon core. */ devclass_t bhnd_pmu_devclass; /**< bhnd(4) PMU device class */ static int bhnd_pmu_sysctl_bus_freq(SYSCTL_HANDLER_ARGS); static int bhnd_pmu_sysctl_cpu_freq(SYSCTL_HANDLER_ARGS); static int bhnd_pmu_sysctl_mem_freq(SYSCTL_HANDLER_ARGS); static uint32_t bhnd_pmu_read_4(bus_size_t reg, void *ctx); static void bhnd_pmu_write_4(bus_size_t reg, uint32_t val, void *ctx); static uint32_t bhnd_pmu_read_chipst(void *ctx); static const struct bhnd_pmu_io bhnd_pmu_res_io = { .rd4 = bhnd_pmu_read_4, .wr4 = bhnd_pmu_write_4, .rd_chipst = bhnd_pmu_read_chipst }; #define BPMU_ASSERT_CLKCTL_AVAIL(_pinfo) \ KASSERT(!bhnd_is_hw_suspended((_pinfo)->pm_dev), \ ("reading clkctl on suspended core will trigger system livelock")) #define BPMU_CLKCTL_READ_4(_pinfo) \ bhnd_bus_read_4((_pinfo)->pm_res, (_pinfo)->pm_regs) #define BPMU_CLKCTL_WRITE_4(_pinfo, _val) \ bhnd_bus_write_4((_pinfo)->pm_res, (_pinfo)->pm_regs, (_val)) #define BPMU_CLKCTL_SET_4(_pinfo, _val, _mask) \ BPMU_CLKCTL_WRITE_4((_pinfo), \ ((_val) & (_mask)) | (BPMU_CLKCTL_READ_4(_pinfo) & ~(_mask))) /** * Default bhnd_pmu driver implementation of DEVICE_PROBE(). */ int bhnd_pmu_probe(device_t dev) { return (BUS_PROBE_DEFAULT); } /** * Default bhnd_pmu driver implementation of DEVICE_ATTACH(). * * @param dev PMU device. * @param res The PMU device registers. The driver will maintain a borrowed * reference to this resource for the lifetime of the device. */ int bhnd_pmu_attach(device_t dev, struct bhnd_resource *res) { struct bhnd_pmu_softc *sc; struct sysctl_ctx_list *ctx; struct sysctl_oid *tree; devclass_t bhnd_class; device_t core, bus; int error; sc = device_get_softc(dev); sc->dev = dev; sc->quirks = 0; sc->res = res; /* Fetch capability flags */ sc->caps = bhnd_bus_read_4(sc->res, BHND_PMU_CAP); - /* Find the bus-attached core */ + /* Find the bus and bus-attached core */ bhnd_class = devclass_find("bhnd"); core = sc->dev; while ((bus = device_get_parent(core)) != NULL) { if (device_get_devclass(bus) == bhnd_class) break; core = bus; } if (core == NULL) { device_printf(sc->dev, "bhnd bus not found\n"); return (ENXIO); } /* Fetch chip and board info */ sc->cid = *bhnd_get_chipid(core); if ((error = bhnd_read_board_info(core, &sc->board))) { device_printf(sc->dev, "error fetching board info: %d\n", error); return (ENXIO); } /* Locate ChipCommon device */ - sc->chipc_dev = bhnd_find_child(bus, BHND_DEVCLASS_CC, 0); + sc->chipc_dev = bhnd_bus_find_child(bus, BHND_DEVCLASS_CC, 0); if (sc->chipc_dev == NULL) { device_printf(sc->dev, "chipcommon device not found\n"); return (ENXIO); } /* Initialize query state */ error = bhnd_pmu_query_init(&sc->query, dev, sc->cid, &bhnd_pmu_res_io, sc); if (error) return (error); sc->io = sc->query.io; sc->io_ctx = sc->query.io_ctx; BPMU_LOCK_INIT(sc); /* Set quirk flags */ switch (sc->cid.chip_id) { case BHND_CHIPID_BCM4328: case BHND_CHIPID_BCM5354: /* HTAVAIL/ALPAVAIL are bitswapped in CLKCTL */ sc->quirks |= BPMU_QUIRK_CLKCTL_CCS0; break; default: break; } /* Initialize PMU */ if ((error = bhnd_pmu_init(sc))) { device_printf(sc->dev, "PMU init failed: %d\n", error); goto failed; } + /* Register ourselves with the bus */ + if ((error = bhnd_register_provider(dev, BHND_SERVICE_PMU))) { + device_printf(sc->dev, "failed to register PMU with bus : %d\n", + error); + goto failed; + } + /* Set up sysctl nodes */ ctx = device_get_sysctl_ctx(dev); tree = device_get_sysctl_tree(dev); SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(tree), OID_AUTO, "bus_freq", CTLTYPE_UINT | CTLFLAG_RD, sc, 0, bhnd_pmu_sysctl_bus_freq, "IU", "Bus clock frequency"); SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(tree), OID_AUTO, "cpu_freq", CTLTYPE_UINT | CTLFLAG_RD, sc, 0, bhnd_pmu_sysctl_cpu_freq, "IU", "CPU clock frequency"); SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(tree), OID_AUTO, "mem_freq", CTLTYPE_UINT | CTLFLAG_RD, sc, 0, bhnd_pmu_sysctl_mem_freq, "IU", "Memory clock frequency"); return (0); failed: BPMU_LOCK_DESTROY(sc); bhnd_pmu_query_fini(&sc->query); return (error); } /** * Default bhnd_pmu driver implementation of DEVICE_DETACH(). */ int bhnd_pmu_detach(device_t dev) { struct bhnd_pmu_softc *sc; + int error; sc = device_get_softc(dev); + + if ((error = bhnd_deregister_provider(dev, BHND_SERVICE_ANY))) + return (error); BPMU_LOCK_DESTROY(sc); bhnd_pmu_query_fini(&sc->query); return (0); } /** * Default bhnd_pmu driver implementation of DEVICE_SUSPEND(). */ int bhnd_pmu_suspend(device_t dev) { return (0); } /** * Default bhnd_pmu driver implementation of DEVICE_RESUME(). */ int bhnd_pmu_resume(device_t dev) { struct bhnd_pmu_softc *sc; int error; sc = device_get_softc(dev); /* Re-initialize PMU */ if ((error = bhnd_pmu_init(sc))) { device_printf(sc->dev, "PMU init failed: %d\n", error); return (error); } return (0); } static int bhnd_pmu_sysctl_bus_freq(SYSCTL_HANDLER_ARGS) { struct bhnd_pmu_softc *sc; uint32_t freq; sc = arg1; BPMU_LOCK(sc); freq = bhnd_pmu_si_clock(&sc->query); BPMU_UNLOCK(sc); return (sysctl_handle_32(oidp, NULL, freq, req)); } static int bhnd_pmu_sysctl_cpu_freq(SYSCTL_HANDLER_ARGS) { struct bhnd_pmu_softc *sc; uint32_t freq; sc = arg1; BPMU_LOCK(sc); freq = bhnd_pmu_cpu_clock(&sc->query); BPMU_UNLOCK(sc); return (sysctl_handle_32(oidp, NULL, freq, req)); } static int bhnd_pmu_sysctl_mem_freq(SYSCTL_HANDLER_ARGS) { struct bhnd_pmu_softc *sc; uint32_t freq; sc = arg1; BPMU_LOCK(sc); freq = bhnd_pmu_mem_clock(&sc->query); BPMU_UNLOCK(sc); return (sysctl_handle_32(oidp, NULL, freq, req)); } static int bhnd_pmu_core_req_clock(device_t dev, struct bhnd_core_pmu_info *pinfo, bhnd_clock clock) { struct bhnd_pmu_softc *sc; uint32_t avail; uint32_t req; BPMU_ASSERT_CLKCTL_AVAIL(pinfo); sc = device_get_softc(dev); avail = 0x0; req = 0x0; switch (clock) { case BHND_CLOCK_DYN: break; case BHND_CLOCK_ILP: req |= BHND_CCS_FORCEILP; break; case BHND_CLOCK_ALP: req |= BHND_CCS_FORCEALP; avail |= BHND_CCS_ALPAVAIL; break; case BHND_CLOCK_HT: req |= BHND_CCS_FORCEHT; avail |= BHND_CCS_HTAVAIL; break; default: device_printf(dev, "%s requested unknown clock: %#x\n", device_get_nameunit(pinfo->pm_dev), clock); return (ENODEV); } BPMU_LOCK(sc); /* Issue request */ BPMU_CLKCTL_SET_4(pinfo, req, BHND_CCS_FORCE_MASK); /* Wait for clock availability */ bhnd_pmu_wait_clkst(sc, pinfo->pm_dev, pinfo->pm_res, pinfo->pm_regs, avail, avail); BPMU_UNLOCK(sc); return (0); } static int bhnd_pmu_core_en_clocks(device_t dev, struct bhnd_core_pmu_info *pinfo, uint32_t clocks) { struct bhnd_pmu_softc *sc; uint32_t avail; uint32_t req; BPMU_ASSERT_CLKCTL_AVAIL(pinfo); sc = device_get_softc(dev); avail = 0x0; req = 0x0; /* Build clock request flags */ if (clocks & BHND_CLOCK_DYN) /* nothing to enable */ clocks &= ~BHND_CLOCK_DYN; if (clocks & BHND_CLOCK_ILP) /* nothing to enable */ clocks &= ~BHND_CLOCK_ILP; if (clocks & BHND_CLOCK_ALP) { req |= BHND_CCS_ALPAREQ; avail |= BHND_CCS_ALPAVAIL; clocks &= ~BHND_CLOCK_ALP; } if (clocks & BHND_CLOCK_HT) { req |= BHND_CCS_HTAREQ; avail |= BHND_CCS_HTAVAIL; clocks &= ~BHND_CLOCK_HT; } /* Check for unknown clock values */ if (clocks != 0x0) { device_printf(dev, "%s requested unknown clocks: %#x\n", device_get_nameunit(pinfo->pm_dev), clocks); return (ENODEV); } BPMU_LOCK(sc); /* Issue request */ BPMU_CLKCTL_SET_4(pinfo, req, BHND_CCS_AREQ_MASK); /* Wait for clock availability */ bhnd_pmu_wait_clkst(sc, pinfo->pm_dev, pinfo->pm_res, pinfo->pm_regs, avail, avail); BPMU_UNLOCK(sc); return (0); } static int bhnd_pmu_core_req_ext_rsrc(device_t dev, struct bhnd_core_pmu_info *pinfo, u_int rsrc) { struct bhnd_pmu_softc *sc; uint32_t req; uint32_t avail; BPMU_ASSERT_CLKCTL_AVAIL(pinfo); sc = device_get_softc(dev); if (rsrc > BHND_CCS_ERSRC_MAX) return (EINVAL); req = BHND_PMU_SET_BITS((1<pm_dev, pinfo->pm_res, pinfo->pm_regs, avail, avail); BPMU_UNLOCK(sc); return (0); } static int bhnd_pmu_core_release_ext_rsrc(device_t dev, struct bhnd_core_pmu_info *pinfo, u_int rsrc) { struct bhnd_pmu_softc *sc; uint32_t mask; BPMU_ASSERT_CLKCTL_AVAIL(pinfo); sc = device_get_softc(dev); if (rsrc > BHND_CCS_ERSRC_MAX) return (EINVAL); mask = BHND_PMU_SET_BITS((1<pm_dev)) return (0); BPMU_LOCK(sc); /* Clear all FORCE, AREQ, and ERSRC flags */ BPMU_CLKCTL_SET_4(pinfo, 0x0, BHND_CCS_FORCE_MASK | BHND_CCS_AREQ_MASK | BHND_CCS_ERSRC_REQ_MASK); BPMU_UNLOCK(sc); return (0); } static uint32_t bhnd_pmu_read_4(bus_size_t reg, void *ctx) { struct bhnd_pmu_softc *sc = ctx; return (bhnd_bus_read_4(sc->res, reg)); } static void bhnd_pmu_write_4(bus_size_t reg, uint32_t val, void *ctx) { struct bhnd_pmu_softc *sc = ctx; return (bhnd_bus_write_4(sc->res, reg, val)); } static uint32_t bhnd_pmu_read_chipst(void *ctx) { struct bhnd_pmu_softc *sc = ctx; return (BHND_CHIPC_READ_CHIPST(sc->chipc_dev)); } static device_method_t bhnd_pmu_methods[] = { /* Device interface */ DEVMETHOD(device_probe, bhnd_pmu_probe), DEVMETHOD(device_detach, bhnd_pmu_detach), DEVMETHOD(device_suspend, bhnd_pmu_suspend), DEVMETHOD(device_resume, bhnd_pmu_resume), /* BHND PMU interface */ DEVMETHOD(bhnd_pmu_core_req_clock, bhnd_pmu_core_req_clock), DEVMETHOD(bhnd_pmu_core_en_clocks, bhnd_pmu_core_en_clocks), DEVMETHOD(bhnd_pmu_core_req_ext_rsrc, bhnd_pmu_core_req_ext_rsrc), DEVMETHOD(bhnd_pmu_core_release_ext_rsrc, bhnd_pmu_core_release_ext_rsrc), DEVMETHOD(bhnd_pmu_core_release, bhnd_pmu_core_release), DEVMETHOD_END }; DEFINE_CLASS_0(bhnd_pmu, bhnd_pmu_driver, bhnd_pmu_methods, sizeof(struct bhnd_pmu_softc)); MODULE_VERSION(bhnd_pmu, 1); Index: head/sys/dev/bhnd/nvram/bhnd_sprom.c =================================================================== --- head/sys/dev/bhnd/nvram/bhnd_sprom.c (revision 324069) +++ head/sys/dev/bhnd/nvram/bhnd_sprom.c (revision 324070) @@ -1,226 +1,250 @@ /*- * 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$"); /* * BHND SPROM driver. * * Abstract driver for memory-mapped SPROM devices. */ #include #include #include #include #include #include #include #include #include #include #include #include "bhnd_nvram_if.h" #include "bhnd_nvram_io.h" #include "bhnd_spromvar.h" /** * Default bhnd sprom driver implementation of DEVICE_PROBE(). */ int bhnd_sprom_probe(device_t dev) { device_set_desc(dev, "SPROM/OTP"); /* Refuse wildcard attachments */ return (BUS_PROBE_NOWILDCARD); } /* Default DEVICE_ATTACH() implementation; assumes a zero offset to the * SPROM data */ static int bhnd_sprom_attach_meth(device_t dev) { return (bhnd_sprom_attach(dev, 0)); } /** * BHND SPROM device attach. * * This should be called from DEVICE_ATTACH() with the @p offset to the * SPROM data. * * Assumes SPROM is mapped via SYS_RES_MEMORY resource with RID 0. * * @param dev BHND SPROM device. * @param offset Offset to the SPROM data. */ int bhnd_sprom_attach(device_t dev, bus_size_t offset) { struct bhnd_sprom_softc *sc; struct bhnd_nvram_io *io; struct bhnd_resource *r; bus_size_t r_size, sprom_size; int rid; int error; sc = device_get_softc(dev); sc->dev = dev; + sc->store = NULL; io = NULL; + r = NULL; /* Allocate SPROM resource */ rid = 0; r = bhnd_alloc_resource_any(dev, SYS_RES_MEMORY, &rid, RF_ACTIVE); if (r == NULL) { device_printf(dev, "failed to allocate resources\n"); return (ENXIO); } /* Determine SPROM size */ r_size = rman_get_size(r->res); if (r_size <= offset || (r_size - offset) > BUS_SPACE_MAXSIZE) { device_printf(dev, "invalid sprom offset\n"); error = ENXIO; goto failed; } sprom_size = r_size - offset; /* Allocate an I/O context for the SPROM parser. All SPROM reads * must be 16-bit aligned */ io = bhnd_nvram_iores_new(r, offset, sprom_size, sizeof(uint16_t)); if (io == NULL) { error = ENXIO; goto failed; } /* Initialize NVRAM data store */ error = bhnd_nvram_store_parse_new(&sc->store, io, &bhnd_nvram_sprom_class); if (error) goto failed; /* Clean up our temporary I/O context and its backing resource */ bhnd_nvram_io_free(io); bhnd_release_resource(dev, SYS_RES_MEMORY, rid, r); + io = NULL; + r = NULL; + + /* Register ourselves with the bus */ + if ((error = bhnd_register_provider(dev, BHND_SERVICE_NVRAM))) { + device_printf(dev, "failed to register NVRAM provider: %d\n", + error); + goto failed; + } + return (0); failed: /* Clean up I/O context before releasing its backing resource */ if (io != NULL) bhnd_nvram_io_free(io); - bhnd_release_resource(dev, SYS_RES_MEMORY, rid, r); + if (r != NULL) + bhnd_release_resource(dev, SYS_RES_MEMORY, rid, r); + if (sc->store != NULL) + bhnd_nvram_store_free(sc->store); + return (error); } /** * Default bhnd_sprom implementation of DEVICE_RESUME(). */ int bhnd_sprom_resume(device_t dev) { return (0); } /** * Default bhnd sprom driver implementation of DEVICE_SUSPEND(). */ int bhnd_sprom_suspend(device_t dev) { return (0); } /** * Default bhnd sprom driver implementation of DEVICE_DETACH(). */ int bhnd_sprom_detach(device_t dev) { struct bhnd_sprom_softc *sc; + int error; sc = device_get_softc(dev); + + if ((error = bhnd_deregister_provider(dev, BHND_SERVICE_ANY))) + return (error); bhnd_nvram_store_free(sc->store); return (0); } /** * Default bhnd sprom driver implementation of BHND_NVRAM_GETVAR(). */ static int bhnd_sprom_getvar_method(device_t dev, const char *name, void *buf, size_t *len, bhnd_nvram_type type) { struct bhnd_sprom_softc *sc = device_get_softc(dev); return (bhnd_nvram_store_getvar(sc->store, name, buf, len, type)); } /** * Default bhnd sprom driver implementation of BHND_NVRAM_SETVAR(). */ static int bhnd_sprom_setvar_method(device_t dev, const char *name, const void *buf, size_t len, bhnd_nvram_type type) { struct bhnd_sprom_softc *sc = device_get_softc(dev); return (bhnd_nvram_store_setvar(sc->store, name, buf, len, type)); } static device_method_t bhnd_sprom_methods[] = { /* Device interface */ DEVMETHOD(device_probe, bhnd_sprom_probe), DEVMETHOD(device_attach, bhnd_sprom_attach_meth), DEVMETHOD(device_resume, bhnd_sprom_resume), DEVMETHOD(device_suspend, bhnd_sprom_suspend), DEVMETHOD(device_detach, bhnd_sprom_detach), /* NVRAM interface */ DEVMETHOD(bhnd_nvram_getvar, bhnd_sprom_getvar_method), DEVMETHOD(bhnd_nvram_setvar, bhnd_sprom_setvar_method), DEVMETHOD_END }; DEFINE_CLASS_0(bhnd_nvram_store, bhnd_sprom_driver, bhnd_sprom_methods, sizeof(struct bhnd_sprom_softc)); MODULE_VERSION(bhnd_sprom, 1); Index: head/sys/dev/bhnd/siba/siba_bhndb.c =================================================================== --- head/sys/dev/bhnd/siba/siba_bhndb.c (revision 324069) +++ head/sys/dev/bhnd/siba/siba_bhndb.c (revision 324070) @@ -1,297 +1,301 @@ /*- - * 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" /* * 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. // static int siba_bhndb_wars_hwup(struct siba_softc *sc); /* Bridge-specific core device 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), BHND_DEVICE_QUIRK_END }; static struct bhnd_device bridge_devs[] = { BHND_DEVICE(BCM, PCI, NULL, 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; int error; sc = device_get_softc(dev); /* Perform initial attach and enumerate our children. */ if ((error = siba_attach(dev))) goto failed; /* 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; 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) continue; BHNDB_SUSPEND_RESOURCE(device_get_parent(dev), dev, SYS_RES_MEMORY, dinfo->cfg[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) continue; error = BHNDB_RESUME_RESOURCE(device_get_parent(dev), dev, SYS_RES_MEMORY, dinfo->cfg[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) { struct siba_devinfo *dinfo; device_t hostb_dev; device_t d11; uint32_t imcfg; /* Only applies when bridged by PCIe */ - if ((hostb_dev = bhnd_find_hostb_device(sc->dev)) == NULL) + 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_match_child(sc->dev, &(struct bhnd_core_match) { + d11 = bhnd_bus_match_child(sc->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")); imcfg = bhnd_bus_read_4(dinfo->cfg[0], SIBA_CFG0_IMCONFIGLOW); imcfg &= ~SIBA_IMCL_RTO_MASK; bhnd_bus_write_4(dinfo->cfg[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) { device_t hostb_dev; uint32_t quirks; int error; - if ((hostb_dev = bhnd_find_hostb_device(sc->dev)) == NULL) + 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 ((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/mips/broadcom/bcm_machdep.c =================================================================== --- head/sys/mips/broadcom/bcm_machdep.c (revision 324069) +++ head/sys/mips/broadcom/bcm_machdep.c (revision 324070) @@ -1,594 +1,603 @@ /*- * 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 "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_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_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); } /** * 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[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_chipid *cid) { bhnd_erom_class_t **clsp; bus_space_tag_t bst; bus_space_handle_t bsh; bus_addr_t bus_addr; int error, prio, result; bus_addr = bcm_get_bus_addr(); *erom_cls = NULL; prio = 0; bst = mips_bus_space_generic; bsh = BCM_SOC_BSH(bus_addr, 0); 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_static(cls, bst, bsh, bus_addr, 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 */ bsh = BCM_SOC_BSH(cid->enum_addr, 0); error = bhnd_erom_init_static(*erom_cls, erom, esize, cid, mips_bus_space_generic, bsh); return (error); } /** * Populate platform configuration data. */ static int bcm_init_platform_data(struct bcm_platform *bp) { bool aob, pmu; int error; #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 */ error = bcm_erom_probe_and_attach(&bp->erom_impl, &bp->erom_ops, &bp->erom.obj, sizeof(bp->erom), &bp->cid); if (error) { BCM_ERR("error attaching erom parser: %d\n", error); 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); } + } + + /* 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 324069) +++ head/sys/mips/broadcom/bcm_machdep.h (revision 324070) @@ -1,123 +1,129 @@ /*- * 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_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 */ /* 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 */ - uintptr_t pmu_addr; /**< PMU core phys address, or + struct bhnd_core_info pmu_id; /**< PMU core info */ + uintptr_t pmu_addr; /**< PMU core phys address, or 0x0 if no PMU */ - struct bhnd_pmu_query pmu; /**< PMU query instance */ + 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 */ + bhnd_erom_class_t *erom_impl; /**< erom parser class */ + struct kobj_ops erom_ops; /**< compiled kobj opcache */ 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_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 */ + 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_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_nvram_cfe.c =================================================================== --- head/sys/mips/broadcom/bcm_nvram_cfe.c (revision 324069) +++ head/sys/mips/broadcom/bcm_nvram_cfe.c (revision 324070) @@ -1,485 +1,504 @@ /*- * 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$"); /* * BHND CFE NVRAM driver. * * Provides access to device NVRAM via CFE. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "bhnd_nvram_if.h" #include "bcm_machdep.h" #include "bcm_nvram_cfevar.h" BHND_NVRAM_IOPS_DEFN(iocfe) #define IOCFE_LOG(_io, _fmt, ...) \ printf("%s/%s: " _fmt, __FUNCTION__, (_io)->dname, ##__VA_ARGS__) static int bcm_nvram_iocfe_init(struct bcm_nvram_iocfe *iocfe, char *dname); /** Known CFE NVRAM device names, in probe order. */ static char *nvram_cfe_devs[] = { "nflash0.nvram", /* NAND */ "nflash1.nvram", "flash0.nvram", "flash1.nvram", }; /** Supported CFE NVRAM formats, in probe order. */ static bhnd_nvram_data_class * const nvram_cfe_fmts[] = { &bhnd_nvram_bcm_class, &bhnd_nvram_tlv_class }; static int bhnd_nvram_cfe_probe(device_t dev) { struct bcm_platform *bp; /* Fetch platform NVRAM I/O context */ bp = bcm_get_platform(); if (bp->nvram_io == NULL) return (ENXIO); KASSERT(bp->nvram_cls != NULL, ("missing NVRAM class")); /* Set the device description */ device_set_desc(dev, bhnd_nvram_data_class_desc(bp->nvram_cls)); /* Refuse wildcard attachments */ return (BUS_PROBE_NOWILDCARD); } static int bhnd_nvram_cfe_attach(device_t dev) { struct bcm_platform *bp; struct bhnd_nvram_cfe_softc *sc; int error; bp = bcm_get_platform(); KASSERT(bp->nvram_io != NULL, ("missing NVRAM I/O context")); KASSERT(bp->nvram_cls != NULL, ("missing NVRAM class")); sc = device_get_softc(dev); sc->dev = dev; error = bhnd_nvram_store_parse_new(&sc->store, bp->nvram_io, bp->nvram_cls); if (error) return (error); + error = bhnd_service_registry_add(&bp->services, dev, + BHND_SERVICE_NVRAM, 0); + if (error) { + bhnd_nvram_store_free(sc->store); + return (error); + } + return (error); } static int bhnd_nvram_cfe_resume(device_t dev) { return (0); } static int bhnd_nvram_cfe_suspend(device_t dev) { return (0); } static int bhnd_nvram_cfe_detach(device_t dev) { - struct bhnd_nvram_cfe_softc *sc; + struct bcm_platform *bp; + struct bhnd_nvram_cfe_softc *sc; + int error; + bp = bcm_get_platform(); sc = device_get_softc(dev); + + error = bhnd_service_registry_remove(&bp->services, dev, + BHND_SERVICE_ANY); + if (error) + return (error); bhnd_nvram_store_free(sc->store); return (0); } static int bhnd_nvram_cfe_getvar(device_t dev, const char *name, void *buf, size_t *len, bhnd_nvram_type type) { struct bhnd_nvram_cfe_softc *sc = device_get_softc(dev); return (bhnd_nvram_store_getvar(sc->store, name, buf, len, type)); } static int bhnd_nvram_cfe_setvar(device_t dev, const char *name, const void *buf, size_t len, bhnd_nvram_type type) { struct bhnd_nvram_cfe_softc *sc = device_get_softc(dev); return (bhnd_nvram_store_setvar(sc->store, name, buf, len, type)); } /** * Find, open, identify, and initialize an I/O context mapping the CFE NVRAM * device. * * @param[out] iocfe On success, an I/O context mapping the CFE NVRAM * device. * @param[out] cls On success, the identified NVRAM data format * class. * * @retval 0 success. the caller inherits ownership of @p iocfe. * @retval non-zero if no usable CFE NVRAM device can be found, a standard * unix error will be returned. */ int bcm_nvram_find_cfedev(struct bcm_nvram_iocfe *iocfe, bhnd_nvram_data_class **cls) { char *dname; int devinfo; int error, result; for (u_int i = 0; i < nitems(nvram_cfe_fmts); i++) { *cls = nvram_cfe_fmts[i]; for (u_int j = 0; j < nitems(nvram_cfe_devs); j++) { dname = nvram_cfe_devs[j]; /* Does the device exist? */ if ((devinfo = cfe_getdevinfo(dname)) < 0) { if (devinfo != CFE_ERR_DEVNOTFOUND) { BCM_ERR("cfe_getdevinfo(%s) failed: " "%d\n", dname, devinfo); } continue; } /* Open for reading */ if ((error = bcm_nvram_iocfe_init(iocfe, dname))) continue; /* Probe */ result = bhnd_nvram_data_probe(*cls, &iocfe->io); if (result <= 0) { /* Found a supporting NVRAM data class */ return (0); } /* Keep searching */ bhnd_nvram_io_free(&iocfe->io); } } return (ENODEV); } /** * Initialize a new CFE device-backed I/O context. * * The caller is responsible for releasing all resources held by the returned * I/O context via bhnd_nvram_io_free(). * * @param[out] io On success, will be initialized as an I/O context for * CFE device @p dname. * @param dname The name of the CFE device to be opened for reading. * * @retval 0 success. * @retval non-zero if opening @p dname otherwise fails, a standard unix * error will be returned. */ static int bcm_nvram_iocfe_init(struct bcm_nvram_iocfe *iocfe, char *dname) { nvram_info_t nvram_info; int cerr, devinfo, dtype, rlen; int64_t nv_offset; u_int nv_size; bool req_blk_erase; int error; iocfe->io.iops = &bhnd_nvram_iocfe_ops; iocfe->dname = dname; /* Try to open the device */ iocfe->fd = cfe_open(dname); if (iocfe->fd <= 0) { IOCFE_LOG(iocfe, "cfe_open() failed: %d\n", iocfe->fd); return (ENXIO); } /* Try to fetch device info */ if ((devinfo = cfe_getdevinfo(iocfe->dname)) < 0) { IOCFE_LOG(iocfe, "cfe_getdevinfo() failed: %d\n", devinfo); error = ENXIO; goto failed; } /* Verify device type */ dtype = devinfo & CFE_DEV_MASK; switch (dtype) { case CFE_DEV_FLASH: case CFE_DEV_NVRAM: /* Valid device type */ break; default: IOCFE_LOG(iocfe, "unknown device type: %d\n", dtype); error = ENXIO; goto failed; } /* Try to fetch nvram info from CFE */ cerr = cfe_ioctl(iocfe->fd, IOCTL_NVRAM_GETINFO, (unsigned char *)&nvram_info, sizeof(nvram_info), &rlen, 0); if (cerr == CFE_OK) { /* Sanity check the result; must not be a negative integer */ if (nvram_info.nvram_size < 0 || nvram_info.nvram_offset < 0) { IOCFE_LOG(iocfe, "invalid NVRAM layout (%d/%d)\n", nvram_info.nvram_size, nvram_info.nvram_offset); error = ENXIO; goto failed; } nv_offset = nvram_info.nvram_offset; nv_size = nvram_info.nvram_size; req_blk_erase = (nvram_info.nvram_eraseflg != 0); } else if (cerr != CFE_OK && cerr != CFE_ERR_INV_COMMAND) { IOCFE_LOG(iocfe, "IOCTL_NVRAM_GETINFO failed: %d\n", cerr); error = ENXIO; goto failed; } /* Fall back on flash info. * * This is known to be required on the Asus RT-N53 (CFE 5.70.55.33, * BBP 1.0.37, BCM5358UB0), where IOCTL_NVRAM_GETINFO returns * CFE_ERR_INV_COMMAND. */ if (cerr == CFE_ERR_INV_COMMAND) { flash_info_t fi; cerr = cfe_ioctl(iocfe->fd, IOCTL_FLASH_GETINFO, (unsigned char *)&fi, sizeof(fi), &rlen, 0); if (cerr != CFE_OK) { IOCFE_LOG(iocfe, "IOCTL_FLASH_GETINFO failed %d\n", cerr); error = ENXIO; goto failed; } nv_offset = 0x0; nv_size = fi.flash_size; req_blk_erase = !(fi.flash_flags & FLASH_FLAG_NOERASE); } /* Verify that the full NVRAM layout can be represented via size_t */ if (nv_size > SIZE_MAX || SIZE_MAX - nv_size < nv_offset) { IOCFE_LOG(iocfe, "invalid NVRAM layout (%#x/%#jx)\n", nv_size, (intmax_t)nv_offset); error = ENXIO; goto failed; } iocfe->offset = nv_offset; iocfe->size = nv_size; iocfe->req_blk_erase = req_blk_erase; return (CFE_OK); failed: if (iocfe->fd >= 0) cfe_close(iocfe->fd); return (error); } static void bhnd_nvram_iocfe_free(struct bhnd_nvram_io *io) { struct bcm_nvram_iocfe *iocfe = (struct bcm_nvram_iocfe *)io; /* CFE I/O instances are statically allocated; we do not need to free * the instance itself */ cfe_close(iocfe->fd); } static size_t bhnd_nvram_iocfe_getsize(struct bhnd_nvram_io *io) { struct bcm_nvram_iocfe *iocfe = (struct bcm_nvram_iocfe *)io; return (iocfe->size); } static int bhnd_nvram_iocfe_setsize(struct bhnd_nvram_io *io, size_t size) { /* unsupported */ return (ENODEV); } static int bhnd_nvram_iocfe_read_ptr(struct bhnd_nvram_io *io, size_t offset, const void **ptr, size_t nbytes, size_t *navail) { /* unsupported */ return (ENODEV); } static int bhnd_nvram_iocfe_write_ptr(struct bhnd_nvram_io *io, size_t offset, void **ptr, size_t nbytes, size_t *navail) { /* unsupported */ return (ENODEV); } static int bhnd_nvram_iocfe_write(struct bhnd_nvram_io *io, size_t offset, void *buffer, size_t nbytes) { /* unsupported */ return (ENODEV); } static int bhnd_nvram_iocfe_read(struct bhnd_nvram_io *io, size_t offset, void *buffer, size_t nbytes) { struct bcm_nvram_iocfe *iocfe; size_t remain; int64_t cfe_offset; int nr, nreq; iocfe = (struct bcm_nvram_iocfe *)io; /* Determine (and validate) the base CFE offset */ #if (SIZE_MAX > INT64_MAX) if (iocfe->offset > INT64_MAX || offset > INT64_MAX) return (ENXIO); #endif if (INT64_MAX - offset < iocfe->offset) return (ENXIO); cfe_offset = iocfe->offset + offset; /* Verify that cfe_offset + nbytes is representable */ if (INT64_MAX - cfe_offset < nbytes) return (ENXIO); /* Perform the read */ for (remain = nbytes; remain > 0;) { void *p; size_t nread; int64_t cfe_noff; nread = (nbytes - remain); cfe_noff = cfe_offset + nread; p = ((uint8_t *)buffer + nread); nreq = ummin(INT_MAX, remain); nr = cfe_readblk(iocfe->fd, cfe_noff, p, nreq); if (nr < 0) { IOCFE_LOG(iocfe, "cfe_readblk() failed: %d\n", nr); return (ENXIO); } /* Check for unexpected short read */ if (nr == 0 && remain > 0) { /* If the request fits entirely within the CFE * device range, we shouldn't hit EOF */ if (remain < iocfe->size && iocfe->size - remain > offset) { IOCFE_LOG(iocfe, "cfe_readblk() returned " "unexpected short read (%d/%d)\n", nr, nreq); return (ENXIO); } } if (nr == 0) break; remain -= nr; } /* Check for short read */ if (remain > 0) return (ENXIO); return (0); } static device_method_t bhnd_nvram_cfe_methods[] = { /* Device interface */ DEVMETHOD(device_probe, bhnd_nvram_cfe_probe), DEVMETHOD(device_attach, bhnd_nvram_cfe_attach), DEVMETHOD(device_resume, bhnd_nvram_cfe_resume), DEVMETHOD(device_suspend, bhnd_nvram_cfe_suspend), DEVMETHOD(device_detach, bhnd_nvram_cfe_detach), /* NVRAM interface */ DEVMETHOD(bhnd_nvram_getvar, bhnd_nvram_cfe_getvar), DEVMETHOD(bhnd_nvram_setvar, bhnd_nvram_cfe_setvar), DEVMETHOD_END }; DEFINE_CLASS_0(bhnd_nvram, bhnd_nvram_cfe, bhnd_nvram_cfe_methods, sizeof(struct bhnd_nvram_cfe_softc)); EARLY_DRIVER_MODULE(bhnd_nvram_cfe, nexus, bhnd_nvram_cfe, bhnd_nvram_devclass, NULL, NULL, BUS_PASS_BUS + BUS_PASS_ORDER_EARLY); Index: head/sys/mips/broadcom/bhnd_nexus.c =================================================================== --- head/sys/mips/broadcom/bhnd_nexus.c (revision 324069) +++ head/sys/mips/broadcom/bhnd_nexus.c (revision 324070) @@ -1,175 +1,195 @@ /*- * 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 "bcm_machdep.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(). */ static int bhnd_nexus_get_intr_count(device_t dev, device_t child) { // TODO: arch-specific interrupt handling. return (0); } /** * Default bhnd_nexus implementation of BHND_BUS_ASSIGN_INTR(). */ static int bhnd_nexus_assign_intr(device_t dev, device_t child, int rid) { uint32_t ivec; int error; if ((error = bhnd_get_core_ivec(child, rid, &ivec))) return (error); return (bus_set_resource(child, SYS_RES_IRQ, rid, ivec, 1)); } 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_END }; DEFINE_CLASS_0(bhnd, bhnd_nexus_driver, bhnd_nexus_methods, sizeof(struct bhnd_softc));