root/sys/dev/acpi/apei_mapreg.c
/*      $NetBSD: apei_mapreg.c,v 1.4 2024/03/22 20:47:52 riastradh Exp $        */

/*-
 * Copyright (c) 2024 The NetBSD Foundation, Inc.
 * All rights reserved.
 *
 * 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 NETBSD FOUNDATION, INC. 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 FOUNDATION 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.
 */

/*
 * Pre-mapped ACPI register access
 *
 * XXX This isn't APEI-specific -- it should be moved into the general
 * ACPI API, and unified with the AcpiRead/AcpiWrite implementation.
 */

#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: apei_mapreg.c,v 1.4 2024/03/22 20:47:52 riastradh Exp $");

#include <sys/types.h>

#include <sys/atomic.h>

#include <dev/acpi/acpivar.h>
#include <dev/acpi/apei_mapreg.h>

/*
 * apei_mapreg_map(reg)
 *
 *      Return a mapping for use with apei_mapreg_read, or NULL if it
 *      can't be mapped.
 */
struct apei_mapreg *
apei_mapreg_map(const ACPI_GENERIC_ADDRESS *reg)
{

        /*
         * Verify the result is reasonable.
         */
        switch (reg->BitWidth) {
        case 8:
        case 16:
        case 32:
        case 64:
                break;
        default:
                return NULL;
        }

        /*
         * Verify we know how to do the access width.
         */
        switch (reg->AccessWidth) {
        case 1:                 /* 8-bit */
        case 2:                 /* 16-bit */
        case 3:                 /* 32-bit */
                break;
        case 4:                 /* 64-bit */
                if (reg->SpaceId == ACPI_ADR_SPACE_SYSTEM_IO)
                        return NULL;
                break;
        default:
                return NULL;
        }

        /*
         * Verify we don't need to shift anything, because I can't
         * figure out how the shifting is supposed to work in five
         * minutes of looking at the spec.
         */
        switch (reg->BitOffset) {
        case 0:
                break;
        default:
                return NULL;
        }

        /*
         * Verify the bit width is a multiple of the access width so
         * we're not accessing more than we need.
         */
        if (reg->BitWidth % (8*(1 << (reg->AccessWidth - 1))))
                return NULL;

        /*
         * Dispatch on the space id.
         */
        switch (reg->SpaceId) {
        case ACPI_ADR_SPACE_SYSTEM_IO:
                /*
                 * Just need to return something non-null -- no state
                 * to record for a mapping.
                 */
                return (struct apei_mapreg *)__UNCONST(reg);
        case ACPI_ADR_SPACE_SYSTEM_MEMORY:
                return AcpiOsMapMemory(reg->Address,
                    1 << (reg->AccessWidth - 1));
        default:
                return NULL;
        }
}

/*
 * apei_mapreg_unmap(reg, map)
 *
 *      Unmap a mapping previously returned by apei_mapreg_map.
 */
void
apei_mapreg_unmap(const ACPI_GENERIC_ADDRESS *reg,
    struct apei_mapreg *map)
{

        switch (reg->SpaceId) {
        case ACPI_ADR_SPACE_SYSTEM_IO:
                KASSERT(map == __UNCONST(reg));
                break;
        case ACPI_ADR_SPACE_SYSTEM_MEMORY:
                AcpiOsUnmapMemory(map, 1 << (reg->AccessWidth - 1));
                break;
        default:
                panic("invalid register mapping");
        }
}

/*
 * apei_mapreg_read(reg, map)
 *
 *      Read from reg via map previously obtained by apei_mapreg_map.
 */
uint64_t
apei_mapreg_read(const ACPI_GENERIC_ADDRESS *reg,
    const struct apei_mapreg *map)
{
        unsigned chunkbits = NBBY*(1 << (reg->AccessWidth - 1));
        unsigned i, n = reg->BitWidth / chunkbits;
        uint64_t v = 0;

        for (i = 0; i < n; i++) {
                uint64_t chunk;

                switch (reg->SpaceId) {
                case ACPI_ADR_SPACE_SYSTEM_IO: {
                        ACPI_IO_ADDRESS addr;
                        uint32_t chunk32 = 0;
                        ACPI_STATUS rv;

                        switch (reg->AccessWidth) {
                        case 1:
                                addr = reg->Address + i;
                                break;
                        case 2:
                                addr = reg->Address + 2*i;
                                break;
                        case 3:
                                addr = reg->Address + 4*i;
                                break;
                        case 4: /* no 64-bit I/O ports */
                        default:
                                __unreachable();
                        }
                        rv = AcpiOsReadPort(addr, &chunk32,
                            NBBY*(1 << (reg->AccessWidth - 1)));
                        KASSERTMSG(!ACPI_FAILURE(rv), "%s",
                            AcpiFormatException(rv));
                        chunk = chunk32;
                        break;
                }
                case ACPI_ADR_SPACE_SYSTEM_MEMORY:
                        switch (reg->AccessWidth) {
                        case 1:
                                chunk = *((volatile const uint8_t *)map + i);
                                break;
                        case 2:
                                chunk = *((volatile const uint16_t *)map + i);
                                break;
                        case 3:
                                chunk = *((volatile const uint32_t *)map + i);
                                break;
                        case 4:
                                chunk = *((volatile const uint64_t *)map + i);
                                break;
                        default:
                                __unreachable();
                        }
                        break;
                default:
                        __unreachable();
                }
                v |= chunk << (i*chunkbits);
        }

        membar_acquire();       /* XXX probably not right for MMIO */
        return v;
}

/*
 * apei_mapreg_write(reg, map, v)
 *
 *      Write to reg via map previously obtained by apei_mapreg_map.
 */
void
apei_mapreg_write(const ACPI_GENERIC_ADDRESS *reg, struct apei_mapreg *map,
    uint64_t v)
{
        unsigned chunkbits = NBBY*(1 << (reg->AccessWidth - 1));
        unsigned i, n = reg->BitWidth / chunkbits;

        membar_release();       /* XXX probably not right for MMIO */
        for (i = 0; i < n; i++) {
                uint64_t chunk = v >> (i*chunkbits);

                switch (reg->SpaceId) {
                case ACPI_ADR_SPACE_SYSTEM_IO: {
                        ACPI_IO_ADDRESS addr;
                        ACPI_STATUS rv;

                        switch (reg->AccessWidth) {
                        case 1:
                                addr = reg->Address + i;
                                break;
                        case 2:
                                addr = reg->Address + 2*i;
                                break;
                        case 3:
                                addr = reg->Address + 4*i;
                                break;
                        case 4: /* no 64-bit I/O ports */
                        default:
                                __unreachable();
                        }
                        rv = AcpiOsWritePort(addr, chunk,
                            NBBY*(1 << (reg->AccessWidth - 1)));
                        KASSERTMSG(!ACPI_FAILURE(rv), "%s",
                            AcpiFormatException(rv));
                        break;
                }
                case ACPI_ADR_SPACE_SYSTEM_MEMORY:
                        switch (reg->AccessWidth) {
                        case 1:
                                *((volatile uint8_t *)map + i) = chunk;
                                break;
                        case 2:
                                *((volatile uint16_t *)map + i) = chunk;
                                break;
                        case 3:
                                *((volatile uint32_t *)map + i) = chunk;
                                break;
                        case 4:
                                *((volatile uint64_t *)map + i) = chunk;
                                break;
                        default:
                                __unreachable();
                        }
                        break;
                default:
                        __unreachable();
                }
        }
}