AML

From OSDev Wiki
Jump to: navigation, search

This article is a stub! This page or section is a stub. You can help the wiki by accurately contributing to it.

ACPI Machine Language (AML) is the platform independent code that ACPI utilizes. A knowledge of it is required to even shutdown the computer. It is found in the DSDT and SSDT tables, which are in turn found by parsing the RSDT or XSDT.

AML code is byte code which is parsed from the beginning of each table when that table is read. It contains definitions of devices and objects within the ACPI namespace. By parsing the code, paying attention to all appropriate control-flow statements, an AML interpreter can build up a database of all devices within a system and the properties and functions they support (in reference to configuration and power management).

The specification is available from the ACPI website. In addition, Intel provides a reference implementation in its ACPICA software.

Contents

ASL and AML

ASL is ACPI source language. It is a more human-readable form of the byte code that is AML. This difference is similar to that between assembly code and the actual binary machine code. The Intel ASL assembler (iasl) is freely available on many Linux distributions and can convert in either direction between these formats.

Sample ASL code

The following is a very simple example of ASL code for a DSDT.

test.asl:

DefinitionBlock ("test.aml", "DSDT", 1, "OEMID ", "TABLEID  ", 0x00000000)
{
    Scope (_SB)
    {
        Device (PCI0)
        {
            Name (_HID, EisaId ("PNP0A03"))
        }
    }
}

This can then be compiled to AML by running 'iasl test.asl' to generate test.aml.

Here is an actual device off an HP pavilion g6 (RTC). This is embedded in a much larger DefinitionBlock:

                Device (RTC)
                {
                    Name (_HID, EisaId ("PNP0B00"))  // _HID: Hardware ID
                    Name (_CRS, ResourceTemplate ()  // _CRS: Current Resource Settings
                    {
                        IO (Decode16,
                            0x0070,             // Range Minimum
                            0x0070,             // Range Maximum
                            0x01,               // Alignment
                            0x08,               // Length
                            )
                        IRQNoFlags ()
                            {8}
                    })
                    OperationRegion (CMS0, SystemCMOS, Zero, 0x40)
                    Field (CMS0, ByteAcc, NoLock, Preserve)
                    {
                        RTSE,   8, 
                        Offset (0x02), 
                        RTMN,   8, 
                        Offset (0x04), 
                        RTHR,   8, 
                        Offset (0x06), 
                        RTDY,   8, 
                        RTDE,   8
                    }
                }

Notice that the region is in the CMOS.

Following on from the RTC, here is a PS/2 Keyboard off the same PC:

                Device (PS2K)
                {
                    Name (_HID, EisaId ("PNP0303"))  // _HID: Hardware ID
                    Method (_STA, 0, NotSerialized)  // _STA: Status
                    {
                        Return (0x0F)
                    }
 
                    Name (_CRS, ResourceTemplate ()  // _CRS: Current Resource Settings
                    {
                        IO (Decode16,
                            0x0060,             // Range Minimum
                            0x0060,             // Range Maximum
                            0x01,               // Alignment
                            0x01,               // Length
                            )
                        IO (Decode16,
                            0x0064,             // Range Minimum
                            0x0064,             // Range Maximum
                            0x01,               // Alignment
                            0x01,               // Length
                            )
                        IRQ (Edge, ActiveHigh, Exclusive, )
                            {1}
                    })
                    Name (_PRS, ResourceTemplate ()  // _PRS: Possible Resource Settings
                    {
                        StartDependentFn (0x00, 0x00)
                        {
                            FixedIO (
                                0x0060,             // Address
                                0x01,               // Length
                                )
                            FixedIO (
                                0x0064,             // Address
                                0x01,               // Length
                                )
                            IRQNoFlags ()
                                {1}
                        }
                        EndDependentFn ()
                    })
                    Name (_PRW, Package (0x02)  // _PRW: Power Resources for Wake
                    {
                        0x18, 
                        0x03
                    })
                    Method (_PSW, 1, NotSerialized)  // _PSW: Power State Wake
                    {
                        Store (Arg0, KBWK)
                    }
                }

the above 2 samples were gathered via a utility I wrote to snatch DSDT data from the Registry, disassembled by iASL.--Bellezzasolo 14:58, 20 January 2013 (CST)

This defines one table (the DSDT), and requests that the output AML be placed in a file called test.aml. The "OEMID " is a 6 character string defining the name of the OEM who made the firmware of the system, the "TABLEID " is an 8 character string defining the name of the table - it is normally OEM specific. The last entry in the DefinitionBlock line is the OEM revision id. It defines one namespace within the root namespace which is called _SB. Note that all device/scope/object names are 4 characters long, therefore this is sometimes represented as "_SB ". _SB is a special name in the ACPI namespace called "System Bus", which is the main scope under which all devices and bus objects are found. See the ACPI specification table 5-67 for a list of predefined names.

