/*-1* SPDX-License-Identifier: BSD-2-Clause2*3* Copyright (c) 2016 Landon Fuller <[email protected]>4* Copyright (c) 2017 The FreeBSD Foundation5* All rights reserved.6*7* Portions of this software were developed by Landon Fuller8* under sponsorship from the FreeBSD Foundation.9*10* Redistribution and use in source and binary forms, with or without11* modification, are permitted provided that the following conditions12* are met:13* 1. Redistributions of source code must retain the above copyright14* notice, this list of conditions and the following disclaimer,15* without modification.16* 2. Redistributions in binary form must reproduce at minimum a disclaimer17* similar to the "NO WARRANTY" disclaimer below ("Disclaimer") and any18* redistribution must be conditioned upon including a substantially19* similar Disclaimer requirement for further binary redistribution.20*21* NO WARRANTY22* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS23* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT24* LIMITED TO, THE IMPLIED WARRANTIES OF NONINFRINGEMENT, MERCHANTIBILITY25* AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL26* THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR SPECIAL, EXEMPLARY,27* OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF28* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS29* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER30* IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)31* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF32* THE POSSIBILITY OF SUCH DAMAGES.33*/3435#include <sys/param.h>36#include <sys/bus.h>37#include <sys/kobj.h>3839#include <machine/bus.h>40#include <sys/rman.h>41#include <machine/resource.h>4243#include <dev/bhnd/bhndreg.h>44#include <dev/bhnd/bhndvar.h>4546#include <dev/bhnd/bhnd_erom.h>47#include <dev/bhnd/bhnd_eromvar.h>4849#include <dev/bhnd/cores/chipc/chipcreg.h>5051static int bhnd_erom_iores_map(struct bhnd_erom_io *eio, bhnd_addr_t addr,52bhnd_size_t size);53static int bhnd_erom_iores_tell(struct bhnd_erom_io *eio,54bhnd_addr_t *addr, bhnd_size_t *size);55static uint32_t bhnd_erom_iores_read(struct bhnd_erom_io *eio,56bhnd_size_t offset, u_int width);57static void bhnd_erom_iores_fini(struct bhnd_erom_io *eio);5859static int bhnd_erom_iobus_map(struct bhnd_erom_io *eio, bhnd_addr_t addr,60bhnd_size_t size);61static int bhnd_erom_iobus_tell(struct bhnd_erom_io *eio,62bhnd_addr_t *addr, bhnd_size_t *size);63static uint32_t bhnd_erom_iobus_read(struct bhnd_erom_io *eio,64bhnd_size_t offset, u_int width);6566/**67* An implementation of bhnd_erom_io that manages mappings via68* bhnd_alloc_resource() and bhnd_release_resource().69*/70struct bhnd_erom_iores {71struct bhnd_erom_io eio;72device_t owner; /**< device from which we'll allocate resources */73int owner_rid; /**< rid to use when allocating new mappings */74struct bhnd_resource *mapped; /**< current mapping, or NULL */75};7677/**78* Fetch the device enumeration parser class from all bhnd(4)-compatible drivers79* registered for @p bus_devclass, probe @p eio for supporting parser classes,80* and return the best available supporting enumeration parser class.81*82* @param bus_devclass The bus device class to be queried for83* bhnd(4)-compatible drivers.84* @param eio An erom bus I/O instance, configured with a85* mapping of the first bus core.86* @param hint Identification hint used to identify the device.87* If the chipset supports standard chip88* identification registers within the first core,89* this parameter should be NULL.90* @param[out] cid On success, the probed chip identifier.91*92* @retval non-NULL on success, the best available EROM class.93* @retval NULL if no erom class returned a successful probe result for94* @p eio.95*/96bhnd_erom_class_t *97bhnd_erom_probe_driver_classes(devclass_t bus_devclass,98struct bhnd_erom_io *eio, const struct bhnd_chipid *hint,99struct bhnd_chipid *cid)100{101driver_t **drivers;102int drv_count;103bhnd_erom_class_t *erom_cls;104int error, prio, result;105106erom_cls = NULL;107prio = 0;108109/* Fetch all available drivers */110error = devclass_get_drivers(bus_devclass, &drivers, &drv_count);111if (error) {112printf("error fetching bhnd(4) drivers for %s: %d\n",113devclass_get_name(bus_devclass), error);114return (NULL);115}116117/* Enumerate the drivers looking for the best available EROM class */118for (int i = 0; i < drv_count; i++) {119struct bhnd_chipid pcid;120bhnd_erom_class_t *cls;121122/* The default implementation of BHND_BUS_GET_EROM_CLASS()123* returns NULL if unimplemented; this should always be safe124* to call on arbitrary drivers */125cls = bhnd_driver_get_erom_class(drivers[i]);126if (cls == NULL)127continue;128129kobj_class_compile(cls);130131/* Probe the bus */132result = bhnd_erom_probe(cls, eio, hint, &pcid);133134/* The parser did not match if an error was returned */135if (result > 0)136continue;137138/* Check for a new highest priority match */139if (erom_cls == NULL || result > prio) {140prio = result;141142*cid = pcid;143erom_cls = cls;144}145146/* Terminate immediately on BUS_PROBE_SPECIFIC */147if (result == BUS_PROBE_SPECIFIC)148break;149}150151free(drivers, M_TEMP);152return (erom_cls);153}154155/**156* Allocate and return a new device enumeration table parser.157*158* @param cls The parser class for which an instance will be159* allocated.160* @param eio The bus I/O callbacks to use when reading the device161* enumeration table.162* @param cid The device's chip identifier.163*164* @retval non-NULL success165* @retval NULL if an error occurred allocating or initializing the166* EROM parser.167*/168bhnd_erom_t *169bhnd_erom_alloc(bhnd_erom_class_t *cls, const struct bhnd_chipid *cid,170struct bhnd_erom_io *eio)171{172bhnd_erom_t *erom;173int error;174175erom = (bhnd_erom_t *)kobj_create((kobj_class_t)cls, M_BHND,176M_WAITOK|M_ZERO);177178if ((error = BHND_EROM_INIT(erom, cid, eio))) {179printf("error initializing %s parser at %#jx: %d\n", cls->name,180(uintmax_t)cid->enum_addr, error);181182kobj_delete((kobj_t)erom, M_BHND);183return (NULL);184}185186return (erom);187}188189/**190* Perform static initialization of a device enumeration table parser.191*192* This may be used to initialize a caller-allocated erom instance state193* during early boot, prior to malloc availability.194*195* @param cls The parser class for which an instance will be196* allocated.197* @param erom The erom parser instance to initialize.198* @param esize The total available number of bytes allocated for199* @p erom. If this is less than is required by @p cls,200* ENOMEM will be returned.201* @param cid The device's chip identifier.202* @param eio The bus I/O callbacks to use when reading the device203* enumeration table.204*205* @retval 0 success206* @retval ENOMEM if @p esize is smaller than required by @p cls.207* @retval non-zero if an error occurs initializing the EROM parser,208* a regular unix error code will be returned.209*/210int211bhnd_erom_init_static(bhnd_erom_class_t *cls, bhnd_erom_t *erom, size_t esize,212const struct bhnd_chipid *cid, struct bhnd_erom_io *eio)213{214kobj_class_t kcls;215216kcls = (kobj_class_t)cls;217218/* Verify allocation size */219if (kcls->size > esize)220return (ENOMEM);221222/* Perform instance initialization */223kobj_init_static((kobj_t)erom, kcls);224return (BHND_EROM_INIT(erom, cid, eio));225}226227/**228* Release any resources held by a @p erom parser previously229* initialized via bhnd_erom_init_static().230*231* @param erom An erom parser instance previously initialized via232* bhnd_erom_init_static().233*/234void235bhnd_erom_fini_static(bhnd_erom_t *erom)236{237return (BHND_EROM_FINI(erom));238}239240/**241* Release all resources held by a @p erom parser previously242* allocated via bhnd_erom_alloc().243*244* @param erom An erom parser instance previously allocated via245* bhnd_erom_alloc().246*/247void248bhnd_erom_free(bhnd_erom_t *erom)249{250BHND_EROM_FINI(erom);251kobj_delete((kobj_t)erom, M_BHND);252}253254/**255* Read the chip identification registers mapped by @p eio, popuating @p cid256* with the parsed result257*258* @param eio A bus I/O instance, configured with a mapping259* of the ChipCommon core.260* @param[out] cid On success, the parsed chip identification.261*262* @warning263* On early siba(4) devices, the ChipCommon core does not provide264* a valid CHIPC_ID_NUMCORE field. On these ChipCommon revisions265* (see CHIPC_NCORES_MIN_HWREV()), this function will parse and return266* an invalid `ncores` value.267*/268int269bhnd_erom_read_chipid(struct bhnd_erom_io *eio, struct bhnd_chipid *cid)270{271bhnd_addr_t cc_addr;272bhnd_size_t cc_size;273uint32_t idreg, cc_caps;274int error;275276/* Fetch ChipCommon address */277if ((error = bhnd_erom_io_tell(eio, &cc_addr, &cc_size)))278return (error);279280/* Read chip identifier */281idreg = bhnd_erom_io_read(eio, CHIPC_ID, 4);282283/* Extract the basic chip info */284cid->chip_id = CHIPC_GET_BITS(idreg, CHIPC_ID_CHIP);285cid->chip_pkg = CHIPC_GET_BITS(idreg, CHIPC_ID_PKG);286cid->chip_rev = CHIPC_GET_BITS(idreg, CHIPC_ID_REV);287cid->chip_type = CHIPC_GET_BITS(idreg, CHIPC_ID_BUS);288cid->ncores = CHIPC_GET_BITS(idreg, CHIPC_ID_NUMCORE);289290/* Populate EROM address */291if (BHND_CHIPTYPE_HAS_EROM(cid->chip_type)) {292cid->enum_addr = bhnd_erom_io_read(eio, CHIPC_EROMPTR, 4);293} else {294cid->enum_addr = cc_addr;295}296297/* Populate capability flags */298cc_caps = bhnd_erom_io_read(eio, CHIPC_CAPABILITIES, 4);299cid->chip_caps = 0x0;300301if (cc_caps & CHIPC_CAP_BKPLN64)302cid->chip_caps |= BHND_CAP_BP64;303304if (cc_caps & CHIPC_CAP_PMU)305cid->chip_caps |= BHND_CAP_PMU;306307return (0);308}309310/**311* Attempt to map @p size bytes at @p addr, replacing any existing312* @p eio mapping.313*314* @param eio I/O instance state.315* @param addr The address to be mapped.316* @param size The number of bytes to be mapped at @p addr.317*318* @retval 0 success319* @retval non-zero if mapping @p addr otherwise fails, a regular320* unix error code should be returned.321*/322int323bhnd_erom_io_map(struct bhnd_erom_io *eio, bhnd_addr_t addr, bhnd_size_t size)324{325return (eio->map(eio, addr, size));326}327328/**329* Return the address range mapped by @p eio, if any.330*331* @param eio I/O instance state.332* @param[out] addr The address mapped by @p eio.333* @param[out] size The number of bytes mapped at @p addr.334*335* @retval 0 success336* @retval ENXIO if @p eio has no mapping.337*/338int339bhnd_erom_io_tell(struct bhnd_erom_io *eio, bhnd_addr_t *addr,340bhnd_size_t *size)341{342return (eio->tell(eio, addr, size));343}344345/**346* Read a 1, 2, or 4 byte data item from @p eio, at the given @p offset347* relative to @p eio's current mapping.348*349* @param eio erom I/O callbacks350* @param offset read offset.351* @param width item width (1, 2, or 4 bytes).352*/353uint32_t354bhnd_erom_io_read(struct bhnd_erom_io *eio, bhnd_size_t offset, u_int width)355{356return (eio->read(eio, offset, width));357}358359/**360* Free all resources held by @p eio.361*/362void363bhnd_erom_io_fini(struct bhnd_erom_io *eio)364{365if (eio->fini != NULL)366return (eio->fini(eio));367}368369/**370* Allocate, initialize, and return a new I/O instance that will perform371* mapping by allocating SYS_RES_MEMORY resources from @p dev using @p rid.372*373* @param dev The device to pass to bhnd_alloc_resource() and374* bhnd_release_resource() functions.375* @param rid The resource ID to be used when allocating memory resources.376*/377struct bhnd_erom_io *378bhnd_erom_iores_new(device_t dev, int rid)379{380struct bhnd_erom_iores *iores;381382iores = malloc(sizeof(*iores), M_BHND, M_WAITOK | M_ZERO);383iores->eio.map = bhnd_erom_iores_map;384iores->eio.tell = bhnd_erom_iores_tell;385iores->eio.read = bhnd_erom_iores_read;386iores->eio.fini = bhnd_erom_iores_fini;387388iores->owner = dev;389iores->owner_rid = rid;390iores->mapped = NULL;391392return (&iores->eio);393}394395static int396bhnd_erom_iores_map(struct bhnd_erom_io *eio, bhnd_addr_t addr,397bhnd_size_t size)398{399struct bhnd_erom_iores *iores;400401iores = (struct bhnd_erom_iores *)eio;402403/* Sanity check the addr/size */404if (size == 0)405return (EINVAL);406407if (BHND_ADDR_MAX - size < addr)408return (EINVAL); /* would overflow */409410/* Check for an existing mapping */411if (iores->mapped) {412/* If already mapped, nothing else to do */413if (rman_get_start(iores->mapped->res) == addr &&414rman_get_size(iores->mapped->res) == size)415{416return (0);417}418419/* Otherwise, we need to drop the existing mapping */420bhnd_release_resource(iores->owner, iores->mapped);421iores->mapped = NULL;422}423424/* Try to allocate the new mapping */425iores->mapped = bhnd_alloc_resource(iores->owner, SYS_RES_MEMORY,426iores->owner_rid, addr, addr+size-1, size,427RF_ACTIVE|RF_SHAREABLE);428if (iores->mapped == NULL) {429return (ENXIO);430}431432return (0);433}434435static int436bhnd_erom_iores_tell(struct bhnd_erom_io *eio, bhnd_addr_t *addr,437bhnd_size_t *size)438{439struct bhnd_erom_iores *iores = (struct bhnd_erom_iores *)eio;440441if (iores->mapped == NULL)442return (ENXIO);443444*addr = rman_get_start(iores->mapped->res);445*size = rman_get_size(iores->mapped->res);446447return (0);448}449450static uint32_t451bhnd_erom_iores_read(struct bhnd_erom_io *eio, bhnd_size_t offset, u_int width)452{453struct bhnd_erom_iores *iores = (struct bhnd_erom_iores *)eio;454455if (iores->mapped == NULL)456panic("read with invalid mapping");457458switch (width) {459case 1:460return (bhnd_bus_read_1(iores->mapped, offset));461case 2:462return (bhnd_bus_read_2(iores->mapped, offset));463case 4:464return (bhnd_bus_read_4(iores->mapped, offset));465default:466panic("invalid width %u", width);467}468}469470static void471bhnd_erom_iores_fini(struct bhnd_erom_io *eio)472{473struct bhnd_erom_iores *iores = (struct bhnd_erom_iores *)eio;474475/* Release any mapping */476if (iores->mapped) {477bhnd_release_resource(iores->owner, iores->mapped);478iores->mapped = NULL;479}480481free(eio, M_BHND);482}483484/**485* Initialize an I/O instance that will perform mapping directly from the486* given bus space tag and handle.487*488* @param iobus The I/O instance to be initialized.489* @param addr The base address mapped by @p bsh.490* @param size The total size mapped by @p bsh.491* @param bst Bus space tag for @p bsh.492* @param bsh Bus space handle mapping the full bus enumeration space.493*494* @retval 0 success495* @retval non-zero if initializing @p iobus otherwise fails, a regular496* unix error code will be returned.497*/498int499bhnd_erom_iobus_init(struct bhnd_erom_iobus *iobus, bhnd_addr_t addr,500bhnd_size_t size, bus_space_tag_t bst, bus_space_handle_t bsh)501{502iobus->eio.map = bhnd_erom_iobus_map;503iobus->eio.tell = bhnd_erom_iobus_tell;504iobus->eio.read = bhnd_erom_iobus_read;505iobus->eio.fini = NULL;506507iobus->addr = addr;508iobus->size = size;509iobus->bst = bst;510iobus->bsh = bsh;511iobus->mapped = false;512513return (0);514}515516static int517bhnd_erom_iobus_map(struct bhnd_erom_io *eio, bhnd_addr_t addr,518bhnd_size_t size)519{520struct bhnd_erom_iobus *iobus = (struct bhnd_erom_iobus *)eio;521522/* Sanity check the addr/size */523if (size == 0)524return (EINVAL);525526/* addr+size must not overflow */527if (BHND_ADDR_MAX - size < addr)528return (EINVAL);529530/* addr/size must fit within our bus tag's mapping */531if (addr < iobus->addr || size > iobus->size)532return (ENXIO);533534if (iobus->size - (addr - iobus->addr) < size)535return (ENXIO);536537/* The new addr offset and size must be representible as a bus_size_t */538if ((addr - iobus->addr) > BUS_SPACE_MAXSIZE)539return (ENXIO);540541if (size > BUS_SPACE_MAXSIZE)542return (ENXIO);543544iobus->offset = addr - iobus->addr;545iobus->limit = size;546iobus->mapped = true;547548return (0);549}550551static int552bhnd_erom_iobus_tell(struct bhnd_erom_io *eio, bhnd_addr_t *addr,553bhnd_size_t *size)554{555struct bhnd_erom_iobus *iobus = (struct bhnd_erom_iobus *)eio;556557if (!iobus->mapped)558return (ENXIO);559560*addr = iobus->addr + iobus->offset;561*size = iobus->limit;562563return (0);564}565566static uint32_t567bhnd_erom_iobus_read(struct bhnd_erom_io *eio, bhnd_size_t offset, u_int width)568{569struct bhnd_erom_iobus *iobus = (struct bhnd_erom_iobus *)eio;570571if (!iobus->mapped)572panic("no active mapping");573574if (iobus->limit < width || iobus->limit - width < offset)575panic("invalid offset %#jx", offset);576577switch (width) {578case 1:579return (bus_space_read_1(iobus->bst, iobus->bsh,580iobus->offset + offset));581case 2:582return (bus_space_read_2(iobus->bst, iobus->bsh,583iobus->offset + offset));584case 4:585return (bus_space_read_4(iobus->bst, iobus->bsh,586iobus->offset + offset));587default:588panic("invalid width %u", width);589}590}591592593