/******************************************************************************
*
* Module Name: aeregion - Handler for operation regions
*
*****************************************************************************/
/*
* Copyright (C) 2000 - 2022, 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 MERCHANTABILITY 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;
char Uuid[ACPI_PRM_INPUT_BUFFER_SIZE + 1];
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);
case ACPI_ADR_SPACE_PLATFORM_RT:
AcpiOsPrintf ("Acpiexec: PRM %s invoked\n",
(Function & ACPI_IO_MASK) ? "Write" : "Read ");
if ((Function & ACPI_IO_MASK) == ACPI_WRITE)
{
AcpiUtConvertUuidToString((char *) Buffer + 10, Uuid);
AcpiOsPrintf ("Mode: %u GUID: %s\n", Buffer[0], Uuid);
}
/* Unpack the input buffer and print the contents for debug */
break;
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);
}