Within the _SB scope we define a device called PCI0, which has one object within it called _HID. _HID is again a predefined name that refers to a device's Plug and Play hardware ID, in this case the built-in macro EisaId is used to generate the value 0x030ad041 from "PNP0A03", which is the PNP ID of a PCI root bus. A reasonably complete list of PNP ids is available from http://tuxmobil.org/pnp_ids.html.

AML Opcodes

This table comes from the ACPI specification, where it is provided mainly for use during debugging an AML parser. For example, if your parser fails and the next byte (that it couldn't parse) is "0x72" then you could refer to this table to see that it is an "Add" operation. Don't let this table confuse you into thinking that AML consists a linear flow of simple instructions to be decoded one at a time. Most AML consists of nested, recursively defined structures and lists.

Value (Hex) Name
0x00 ZeroOp
0x01 OneOp
0x06 AliasOp
0x08 NameOp
0x0A BytePrefix
0x0B WordPrefix
0x0C DWordPrefix
0x0D StringPrefix
0x0E QWordPrefix
0x10 ScopeOp
0x11 BufferOp
0x12 PackageOp
0x13 VarPackageOp
0x14 MethodOp
0x2E (‘.’) DualNamePrefix
0x2F (‘/’) MultiNamePrefix
0x30-0x39 ('0'-'9') DigitChar
0x41-0x5A (‘A’-‘Z’) NameChar
0x5B (‘[’) ExtOpPrefix
0x5B 0x01 MutexOp
0x5B 0x02 EventOp
0x5B 0x12 CondRefOfOp
0x5B 0x13 CreateFieldOp
0x5B 0x1F LoadTableOp
0x5B 0x20 LoadOp
0x5B 0x21 StallOp
0x5B 0x22 SleepOp
0x5B 0x23 AcquireOp
0x5B 0x24 SignalOp
0x5B 0x25 WaitOp
0x5B 0x26 ResetOp
0x5B 0x27 ReleaseOp
0x5B 0x28 FromBCDOp
0x5B 0x29 ToBCD
0x5B 0x2A UnloadOp
0x5B 0x30 RevisionOp
0x5B 0x31 DebugOp
0x5B 0x32 FatalOp
0x5B 0x33 TimerOp
0x5B 0x80 OpRegionOp
0x5B 0x81 FieldOp
0x5B 0x82 DeviceOpList
0x5B 0x83 ProcessorOp
0x5B 0x84 PowerResOp
0x5B 0x85 ThermalZoneOpList
0x5B 0x86 IndexFieldOp
0x5B 0x87 BankFieldOp
0x5B 0x88 DataRegionOp
0x5C (‘\’) RootChar
0x5E (‘^’) ParentPrefixChar
0x5F(‘_’) NameChar
0x60 (‘`’) Local0Op
0x61 (‘a’) Local1Op
0x62 (‘b’) Local2Op
0x63 (‘c’) Local3Op
0x64 (‘d’) Local4Op
0x65 (‘e’) Local5Op
0x66 (‘f’) Local6Op
0x67 (‘g’) Local7Op
0x68 (‘h’) Arg0Op
0x69 (‘i’) Arg1Op
0x6A (‘j’) Arg2Op
0x6B (‘k’) Arg3Op
0x6C (‘l’) Arg4Op
0x6D (‘m’) Arg5Op
0x6E (‘n’) Arg6Op
0x70 StoreOp
0x71 RefOfOp
0x72 AddOp
0x73 ConcatOp
0x74 SubtractOp
0x75 IncrementOp
0x76 DecrementOp
0x77 MultiplyOp
0x78 DivideOp
0x79 ShiftLeftOp
0x7A ShiftRightOp
0x7B AndOp
0x7C NandOp
0x7D OrOp
0x7E NorOp
0x7F XorOp
0x80 NotOp
0x81 FindSetLeftBitOp
0x82 FindSetRightBitOp
0x83 DerefOfOp
0x84 ConcatResOp
0x85 ModOp
0x86 NotifyOp
0x87 SizeOfOp
0x88 IndexOp
0x89 MatchOp
0x8A CreateDWordFieldOp
0x8B CreateWordFieldOp
0x8C CreateByteFieldOp
0x8D CreateBitFieldOp
0x8E TypeOp
0x8F CreateQWordFieldOp
0x90 LandOp
0x91 LorOp
0x92 LnotOp
0x92 0x93 LNotEqualOp
0x92 0x94 LLessEqualOp
0x92 0x95 LGreaterEqualOp
0x93 LEqualOp
0x94 LGreaterOp
0x95 LLessOp
0x96 ToBufferOp
0x97 ToDecimalStringOp
0x98 ToHexStringOp
0x99 ToIntegerOp
0x9C ToStringOp
0x9D CopyObjectOp
0x9E MidOp
0x9F ContinueOp
0xA0 IfOp
0xA1 ElseOp
0xA2 WhileOp
0xA3 NoopOp
0xA4 ReturnOp
0xA5 BreakOp
0xCC BreakPointOp
0xFF OnesOp

Parsing AML

While the ACPI specification (and therefore the meaning of the AML in the DSDT and SSDT) is very complicated, the bytecode itself is described by a formal grammar described in the ACPI specification. For example, here is the grammar that describes a simple ASCII string.

String := StringPrefix AsciiCharList NullChar
StringPrefix := 0x0D
AsciiCharList := Nothing | <AsciiChar AsciiCharList>
AsciiChar := 0x01 - 0x7F

It seems reasonable that you could write recursive descent parsers for these individual objects, as below.

function parse_string:
  if parse_string_prefix:
    if parse_ascii_char_list:
      if parse_null_char:
        return 1
      else:
        return 0
    else:
      return 0
  else:
    return 0
 
function parse_string_prefix:
  if next char == 0x0D:
    return 1
  else:
    return 0
 
function parse_ascii_char_list:
  if parse_ascii_char:
    return parse_ascii_char_list
  else:
    return parse_nothing
 
function parse_ascii_char:
  if next char < 0x01:
    return 0
  else if next char > 0x7F:
    return 0
  else:
    return 1
 
function parse_null_char:
  if next char == 0x00:
    return 1
  else:
    return 0
 
function parse_nothing:
  return 1

This will work for the vast majority of the AML grammar. In fact, if it weren't for two things, you could write a parser for all of AML by referring only to the 10 pages of formal grammar in the specification and not knowing a thing about what the objects you're parsing mean. Unfortunately, those two things are PkgLength and MethodInvocation.

PkgLength

PkgLength is an object that appears in some grammars, like DefScope:

DefScope := ScopeOp PkgLength NameString TermList
ScopeOp := 0x10
TermList := Nothing | <TermObj TermList>
...

Every AML object is either "implicit-length, fixed-length", "implicit-length, nested", or "explicit length". ScopeOp is a simple example of an "implicit-length, fixed-length" object: it's one byte long and wherever it appears, will only be one byte long. TermList is an example of an "implicit-length, nested" object. It's a recursively defined list, and who knows how long it might be. There's a problem with implicit-length, nested objects. What if two are nested within each-other? For example:

TermObj TermObj TermObj

Is this a single TermList containing one TermObj? Or is it a single TermList containing one TermObj and another TermList, which itself contains two TermObjs?

To resolve this confusion, "implicit length, nested" objects appear within "explicit length" objects. DefScope is an example of an "explicit length" object. The PkgLength object tells you how many more bytes in the AML bytecode (counting the PkgLength field itself) are part of the explicit length object. This way you know when to stop parsing any implicit-length, nested objects like TermLists.

This isn't a huge problem for your parser though. A parsing function for an "implicit length, nested" object like TermList needs to take an argument that tells it how many bytes to parse. This number is determined by the previous PkgLength parser.

MethodInvocation

The MethodInvocation object is a far bigger problem. MethodInvocation is an "implicit length, nested" object that does not always fall within an "explicit length" object.

MethodInvocation := NameString TermArgList
TermArgList := Nothing | <TermArg TermArgList>

A MethodInvocation can appear all over the place in AML and most of the time the parent object will not have a PkgLength object that can be used to know when to stop parsing the recursively defined TermArgList. Instead, every time you parse the NameString of a MethodInvocation you have to look through the AML for a matching DefMethod and parse its MethodFlags child object to see how many items are in the MethodInvocation's TermArgList. This means you have to understand how to deal with NameString objects, which are similar to filesystem paths. And this means you have to be able to walk through partially-parsed AML looking at paths and scopes and recursing in and out of various objects looking for a DefMethod.

Even worse, sometimes the corresponding DefMethod is defined after the MethodInvocation! Such "forward references" mean that you can't parse AML in one pass. Your parser will have to determine that it can't resolve a MethodInvocation right now and try again later after it parses the corresponding DefMethod object.

Suddenly your AML parser is not just a simple recursive descent parser implementing a formal grammar, but a multi-pass beast that understands AML scoping and object paths.

On the plus side, MethodInvocation is the only AML object like that.

Personal tools
Namespaces
Variants
Actions
Navigation
About
Toolbox