From 667aa7ccbf8311a3bbe6ae541cdb6915711a9f3e Mon Sep 17 00:00:00 2001 From: Pierre Gondois Date: Tue, 4 Aug 2020 09:01:18 +0100 Subject: DynamicTablesPkg: AML utility interfaces The AML utility interfaces are a collection of helper functions that assist in computing the checksum, size and to propagate the node information as a result of addition or update of AML nodes. Signed-off-by: Pierre Gondois Signed-off-by: Sami Mujawar Reviewed-by: Alexei Fedorov --- .../Library/Common/AmlLib/Utils/AmlUtility.c | 906 +++++++++++++++++++++ .../Library/Common/AmlLib/Utils/AmlUtility.h | 95 +++ 2 files changed, 1001 insertions(+) create mode 100644 DynamicTablesPkg/Library/Common/AmlLib/Utils/AmlUtility.c create mode 100644 DynamicTablesPkg/Library/Common/AmlLib/Utils/AmlUtility.h (limited to 'DynamicTablesPkg') diff --git a/DynamicTablesPkg/Library/Common/AmlLib/Utils/AmlUtility.c b/DynamicTablesPkg/Library/Common/AmlLib/Utils/AmlUtility.c new file mode 100644 index 0000000000..7ebd08f945 --- /dev/null +++ b/DynamicTablesPkg/Library/Common/AmlLib/Utils/AmlUtility.c @@ -0,0 +1,906 @@ +/** @file + AML Utility. + + Copyright (c) 2019 - 2020, Arm Limited. All rights reserved.
+ + SPDX-License-Identifier: BSD-2-Clause-Patent +**/ + +#include + +#include +#include +#include + +/** This function computes and updates the ACPI table checksum. + + @param [in] AcpiTable Pointer to an Acpi table. + + @retval EFI_SUCCESS The function completed successfully. + @retval EFI_INVALID_PARAMETER Invalid parameter. +**/ +EFI_STATUS +EFIAPI +AcpiPlatformChecksum ( + IN EFI_ACPI_DESCRIPTION_HEADER * AcpiTable + ) +{ + UINT8 * Ptr; + UINT8 Sum; + UINT32 Size; + + if (AcpiTable == NULL) { + ASSERT (0); + return EFI_INVALID_PARAMETER; + } + + Ptr = (UINT8*)AcpiTable; + Size = AcpiTable->Length; + Sum = 0; + + // Set the checksum field to 0 first. + AcpiTable->Checksum = 0; + + // Compute the checksum. + while ((Size--) != 0) { + Sum = (UINT8)(Sum + (*Ptr++)); + } + + // Set the checksum. + AcpiTable->Checksum = (UINT8)(0xFF - Sum + 1); + + return EFI_SUCCESS; +} + +/** A callback function that computes the size of a Node and adds it to the + Size pointer stored in the Context. + Calling this function on the root node will compute the total size of the + AML bytestream. + + @param [in] Node Node to compute the size. + @param [in, out] Context Pointer holding the computed size. + (UINT32 *) Context. + @param [in, out] Status Pointer holding: + - At entry, the Status returned by the + last call to this exact function during + the enumeration; + - At exit, he returned status of the + call to this function. + Optional, can be NULL. + + @retval TRUE if the enumeration can continue or has finished without + interruption. + @retval FALSE if the enumeration needs to stopped or has stopped. +**/ +STATIC +BOOLEAN +EFIAPI +AmlComputeSizeCallback ( + IN AML_NODE_HEADER * Node, + IN OUT VOID * Context, + IN OUT EFI_STATUS * Status OPTIONAL + ) +{ + UINT32 Size; + EAML_PARSE_INDEX IndexPtr; + CONST AML_OBJECT_NODE * ParentNode; + + if (!IS_AML_NODE_VALID (Node) || + (Context == NULL)) { + ASSERT (0); + if (Status != NULL) { + *Status = EFI_INVALID_PARAMETER; + } + return FALSE; + } + + // Ignore the second fixed argument of method invocation nodes + // as the information stored there (the argument count) is not in the + // ACPI specification. + ParentNode = (CONST AML_OBJECT_NODE*)AmlGetParent (Node); + if (IS_AML_OBJECT_NODE (ParentNode) && + AmlNodeCompareOpCode (ParentNode, AML_METHOD_INVOC_OP, 0) && + AmlIsNodeFixedArgument (Node, &IndexPtr)) { + if (IndexPtr == EAmlParseIndexTerm1) { + if (Status != NULL) { + *Status = EFI_SUCCESS; + } + return TRUE; + } + } + + Size = *((UINT32*)Context); + + if (IS_AML_DATA_NODE (Node)) { + Size += ((AML_DATA_NODE*)Node)->Size; + } else if (IS_AML_OBJECT_NODE (Node) && + !AmlNodeHasAttribute ( + (CONST AML_OBJECT_NODE*)Node, + AML_IS_PSEUDO_OPCODE)) { + // Ignore pseudo-opcodes as they are not part of the + // ACPI specification. + + Size += (((AML_OBJECT_NODE*)Node)->AmlByteEncoding->OpCode == + AML_EXT_OP) ? 2 : 1; + + // Add the size of the PkgLen. + if (AmlNodeHasAttribute ( + (AML_OBJECT_NODE*)Node, + AML_HAS_PKG_LENGTH)) { + Size += AmlComputePkgLengthWidth (((AML_OBJECT_NODE*)Node)->PkgLen); + } + } + + // Check for overflow. + // The root node has a null size, thus the strict comparison. + if (*((UINT32*)Context) > Size) { + ASSERT (0); + *Status = EFI_INVALID_PARAMETER; + return FALSE; + } + + *((UINT32*)Context) = Size; + + if (Status != NULL) { + *Status = EFI_SUCCESS; + } + + return TRUE; +} + +/** Compute the size of a tree/sub-tree. + + @param [in] Node Node to compute the size. + @param [in, out] Size Pointer holding the computed size. + + @retval EFI_SUCCESS The function completed successfully. + @retval EFI_INVALID_PARAMETER Invalid parameter. +**/ +EFI_STATUS +EFIAPI +AmlComputeSize ( + IN CONST AML_NODE_HEADER * Node, + IN OUT UINT32 * Size + ) +{ + EFI_STATUS Status; + + if (!IS_AML_NODE_VALID (Node) || + (Size == NULL)) { + ASSERT (0); + return EFI_INVALID_PARAMETER; + } + + *Size = 0; + + AmlEnumTree ( + (AML_NODE_HEADER*)Node, + AmlComputeSizeCallback, + (VOID*)Size, + &Status + ); + + return Status; +} + +/** Get the value contained in an integer node. + + @param [in] Node Pointer to an integer node. + Must be an object node. + @param [out] Value Value contained in the integer node. + + @retval EFI_SUCCESS The function completed successfully. + @retval EFI_INVALID_PARAMETER Invalid parameter. +**/ +STATIC +EFI_STATUS +EFIAPI +AmlNodeGetIntegerValue ( + IN AML_OBJECT_NODE * Node, + OUT UINT64 * Value + ) +{ + AML_DATA_NODE * DataNode; + + if ((!IsIntegerNode (Node) && + !IsSpecialIntegerNode (Node)) || + (Value == NULL)) { + ASSERT (0); + return EFI_INVALID_PARAMETER; + } + + // For ZeroOp and OneOp, there is no data node. + if (IsSpecialIntegerNode (Node)) { + if (AmlNodeCompareOpCode (Node, AML_ZERO_OP, 0)) { + *Value = 0; + } else if (AmlNodeCompareOpCode (Node, AML_ONE_OP, 0)) { + *Value = 1; + } else { + // OnesOp cannot be handled: it represents a maximum value. + ASSERT (0); + return EFI_INVALID_PARAMETER; + } + return EFI_SUCCESS; + } + + // For integer nodes, the value is in the first fixed argument. + DataNode = (AML_DATA_NODE*)Node->FixedArgs[EAmlParseIndexTerm0]; + if (!IS_AML_DATA_NODE (DataNode) || + (DataNode->DataType != EAmlNodeDataTypeUInt)) { + ASSERT (0); + return EFI_INVALID_PARAMETER; + } + + switch (DataNode->Size) { + case 1: + { + *Value = *((UINT8*)(DataNode->Buffer)); + break; + } + case 2: + { + *Value = *((UINT16*)(DataNode->Buffer)); + break; + } + case 4: + { + *Value = *((UINT32*)(DataNode->Buffer)); + break; + } + case 8: + { + *Value = *((UINT64*)(DataNode->Buffer)); + break; + } + default: + { + ASSERT (0); + return EFI_INVALID_PARAMETER; + } + } // switch + + return EFI_SUCCESS; +} + +/** Replace a Zero (AML_ZERO_OP) or One (AML_ONE_OP) object node + with a byte integer (AML_BYTE_PREFIX) object node having the same value. + + @param [in] Node Pointer to an integer node. + Must be an object node having ZeroOp or OneOp. + + @retval EFI_SUCCESS The function completed successfully. + @retval EFI_INVALID_PARAMETER Invalid parameter. +**/ +STATIC +EFI_STATUS +EFIAPI +AmlUnwindSpecialInteger ( + IN AML_OBJECT_NODE * Node + ) +{ + EFI_STATUS Status; + + AML_DATA_NODE * NewDataNode; + UINT8 Value; + CONST AML_BYTE_ENCODING * ByteEncoding; + + if (!IsSpecialIntegerNode (Node)) { + ASSERT (0); + return EFI_INVALID_PARAMETER; + } + + // Find the value. + if (AmlNodeCompareOpCode (Node, AML_ZERO_OP, 0)) { + Value = 0; + } else if (AmlNodeCompareOpCode (Node, AML_ONE_OP, 0)) { + Value = 1; + } else { + // OnesOp cannot be handled: it represents a maximum value. + ASSERT (0); + return EFI_INVALID_PARAMETER; + } + + Status = AmlCreateDataNode ( + EAmlNodeDataTypeUInt, + &Value, + sizeof (UINT8), + (AML_DATA_NODE**)&NewDataNode + ); + if (EFI_ERROR (Status)) { + ASSERT (0); + return Status; + } + + // Change the encoding of the special node to a ByteOp encoding. + ByteEncoding = AmlGetByteEncodingByOpCode (AML_BYTE_PREFIX, 0); + if (ByteEncoding == NULL) { + ASSERT (0); + Status = EFI_INVALID_PARAMETER; + goto error_handler; + } + + // Update the ByteEncoding from ZERO_OP/ONE_OP to AML_BYTE_PREFIX. + Node->AmlByteEncoding = ByteEncoding; + + // Add the data node as the first fixed argument of the ByteOp object. + Status = AmlSetFixedArgument ( + (AML_OBJECT_NODE*)Node, + EAmlParseIndexTerm0, + (AML_NODE_HEADER*)NewDataNode + ); + if (EFI_ERROR (Status)) { + ASSERT (0); + goto error_handler; + } + + return Status; + +error_handler: + AmlDeleteTree ((AML_NODE_HEADER*)NewDataNode); + return Status; +} + +/** Set the value contained in an integer node. + + The OpCode is updated accordingly to the new value + (e.g.: If the original value was a UINT8 value, then the OpCode + would be AML_BYTE_PREFIX. If it the new value is a UINT16 + value then the OpCode will be updated to AML_WORD_PREFIX). + + @param [in] Node Pointer to an integer node. + Must be an object node. + @param [in] NewValue New value to write in the integer node. + @param [out] ValueWidthDiff Difference in number of bytes used to store + the new value. + Can be negative. + + @retval EFI_SUCCESS The function completed successfully. + @retval EFI_INVALID_PARAMETER Invalid parameter. + @retval EFI_OUT_OF_RESOURCES Could not allocate memory. +**/ +EFI_STATUS +EFIAPI +AmlNodeSetIntegerValue ( + IN AML_OBJECT_NODE * Node, + IN UINT64 NewValue, + OUT INT8 * ValueWidthDiff + ) +{ + EFI_STATUS Status; + AML_DATA_NODE * DataNode; + + UINT8 NewOpCode; + UINT8 NumberOfBytes; + + if ((!IsIntegerNode (Node) && + !IsSpecialIntegerNode (Node)) || + (ValueWidthDiff == NULL)) { + ASSERT (0); + return EFI_INVALID_PARAMETER; + } + + *ValueWidthDiff = 0; + // For ZeroOp and OneOp, there is no data node. + // Thus the object node is converted to a byte object node holding 0 or 1. + if (IsSpecialIntegerNode (Node)) { + switch (NewValue) { + case AML_ZERO_OP: + Node->AmlByteEncoding = AmlGetByteEncodingByOpCode (AML_ZERO_OP, 0); + return EFI_SUCCESS; + case AML_ONE_OP: + Node->AmlByteEncoding = AmlGetByteEncodingByOpCode (AML_ONE_OP, 0); + return EFI_SUCCESS; + default: + { + Status = AmlUnwindSpecialInteger (Node); + if (EFI_ERROR (Status)) { + ASSERT (0); + return Status; + } + // The AmlUnwindSpecialInteger functions converts a special integer + // node to a UInt8/Byte data node. Thus, the size increments by one: + // special integer are encoded as one byte (the opcode only) while byte + // integers are encoded as two bytes (the opcode + the value). + *ValueWidthDiff += sizeof (UINT8); + } + } // switch + } // IsSpecialIntegerNode (Node) + + // For integer nodes, the value is in the first fixed argument. + DataNode = (AML_DATA_NODE*)Node->FixedArgs[EAmlParseIndexTerm0]; + if (!IS_AML_DATA_NODE (DataNode) || + (DataNode->DataType != EAmlNodeDataTypeUInt)) { + ASSERT (0); + return EFI_INVALID_PARAMETER; + } + + // The value can be encoded with a special 0 or 1 OpCode. + // The AML_ONES_OP is not handled. + if (NewValue <= 1) { + NewOpCode = (NewValue == 0) ? AML_ZERO_OP : AML_ONE_OP; + Node->AmlByteEncoding = AmlGetByteEncodingByOpCode (NewOpCode, 0); + + // The value is encoded with a AML_ZERO_OP or AML_ONE_OP. + // This means there is no need for a DataNode containing the value. + // The change in size is equal to the size of the DataNode's buffer. + *ValueWidthDiff = -((INT8)DataNode->Size); + + // Detach and free the DataNode containing the integer value. + DataNode->NodeHeader.Parent = NULL; + Node->FixedArgs[EAmlParseIndexTerm0] = NULL; + Status = AmlDeleteNode ((AML_NODE_HEADER*)DataNode); + if (EFI_ERROR (Status)) { + ASSERT (0); + return Status; + } + + return EFI_SUCCESS; + } + + // Check the number of bits needed to represent the value. + if (NewValue > MAX_UINT32) { + // Value is 64 bits. + NewOpCode = AML_QWORD_PREFIX; + NumberOfBytes = 8; + } else if (NewValue > MAX_UINT16) { + // Value is 32 bits. + NewOpCode = AML_DWORD_PREFIX; + NumberOfBytes = 4; + } else if (NewValue > MAX_UINT8) { + // Value is 16 bits. + NewOpCode = AML_WORD_PREFIX; + NumberOfBytes = 2; + } else { + // Value is 8 bits. + NewOpCode = AML_BYTE_PREFIX; + NumberOfBytes = 1; + } + + *ValueWidthDiff += (INT8)(NumberOfBytes - DataNode->Size); + + // Update the ByteEncoding as it may have changed between [8 .. 64] bits. + Node->AmlByteEncoding = AmlGetByteEncodingByOpCode (NewOpCode, 0); + if (Node->AmlByteEncoding == NULL) { + ASSERT (0); + return EFI_INVALID_PARAMETER; + } + + // Free the old DataNode buffer and allocate a buffer with the right size + // to store the new data. + if (*ValueWidthDiff != 0) { + FreePool (DataNode->Buffer); + DataNode->Buffer = AllocateZeroPool (NumberOfBytes); + if (DataNode->Buffer == NULL) { + ASSERT (0); + return EFI_OUT_OF_RESOURCES; + } + DataNode->Size = NumberOfBytes; + } + + // Write the new value. + CopyMem (DataNode->Buffer, &NewValue, NumberOfBytes); + + return EFI_SUCCESS; +} + +/** Increment/decrement the value contained in the IntegerNode. + + @param [in] IntegerNode Pointer to an object node containing + an integer. + @param [in] IsIncrement Choose the operation to do: + - TRUE: Increment the Node's size and + the Node's count; + - FALSE: Decrement the Node's size and + the Node's count. + @param [in] Diff Value to add/subtract to the integer. + @param [out] ValueWidthDiff When modifying the integer, it can be + promoted/demoted, e.g. from UINT8 to UINT16. + Stores the change in width. + Can be negative. + + @retval EFI_SUCCESS The function completed successfully. + @retval EFI_INVALID_PARAMETER Invalid parameter. +**/ +STATIC +EFI_STATUS +EFIAPI +AmlNodeUpdateIntegerValue ( + IN AML_OBJECT_NODE * IntegerNode, + IN BOOLEAN IsIncrement, + IN UINT64 Diff, + OUT INT8 * ValueWidthDiff + ) +{ + EFI_STATUS Status; + UINT64 Value; + + if (ValueWidthDiff == NULL) { + ASSERT (0); + return EFI_INVALID_PARAMETER; + } + + // Get the current value. + // Checks on the IntegerNode are done in the call. + Status = AmlNodeGetIntegerValue (IntegerNode, &Value); + if (EFI_ERROR (Status)) { + ASSERT (0); + return Status; + } + + // Check for UINT64 over/underflow. + if ((IsIncrement && (Value > (MAX_UINT64 - Diff))) || + (!IsIncrement && (Value < Diff))) { + ASSERT (0); + return EFI_INVALID_PARAMETER; + } + + // Compute the new value. + if (IsIncrement) { + Value += Diff; + } else { + Value -= Diff; + } + + Status = AmlNodeSetIntegerValue ( + IntegerNode, + Value, + ValueWidthDiff + ); + ASSERT_EFI_ERROR (Status); + return Status; +} + +/** Propagate the size information up the tree. + + The length of the ACPI table is updated in the RootNode, + but not the checksum. + + @param [in] Node Pointer to a node. + Must be a root node or an object node. + @param [in] IsIncrement Choose the operation to do: + - TRUE: Increment the Node's size and + the Node's count; + - FALSE: Decrement the Node's size and + the Node's count. + @param [in] Diff Value to add/subtract to the Node's size. + + @retval EFI_SUCCESS The function completed successfully. + @retval EFI_INVALID_PARAMETER Invalid parameter. +**/ +STATIC +EFI_STATUS +EFIAPI +AmlPropagateSize ( + IN AML_NODE_HEADER * Node, + IN BOOLEAN IsIncrement, + IN UINT32 * Diff + ) +{ + EFI_STATUS Status; + AML_OBJECT_NODE * ObjectNode; + AML_NODE_HEADER * ParentNode; + + UINT32 Value; + UINT32 InitialPkgLenWidth; + UINT32 NewPkgLenWidth; + UINT32 ReComputedPkgLenWidth; + INT8 FieldWidthChange; + + if (!IS_AML_OBJECT_NODE (Node) && + !IS_AML_ROOT_NODE (Node)) { + ASSERT (0); + return EFI_INVALID_PARAMETER; + } + + if (IS_AML_OBJECT_NODE (Node)) { + ObjectNode = (AML_OBJECT_NODE*)Node; + + // For BufferOp, the buffer size is stored in BufferSize. Therefore, + // BufferOp needs special handling to update the BufferSize. + // BufferSize must be updated before the PkgLen to accommodate any + // increment resulting from the update of the BufferSize. + // DefBuffer := BufferOp PkgLength BufferSize ByteList + // BufferOp := 0x11 + // BufferSize := TermArg => Integer + if (AmlNodeCompareOpCode (ObjectNode, AML_BUFFER_OP, 0)) { + // First fixed argument of BufferOp is an integer (BufferSize) + // (can be a BYTE, WORD, DWORD or QWORD). + // BufferSize is an object node. + Status = AmlNodeUpdateIntegerValue ( + (AML_OBJECT_NODE*)AmlGetFixedArgument ( + ObjectNode, + EAmlParseIndexTerm0 + ), + IsIncrement, + (UINT64)(*Diff), + &FieldWidthChange + ); + if (EFI_ERROR (Status)) { + ASSERT (0); + return Status; + } + + // FieldWidthChange is an integer. + // It must be positive if IsIncrement is TRUE, negative otherwise. + if ((IsIncrement && + (FieldWidthChange < 0)) || + (!IsIncrement && + (FieldWidthChange > 0))) { + ASSERT (0); + return EFI_INVALID_PARAMETER; + } + + // Check for UINT32 overflow. + if (*Diff > (MAX_UINT32 - (UINT32)ABS (FieldWidthChange))) { + ASSERT (0); + return EFI_INVALID_PARAMETER; + } + + // Update Diff if the field width changed. + *Diff = (UINT32)(*Diff + ABS (FieldWidthChange)); + } // AML_BUFFER_OP node. + + // Update the PgkLen. + // Needs to be done at last to reflect the potential field width changes. + if (AmlNodeHasAttribute (ObjectNode, AML_HAS_PKG_LENGTH)) { + Value = ObjectNode->PkgLen; + + // Subtract the size of the PkgLen encoding. The size of the PkgLen + // encoding must be computed after having updated Value. + InitialPkgLenWidth = AmlComputePkgLengthWidth (Value); + Value -= InitialPkgLenWidth; + + // Check for an over/underflows. + // PkgLen is a 28 bit value, cf 20.2.4 Package Length Encoding + // i.e. the maximum value is (2^28 - 1) = ((BIT0 << 28) - 1). + if ((IsIncrement && ((((BIT0 << 28) - 1) - Value) < *Diff)) || + (!IsIncrement && (Value < *Diff))) { + ASSERT (0); + return EFI_INVALID_PARAMETER; + } + + // Update the size. + if (IsIncrement) { + Value += *Diff; + } else { + Value -= *Diff; + } + + // Compute the new PkgLenWidth. + NewPkgLenWidth = AmlComputePkgLengthWidth (Value); + if (NewPkgLenWidth == 0) { + ASSERT (0); + return EFI_INVALID_PARAMETER; + } + + // Add it to the Value. + Value += NewPkgLenWidth; + + // Check that adding the PkgLenWidth didn't trigger a domino effect, + // increasing the encoding width of the PkgLen again. + // The PkgLen is encoded on at most 4 bytes. It is possible to increase + // the PkgLen width if its encoding is on less than 3 bytes. + ReComputedPkgLenWidth = AmlComputePkgLengthWidth (Value); + if (ReComputedPkgLenWidth != NewPkgLenWidth) { + if ((ReComputedPkgLenWidth != 0) && + (ReComputedPkgLenWidth < 4)) { + // No need to recompute the PkgLen since a new threshold cannot + // be reached by incrementing the value by one. + Value += 1; + } else { + ASSERT (0); + return EFI_INVALID_PARAMETER; + } + } + + *Diff += (InitialPkgLenWidth > ReComputedPkgLenWidth) ? + (InitialPkgLenWidth - ReComputedPkgLenWidth) : + (ReComputedPkgLenWidth - InitialPkgLenWidth); + ObjectNode->PkgLen = Value; + } // PkgLen update. + + // During CodeGeneration, the tree is incomplete and + // there is no root node at the top of the tree. Stop + // propagating the new size when finding a root node + // OR when a NULL parent is found. + ParentNode = AmlGetParent ((AML_NODE_HEADER*)Node); + if (ParentNode != NULL) { + // Propagate the size up the tree. + Status = AmlPropagateSize ( + Node->Parent, + IsIncrement, + Diff + ); + if (EFI_ERROR (Status)) { + ASSERT (0); + return Status; + } + } + + } else if (IS_AML_ROOT_NODE (Node)) { + // Update the length field in the SDT header. + Value = ((AML_ROOT_NODE*)Node)->SdtHeader->Length; + + // Check for an over/underflows. + if ((IsIncrement && (Value > (MAX_UINT32 - *Diff))) || + (!IsIncrement && (Value < *Diff))) { + ASSERT (0); + return EFI_INVALID_PARAMETER; + } + + // Update the size. + if (IsIncrement) { + Value += *Diff; + } else { + Value -= *Diff; + } + + ((AML_ROOT_NODE*)Node)->SdtHeader->Length = Value; + } + + return EFI_SUCCESS; +} + +/** Propagate the node count information up the tree. + + @param [in] ObjectNode Pointer to an object node. + @param [in] IsIncrement Choose the operation to do: + - TRUE: Increment the Node's size and + the Node's count; + - FALSE: Decrement the Node's size and + the Node's count. + @param [in] NodeCount Number of nodes added/removed (depends on the + value of Operation). + @param [out] FieldWidthChange When modifying the integer, it can be + promoted/demoted, e.g. from UINT8 to UINT16. + Stores the change in width. + Can be negative. + + @retval EFI_SUCCESS The function completed successfully. + @retval EFI_INVALID_PARAMETER Invalid parameter. +**/ +STATIC +EFI_STATUS +EFIAPI +AmlPropagateNodeCount ( + IN AML_OBJECT_NODE * ObjectNode, + IN BOOLEAN IsIncrement, + IN UINT8 NodeCount, + OUT INT8 * FieldWidthChange + ) +{ + EFI_STATUS Status; + + AML_NODE_HEADER * NodeCountArg; + UINT8 CurrNodeCount; + + // Currently there is no use case where (NodeCount > 1). + if (!IS_AML_OBJECT_NODE (ObjectNode) || + (FieldWidthChange == NULL) || + (NodeCount > 1)) { + ASSERT (0); + return EFI_INVALID_PARAMETER; + } + + *FieldWidthChange = 0; + + // Update the number of elements stored in PackageOp and VarPackageOp. + // The number of elements is stored as the first fixed argument. + // DefPackage := PackageOp PkgLength NumElements PackageElementList + // PackageOp := 0x12 + // DefVarPackage := VarPackageOp PkgLength VarNumElements PackageElementList + // VarPackageOp := 0x13 + // NumElements := ByteData + // VarNumElements := TermArg => Integer + NodeCountArg = AmlGetFixedArgument (ObjectNode, EAmlParseIndexTerm0); + if (AmlNodeCompareOpCode (ObjectNode, AML_PACKAGE_OP, 0)) { + // First fixed argument of PackageOp stores the number of elements + // in the package. It is an UINT8. + + // Check for over/underflow. + CurrNodeCount = *(((AML_DATA_NODE*)NodeCountArg)->Buffer); + if ((IsIncrement && (CurrNodeCount == MAX_UINT8)) || + (!IsIncrement && (CurrNodeCount == 0))) { + ASSERT (0); + return EFI_INVALID_PARAMETER; + } + + // Update the node count in the DataNode. + CurrNodeCount = IsIncrement ? (CurrNodeCount + 1) : (CurrNodeCount - 1); + *(((AML_DATA_NODE*)NodeCountArg)->Buffer) = CurrNodeCount; + } else if (AmlNodeCompareOpCode (ObjectNode, AML_VAR_PACKAGE_OP, 0)) { + // First fixed argument of PackageOp stores the number of elements + // in the package. It is an integer (can be a BYTE, WORD, DWORD, QWORD). + Status = AmlNodeUpdateIntegerValue ( + (AML_OBJECT_NODE*)NodeCountArg, + IsIncrement, + NodeCount, + FieldWidthChange + ); + if (EFI_ERROR (Status)) { + ASSERT (0); + return Status; + } + } + + return EFI_SUCCESS; +} + +/** Propagate information up the tree. + + The information can be a new size, a new number of arguments. + + @param [in] Node Pointer to a node. + Must be a root node or an object node. + @param [in] IsIncrement Choose the operation to do: + - TRUE: Increment the Node's size and + the Node's count; + - FALSE: Decrement the Node's size and + the Node's count. + @param [in] Diff Value to add/subtract to the Node's size. + @param [in] NodeCount Number of nodes added/removed. + + @retval EFI_SUCCESS The function completed successfully. + @retval EFI_INVALID_PARAMETER Invalid parameter. +**/ +EFI_STATUS +EFIAPI +AmlPropagateInformation ( + IN AML_NODE_HEADER * Node, + IN BOOLEAN IsIncrement, + IN UINT32 Diff, + IN UINT8 NodeCount + ) +{ + EFI_STATUS Status; + INT8 FieldWidthChange; + + // Currently there is no use case where (NodeCount > 1). + if ((!IS_AML_ROOT_NODE (Node) && + !IS_AML_OBJECT_NODE (Node)) || + (NodeCount > 1)) { + ASSERT (0); + return EFI_INVALID_PARAMETER; + } + + // Propagate the node count first as it may change the number of bytes + // needed to store the node count, and then impact FieldWidthChange. + if ((NodeCount != 0) && + IS_AML_OBJECT_NODE (Node)) { + Status = AmlPropagateNodeCount ( + (AML_OBJECT_NODE*)Node, + IsIncrement, + NodeCount, + &FieldWidthChange + ); + if (EFI_ERROR (Status)) { + ASSERT (0); + return Status; + } + + // Propagate the potential field width change. + // Maximum change is between UINT8/UINT64: 8 bytes. + if ((ABS (FieldWidthChange) > 8) || + (IsIncrement && + ((FieldWidthChange < 0) || + ((Diff + (UINT8)FieldWidthChange) > MAX_UINT32))) || + (!IsIncrement && + ((FieldWidthChange > 0) || + (Diff < (UINT32)ABS (FieldWidthChange))))) { + ASSERT (0); + return EFI_INVALID_PARAMETER; + } + Diff = (UINT32)(Diff + (UINT8)ABS (FieldWidthChange)); + } + + // Diff can be zero if some data is updated without modifying the data size. + if (Diff != 0) { + Status = AmlPropagateSize (Node, IsIncrement, &Diff); + if (EFI_ERROR (Status)) { + ASSERT (0); + return Status; + } + } + + return EFI_SUCCESS; +} diff --git a/DynamicTablesPkg/Library/Common/AmlLib/Utils/AmlUtility.h b/DynamicTablesPkg/Library/Common/AmlLib/Utils/AmlUtility.h new file mode 100644 index 0000000000..c57d780140 --- /dev/null +++ b/DynamicTablesPkg/Library/Common/AmlLib/Utils/AmlUtility.h @@ -0,0 +1,95 @@ +/** @file + AML Utility. + + Copyright (c) 2019 - 2020, Arm Limited. All rights reserved.
+ + SPDX-License-Identifier: BSD-2-Clause-Patent +**/ + +#ifndef AML_UTILITY_H_ +#define AML_UTILITY_H_ + +#include + +/** This function computes and updates the ACPI table checksum. + + @param [in] AcpiTable Pointer to an Acpi table. + + @retval EFI_SUCCESS The function completed successfully. + @retval EFI_INVALID_PARAMETER Invalid parameter. +**/ +EFI_STATUS +EFIAPI +AcpiPlatformChecksum ( + IN EFI_ACPI_DESCRIPTION_HEADER * AcpiTable + ); + +/** Compute the size of a tree/sub-tree. + + @param [in] Node Node to compute the size. + @param [in, out] Size Pointer holding the computed size. + + @retval EFI_SUCCESS The function completed successfully. + @retval EFI_INVALID_PARAMETER Invalid parameter. +**/ +EFI_STATUS +EFIAPI +AmlComputeSize ( + IN CONST AML_NODE_HEADER * Node, + IN OUT UINT32 * Size + ); + +/** Set the value contained in an integer node. + + The OpCode is updated accordingly to the new value + (e.g.: If the original value was a UINT8 value, then the OpCode + would be AML_BYTE_PREFIX. If it the new value is a UINT16 + value then the OpCode will be updated to AML_WORD_PREFIX). + + @param [in] Node Pointer to an integer node. + Must be an object node. + @param [in] NewValue New value to write in the integer node. + @param [out] ValueWidthDiff Difference in number of bytes used to store + the new value. + Can be negative. + + @retval EFI_SUCCESS The function completed successfully. + @retval EFI_INVALID_PARAMETER Invalid parameter. + @retval EFI_OUT_OF_RESOURCES Could not allocate memory. +**/ +EFI_STATUS +EFIAPI +AmlNodeSetIntegerValue ( + IN AML_OBJECT_NODE * Node, + IN UINT64 NewValue, + OUT INT8 * ValueWidthDiff + ); + +/** Propagate information up the tree. + + The information can be a new size, a new number of arguments. + + @param [in] Node Pointer to a node. + Must be a root node or an object node. + @param [in] IsIncrement Choose the operation to do: + - TRUE: Increment the Node's size and + the Node's count; + - FALSE: Decrement the Node's size and + the Node's count. + @param [in] Diff Value to add/subtract to the Node's size. + @param [in] NodeCount Number of nodes added/removed. + + @retval EFI_SUCCESS The function completed successfully. + @retval EFI_INVALID_PARAMETER Invalid parameter. +**/ +EFI_STATUS +EFIAPI +AmlPropagateInformation ( + IN AML_NODE_HEADER * Node, + IN BOOLEAN IsIncrement, + IN UINT32 Diff, + IN UINT8 NodeCount + ); + +#endif // AML_UTILITY_H_ + -- cgit v1.2.3