/*      $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();
               }
       }
}