/*- * SPDX-License-Identifier: BSD-3-Clause AND BSD-2-Clause */ /* * // SPDX-License-Identifier: (GPL-2.0+ OR BSD-3-Clause) * * Freescale DPAA2 Platforms Console Driver * * Copyright 2015-2016 Freescale Semiconductor Inc. * Copyright 2018 NXP * * git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/drivers/soc/fsl/dpaa2-console.c#8120bd469f5525da229953c1197f2b826c0109f4 */ /* * Copyright (c) 2022-2023 Bjoern A. Zeeb * * 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. */ /* * Some docs are in: * - https://www.nxp.com.cn/docs/en/application-note/AN13329.pdf * - DPAA2UM * - git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/drivers/soc/fsl/dpaa2-console.c */ #include "opt_platform.h" #ifdef __notyet__ #include "opt_acpi.h" #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef FDT #include #include #endif /* Table 6-1. MC Memory Map */ #define MC_REG_MCFBA 0x20 #define MC_REG_MCFBALR_OFF 0x00 #define MC_REG_MCFBALR_MASK 0xe0000000 #define MC_REG_MCFBAHR_OFF 0x04 #define MC_REG_MCFBAHR_MASK 0x0001ffff /* Firmware Consoles. */ #define MC_BUFFER_OFFSET 0x01000000 #define MC_BUFFER_SIZE (1024 * 1024 * 16) #define MC_OFFSET_DELTA MC_BUFFER_OFFSET #define AIOP_BUFFER_OFFSET 0x06000000 #define AIOP_BUFFER_SIZE (1024 * 1024 * 16) #define AIOP_OFFSET_DELTA 0 /* MC and AIOP Magic words */ #define MAGIC_MC 0x4d430100 #define MAGIC_AIOP 0X41494f50 #define LOG_HEADER_FLAG_BUFFER_WRAPAROUND \ 0x80000000 #define LAST_BYTE(a) \ ((a) & ~(LOG_HEADER_FLAG_BUFFER_WRAPAROUND)) struct dpaa2_cons_dev { struct cdev *cdev; struct mtx mtx; size_t offset; uint32_t magic; uint32_t hdr_magic; uint32_t hdr_eobyte; uint32_t hdr_start; uint32_t hdr_len; uoff_t start; uoff_t end; uoff_t eod; uoff_t cur; bus_space_tag_t bst; bus_space_handle_t bsh; vm_size_t size; }; struct dpaa2_cons_softc { struct resource *res; bus_space_tag_t bst; uint64_t mcfba; struct dpaa2_cons_dev mc_cd; struct dpaa2_cons_dev aiop_cd; }; struct dpaa2_cons_hdr { uint32_t magic; uint32_t _reserved; uint32_t start; uint32_t len; uint32_t eobyte; }; #define DPAA2_MC_READ_4(_sc, _r) bus_read_4((_sc)->res, (_r)) /* Management interface */ static d_open_t dpaa2_cons_open; static d_close_t dpaa2_cons_close; static d_read_t dpaa2_cons_read; static struct cdevsw dpaa2_mc_cons_cdevsw = { .d_version = D_VERSION, .d_flags = 0, .d_open = dpaa2_cons_open, .d_close = dpaa2_cons_close, .d_read = dpaa2_cons_read, .d_name = "fsl_mc_console", }; static struct cdevsw dpaa2_aiop_cons_cdevsw = { .d_version = D_VERSION, .d_flags = 0, .d_open = dpaa2_cons_open, .d_close = dpaa2_cons_close, .d_read = dpaa2_cons_read, .d_name = "fsl_aiop_console", }; static size_t dpaa2_cons_read_bs(struct dpaa2_cons_dev *cd, size_t offset, void *dst, size_t len) { size_t count, l; uint8_t *p; count = 0; p = dst; l = offset % 8; if (l != 0) { bus_space_read_region_1(cd->bst, cd->bsh, offset + count, p + count, l); count += l; len -= l; } l = len / 8; if (l != 0) { bus_space_read_region_8(cd->bst, cd->bsh, offset + count, (uint64_t *)(p + count), l); l *= 8; count += l; len -= l; } l = len / 4; if (l != 0) { bus_space_read_region_4(cd->bst, cd->bsh, offset + count, (uint32_t *)(p + count), l); l *= 4; count += l; len -= l; } l = len / 2; if (l != 0) { bus_space_read_region_2(cd->bst, cd->bsh, offset + count, (uint16_t *)(p + count), l); l *= 2; count += l; len -= l; } if (len != 0) { bus_space_read_region_1(cd->bst, cd->bsh, offset + count, p + count, len); count += len; } return (count); } static int dpaa2_cons_open(struct cdev *cdev, int flags, int fmt, struct thread *td) { struct dpaa2_cons_dev *cd; struct dpaa2_cons_hdr hdr; size_t rlen __diagused; uint32_t wrapped; if (flags & FWRITE) return (EACCES); cd = cdev->si_drv1; if (cd->size == 0) return (ENODEV); mtx_lock(&cd->mtx); rlen = dpaa2_cons_read_bs(cd, 0, &hdr, sizeof(hdr)); KASSERT(rlen == sizeof(hdr), ("%s:%d: rlen %zu != count %zu, cdev %p " "cd %p\n", __func__, __LINE__, rlen, sizeof(hdr), cdev, cd)); cd->hdr_magic = hdr.magic; if (cd->hdr_magic != cd->magic) { mtx_unlock(&cd->mtx); return (ENODEV); } cd->hdr_eobyte = hdr.eobyte; cd->hdr_start = hdr.start; cd->hdr_len = hdr.len; cd->start = cd->hdr_start - cd->offset; cd->end = cd->start + cd->hdr_len; wrapped = cd->hdr_eobyte & LOG_HEADER_FLAG_BUFFER_WRAPAROUND; cd->eod = cd->start + LAST_BYTE(cd->hdr_eobyte); if (wrapped && cd->eod != cd->end) cd->cur = cd->eod + 1; else cd->cur = cd->start; mtx_unlock(&cd->mtx); return (0); } static int dpaa2_cons_close(struct cdev *cdev, int flags, int fmt, struct thread *td) { struct dpaa2_cons_dev *cd; cd = cdev->si_drv1; mtx_lock(&cd->mtx); cd->hdr_magic = cd->hdr_eobyte = cd->hdr_start = cd->hdr_len = 0; cd->start = cd->end = 0; mtx_unlock(&cd->mtx); return (0); } static int dpaa2_cons_read(struct cdev *cdev, struct uio *uio, int flag) { char buf[128]; struct dpaa2_cons_dev *cd; size_t len, size, count; size_t rlen __diagused; int error; cd = cdev->si_drv1; mtx_lock(&cd->mtx); if (cd->cur == cd->eod) { mtx_unlock(&cd->mtx); return (0); } else if (cd->cur > cd->eod) { len = (cd->end - cd->cur) + (cd->eod - cd->start); } else { len = cd->eod - cd->cur; } size = cd->end - cd->cur; if (len > size) { /* dump cur [size] */ while (uio->uio_resid > 0) { count = imin(sizeof(buf), uio->uio_resid); if (count > size) count = size; rlen = dpaa2_cons_read_bs(cd, cd->cur, buf, count); KASSERT(rlen == count, ("%s:%d: rlen %zu != count %zu, " "cdev %p cd %p\n", __func__, __LINE__, rlen, count, cdev, cd)); cd->cur += count; len -= count; size -= count; mtx_unlock(&cd->mtx); error = uiomove(buf, count, uio); if (error != 0 || uio->uio_resid == 0) return (error); mtx_lock(&cd->mtx); } cd->cur = cd->start; } /* dump cur [len] */ while (len > 0 && uio->uio_resid > 0) { count = imin(sizeof(buf), uio->uio_resid); if (count > len) count = len; rlen = dpaa2_cons_read_bs(cd, cd->cur, buf, count); KASSERT(rlen == count, ("%s:%d: rlen %zu != count %zu, cdev %p " "cd %p\n", __func__, __LINE__, rlen, count, cdev, cd)); cd->cur += count; len -= count; mtx_unlock(&cd->mtx); error = uiomove(buf, count, uio); if (error != 0 || uio->uio_resid == 0) return (error); mtx_lock(&cd->mtx); } mtx_unlock(&cd->mtx); return (0); } static int dpaa2_cons_create_dev(device_t dev, bus_addr_t pa, size_t size, size_t offset, uint32_t magic, struct cdevsw *devsw, struct dpaa2_cons_dev *cd) { struct dpaa2_cons_softc *sc; struct dpaa2_cons_hdr hdr; struct make_dev_args md_args; size_t len; int error; sc = device_get_softc(dev); error = bus_space_map(sc->bst, pa, size, 0, &cd->bsh); if (error != 0) { device_printf(dev, "%s: failed to map bus space %#jx/%zu: %d\n", __func__, (uintmax_t)pa, size, error); return (error); } cd->bst = sc->bst; cd->size = size; len = dpaa2_cons_read_bs(cd, 0, &hdr, sizeof(hdr)); if (len != sizeof(hdr)) { device_printf(dev, "%s: failed to read hdr for %#jx/%zu: %d\n", __func__, (uintmax_t)pa, size, error); bus_space_unmap(cd->bst, cd->bsh, cd->size); cd->size = 0; return (EIO); } /* This checks probably needs to be removed one day? */ if (hdr.magic != magic) { if (bootverbose) device_printf(dev, "%s: magic wrong for %#jx/%zu: " "%#010x != %#010x, no console?\n", __func__, (uintmax_t)pa, size, hdr.magic, magic); bus_space_unmap(cd->bst, cd->bsh, cd->size); cd->size = 0; return (ENOENT); } if (hdr.start - offset > size) { device_printf(dev, "%s: range wrong for %#jx/%zu: %u - %zu > %zu\n", __func__, (uintmax_t)pa, size, hdr.start, offset, size); bus_space_unmap(cd->bst, cd->bsh, cd->size); cd->size = 0; return (ERANGE); } cd->offset = offset; cd->magic = magic; mtx_init(&cd->mtx, "dpaa2 cons", NULL, MTX_DEF); make_dev_args_init(&md_args); md_args.mda_devsw = devsw; md_args.mda_uid = 0; md_args.mda_gid = 69; md_args.mda_mode = S_IRUSR | S_IRGRP; md_args.mda_unit = 0; md_args.mda_si_drv1 = cd; error = make_dev_s(&md_args, &cd->cdev, "%s", devsw->d_name); if (error != 0) { device_printf(dev, "%s: make_dev_s failed for %#jx/%zu: %d\n", __func__, (uintmax_t)pa, size, error); mtx_destroy(&cd->mtx); bus_space_unmap(cd->bst, cd->bsh, cd->size); cd->size = 0; return (error); } if (bootverbose) device_printf(dev, "dpaa2 console %s at pa %#jx size %#010zx " "(%zu) offset %zu, hdr: magic %#010x start %#010x len " "%#010x eobyte %#010x\n", devsw->d_name, (uintmax_t)pa, size, size, offset, hdr.magic, hdr.start, hdr.len, hdr.eobyte); return (0); } static int dpaa2_cons_attach_common(device_t dev) { struct dpaa2_cons_softc *sc; sc = device_get_softc(dev); dpaa2_cons_create_dev(dev, (bus_addr_t)(sc->mcfba + MC_BUFFER_OFFSET), MC_BUFFER_SIZE, MC_OFFSET_DELTA, MAGIC_MC, &dpaa2_mc_cons_cdevsw, &sc->mc_cd); dpaa2_cons_create_dev(dev, (bus_addr_t)(sc->mcfba + AIOP_BUFFER_OFFSET), AIOP_BUFFER_SIZE, AIOP_OFFSET_DELTA, MAGIC_AIOP, &dpaa2_aiop_cons_cdevsw, &sc->aiop_cd); return (0); } static void dpaa2_cons_detach_common(struct dpaa2_cons_dev *cd) { bus_space_unmap(cd->bst, cd->bsh, cd->size); mtx_destroy(&cd->mtx); } static int dpaa2_cons_detach(device_t dev) { struct dpaa2_cons_softc *sc; sc = device_get_softc(dev); if (sc->aiop_cd.cdev != NULL) destroy_dev(sc->aiop_cd.cdev); if (sc->mc_cd.cdev != NULL) destroy_dev(sc->mc_cd.cdev); if (sc->aiop_cd.size != 0) dpaa2_cons_detach_common(&sc->aiop_cd); if (sc->mc_cd.size != 0) dpaa2_cons_detach_common(&sc->mc_cd); if (sc->res != NULL) bus_release_resource(dev, SYS_RES_MEMORY, rman_get_rid(sc->res), sc->res); return (0); } #ifdef __notyet__ #ifdef ACPI static int dpaa2_cons_acpi_probe(device_t dev) { if (!ofw_bus_status_okay(dev)) return (ENXIO); device_set_desc(dev, "DPAA2 Console Driver"); return (BUS_PROBE_DEFAULT); } static int dpaa2_cons_acpi_attach(device_t dev) { struct dpaa2_cons_softc *sc; uint32_t val; int error, rid; sc = device_get_softc(dev); rid = 0; sc->res = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid, RF_ACTIVE); if (sc->res == NULL) { device_printf(dev, "Could not allocate memory\n"); return (ENXIO); } sc->bst = rman_get_bustag(sc->res); val = DPAA2_MC_READ_4(sc, MC_REG_MCFBALR_OFF); val &= MC_REG_MCFBALR_MASK; sc->mcfba |= val; val = DPAA2_MC_READ_4(sc, MC_REG_MCFBAHR_OFF); val &= MC_REG_MCFBAHR_MASK; sc->mcfba |= ((uint64_t)val << 32); error = dpaa2_cons_attach_common(dev); return (error); } static device_method_t dpaa2_cons_acpi_methods[] = { /* Device interface */ DEVMETHOD(device_probe, dpaa2_cons_acpi_probe), DEVMETHOD(device_attach, dpaa2_cons_acpi_attach), DEVMETHOD(device_detach, dpaa2_cons_detach), DEVMETHOD_END }; DEFINE_CLASS_0(dpaa2_cons_acpi, dpaa2_cons_acpi_driver, dpaa2_cons_acpi_methods, sizeof(struct dpaa2_cons_softc)); DRIVER_MODULE(dpaa2_cons_acpi, simplebus, dpaa2_cons_acpi_driver, 0, 0); MODULE_DEPEND(dpaa2_cons_acpi, dpaa2_mc, 1, 1, 1); #endif #endif /* 0 */ #ifdef FDT static struct ofw_compat_data compat_data[] = { { "fsl,dpaa2-console", 1 }, { NULL, 0 } }; static int dpaa2_cons_fdt_probe(device_t dev) { if (!ofw_bus_status_okay(dev)) return (ENXIO); if (!ofw_bus_search_compatible(dev, compat_data)->ocd_data) return (ENXIO); device_set_desc(dev, "DPAA2 Console Driver"); return (BUS_PROBE_DEFAULT); } static int dpaa2_cons_fdt_attach(device_t dev) { struct dpaa2_cons_softc *sc; uint32_t val; int error, rid; sc = device_get_softc(dev); rid = 0; sc->res = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid, RF_ACTIVE); if (sc->res == NULL) { device_printf(dev, "Could not allocate memory\n"); return (ENXIO); } sc->bst = rman_get_bustag(sc->res); val = DPAA2_MC_READ_4(sc, MC_REG_MCFBALR_OFF); val &= MC_REG_MCFBALR_MASK; sc->mcfba |= val; val = DPAA2_MC_READ_4(sc, MC_REG_MCFBAHR_OFF); val &= MC_REG_MCFBAHR_MASK; sc->mcfba |= ((uint64_t)val << 32); error = dpaa2_cons_attach_common(dev); return (error); } static device_method_t dpaa2_cons_fdt_methods[] = { /* Device interface */ DEVMETHOD(device_probe, dpaa2_cons_fdt_probe), DEVMETHOD(device_attach, dpaa2_cons_fdt_attach), DEVMETHOD(device_detach, dpaa2_cons_detach), DEVMETHOD_END }; DEFINE_CLASS_0(dpaa2_cons_fdt, dpaa2_cons_fdt_driver, dpaa2_cons_fdt_methods, sizeof(struct dpaa2_cons_softc)); DRIVER_MODULE(dpaa2_cons_fdt, simplebus, dpaa2_cons_fdt_driver, 0, 0); #endif