Asl Tutorial v20190625
Asl Tutorial v20190625
version 20190625
License
License: This work is licensed under a Creative Commons Attribution 4.0 International
License. Code samples contained in this work are licensed under BSD-3-Clause.
© Intel Corporation
0 Prerequisites
This tutorial assumes that the reader is familiar with ACPI concepts illustrated in the Introduction
to ACPI paper. If not, the reader is highly encouraged to read the paper available here:
[Link]
1 Overview
1.1 Operating Systems and ACPI
One role of an operating system (OS) is to configure and manage the system’s hardware resources.
These resources could include timers, removable devices, and so on. In order to do so, the OS must
be able to correctly find and configure devices and system components.
Some components have a hardware infrastructure so that operating systems can easily enumerate
and configure certain devices. Other devices cannot be enumerated natively, and their configuration
may be dependent on the platform or motherboard. Devices that cannot be enumerated natively can
encode their platform-specific information in the Advanced Configuration and Power Interface
(ACPI) firmware so they can be enumerated by the OS. ACPI firmware helps the OS by providing
information about devices that cannot be enumerated natively.
Generally, ACPI development starts with datasheets that describe hardware components.
Firmware developers translate relevant portions of the hardware specification to a file containing
code written in ACPI source language (ASL). This ASL file is compiled to ACPI machine language
(AML) bytecode. AML is packaged along with other firmware code and stored in the platform’s
non-volatile read-only memory.
This tutorial introduces ASL, a programming language with syntax similar to C and also touches
on the basics of other components defined by the ACPI specification.
Once the operating system boots, the AML interpreter starts building the ACPI namespace from
AML tables (DSDT and SSDT) contained inside of the firmware package. The ACPI namespace is
a tree-like data structure that maps variable names to internal objects. When the OS queries the AML
interpreter, the interpreter searches the namespace for the requested variable, evaluates the object
associated with the variable, and returns the result of the computation. This is similar to the act of
loading a program file in an interpreter, like Python, and interactively invoking functions from the
program file. Another similar example is loading SQL files in a database management system and
submitting queries to the database from an interactive SQL prompt.
The ACPI namespace is owned by the AML interpreter that resides in kernel space. The
interpreter acts as a mediator between the ACPI namespace and other OS kernel components.
Operating systems are not allowed to directly alter the ACPI namespace. The primary operations
that the OS performs with the AML interpreter are to load firmware tables and to query the
interpreter to evaluate objects within the namespace.
The internal objects associated with the ACPI variable names represent data, device hierarchies,
and subroutines that are used for configuration and power management. These objects are evaluated
according to the ACPI specification, which may result in change to hardware registers owned by the
AML interpreter or the ACPI namespace.
1.2.1 Example
ASL files typically have content that looks like this:
This DefinitionBlock adds named objects called OBJ0 and OBJ1 to the ACPI namespace.
In the namespace, OBJ0 is bound to an object with a value of 0x1234 and OBJ1 is bound to a
string object with a value of "Hello world".
The Name keyword creates a new object named ObjectName and attaches Object to ObjectName
in the global ACPI namespace.
ObjectName is a four-letter variable name (also called NameSeg) that starts with an alphabetical
letter or _ (underscore) and contains up to three or more additional letters, numbers, or underscores.
Lowercase letters are converted to uppercase during compilation. Although NameSegs shorter than
four characters are padded with additional underscores, this tutorial will use NameSegs that are four
characters. Only four characters are allowed in a NameSeg because four bytes fit nicely into a
DWORD. The following examples are valid named object declarations:
Named objects are bound to the types and values of objects in the ACPI namespace through the
use of specific keywords. Adding named objects to the ACPI namespace allows the OS to query the
AML interpreter to fetch the value of the given named object.
The Intel ASL compiler (iASL) is used to translate ASL to AML bytecode. This short section
introduces how to use iASL tools. You can find information on how to build and install the ACPICA
tools in the appendix.
First, create a file called [Link] in a text editor, and then enter the following:
DefinitionBlock ("", "DSDT", 2,"","",0x0)
{
Name (OBJ1, 0x1234)
Name (OBJ2, "HELLO WORLD")
Method (TEST, 2)
{
printf ("Arg0 %o", Arg0)
printf ("Arg2 %o", Arg2)
}
}
iasl [Link]
This output indicates that compilation has completed successfully and the AML has been generated
in an output file called [Link]. The AML inside this file defines an ACPI Namespace.
Typically, the ACPI Namespace is created inside the operating system kernel space. However, in
this tutorial we will demonstrate how to simulate creation of the ACPI Namespace in user space.
This is useful because it can help prototype ASL without having to constantly reflash new firmware
images, and it allows developers to interact with the ACPI Namespace with some limitations.
This user space simulation can be done through acpiexec, a userspace version of the AML
interpreter that simulates hardware accesses that happen while executing instructions. Execute the
following command in the terminal to load [Link]:
acpiexec [Link]
The line below indicates that the DSDT was loaded successfully.
- n
ACPI Namespace (from Namespace Root):
0 _GPE Scope 0x21436c0 00
0 _PR_ Scope 0x2143720 00
0 _SB_ Device 0x2143780 00 Notify Object: 0x2148590
0 _SI_ Scope 0x21437e0 00
0 _TZ_ Device 0x2143840 00
0 _REV Integer 0x21438a0 00 = 0000000000000002
0 _OS_ String 0x2143980 00 Len 14 "Microsoft Windows NT"
0 _GL_ Mutex 0x2143a60 00 Object 0x2143ac0
0 _OSI Method 0x2143ba0 00 Args 1 Len 0000 Aml (nil)
0 OBJ1 Integer 0x2149430 01 = 0000000000001234
0 OBJ2 String 0x2149770 01 Len 0B "HELLO WORLD"
0 TEST Method 0x2149850 01 Args 2 Len 001A Aml 0x21494b5
0 _TI_ Untyped 0x2148e20 00
1 _T97 Method 0x2148e80 00 Args 1 Len 0023 Aml 0x2148ee0
In the output, OBJ1 and OBJ2 exist in the namespace along with several other items.
To simulate the evaluation of OBJ1, type evaluate OBJ1.
- evaluate OBJ1
Evaluating \OBJ1
Evaluation of \OBJ1 returned object 0xffac90, external buffer
length 18
[Integer] = 0000000000001234
As expected, the evaluation of OBJ1 returned an integer with a value of 0x1234.
You can evaluate control methods in the same way with parameters separated by spaces. To
evaluate a sample control method, type evaluate TEST "Hello world" 0xABCD
- evaluate TEST "Hello world" 0xABCD
Evaluating \TEST
ACPI Debug: "arg0 Hello world"
ACPI Debug: "arg1 000000000000ABCD"
No object was returned from evaluation of \TEST
-
To exit acpiexec, enter q.
The above code snippet describes two buffer objects: BUF1 and BUF2. The use of buffer as
a parameter indicates that the contents inside {} are encoded as a buffer. Each element of the
comma-separated list is a value between 0x00 and 0xff. There is an optional parameter to this
operator that denotes the length of the buffer. If the length parameter is not present, a length will be
automatically inserted during compilation.
A package is an array containing ASL objects. The elements of packages can include Integer,
String, Buffer, Package, or other named objects. The following are examples of package
declarations:
Notice that PKG3 contains two elements that are packages. ASL supports multiple nesting of
packages. This is similar to n-dimensional arrays in languages like C. However, ASL packages can
contain different types within packages. PKG4 is a package containing a string and a package. This
example is also a valid package declaration.
This operation region is called OPR1. It represents system memory, and it starts at address
0x10000 with a length of 5 bytes. FLD1 through FLD4 are declared inside of OPR1. FLD1 and
FLD2 span 8 bits each, while FLD3 starts at byte offset 3 and spans 4 bits, and FLD4 spans 12 bits.
The primary motivation for operation region and field declaration is to read and write values to
FLD1 through FLD4 inside of control methods (discussed later).
There are many operation region subtypes other than SystemMemory. To learn more, consult
the ACPI specification.
3 Scopes
3.1 Defining scopes using Devices
The ACPI namespace is a tree-like data structure that describes a hierarchy of named objects. Each
layer in this hierarchy is called a scope. Once a scope is defined, additional named objects can be
inserted in the defined scope.
The contents inside of DefinitionBlock declarations represent the top-most scope of the ACPI
namespace called the root scope. Therefore, declaring a named object inside a DefinitionBlock will
add the named object to the root scope. Consider the following table:
The braces after Device (DEV1) represent the scope of the device. INT1, STR1, BUF1, and
PKG1 contain information about DEV1. This results in a namespace that looks like this:
Device declarations could be nested as well:
Notice that USB1 and USB2 contain the same named objects. This is allowed because these
objects are in a different scope. If the same named objects were defined under the same scope, this
would result in a compiler error because it is illegal to declare two named objects that have the same
NameSeg in the same scope. Similar to many other programming languages, multiple definitions of
the same name within the same scope are not allowed in ASL.
3.2 NamePaths
The above namespace contains nested scopes, and there may be situations where other ASL code
may need to refer to one of the objects underneath USB1. In order to do so, individual objects
underneath USB1 can be referenced by their full pathname. Below is the same namespace annotated
with full pathnames for each named object:
The \ represents the root scope. Therefore, \OBJ0 indicates that OBJ0 is defined at the root
scope. USB1 is declared inside of USBH and can be referenced as USBH.USB1. The . (dot) operator
is called the path separator. This indicates that USB1 is in USBH’s scope. For any named object that
defines a scope, the dot symbol indicates that the name segment before the symbol is inside the
scope of the NameSeg after the dot symbol. For INT1 declared in USB1, the full pathname would
be \USBH.USB1.INT1.
4 Predefined Names
Once the ACPI namespace is populated, the OS will start searching for various named objects to
initialize device drivers. In order to do so, the OS looks for named objects that have a predefined
meaning and use the data as intended. So far, the names INT1, STR1, BUF1, PKG1, and other
objects have been used in examples. These object names do not represent any useful information
about devices; they were only used to illustrate ASL concepts. If an OS were to boot using the
contents of the ACPI namespace in the USB example, the object names INT1, STR1, BUF1, and
PKG1 are likely to be completely useless to a driver because the driver does not need the data
associated with these NameSegs.
The semantics of objects with NameSegs starting with _ are predefined by the ACPI
specification. For example, the _HID represents a hardware ID of a device. When this named object
is defined under a device, it represents the ID associated with that device. According to the
specification, _HID needs to be defined with data that is either a string or an integer. _CRS is a
predefined name that returns a buffer that represents the current resource setting. Below we show
an example of a device that has a plug and play (PNP) ID as well as a buffer that represents an empty
resource.
DefinitionBlock ("", "DSDT", 2, "", "", 0x1)
{
Device (DEV1)
{
Name (_HID, 0x1234)
Name (_CRS, Buffer() {})
}
}
Note that a more realistic table would contain many other predefined named objects. A list of
these predefined names and their meanings can be found in section 5.6.8 of the ACPI specification.
In a typical operating system, _HID will be associated with a device driver for DEV1.
5 Executable ASL
The previous section described named objects and their data types as hard-coded values. Some
named objects may need to be computed by performing operations on data or by performing certain
actions. In this case, a named object should be defined as an ASL control method.
When MTH1 runs, it stores the value 0x00 to INT1. This value is persistent until the OS shuts
down or another control method changes the value. In other words, if the OS evaluates INT1 after
evaluating MTH1, the value of INT1 will be 0x00. Notice that INT1 is declared outside of the
declaration of MTH1. Methods can refer to any named objects that exist outside of its scope.
Control methods have the following syntax:
The printf is similar to the one found in C. The format specifier %o used is for named objects.
The following example will append the contents of STR1 to "Hello world":
Method (CTDW, 1)
{
local0 = arg0
while (local0)
{
printf ("%o",local0)
local0--;
}
}
Method (CTUP, 1)
{
local0 = arg0
while (local0 < 10)
{
if (!(local0 % 2))
{
printf ("%o is even", local0)
}
else
{
printf ("%o is odd", local0)
}
local0++;
}
}
}
5.2.4 Exercises
1. Write a method to determine if the input number is a prime. If the number is a prime,
return the number. Otherwise return 0.
Note: 0 and 1 are not prime numbers.
2. Write a method to return the nth prime number.
Hint: Use the method from the previous exercise.
In addition to Concatenate, there are several useful macros that generate buffers from
strings. For example, the ToUUID macro takes a string of the form
aabbccdd-eeff-gghh-iijj-kkllmmnnoopp where aa through pp represent one byte
values encoded with hexadecimal characters. This string gets converted to a 16-byte buffer that
looks like the following:
Buffer()
{
dd, cc, bb, aa,
ff, ee,
hh, gg,
ii, jj, kk, ll, mm, nn, oo, pp
}
This mixture of little endian and big-endian encoding UUID is called a mixed-endian format.
The use of strings and the ToUUID macro is a convenient way to avoid having to manually encode
the mixed-endian format. There are many other macros that provide similar conveniences, such as
EISAID.
Packages are similar to buffers except their elements can contain strings, integers, buffers, and
other named objects. The above method works for both buffers and packages.
Unlike C, out-of-bounds accesses in buffers and packages in ASL result in runtime errors from
the interpreter. In the example above, OVFL accesses the input buffer or package one past the max
index when local0 == local1. When the runtime error occurs, the method execution is
terminated. This means that local1++ and printf ("Complete") are not executed after the
runtime error.
5.4.1 Exercises
1. What happens when the DeRefOf operator is removed in IDXA?
2. Which component invokes the runtime error?
3. Write a method to return a buffer where the length of the buffer is passed as arg0.
4. Write an ASL method to compute the sum of all elements in a buffer. A buffer will be
passed to this method as arg0.
Method (XCH1)
{
BUFF[1] = 0x00
BUFF[2] = 0x00
}
XCH1 and XCH2 result in the same outcomes. However, XCH2 incurs fewer instructions.
To provide more flexibility, ASL supports opcodes similar to CreateWordField. Below is
a list of opcodes that create named objects that span the length of buffers with various lengths:
• CreateBitField—Length: 1 bit
• CreateByteField—Length: 1 byte
• CreateWordField—Length: 2 bytes
• CreateDWordField—Length: 4 bytes
• CreateQWordField—Length: 8 bytes
• CreateField—Length: Arbitrary
5.5.1 Exercises
1. What is the value of DWRD after XCH1 is evaluated?
2. Write a method to clear bits 5 through 12 of BUFF without using the opcodes discussed
above. Write another method to do the same thing using CreateField.
5.6 ResourceTemplates
Buffers are useful for encoding information that describe resources used by device drivers such as
IRQ, DMA, and I/O ports. Each of these resources has a particular format. The following buffer
describes an I/O resource:
When a driver wants to understand the configuration of devices through ACPI, it calls the AML
interpreter for a named object, which may be assigned to a buffer similar to BUF1. Programming
this by hand in the above example can lead to mistakes because this notation lacks semantic
meanings. If device resources need to be described, use the ResourceTemplate macro instead.
By using the ResourceTemplate macro, the buffer can be encoded like so:
When compiled, RES2 will be translated during compilation to look exactly like RES1. For
RES1 and RES2, the translations are depicted in the image below.
To read or write to elements of RES2, create bit fields that overlay a parameter and write to the
bit field. In the above example, the second parameter of the first IO macro can be written like this:
As a cautionary note, IOP1 and IOP2 are macros that get transformed during compilation to
integer values used to index into the ResourceTemplate. These labels do not get inserted in the ACPI
namespace. However, RES2 gets inserted into the namespace, and the contents of IOP1 and IOP2
will be available for use by the driver.
In reality, IOP1, IOP2, and _MIN are macros that translate to integers that indicate an offset
into RES2.
5.6.1 Exercises
1. Write a method to change _MAX of IOP2 to 0xABCD.
In the table above, M001 passes INT1 to CHNG and arg0 is incremented. However, the value
of INT1 will remain as 0x00. Arguments can be overwritten if they are references. In order to
access package or buffer elements, the index operator [] is needed. After evaluating M001, the
value of INT1 will remain 0x01. However, PKG1 will contain 0x13, and BUF1 will contain 0x20.
Method (CHNG, 1)
{
arg0[0] = 0x12 // overwrite
}
Method (M001)
{
CHNG (PKG1)
CHNG (BUF1)
}
}
5.7.1 Exercises
1. Write a method to increment all elements of a buffer or a package containing integers by 5.
The buffer or package will be passed as arg0.
On a Windows* system, the project binaries can be downloaded from the ACPICA website here:
[Link]