/*- * SPDX-License-Identifier: BSD-2-Clause * * 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 "bhnd_chipc_if.h" #include "bhnd_nvram_if.h" #include "bhnd.h" #include "bhndreg.h" #include "bhndvar.h" #include "bhnd_private.h" MALLOC_DEFINE(M_BHND, "bhnd", "bhnd bus data structures"); /** * bhnd_generic_probe_nomatch() reporting configuration. */ static const struct bhnd_nomatch { uint16_t vendor; /**< core designer */ uint16_t device; /**< core id */ bool if_verbose; /**< print when bootverbose is set. */ } bhnd_nomatch_table[] = { { BHND_MFGID_ARM, BHND_COREID_OOB_ROUTER, true }, { BHND_MFGID_ARM, BHND_COREID_EROM, true }, { BHND_MFGID_ARM, BHND_COREID_PL301, true }, { BHND_MFGID_ARM, BHND_COREID_APB_BRIDGE, true }, { BHND_MFGID_ARM, BHND_COREID_AXI_UNMAPPED, false }, { BHND_MFGID_INVALID, BHND_COREID_INVALID, false } }; static int bhnd_delete_children(struct bhnd_softc *sc); /** * Default bhnd(4) bus driver implementation of DEVICE_ATTACH(). * * This implementation calls device_probe_and_attach() for each of the device's * children, in bhnd probe order. */ int bhnd_generic_attach(device_t dev) { struct bhnd_softc *sc; int error; if (device_is_attached(dev)) return (EBUSY); sc = device_get_softc(dev); sc->dev = dev; /* Probe and attach all children */ if ((error = bhnd_bus_probe_children(dev))) { bhnd_delete_children(sc); return (error); } return (0); } /** * Detach and delete all children, in reverse of their attach order. */ static int bhnd_delete_children(struct bhnd_softc *sc) { device_t *devs; int ndevs; int error; /* Fetch children in detach order */ error = bhnd_bus_get_children(sc->dev, &devs, &ndevs, BHND_DEVICE_ORDER_DETACH); if (error) return (error); /* Perform detach */ for (int i = 0; i < ndevs; i++) { device_t child = devs[i]; /* Terminate on first error */ if ((error = device_delete_child(sc->dev, child))) goto cleanup; } cleanup: bhnd_bus_free_children(devs); return (error); } /** * Default bhnd(4) bus driver implementation of DEVICE_DETACH(). * * This implementation calls device_detach() for each of the device's * children, in reverse bhnd probe order, terminating if any call to * device_detach() fails. */ int bhnd_generic_detach(device_t dev) { struct bhnd_softc *sc; int error; if (!device_is_attached(dev)) return (EBUSY); sc = device_get_softc(dev); if ((error = bhnd_delete_children(sc))) return (error); return (0); } /** * Default bhnd(4) bus driver implementation of DEVICE_SHUTDOWN(). * * This implementation calls device_shutdown() for each of the device's * children, in reverse bhnd probe order, terminating if any call to * device_shutdown() fails. */ int bhnd_generic_shutdown(device_t dev) { device_t *devs; int ndevs; int error; if (!device_is_attached(dev)) return (EBUSY); /* Fetch children in detach order */ error = bhnd_bus_get_children(dev, &devs, &ndevs, BHND_DEVICE_ORDER_DETACH); if (error) return (error); /* Perform shutdown */ for (int i = 0; i < ndevs; i++) { device_t child = devs[i]; /* Terminate on first error */ if ((error = device_shutdown(child))) goto cleanup; } cleanup: bhnd_bus_free_children(devs); return (error); } /** * Default bhnd(4) bus driver implementation of DEVICE_RESUME(). * * This implementation calls BUS_RESUME_CHILD() for each of the device's * children in bhnd probe order, terminating if any call to BUS_RESUME_CHILD() * fails. */ int bhnd_generic_resume(device_t dev) { device_t *devs; int ndevs; int error; if (!device_is_attached(dev)) return (EBUSY); /* Fetch children in attach order */ error = bhnd_bus_get_children(dev, &devs, &ndevs, BHND_DEVICE_ORDER_ATTACH); if (error) return (error); /* Perform resume */ for (int i = 0; i < ndevs; i++) { device_t child = devs[i]; /* Terminate on first error */ if ((error = BUS_RESUME_CHILD(device_get_parent(child), child))) goto cleanup; } cleanup: bhnd_bus_free_children(devs); return (error); } /** * Default bhnd(4) bus driver implementation of DEVICE_SUSPEND(). * * This implementation calls BUS_SUSPEND_CHILD() for each of the device's * children in reverse bhnd probe order. If any call to BUS_SUSPEND_CHILD() * fails, the suspend operation is terminated and any devices that were * suspended are resumed immediately by calling their BUS_RESUME_CHILD() * methods. */ int bhnd_generic_suspend(device_t dev) { device_t *devs; int ndevs; int error; if (!device_is_attached(dev)) return (EBUSY); /* Fetch children in detach order */ error = bhnd_bus_get_children(dev, &devs, &ndevs, BHND_DEVICE_ORDER_DETACH); if (error) return (error); /* Perform suspend */ for (int i = 0; i < ndevs; i++) { device_t child = devs[i]; error = BUS_SUSPEND_CHILD(device_get_parent(child), child); /* On error, resume suspended devices and then terminate */ if (error) { for (int j = 0; j < i; j++) { BUS_RESUME_CHILD(device_get_parent(devs[j]), devs[j]); } goto cleanup; } } cleanup: bhnd_bus_free_children(devs); return (error); } /** * Default bhnd(4) bus driver implementation of BHND_BUS_GET_PROBE_ORDER(). * * This implementation determines probe ordering based on the device's class * and other properties, including whether the device is serving as a host * bridge. */ int bhnd_generic_get_probe_order(device_t dev, device_t child) { switch (bhnd_get_class(child)) { case BHND_DEVCLASS_CC: /* Must be early enough to provide NVRAM access to the * host bridge */ return (BHND_PROBE_ROOT + BHND_PROBE_ORDER_FIRST); case BHND_DEVCLASS_CC_B: /* fall through */ case BHND_DEVCLASS_PMU: return (BHND_PROBE_BUS + BHND_PROBE_ORDER_EARLY); case BHND_DEVCLASS_SOC_ROUTER: return (BHND_PROBE_BUS + BHND_PROBE_ORDER_LATE); case BHND_DEVCLASS_SOC_BRIDGE: return (BHND_PROBE_BUS + BHND_PROBE_ORDER_LAST); case BHND_DEVCLASS_CPU: return (BHND_PROBE_CPU + BHND_PROBE_ORDER_FIRST); case BHND_DEVCLASS_RAM: /* fall through */ case BHND_DEVCLASS_MEMC: return (BHND_PROBE_CPU + BHND_PROBE_ORDER_EARLY); case BHND_DEVCLASS_NVRAM: return (BHND_PROBE_RESOURCE + BHND_PROBE_ORDER_EARLY); case BHND_DEVCLASS_PCI: case BHND_DEVCLASS_PCIE: case BHND_DEVCLASS_PCCARD: case BHND_DEVCLASS_ENET: case BHND_DEVCLASS_ENET_MAC: case BHND_DEVCLASS_ENET_PHY: case BHND_DEVCLASS_WLAN: case BHND_DEVCLASS_WLAN_MAC: case BHND_DEVCLASS_WLAN_PHY: case BHND_DEVCLASS_EROM: case BHND_DEVCLASS_OTHER: case BHND_DEVCLASS_INVALID: if (bhnd_bus_find_hostb_device(dev) == child) return (BHND_PROBE_ROOT + BHND_PROBE_ORDER_EARLY); return (BHND_PROBE_DEFAULT); default: return (BHND_PROBE_DEFAULT); } } /** * Default bhnd(4) bus driver implementation of BHND_BUS_ALLOC_PMU(). */ int bhnd_generic_alloc_pmu(device_t dev, device_t child) { struct bhnd_softc *sc; struct bhnd_resource *r; struct bhnd_core_clkctl *clkctl; 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; u_int max_latency; int error; bus_topo_assert(); if (device_get_parent(child) != dev) return (EINVAL); sc = device_get_softc(dev); clkctl = bhnd_get_pmu_info(child); pmu_regs = BHND_CLK_CTL_ST; /* already allocated? */ if (clkctl != 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 a PMU reference for the clkctl instance state */ pmu_dev = bhnd_retain_provider(child, BHND_SERVICE_PMU); if (pmu_dev == NULL) { device_printf(sc->dev, "PMU not found\n"); return (ENXIO); } /* Fetch the maximum transition latency from our PMU */ max_latency = bhnd_pmu_get_max_transition_latency(pmu_dev); /* Allocate a new bhnd_resource wrapping the standard resource we * fetched from the resource list; we'll free this in * bhnd_generic_release_pmu() */ r = malloc(sizeof(struct bhnd_resource), M_BHND, M_NOWAIT); if (r == NULL) { bhnd_release_provider(child, pmu_dev, BHND_SERVICE_PMU); return (ENOMEM); } r->res = rle->res; r->direct = ((rman_get_flags(rle->res) & RF_ACTIVE) != 0); /* Allocate the clkctl instance */ clkctl = bhnd_alloc_core_clkctl(child, pmu_dev, r, pmu_regs, max_latency); if (clkctl == NULL) { free(r, M_BHND); bhnd_release_provider(child, pmu_dev, BHND_SERVICE_PMU); return (ENOMEM); } bhnd_set_pmu_info(child, clkctl); 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_core_clkctl *clkctl; struct bhnd_resource *r; device_t pmu_dev; bus_topo_assert(); if (device_get_parent(child) != dev) return (EINVAL); clkctl = bhnd_get_pmu_info(child); if (clkctl == NULL) panic("pmu over-release for %s", device_get_nameunit(child)); /* Clear all FORCE, AREQ, and ERSRC flags, unless we're already in * RESET. Suspending a core clears clkctl automatically (and attempting * to access the PMU registers in a suspended core will trigger a * system livelock). */ if (!bhnd_is_hw_suspended(clkctl->cc_dev)) { BHND_CLKCTL_LOCK(clkctl); /* Clear all FORCE, AREQ, and ERSRC flags */ BHND_CLKCTL_SET_4(clkctl, 0x0, BHND_CCS_FORCE_MASK | BHND_CCS_AREQ_MASK | BHND_CCS_ERSRC_REQ_MASK); BHND_CLKCTL_UNLOCK(clkctl); } /* Clear child's PMU info reference */ bhnd_set_pmu_info(child, NULL); /* Before freeing the clkctl instance, save a pointer to resources we * need to clean up manually */ r = clkctl->cc_res; pmu_dev = clkctl->cc_pmu_dev; /* Free the clkctl instance */ bhnd_free_core_clkctl(clkctl); /* Free the child's bhnd resource wrapper */ free(r, M_BHND); /* Release the child's PMU provider reference */ bhnd_release_provider(child, pmu_dev, BHND_SERVICE_PMU); return (0); } /** * Default bhnd(4) bus driver implementation of BHND_BUS_GET_CLOCK_LATENCY(). */ int bhnd_generic_get_clock_latency(device_t dev, device_t child, bhnd_clock clock, u_int *latency) { struct bhnd_core_clkctl *clkctl; if (device_get_parent(child) != dev) return (EINVAL); if ((clkctl = bhnd_get_pmu_info(child)) == NULL) panic("no active PMU allocation"); return (bhnd_pmu_get_clock_latency(clkctl->cc_pmu_dev, clock, latency)); } /** * Default bhnd(4) bus driver implementation of BHND_BUS_GET_CLOCK_FREQ(). */ int bhnd_generic_get_clock_freq(device_t dev, device_t child, bhnd_clock clock, u_int *freq) { struct bhnd_core_clkctl *clkctl; if (device_get_parent(child) != dev) return (EINVAL); if ((clkctl = bhnd_get_pmu_info(child)) == NULL) panic("no active PMU allocation"); return (bhnd_pmu_get_clock_freq(clkctl->cc_pmu_dev, clock, freq)); } /** * 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_core_clkctl *clkctl; uint32_t avail; uint32_t req; int error; if (device_get_parent(child) != dev) return (EINVAL); if ((clkctl = bhnd_get_pmu_info(child)) == NULL) panic("no active PMU allocation"); BHND_ASSERT_CLKCTL_AVAIL(clkctl); 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(clkctl->cc_dev), clock); return (ENODEV); } BHND_CLKCTL_LOCK(clkctl); /* Issue request */ BHND_CLKCTL_SET_4(clkctl, req, BHND_CCS_FORCE_MASK); /* Wait for clock availability */ error = bhnd_core_clkctl_wait(clkctl, avail, avail); BHND_CLKCTL_UNLOCK(clkctl); return (error); } /** * 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_core_clkctl *clkctl; uint32_t avail; uint32_t req; int error; if (device_get_parent(child) != dev) return (EINVAL); if ((clkctl = bhnd_get_pmu_info(child)) == NULL) panic("no active PMU allocation"); BHND_ASSERT_CLKCTL_AVAIL(clkctl); 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(clkctl->cc_dev), clocks); return (ENODEV); } BHND_CLKCTL_LOCK(clkctl); /* Issue request */ BHND_CLKCTL_SET_4(clkctl, req, BHND_CCS_AREQ_MASK); /* Wait for clock availability */ error = bhnd_core_clkctl_wait(clkctl, avail, avail); BHND_CLKCTL_UNLOCK(clkctl); return (error); } /** * 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_core_clkctl *clkctl; uint32_t req; uint32_t avail; int error; if (device_get_parent(child) != dev) return (EINVAL); if ((clkctl = bhnd_get_pmu_info(child)) == NULL) panic("no active PMU allocation"); BHND_ASSERT_CLKCTL_AVAIL(clkctl); if (rsrc > BHND_CCS_ERSRC_MAX) return (EINVAL); req = BHND_CCS_SET_BITS((1< BHND_CCS_ERSRC_MAX) return (EINVAL); mask = BHND_CCS_SET_BITS((1<= bhnd_get_port_count(child, type)) return (false); if (region >= bhnd_get_region_count(child, type, port)) return (false); return (true); } /** * Default bhnd(4) bus driver implementation of BHND_BUS_GET_NVRAM_VAR(). * * This implementation searches @p dev for a registered NVRAM child device. * * If no NVRAM device is registered with @p dev, the request is delegated to * the BHND_BUS_GET_NVRAM_VAR() method on the parent of @p dev. */ int bhnd_generic_get_nvram_var(device_t dev, device_t child, const char *name, void *buf, size_t *size, bhnd_nvram_type type) { device_t nvram, parent; int error; /* If a NVRAM device is available, consult it first */ nvram = bhnd_retain_provider(child, BHND_SERVICE_NVRAM); if (nvram != NULL) { error = BHND_NVRAM_GETVAR(nvram, name, buf, size, type); bhnd_release_provider(child, nvram, BHND_SERVICE_NVRAM); return (error); } /* Otherwise, try to delegate to parent */ if ((parent = device_get_parent(dev)) == NULL) return (ENODEV); return (BHND_BUS_GET_NVRAM_VAR(device_get_parent(dev), child, name, buf, size, type)); } /** * Default bhnd(4) bus driver implementation of BUS_PRINT_CHILD(). * * This implementation requests the device's struct resource_list via * BUS_GET_RESOURCE_LIST. */ int bhnd_generic_print_child(device_t dev, device_t child) { struct resource_list *rl; int retval = 0; retval += bus_print_child_header(dev, child); rl = BUS_GET_RESOURCE_LIST(dev, child); if (rl != NULL) { retval += resource_list_print_type(rl, "mem", SYS_RES_MEMORY, "%#jx"); retval += resource_list_print_type(rl, "irq", SYS_RES_IRQ, "%#jd"); } retval += printf(" at core %u", bhnd_get_core_index(child)); retval += bus_print_child_domain(dev, child); retval += bus_print_child_footer(dev, child); return (retval); } /** * Default bhnd(4) bus driver implementation of BUS_PROBE_NOMATCH(). * * This implementation requests the device's struct resource_list via * BUS_GET_RESOURCE_LIST. */ void bhnd_generic_probe_nomatch(device_t dev, device_t child) { struct resource_list *rl; const struct bhnd_nomatch *nm; bool report; /* Fetch reporting configuration for this device */ report = true; for (nm = bhnd_nomatch_table; nm->device != BHND_COREID_INVALID; nm++) { if (nm->vendor != bhnd_get_vendor(child)) continue; if (nm->device != bhnd_get_device(child)) continue; report = false; if (bootverbose && nm->if_verbose) report = true; break; } if (!report) return; /* Print the non-matched device info */ device_printf(dev, "<%s %s, rev %hhu>", bhnd_get_vendor_name(child), bhnd_get_device_name(child), bhnd_get_hwrev(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)); } static int bhnd_child_pnpinfo(device_t dev, device_t child, struct sbuf *sb) { if (device_get_parent(child) != dev) return (BUS_CHILD_PNPINFO(device_get_parent(dev), child, sb)); sbuf_printf(sb, "vendor=0x%hx device=0x%hx rev=0x%hhx", bhnd_get_vendor(child), bhnd_get_device(child), bhnd_get_hwrev(child)); return (0); } static int bhnd_child_location(device_t dev, device_t child, struct sbuf *sb) { bhnd_addr_t addr; bhnd_size_t size; if (device_get_parent(child) != dev) return (BUS_CHILD_LOCATION(device_get_parent(dev), child, sb)); if (bhnd_get_region_addr(child, BHND_PORT_DEVICE, 0, 0, &addr, &size)) return (0); sbuf_printf(sb, "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) { /* Free device info */ if (bhnd_get_pmu_info(child) != NULL) { /* Releasing PMU requests automatically would be nice, * but we can't reference per-core PMU register * resource after driver detach */ panic("%s leaked device pmu state\n", device_get_nameunit(child)); } } /** * Helper function for implementing BUS_SUSPEND_CHILD(). * * TODO: Power management * * If @p child is not a direct child of @p dev, suspension is delegated to * the @p dev parent. */ int bhnd_generic_suspend_child(device_t dev, device_t child) { if (device_get_parent(child) != dev) BUS_SUSPEND_CHILD(device_get_parent(dev), child); return bus_generic_suspend_child(dev, child); } /** * Helper function for implementing BUS_RESUME_CHILD(). * * TODO: Power management * * If @p child is not a direct child of @p dev, suspension is delegated to * the @p dev parent. */ int bhnd_generic_resume_child(device_t dev, device_t child) { if (device_get_parent(child) != dev) BUS_RESUME_CHILD(device_get_parent(dev), child); return bus_generic_resume_child(dev, child); } /** * Default bhnd(4) bus driver implementation of BUS_SETUP_INTR(). * * This implementation of BUS_SETUP_INTR() will delegate interrupt setup * to the parent of @p dev, if any. */ int bhnd_generic_setup_intr(device_t dev, device_t child, struct resource *irq, int flags, driver_filter_t *filter, driver_intr_t *intr, void *arg, void **cookiep) { return (bus_generic_setup_intr(dev, child, irq, flags, filter, intr, arg, cookiep)); } /* * Delegate all indirect I/O to the parent device. When inherited by * non-bridged bus implementations, resources will never be marked as * indirect, and these methods will never be called. */ #define BHND_IO_READ(_type, _name, _method) \ static _type \ bhnd_read_ ## _name (device_t dev, device_t child, \ struct bhnd_resource *r, bus_size_t offset) \ { \ return (BHND_BUS_READ_ ## _method( \ device_get_parent(dev), child, r, offset)); \ } #define BHND_IO_WRITE(_type, _name, _method) \ static void \ bhnd_write_ ## _name (device_t dev, device_t child, \ struct bhnd_resource *r, bus_size_t offset, _type value) \ { \ return (BHND_BUS_WRITE_ ## _method( \ device_get_parent(dev), child, r, offset, \ value)); \ } #define BHND_IO_MISC(_type, _op, _method) \ static void \ bhnd_ ## _op (device_t dev, device_t child, \ struct bhnd_resource *r, bus_size_t offset, _type datap, \ bus_size_t count) \ { \ BHND_BUS_ ## _method(device_get_parent(dev), child, r, \ offset, datap, count); \ } #define BHND_IO_METHODS(_type, _size) \ BHND_IO_READ(_type, _size, _size) \ BHND_IO_WRITE(_type, _size, _size) \ \ BHND_IO_READ(_type, stream_ ## _size, STREAM_ ## _size) \ BHND_IO_WRITE(_type, stream_ ## _size, STREAM_ ## _size) \ \ BHND_IO_MISC(_type*, read_multi_ ## _size, \ READ_MULTI_ ## _size) \ BHND_IO_MISC(_type*, write_multi_ ## _size, \ WRITE_MULTI_ ## _size) \ \ BHND_IO_MISC(_type*, read_multi_stream_ ## _size, \ READ_MULTI_STREAM_ ## _size) \ BHND_IO_MISC(_type*, write_multi_stream_ ## _size, \ WRITE_MULTI_STREAM_ ## _size) \ \ BHND_IO_MISC(_type, set_multi_ ## _size, SET_MULTI_ ## _size) \ BHND_IO_MISC(_type, set_region_ ## _size, SET_REGION_ ## _size) \ \ BHND_IO_MISC(_type*, read_region_ ## _size, \ READ_REGION_ ## _size) \ BHND_IO_MISC(_type*, write_region_ ## _size, \ WRITE_REGION_ ## _size) \ \ BHND_IO_MISC(_type*, read_region_stream_ ## _size, \ READ_REGION_STREAM_ ## _size) \ BHND_IO_MISC(_type*, write_region_stream_ ## _size, \ WRITE_REGION_STREAM_ ## _size) \ BHND_IO_METHODS(uint8_t, 1); BHND_IO_METHODS(uint16_t, 2); BHND_IO_METHODS(uint32_t, 4); static void bhnd_barrier(device_t dev, device_t child, struct bhnd_resource *r, bus_size_t offset, bus_size_t length, int flags) { BHND_BUS_BARRIER(device_get_parent(dev), child, r, offset, length, flags); } static device_method_t bhnd_methods[] = { /* Device interface */ \ DEVMETHOD(device_attach, bhnd_generic_attach), DEVMETHOD(device_detach, bhnd_generic_detach), DEVMETHOD(device_shutdown, bhnd_generic_shutdown), DEVMETHOD(device_suspend, bhnd_generic_suspend), DEVMETHOD(device_resume, bhnd_generic_resume), /* Bus interface */ DEVMETHOD(bus_child_deleted, bhnd_generic_child_deleted), DEVMETHOD(bus_probe_nomatch, bhnd_generic_probe_nomatch), DEVMETHOD(bus_print_child, bhnd_generic_print_child), DEVMETHOD(bus_child_pnpinfo, bhnd_child_pnpinfo), DEVMETHOD(bus_child_location, bhnd_child_location), 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, bhnd_generic_setup_intr), DEVMETHOD(bus_teardown_intr, bus_generic_teardown_intr), DEVMETHOD(bus_config_intr, bus_generic_config_intr), DEVMETHOD(bus_bind_intr, bus_generic_bind_intr), DEVMETHOD(bus_describe_intr, bus_generic_describe_intr), DEVMETHOD(bus_get_dma_tag, bus_generic_get_dma_tag), /* BHND interface */ DEVMETHOD(bhnd_bus_get_chipid, bhnd_bus_generic_get_chipid), DEVMETHOD(bhnd_bus_is_hw_disabled, bhnd_bus_generic_is_hw_disabled), DEVMETHOD(bhnd_bus_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_get_clock_latency, bhnd_generic_get_clock_latency), DEVMETHOD(bhnd_bus_get_clock_freq, bhnd_generic_get_clock_freq), 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 }; DEFINE_CLASS_0(bhnd, bhnd_driver, bhnd_methods, sizeof(struct bhnd_softc)); MODULE_VERSION(bhnd, 1);