/*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 2016 Landon Fuller * Copyright (c) 2017 The FreeBSD Foundation * All rights reserved. * * Portions of this software were developed by Landon Fuller * under sponsorship from the FreeBSD Foundation. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer, * without modification. * 2. Redistributions in binary form must reproduce at minimum a disclaimer * similar to the "NO WARRANTY" disclaimer below ("Disclaimer") and any * redistribution must be conditioned upon including a substantially * similar Disclaimer requirement for further binary redistribution. * * NO WARRANTY * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF NONINFRINGEMENT, MERCHANTIBILITY * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL * THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR SPECIAL, EXEMPLARY, * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF * THE POSSIBILITY OF SUCH DAMAGES. */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include static int bhnd_erom_iores_map(struct bhnd_erom_io *eio, bhnd_addr_t addr, bhnd_size_t size); static int bhnd_erom_iores_tell(struct bhnd_erom_io *eio, bhnd_addr_t *addr, bhnd_size_t *size); static uint32_t bhnd_erom_iores_read(struct bhnd_erom_io *eio, bhnd_size_t offset, u_int width); static void bhnd_erom_iores_fini(struct bhnd_erom_io *eio); static int bhnd_erom_iobus_map(struct bhnd_erom_io *eio, bhnd_addr_t addr, bhnd_size_t size); static int bhnd_erom_iobus_tell(struct bhnd_erom_io *eio, bhnd_addr_t *addr, bhnd_size_t *size); static uint32_t bhnd_erom_iobus_read(struct bhnd_erom_io *eio, bhnd_size_t offset, u_int width); /** * An implementation of bhnd_erom_io that manages mappings via * bhnd_alloc_resource() and bhnd_release_resource(). */ struct bhnd_erom_iores { struct bhnd_erom_io eio; device_t owner; /**< device from which we'll allocate resources */ int owner_rid; /**< rid to use when allocating new mappings */ struct bhnd_resource *mapped; /**< current mapping, or NULL */ int mapped_rid; /**< resource ID of current mapping, or -1 */ }; /** * Fetch the device enumeration parser class from all bhnd(4)-compatible drivers * registered for @p bus_devclass, probe @p eio for supporting parser classes, * and return the best available supporting enumeration parser class. * * @param bus_devclass The bus device class to be queried for * bhnd(4)-compatible drivers. * @param eio An erom bus I/O instance, configured with a * mapping of the first bus core. * @param hint Identification hint used to identify the device. * If the chipset supports standard chip * identification registers within the first core, * this parameter should be NULL. * @param[out] cid On success, the probed chip identifier. * * @retval non-NULL on success, the best available EROM class. * @retval NULL if no erom class returned a successful probe result for * @p eio. */ bhnd_erom_class_t * bhnd_erom_probe_driver_classes(devclass_t bus_devclass, struct bhnd_erom_io *eio, const struct bhnd_chipid *hint, struct bhnd_chipid *cid) { driver_t **drivers; int drv_count; bhnd_erom_class_t *erom_cls; int error, prio, result; erom_cls = NULL; prio = 0; /* Fetch all available drivers */ error = devclass_get_drivers(bus_devclass, &drivers, &drv_count); if (error) { printf("error fetching bhnd(4) drivers for %s: %d\n", devclass_get_name(bus_devclass), error); 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; /* The default implementation of BHND_BUS_GET_EROM_CLASS() * returns NULL if unimplemented; this should always be safe * to call on arbitrary drivers */ cls = bhnd_driver_get_erom_class(drivers[i]); if (cls == NULL) continue; kobj_class_compile(cls); /* Probe the bus */ result = bhnd_erom_probe(cls, eio, 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; } free(drivers, M_TEMP); return (erom_cls); } /** * Allocate and return a new device enumeration table parser. * * @param cls The parser class for which an instance will be * allocated. * @param eio The bus I/O callbacks to use when reading the device * enumeration table. * @param cid The device's chip identifier. * * @retval non-NULL success * @retval NULL if an error occurred allocating or initializing the * EROM parser. */ bhnd_erom_t * bhnd_erom_alloc(bhnd_erom_class_t *cls, const struct bhnd_chipid *cid, struct bhnd_erom_io *eio) { bhnd_erom_t *erom; int error; erom = (bhnd_erom_t *)kobj_create((kobj_class_t)cls, M_BHND, M_WAITOK|M_ZERO); if ((error = BHND_EROM_INIT(erom, cid, eio))) { printf("error initializing %s parser at %#jx: %d\n", cls->name, (uintmax_t)cid->enum_addr, error); kobj_delete((kobj_t)erom, M_BHND); return (NULL); } return (erom); } /** * Perform static initialization of a device enumeration table parser. * * This may be used to initialize a caller-allocated erom instance state * during early boot, prior to malloc availability. * * @param cls The parser class for which an instance will be * allocated. * @param erom The erom parser instance to initialize. * @param esize The total available number of bytes allocated for * @p erom. If this is less than is required by @p cls, * ENOMEM will be returned. * @param cid The device's chip identifier. * @param eio The bus I/O callbacks to use when reading the device * enumeration table. * * @retval 0 success * @retval ENOMEM if @p esize is smaller than required by @p cls. * @retval non-zero if an error occurs initializing the EROM parser, * a regular unix error code will be returned. */ int bhnd_erom_init_static(bhnd_erom_class_t *cls, bhnd_erom_t *erom, size_t esize, const struct bhnd_chipid *cid, struct bhnd_erom_io *eio) { kobj_class_t kcls; kcls = (kobj_class_t)cls; /* Verify allocation size */ if (kcls->size > esize) return (ENOMEM); /* Perform instance initialization */ kobj_init_static((kobj_t)erom, kcls); return (BHND_EROM_INIT(erom, cid, eio)); } /** * Release any resources held by a @p erom parser previously * initialized via bhnd_erom_init_static(). * * @param erom An erom parser instance previously initialized via * bhnd_erom_init_static(). */ void bhnd_erom_fini_static(bhnd_erom_t *erom) { return (BHND_EROM_FINI(erom)); } /** * Release all resources held by a @p erom parser previously * allocated via bhnd_erom_alloc(). * * @param erom An erom parser instance previously allocated via * bhnd_erom_alloc(). */ void bhnd_erom_free(bhnd_erom_t *erom) { BHND_EROM_FINI(erom); kobj_delete((kobj_t)erom, M_BHND); } /** * Read the chip identification registers mapped by @p eio, popuating @p cid * with the parsed result * * @param eio A bus I/O instance, configured with a mapping * of the ChipCommon core. * @param[out] cid On success, the parsed chip identification. * * @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. */ int bhnd_erom_read_chipid(struct bhnd_erom_io *eio, struct bhnd_chipid *cid) { bhnd_addr_t cc_addr; bhnd_size_t cc_size; uint32_t idreg, cc_caps; int error; /* Fetch ChipCommon address */ if ((error = bhnd_erom_io_tell(eio, &cc_addr, &cc_size))) return (error); /* Read chip identifier */ idreg = bhnd_erom_io_read(eio, CHIPC_ID, 4); /* Extract the basic chip info */ cid->chip_id = CHIPC_GET_BITS(idreg, CHIPC_ID_CHIP); cid->chip_pkg = CHIPC_GET_BITS(idreg, CHIPC_ID_PKG); cid->chip_rev = CHIPC_GET_BITS(idreg, CHIPC_ID_REV); cid->chip_type = CHIPC_GET_BITS(idreg, CHIPC_ID_BUS); cid->ncores = CHIPC_GET_BITS(idreg, CHIPC_ID_NUMCORE); /* Populate EROM address */ if (BHND_CHIPTYPE_HAS_EROM(cid->chip_type)) { cid->enum_addr = bhnd_erom_io_read(eio, CHIPC_EROMPTR, 4); } else { cid->enum_addr = cc_addr; } /* Populate capability flags */ cc_caps = bhnd_erom_io_read(eio, CHIPC_CAPABILITIES, 4); cid->chip_caps = 0x0; if (cc_caps & CHIPC_CAP_BKPLN64) cid->chip_caps |= BHND_CAP_BP64; if (cc_caps & CHIPC_CAP_PMU) cid->chip_caps |= BHND_CAP_PMU; return (0); } /** * Attempt to map @p size bytes at @p addr, replacing any existing * @p eio mapping. * * @param eio I/O instance state. * @param addr The address to be mapped. * @param size The number of bytes to be mapped at @p addr. * * @retval 0 success * @retval non-zero if mapping @p addr otherwise fails, a regular * unix error code should be returned. */ int bhnd_erom_io_map(struct bhnd_erom_io *eio, bhnd_addr_t addr, bhnd_size_t size) { return (eio->map(eio, addr, size)); } /** * Return the address range mapped by @p eio, if any. * * @param eio I/O instance state. * @param[out] addr The address mapped by @p eio. * @param[out] size The number of bytes mapped at @p addr. * * @retval 0 success * @retval ENXIO if @p eio has no mapping. */ int bhnd_erom_io_tell(struct bhnd_erom_io *eio, bhnd_addr_t *addr, bhnd_size_t *size) { return (eio->tell(eio, addr, size)); } /** * Read a 1, 2, or 4 byte data item from @p eio, at the given @p offset * relative to @p eio's current mapping. * * @param eio erom I/O callbacks * @param offset read offset. * @param width item width (1, 2, or 4 bytes). */ uint32_t bhnd_erom_io_read(struct bhnd_erom_io *eio, bhnd_size_t offset, u_int width) { return (eio->read(eio, offset, width)); } /** * Free all resources held by @p eio. */ void bhnd_erom_io_fini(struct bhnd_erom_io *eio) { if (eio->fini != NULL) return (eio->fini(eio)); } /** * Allocate, initialize, and return a new I/O instance that will perform * mapping by allocating SYS_RES_MEMORY resources from @p dev using @p rid. * * @param dev The device to pass to bhnd_alloc_resource() and * bhnd_release_resource() functions. * @param rid The resource ID to be used when allocating memory resources. */ struct bhnd_erom_io * bhnd_erom_iores_new(device_t dev, int rid) { struct bhnd_erom_iores *iores; iores = malloc(sizeof(*iores), M_BHND, M_WAITOK | M_ZERO); iores->eio.map = bhnd_erom_iores_map; iores->eio.tell = bhnd_erom_iores_tell; iores->eio.read = bhnd_erom_iores_read; iores->eio.fini = bhnd_erom_iores_fini; iores->owner = dev; iores->owner_rid = rid; iores->mapped = NULL; iores->mapped_rid = -1; return (&iores->eio); } static int bhnd_erom_iores_map(struct bhnd_erom_io *eio, bhnd_addr_t addr, bhnd_size_t size) { struct bhnd_erom_iores *iores; iores = (struct bhnd_erom_iores *)eio; /* Sanity check the addr/size */ if (size == 0) return (EINVAL); if (BHND_ADDR_MAX - size < addr) return (EINVAL); /* would overflow */ /* Check for an existing mapping */ if (iores->mapped) { /* If already mapped, nothing else to do */ if (rman_get_start(iores->mapped->res) == addr && rman_get_size(iores->mapped->res) == size) { return (0); } /* Otherwise, we need to drop the existing mapping */ bhnd_release_resource(iores->owner, SYS_RES_MEMORY, iores->mapped_rid, iores->mapped); iores->mapped = NULL; iores->mapped_rid = -1; } /* Try to allocate the new mapping */ iores->mapped_rid = iores->owner_rid; iores->mapped = bhnd_alloc_resource(iores->owner, SYS_RES_MEMORY, &iores->mapped_rid, addr, addr+size-1, size, RF_ACTIVE|RF_SHAREABLE); if (iores->mapped == NULL) { iores->mapped_rid = -1; return (ENXIO); } return (0); } static int bhnd_erom_iores_tell(struct bhnd_erom_io *eio, bhnd_addr_t *addr, bhnd_size_t *size) { struct bhnd_erom_iores *iores = (struct bhnd_erom_iores *)eio; if (iores->mapped == NULL) return (ENXIO); *addr = rman_get_start(iores->mapped->res); *size = rman_get_size(iores->mapped->res); return (0); } static uint32_t bhnd_erom_iores_read(struct bhnd_erom_io *eio, bhnd_size_t offset, u_int width) { struct bhnd_erom_iores *iores = (struct bhnd_erom_iores *)eio; if (iores->mapped == NULL) panic("read with invalid mapping"); switch (width) { case 1: return (bhnd_bus_read_1(iores->mapped, offset)); case 2: return (bhnd_bus_read_2(iores->mapped, offset)); case 4: return (bhnd_bus_read_4(iores->mapped, offset)); default: panic("invalid width %u", width); } } static void bhnd_erom_iores_fini(struct bhnd_erom_io *eio) { struct bhnd_erom_iores *iores = (struct bhnd_erom_iores *)eio; /* Release any mapping */ if (iores->mapped) { bhnd_release_resource(iores->owner, SYS_RES_MEMORY, iores->mapped_rid, iores->mapped); iores->mapped = NULL; iores->mapped_rid = -1; } free(eio, M_BHND); } /** * Initialize an I/O instance that will perform mapping directly from the * given bus space tag and handle. * * @param iobus The I/O instance to be initialized. * @param addr The base address mapped by @p bsh. * @param size The total size mapped by @p bsh. * @param bst Bus space tag for @p bsh. * @param bsh Bus space handle mapping the full bus enumeration space. * * @retval 0 success * @retval non-zero if initializing @p iobus otherwise fails, a regular * unix error code will be returned. */ int bhnd_erom_iobus_init(struct bhnd_erom_iobus *iobus, bhnd_addr_t addr, bhnd_size_t size, bus_space_tag_t bst, bus_space_handle_t bsh) { iobus->eio.map = bhnd_erom_iobus_map; iobus->eio.tell = bhnd_erom_iobus_tell; iobus->eio.read = bhnd_erom_iobus_read; iobus->eio.fini = NULL; iobus->addr = addr; iobus->size = size; iobus->bst = bst; iobus->bsh = bsh; iobus->mapped = false; return (0); } static int bhnd_erom_iobus_map(struct bhnd_erom_io *eio, bhnd_addr_t addr, bhnd_size_t size) { struct bhnd_erom_iobus *iobus = (struct bhnd_erom_iobus *)eio; /* Sanity check the addr/size */ if (size == 0) return (EINVAL); /* addr+size must not overflow */ if (BHND_ADDR_MAX - size < addr) return (EINVAL); /* addr/size must fit within our bus tag's mapping */ if (addr < iobus->addr || size > iobus->size) return (ENXIO); if (iobus->size - (addr - iobus->addr) < size) return (ENXIO); /* The new addr offset and size must be representible as a bus_size_t */ if ((addr - iobus->addr) > BUS_SPACE_MAXSIZE) return (ENXIO); if (size > BUS_SPACE_MAXSIZE) return (ENXIO); iobus->offset = addr - iobus->addr; iobus->limit = size; iobus->mapped = true; return (0); } static int bhnd_erom_iobus_tell(struct bhnd_erom_io *eio, bhnd_addr_t *addr, bhnd_size_t *size) { struct bhnd_erom_iobus *iobus = (struct bhnd_erom_iobus *)eio; if (!iobus->mapped) return (ENXIO); *addr = iobus->addr + iobus->offset; *size = iobus->limit; return (0); } static uint32_t bhnd_erom_iobus_read(struct bhnd_erom_io *eio, bhnd_size_t offset, u_int width) { struct bhnd_erom_iobus *iobus = (struct bhnd_erom_iobus *)eio; if (!iobus->mapped) panic("no active mapping"); if (iobus->limit < width || iobus->limit - width < offset) panic("invalid offset %#jx", offset); switch (width) { case 1: return (bus_space_read_1(iobus->bst, iobus->bsh, iobus->offset + offset)); case 2: return (bus_space_read_2(iobus->bst, iobus->bsh, iobus->offset + offset)); case 4: return (bus_space_read_4(iobus->bst, iobus->bsh, iobus->offset + offset)); default: panic("invalid width %u", width); } }