blob: 77eea59d716b971bd7fa13a138a497a8f2629a0f [file] [log] [blame]
/******************************************************************************
*
* Module Name: adwalk - Disassembler routines for switch statements
*
*****************************************************************************/
/*
* Copyright (C) 2000 - 2020, 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.
*
* 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 "acpi.h"
#include "accommon.h"
#include "acparser.h"
#include "amlcode.h"
#include "acdisasm.h"
#include "acdispat.h"
#include "acnamesp.h"
#include "acapps.h"
#define _COMPONENT ACPI_CA_DISASSEMBLER
ACPI_MODULE_NAME ("dmswitch")
static BOOLEAN
AcpiDmIsSwitchBlock (
ACPI_PARSE_OBJECT *Op,
char **Temp);
static BOOLEAN
AcpiDmIsCaseBlock (
ACPI_PARSE_OBJECT *Op);
/*******************************************************************************
*
* FUNCTION: AcpiDmProcessSwitch
*
* PARAMETERS: Op - Object to be examined
*
* RETURN: ACPI_STATUS
*
* DESCRIPTION: Walk function to create a list of all temporary (_T_) objects.
* If a While loop is found that can be converted to a Switch, do
* the conversion, remove the temporary name from the list, and
* mark the parse op with an IGNORE flag.
*
******************************************************************************/
ACPI_STATUS
AcpiDmProcessSwitch (
ACPI_PARSE_OBJECT *Op)
{
char *Temp = NULL;
ACPI_PARSE_OBJECT_LIST *NewTemp;
ACPI_PARSE_OBJECT_LIST *Current;
ACPI_PARSE_OBJECT_LIST *Previous;
BOOLEAN FoundTemp = FALSE;
switch (Op->Common.AmlOpcode)
{
case AML_NAME_OP:
Temp = (char *) (&Op->Named.Name);
if (!strncmp(Temp, "_T_", 3))
{
/* Allocate and init a new Temp List node */
NewTemp = ACPI_ALLOCATE_ZEROED (sizeof (ACPI_PARSE_OBJECT_LIST));
if (!NewTemp)
{
return (AE_NO_MEMORY);
}
if (AcpiGbl_TempListHead)
{
Current = AcpiGbl_TempListHead;
AcpiGbl_TempListHead = NewTemp;
AcpiGbl_TempListHead->Op = Op;
AcpiGbl_TempListHead->Next = Current;
}
else
{
AcpiGbl_TempListHead = NewTemp;
AcpiGbl_TempListHead->Op = Op;
AcpiGbl_TempListHead->Next = NULL;
}
}
break;
case AML_WHILE_OP:
if (!AcpiDmIsSwitchBlock (Op, &Temp))
{
break;
}
/* Found a Switch */
Op->Common.DisasmOpcode = ACPI_DASM_SWITCH;
Previous = Current = AcpiGbl_TempListHead;
while (Current)
{
/* Note, if we get here Temp is not NULL */
if (!strncmp(Temp, (char *) (&Current->Op->Named.Name), 4))
{
/* Match found. Ignore disassembly */
Current->Op->Common.DisasmFlags |= ACPI_PARSEOP_IGNORE;
/* Remove from list */
if (Current == AcpiGbl_TempListHead)
{
AcpiGbl_TempListHead = Current->Next;
}
else
{
Previous->Next = Current->Next;
}
Current->Op = NULL;
Current->Next = NULL;
ACPI_FREE (Current);
FoundTemp = TRUE;
break;
}
Previous = Current;
Current = Current->Next;
}
if (!FoundTemp)
{
fprintf (stderr,
"Warning: Declaration for temp name %.4s not found\n", Temp);
}
break;
default:
break;
}
return (AE_OK);
}
/*******************************************************************************
*
* FUNCTION: AcpiDmClearTempList
*
* PARAMETERS: None
*
* RETURN: None
*
* DESCRIPTION: Removes any remaining temporary objects from global list and
* frees
*
******************************************************************************/
void
AcpiDmClearTempList (
void)
{
ACPI_PARSE_OBJECT_LIST *Current;
while (AcpiGbl_TempListHead)
{
Current = AcpiGbl_TempListHead;
AcpiGbl_TempListHead = AcpiGbl_TempListHead->Next;
Current->Op = NULL;
Current->Next = NULL;
ACPI_FREE (Current);
}
}
/*******************************************************************************
*
* FUNCTION: AcpiDmIsSwitchBlock
*
* PARAMETERS: Op - While Object
* Temp - Where the compiler temp name is returned
* (_T_x)
*
* RETURN: TRUE if While block can be converted to a Switch/Case block
*
* DESCRIPTION: Determines if While block is a Switch/Case statement. Modifies
* parse tree to allow for Switch/Case disassembly during walk.
*
* EXAMPLE: Example of parse tree to be converted
*
* While
* One
* Store
* ByteConst
* -NamePath-
* If
* LEqual
* -NamePath-
* Zero
* Return
* One
* Else
* Return
* WordConst
* Break
*
******************************************************************************/
BOOLEAN
AcpiDmIsSwitchBlock (
ACPI_PARSE_OBJECT *Op,
char **Temp)
{
ACPI_PARSE_OBJECT *OneOp;
ACPI_PARSE_OBJECT *StoreOp;
ACPI_PARSE_OBJECT *NamePathOp;
ACPI_PARSE_OBJECT *PredicateOp;
ACPI_PARSE_OBJECT *CurrentOp;
ACPI_PARSE_OBJECT *TempOp;
/* Check for One Op Predicate */
OneOp = AcpiPsGetArg (Op, 0);
if (!OneOp || (OneOp->Common.AmlOpcode != AML_ONE_OP))
{
return (FALSE);
}
/* Check for Store Op */
StoreOp = OneOp->Common.Next;
if (!StoreOp || (StoreOp->Common.AmlOpcode != AML_STORE_OP))
{
return (FALSE);
}
/* Check for Name Op with _T_ string */
NamePathOp = AcpiPsGetArg (StoreOp, 1);
if (!NamePathOp ||
(NamePathOp->Common.AmlOpcode != AML_INT_NAMEPATH_OP))
{
return (FALSE);
}
if (strncmp ((char *) (NamePathOp->Common.Value.Name), "_T_", 3))
{
return (FALSE);
}
*Temp = (char *) (NamePathOp->Common.Value.Name);
/* This is a Switch/Case control block */
/* Ignore the One Op Predicate */
OneOp->Common.DisasmFlags |= ACPI_PARSEOP_IGNORE;
/* Ignore the Store Op, but not the children */
StoreOp->Common.DisasmOpcode = ACPI_DASM_IGNORE_SINGLE;
/*
* First arg of Store Op is the Switch condition.
* Mark it as a Switch predicate and as a parameter list for paren
* closing and correct indentation.
*/
PredicateOp = AcpiPsGetArg (StoreOp, 0);
PredicateOp->Common.DisasmOpcode = ACPI_DASM_SWITCH_PREDICATE;
PredicateOp->Common.DisasmFlags |= ACPI_PARSEOP_PARAMETER_LIST;
/* Ignore the Name Op */
NamePathOp->Common.DisasmFlags = ACPI_PARSEOP_IGNORE;
/* Remaining opcodes are the Case statements (If/ElseIf's) */
CurrentOp = StoreOp->Common.Next;
while (AcpiDmIsCaseBlock (CurrentOp))
{
/* Block is a Case structure */
if (CurrentOp->Common.AmlOpcode == AML_ELSE_OP)
{
/* ElseIf */
CurrentOp->Common.DisasmOpcode = ACPI_DASM_CASE;
CurrentOp = AcpiPsGetArg (CurrentOp, 0);
}
/* If */
CurrentOp->Common.DisasmOpcode = ACPI_DASM_CASE;
/*
* Mark the parse tree for Case disassembly. There are two
* types of Case statements. The first type of statement begins with
* an LEqual. The second starts with an LNot and uses a Match statement
* on a Package of constants.
*/
TempOp = AcpiPsGetArg (CurrentOp, 0);
switch (TempOp->Common.AmlOpcode)
{
case (AML_LOGICAL_EQUAL_OP):
/* Ignore just the LEqual Op */
TempOp->Common.DisasmOpcode = ACPI_DASM_IGNORE_SINGLE;
/* Ignore the NamePath Op */
TempOp = AcpiPsGetArg (TempOp, 0);
TempOp->Common.DisasmFlags = ACPI_PARSEOP_IGNORE;
/*
* Second arg of LEqual will be the Case predicate.
* Mark it as a predicate and also as a parameter list for paren
* closing and correct indentation.
*/
PredicateOp = TempOp->Common.Next;
PredicateOp->Common.DisasmOpcode = ACPI_DASM_SWITCH_PREDICATE;
PredicateOp->Common.DisasmFlags |= ACPI_PARSEOP_PARAMETER_LIST;
break;
case (AML_LOGICAL_NOT_OP):
/*
* The Package will be the predicate of the Case statement.
* It's under:
* LNOT
* LEQUAL
* MATCH
* PACKAGE
*/
/* Get the LEqual Op from LNot */
TempOp = AcpiPsGetArg (TempOp, 0);
/* Get the Match Op from LEqual */
TempOp = AcpiPsGetArg (TempOp, 0);
/* Get the Package Op from Match */
PredicateOp = AcpiPsGetArg (TempOp, 0);
/* Mark as parameter list for paren closing */
PredicateOp->Common.DisasmFlags |= ACPI_PARSEOP_PARAMETER_LIST;
/*
* The Package list would be too deeply indented if we
* chose to simply ignore the all the parent opcodes, so
* we rearrange the parse tree instead.
*/
/*
* Save the second arg of the If/Else Op which is the
* block code of code for this Case statement.
*/
TempOp = AcpiPsGetArg (CurrentOp, 1);
/*
* Move the Package Op to the child (predicate) of the
* Case statement.
*/
CurrentOp->Common.Value.Arg = PredicateOp;
PredicateOp->Common.Parent = CurrentOp;
/* Add the block code */
PredicateOp->Common.Next = TempOp;
break;
default:
/* Should never get here */
break;
}
/* Advance to next Case block */
CurrentOp = CurrentOp->Common.Next;
}
/* If CurrentOp is now an Else, then this is a Default block */
if (CurrentOp && CurrentOp->Common.AmlOpcode == AML_ELSE_OP)
{
CurrentOp->Common.DisasmOpcode = ACPI_DASM_DEFAULT;
}
/*
* From the first If advance to the Break op. It's possible to
* have an Else (Default) op here when there is only one Case
* statement, so check for it.
*/
CurrentOp = StoreOp->Common.Next->Common.Next;
if (!CurrentOp)
{
return (FALSE);
}
if (CurrentOp->Common.AmlOpcode == AML_ELSE_OP)
{
CurrentOp = CurrentOp->Common.Next;
if (!CurrentOp)
{
return (FALSE);
}
}
/* Ignore the Break Op */
CurrentOp->Common.DisasmFlags |= ACPI_PARSEOP_IGNORE;
return (TRUE);
}
/*******************************************************************************
*
* FUNCTION: AcpiDmIsCaseBlock
*
* PARAMETERS: Op - Object to test
*
* RETURN: TRUE if Object is beginning of a Case block.
*
* DESCRIPTION: Determines if an Object is the beginning of a Case block for a
* Switch/Case statement. Parse tree must be one of the following
* forms:
*
* Else (Optional)
* If
* LEqual
* -NamePath- _T_x
*
* Else (Optional)
* If
* LNot
* LEqual
* Match
* Package
* ByteConst
* -NamePath- _T_x
*
******************************************************************************/
static BOOLEAN
AcpiDmIsCaseBlock (
ACPI_PARSE_OBJECT *Op)
{
ACPI_PARSE_OBJECT *CurrentOp;
if (!Op)
{
return (FALSE);
}
/* Look for an If or ElseIf */
CurrentOp = Op;
if (CurrentOp->Common.AmlOpcode == AML_ELSE_OP)
{
CurrentOp = AcpiPsGetArg (CurrentOp, 0);
if (!CurrentOp)
{
return (FALSE);
}
}
if (!CurrentOp || CurrentOp->Common.AmlOpcode != AML_IF_OP)
{
return (FALSE);
}
/* Child must be LEqual or LNot */
CurrentOp = AcpiPsGetArg (CurrentOp, 0);
if (!CurrentOp)
{
return (FALSE);
}
switch (CurrentOp->Common.AmlOpcode)
{
case (AML_LOGICAL_EQUAL_OP):
/* Next child must be NamePath with string _T_ */
CurrentOp = AcpiPsGetArg (CurrentOp, 0);
if (!CurrentOp || !CurrentOp->Common.Value.Name ||
strncmp(CurrentOp->Common.Value.Name, "_T_", 3))
{
return (FALSE);
}
break;
case (AML_LOGICAL_NOT_OP):
/* Child of LNot must be LEqual op */
CurrentOp = AcpiPsGetArg (CurrentOp, 0);
if (!CurrentOp || (CurrentOp->Common.AmlOpcode != AML_LOGICAL_EQUAL_OP))
{
return (FALSE);
}
/* Child of LNot must be Match op */
CurrentOp = AcpiPsGetArg (CurrentOp, 0);
if (!CurrentOp || (CurrentOp->Common.AmlOpcode != AML_MATCH_OP))
{
return (FALSE);
}
/* First child of Match must be Package op */
CurrentOp = AcpiPsGetArg (CurrentOp, 0);
if (!CurrentOp || (CurrentOp->Common.AmlOpcode != AML_PACKAGE_OP))
{
return (FALSE);
}
/* Third child of Match must be NamePath with string _T_ */
CurrentOp = AcpiPsGetArg (CurrentOp->Common.Parent, 2);
if (!CurrentOp || !CurrentOp->Common.Value.Name ||
strncmp(CurrentOp->Common.Value.Name, "_T_", 3))
{
return (FALSE);
}
break;
default:
return (FALSE);
}
return (TRUE);
}