/******************************************************************************
 *
 * Module Name: aeregion - Handler for operation regions
 *
 *****************************************************************************/

/*
 * Copyright (C) 2000 - 2019, Intel Corp.
 * 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,
 *    without modification.
 * 2. Redistributions in binary form must reproduce at minimum a disclaimer
 *    substantially 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.
 * 3. Neither the names of the above-listed copyright holders nor the names
 *    of any contributors may be used to endorse or promote products derived
 *    from this software without specific prior written permission.
 *
 * Alternatively, this software may be distributed under the terms of the
 * GNU General Public License ("GPL") version 2 as published by the Free
 * Software Foundation.
 *
 * 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 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 "aecommon.h"

#define _COMPONENT          ACPI_TOOLS
        ACPI_MODULE_NAME    ("aeregion")


static AE_DEBUG_REGIONS     AeRegions;


/******************************************************************************
 *
 * FUNCTION:    AeRegionHandler
 *
 * PARAMETERS:  Standard region handler parameters
 *
 * RETURN:      Status
 *
 * DESCRIPTION: Test handler - Handles some dummy regions via memory that can
 *              be manipulated in Ring 3. Simulates actual reads and writes.
 *
 *****************************************************************************/

ACPI_STATUS
AeRegionHandler (
    UINT32                  Function,
    ACPI_PHYSICAL_ADDRESS   Address,
    UINT32                  BitWidth,
    UINT64                  *Value,
    void                    *HandlerContext,
    void                    *RegionContext)
{

    ACPI_OPERAND_OBJECT     *RegionObject = ACPI_CAST_PTR (ACPI_OPERAND_OBJECT, RegionContext);
    UINT8                   *Buffer = ACPI_CAST_PTR (UINT8, Value);
    UINT8                   *OldBuffer;
    UINT8                   *NewBuffer;
    ACPI_PHYSICAL_ADDRESS   BaseAddress;
    ACPI_PHYSICAL_ADDRESS   BaseAddressEnd;
    ACPI_PHYSICAL_ADDRESS   RegionAddress;
    ACPI_PHYSICAL_ADDRESS   RegionAddressEnd;
    UINT32                  Length;
    UINT8                   DataLength;
    UINT8                   *DataBuffer;
    BOOLEAN                 BufferExists;
    BOOLEAN                 BufferResize;
    AE_REGION               *RegionElement;
    void                    *BufferValue;
    ACPI_STATUS             Status;
    UINT32                  ByteWidth;
    UINT32                  RegionLength;
    UINT32                  i;
    UINT8                   SpaceId;
    ACPI_CONNECTION_INFO    *MyContext;
    UINT32                  Value1;
    UINT32                  Value2;
    ACPI_RESOURCE           *Resource;


    ACPI_FUNCTION_NAME (AeRegionHandler);


    /* If the object is not a region, simply return */

    if (RegionObject->Region.Type != ACPI_TYPE_REGION)
    {
        return (AE_OK);
    }

    /* Check that we actually got back our context parameter */

    if (HandlerContext != &AeMyContext)
    {
        AcpiOsPrintf (
            "Region handler received incorrect context %p, should be %p\n",
            HandlerContext, &AeMyContext);
    }

    MyContext = ACPI_CAST_PTR (ACPI_CONNECTION_INFO, HandlerContext);

    /*
     * Find the region's address space and length before searching
     * the linked list.
     */
    BaseAddress = RegionObject->Region.Address;
    Length = RegionObject->Region.Length;
    SpaceId = RegionObject->Region.SpaceId;

    ACPI_DEBUG_PRINT ((ACPI_DB_OPREGION,
        "Operation Region request on %s at 0x%X, BitWidth 0x%X, RegionLength 0x%X\n",
        AcpiUtGetRegionName (RegionObject->Region.SpaceId),
        (UINT32) Address, BitWidth, (UINT32) Length));

    /*
     * Region support can be disabled with the -do option.
     * We use this to support dynamically loaded tables where we pass a valid
     * address to the AML.
     */
    if (AcpiGbl_DbOpt_NoRegionSupport)
    {
        BufferValue = ACPI_TO_POINTER (Address);
        ByteWidth = (BitWidth / 8);

        if (BitWidth % 8)
        {
            ByteWidth += 1;
        }
        goto DoFunction;
    }

    switch (SpaceId)
    {
    case ACPI_ADR_SPACE_SYSTEM_IO:
        /*
         * For I/O space, exercise the port validation
         * Note: ReadPort currently always returns all ones, length=BitLength
         */
        switch (Function & ACPI_IO_MASK)
        {
        case ACPI_READ:

            if (BitWidth == 64)
            {
                /* Split the 64-bit request into two 32-bit requests */

                Status = AcpiHwReadPort (Address, &Value1, 32);
                ACPI_CHECK_OK (AcpiHwReadPort, Status);
                Status = AcpiHwReadPort (Address+4, &Value2, 32);
                ACPI_CHECK_OK (AcpiHwReadPort, Status);

                *Value = Value1 | ((UINT64) Value2 << 32);
            }
            else
            {
                Status = AcpiHwReadPort (Address, &Value1, BitWidth);
                ACPI_CHECK_OK (AcpiHwReadPort, Status);
                *Value = (UINT64) Value1;
            }
            break;

        case ACPI_WRITE:

            if (BitWidth == 64)
            {
                /* Split the 64-bit request into two 32-bit requests */

                Status = AcpiHwWritePort (Address, ACPI_LODWORD (*Value), 32);
                ACPI_CHECK_OK (AcpiHwWritePort, Status);
                Status = AcpiHwWritePort (Address+4, ACPI_HIDWORD (*Value), 32);
                ACPI_CHECK_OK (AcpiHwWritePort, Status);
            }
            else
            {
                Status = AcpiHwWritePort (Address, (UINT32) *Value, BitWidth);
                ACPI_CHECK_OK (AcpiHwWritePort, Status);
            }
            break;

        default:

            Status = AE_BAD_PARAMETER;
            break;
        }

        if (ACPI_FAILURE (Status))
        {
            return (Status);
        }

        /* Now go ahead and simulate the hardware */
        break;

    /*
     * SMBus and GenericSerialBus support the various bidirectional
     * protocols.
     */
    case ACPI_ADR_SPACE_SMBUS:
    case ACPI_ADR_SPACE_GSBUS:  /* ACPI 5.0 */

        Status = AcpiExGetProtocolBufferLength ((Function >> 16), &Length);
        if (ACPI_FAILURE (Status))
        {
            AcpiOsPrintf ("AcpiExec: Invalid SMbus/GSbus protocol ID: 0x%X\n",
                (Function >> 16));
            return (Status);
        }

        /* Adjust for fixed SMBus buffer size */

        if ((SpaceId == ACPI_ADR_SPACE_SMBUS) &&
            (Length > ACPI_SMBUS_DATA_SIZE))
        {
            Length = ACPI_SMBUS_DATA_SIZE; /* SMBus buffer is fixed-length */
        }

        if (AcpiGbl_DisplayRegionAccess)
        {
            AcpiOsPrintf ("AcpiExec: %s "
                "%s: Attr %X Addr %.4X BaseAddr %.4X Length %.2X BitWidth %X BufLen %X\n",
                AcpiUtGetRegionName (SpaceId),
                (Function & ACPI_IO_MASK) ? "Write" : "Read ",
                (UINT32) (Function >> 16),
                (UINT32) Address, (UINT32) BaseAddress,
                Length, BitWidth, Buffer[1]);

            /* GenericSerialBus has a Connection() parameter */

            if ((SpaceId == ACPI_ADR_SPACE_GSBUS) && MyContext)
            {
                Status = AcpiBufferToResource (MyContext->Connection,
                    MyContext->Length, &Resource);
                if (ACPI_SUCCESS (Status))
                {
                    ACPI_FREE (Resource);
                }

                AcpiOsPrintf (" [AccessLength %.2X Connection %p]",
                    MyContext->AccessLength, MyContext->Connection);
            }

            AcpiOsPrintf ("\n");
        }

        DataBuffer = &Buffer[2];
        DataLength = (UINT8) Length;

        /* Setup the return buffer. Note: ASLTS depends on these fill values */

        if (Length == ACPI_MAX_GSBUS_DATA_SIZE)
        {
            DataLength = 0x20; /* For ASLTS only */
        }

        for (i = 0; i < Length; i++)
        {
            DataBuffer[i] = (UINT8) (0xA0 + i);
        }

        Buffer[0] = 0;                  /* Return Status, OK */
        Buffer[1] = DataLength;         /* Length of valid data */
        return (AE_OK);

    case ACPI_ADR_SPACE_IPMI: /* ACPI 4.0 */

        if (AcpiGbl_DisplayRegionAccess)
        {
            AcpiOsPrintf ("AcpiExec: IPMI "
                "%s: Attr %X Addr %.4X BaseAddr %.4X Len %.2X Width %X BufLen %X\n",
                (Function & ACPI_IO_MASK) ? "Write" : "Read ",
                (UINT32) (Function >> 16), (UINT32) Address, (UINT32) BaseAddress,
                Length, BitWidth, Buffer[1]);
        }

        /*
         * Regardless of a READ or WRITE, this handler is passed a 66-byte
         * buffer in which to return the IPMI status/length/data.
         *
         * Return some example data to show use of the bidirectional buffer
         */
        Buffer[0] = 0;                      /* Status byte */
        Buffer[1] = ACPI_IPMI_DATA_SIZE;    /* Return buffer data length */
        Buffer[2] = 0;                      /* Completion code */
        Buffer[3] = 0;                      /* Reserved */

        /*
         * Fill the 66-byte buffer with the return data.
         * Note: ASLTS depends on these fill values.
         */
        for (i = 4; i < ACPI_IPMI_BUFFER_SIZE; i++)
        {
            Buffer[i] = (UINT8) (i);
        }
        return (AE_OK);

    /*
     * GPIO has some special semantics:
     * 1) Address is the pin number index into the Connection() pin list
     * 2) BitWidth is the actual number of bits (pins) defined by the field
     */
    case ACPI_ADR_SPACE_GPIO: /* ACPI 5.0 */

        if (AcpiGbl_DisplayRegionAccess)
        {
            AcpiOsPrintf ("AcpiExec: GPIO "
                "%s: Address %.4X Length %X BitWidth %X Conn %p\n",
                (Function & ACPI_IO_MASK) ? "Write" : "Read ",
                (UINT32) Address, Length, BitWidth, MyContext->Connection);
        }

        /* Now perform the "normal" SystemMemory handling, for AcpiExec only */
        break;

    /*
     * PCC operation region will write the entire subspace's data and expect
     * a response from the hardware. For acpiexec, we'll fill the buffer with
     * default values. Note: ASLTS will depend on these values.
     */
    case ACPI_ADR_SPACE_PLATFORM_COMM: /* ACPI 6.3 */
        if (AcpiGbl_DisplayRegionAccess)
        {
            AcpiOsPrintf ("AcpiExec: PCC Write : Addr %.4X Width %X\n",
                (UINT32) Address, BitWidth);
        }
        for (i = 0; i < Length; ++i)
        {
            Buffer[i] = (UINT8) i;
        }
        return (AE_OK);

    default:
        break;
    }

    /*
     * Search through the linked list for this region's buffer
     */
    BufferExists = FALSE;
    BufferResize = FALSE;
    RegionElement = AeRegions.RegionList;

    if (AeRegions.NumberOfRegions)
    {
        BaseAddressEnd = BaseAddress + Length - 1;
        while (!BufferExists && RegionElement)
        {
            RegionAddress = RegionElement->Address;
            RegionAddressEnd = RegionElement->Address + RegionElement->Length - 1;
            RegionLength = RegionElement->Length;

            /*
             * Overlapping Region Support
             *
             * While searching through the region buffer list, determine if an
             * overlap exists between the requested buffer space and the current
             * RegionElement space. If there is an overlap then replace the old
             * buffer with a new buffer of increased size before continuing to
             * do the read or write
             */
            if (RegionElement->SpaceId != SpaceId ||
                BaseAddressEnd < RegionAddress ||
                BaseAddress > RegionAddressEnd)
            {
                /*
                 * Requested buffer is outside of the current RegionElement
                 * bounds
                 */
                RegionElement = RegionElement->NextRegion;
            }
            else
            {
                /*
                 * Some amount of buffer space sharing exists. There are 4 cases
                 * to consider:
                 *
                 * 1. Right overlap
                 * 2. Left overlap
                 * 3. Left and right overlap
                 * 4. Fully contained - no resizing required
                 */
                BufferExists = TRUE;

                if ((BaseAddress >= RegionAddress) &&
                    (BaseAddress <= RegionAddressEnd) &&
                    (BaseAddressEnd > RegionAddressEnd))
                {
                    /* Right overlap */

                    RegionElement->Length = (UINT32) (BaseAddress -
                        RegionAddress + Length);
                    BufferResize = TRUE;
                }

                else if ((BaseAddressEnd >= RegionAddress) &&
                         (BaseAddressEnd <= RegionAddressEnd) &&
                         (BaseAddress < RegionAddress))
                {
                    /* Left overlap */

                    RegionElement->Address = BaseAddress;
                    RegionElement->Length = (UINT32) (RegionAddress -
                        BaseAddress + RegionElement->Length);
                    BufferResize = TRUE;
                }

                else if ((BaseAddress < RegionAddress) &&
                         (BaseAddressEnd > RegionAddressEnd))
                {
                    /* Left and right overlap */

                    RegionElement->Address = BaseAddress;
                    RegionElement->Length = Length;
                    BufferResize = TRUE;
                }

                /*
                 * only remaining case is fully contained for which we don't
                 * need to do anything
                 */
                if (BufferResize)
                {
                    NewBuffer = AcpiOsAllocate (RegionElement->Length);
                    if (!NewBuffer)
                    {
                        return (AE_NO_MEMORY);
                    }

                    OldBuffer = RegionElement->Buffer;
                    RegionElement->Buffer = NewBuffer;
                    NewBuffer = NULL;

                    /* Initialize the region with the default fill value */

                    memset (RegionElement->Buffer,
                        AcpiGbl_RegionFillValue, RegionElement->Length);

                    /*
                     * Get BufferValue to point (within the new buffer) to the
                     * base address of the old buffer
                     */
                    BufferValue = (UINT8 *) RegionElement->Buffer +
                        (UINT64) RegionAddress -
                        (UINT64) RegionElement->Address;

                    /*
                     * Copy the old buffer to its same location within the new
                     * buffer
                     */
                    memcpy (BufferValue, OldBuffer, RegionLength);
                    AcpiOsFree (OldBuffer);
                }
            }
        }
    }

    /*
     * If the Region buffer does not exist, create it now
     */
    if (!BufferExists)
    {
        /* Do the memory allocations first */

        RegionElement = AcpiOsAllocate (sizeof (AE_REGION));
        if (!RegionElement)
        {
            return (AE_NO_MEMORY);
        }

        RegionElement->Buffer = AcpiOsAllocate (Length);
        if (!RegionElement->Buffer)
        {
            AcpiOsFree (RegionElement);
            return (AE_NO_MEMORY);
        }

        /* Initialize the region with the default fill value */

        memset (RegionElement->Buffer, AcpiGbl_RegionFillValue, Length);

        RegionElement->Address      = BaseAddress;
        RegionElement->Length       = Length;
        RegionElement->SpaceId      = SpaceId;
        RegionElement->NextRegion   = NULL;

        /*
         * Increment the number of regions and put this one
         * at the head of the list as it will probably get accessed
         * more often anyway.
         */
        AeRegions.NumberOfRegions += 1;

        if (AeRegions.RegionList)
        {
            RegionElement->NextRegion = AeRegions.RegionList;
        }

        AeRegions.RegionList = RegionElement;
    }

    /* Calculate the size of the memory copy */

    ByteWidth = (BitWidth / 8);
    if (BitWidth % 8)
    {
        ByteWidth += 1;
    }

    /*
     * The buffer exists and is pointed to by RegionElement.
     * We now need to verify the request is valid and perform the operation.
     *
     * NOTE: RegionElement->Length is in bytes, therefore it we compare against
     * ByteWidth (see above)
     */
    if ((RegionObject->Region.SpaceId != ACPI_ADR_SPACE_GPIO) &&
        ((UINT64) Address + ByteWidth) >
        ((UINT64)(RegionElement->Address) + RegionElement->Length))
    {
        ACPI_WARNING ((AE_INFO,
            "Request on [%4.4s] is beyond region limit "
            "Req-0x%X+0x%X, Base=0x%X, Len-0x%X",
            (RegionObject->Region.Node)->Name.Ascii, (UINT32) Address,
            ByteWidth, (UINT32)(RegionElement->Address),
            RegionElement->Length));

        return (AE_AML_REGION_LIMIT);
    }

    /*
     * Get BufferValue to point to the "address" in the buffer
     */
    BufferValue = ((UINT8 *) RegionElement->Buffer +
        ((UINT64) Address - (UINT64) RegionElement->Address));

DoFunction:
    /*
     * Perform a read or write to the buffer space
     */
    switch (Function)
    {
    case ACPI_READ:
        /*
         * Set the pointer Value to whatever is in the buffer
         */
        memcpy (Value, BufferValue, ByteWidth);
        break;

    case ACPI_WRITE:
        /*
         * Write the contents of Value to the buffer
         */
        memcpy (BufferValue, Value, ByteWidth);
        break;

    default:

        return (AE_BAD_PARAMETER);
    }

    if (AcpiGbl_DisplayRegionAccess)
    {
        switch (SpaceId)
        {
        case ACPI_ADR_SPACE_SYSTEM_MEMORY:

            AcpiOsPrintf ("AcpiExec: SystemMemory "
                "%s: Val %.8X Addr %.4X BitWidth %X [REGION: BaseAddr %.4X Len %.2X]\n",
                (Function & ACPI_IO_MASK) ? "Write" : "Read ",
                (UINT32) *Value, (UINT32) Address, BitWidth, (UINT32) BaseAddress, Length);
            break;

        case ACPI_ADR_SPACE_GSBUS:

            AcpiOsPrintf ("AcpiExec: GenericSerialBus\n");
            break;

        case ACPI_ADR_SPACE_GPIO:   /* ACPI 5.0 */

            /* This space is required to always be ByteAcc */

            Status = AcpiBufferToResource (MyContext->Connection,
                MyContext->Length, &Resource);

            AcpiOsPrintf ("AcpiExec: GeneralPurposeIo "
                "%s: %.8X Addr %.4X BaseAddr %.4X Length %.2X "
                "BitWidth %X AccLen %.2X Conn %p\n",
                (Function & ACPI_IO_MASK) ? "Write" : "Read ", (UINT32) *Value,
                (UINT32) Address, (UINT32) BaseAddress, Length, BitWidth,
                MyContext->AccessLength, MyContext->Connection);
            if (ACPI_SUCCESS (Status))
            {
                ACPI_FREE (Resource);
            }
            break;

        default:

            AcpiOsPrintf ("AcpiExec: Region access on SpaceId %2.2X\n", SpaceId);
            break;
        }
    }

    return (AE_OK);
}
