ACPI (Advanced Configuration and Power Interface) is a Power Management and configuration standard for the PC, it allows operating systems to control many different hardware features, like the amount of power on each device, thermal zones, fan control, IRQs, battery levels, etc.
We need to access the ACPI Tables in order to read the IO-APIC information, used to receive hardware interrupts (it will be explained later).
Most of the information is organized and accessible through different data structures, but since the ACPI spec is quite big, and covers so many different components, we focus only on what we need to get the information about the APIC.
Before proceeding, let’s keep in mind that all address described below are physical, so if we we have enabled paging, we need to ensure they are properly mapped in the virtual memory space.
The RSDP (Root System Description Pointer) used in the ACPI programming interface is the pointer to the RSDT (Root System Descriptor Table), the full structure depends if the version of ACPI used is 1 or 2, the newer version is just extending the previous one.
The newer version is backward compatible with the older.
Accessing the RSDP register depends on the boot system used, if we are using grub, we get a copy of the RSDT/XSDT in one of the multiboot2 header tags. The specs contains two possible tags for the RSDP value, which one is used depend on the version:
MULTIBOOT_TAG_TYPE_ACPI_OLD
is used (type 14)MULTIBOOT_TAG_TYPE_ACPI_NEW
is used (type 15)Both headers are identical, with the only difference being in the type value, they are composed of just two fields:
And is followed by the RSDP itself.
As already mentioned there are two different version of RSDP, basic data structure for RSDP v1 is:
struct RSDPDescriptor {
char Signature[8];
uint8_t Checksum;
char OEMID[6];
uint8_t Revision;
uint32_t RsdtAddress;
} __attribute__ ((packed));
Where the fields are:
The structure for the v2 header is an extension of the previous one, so the fields above are still valid, but in addition it has also the following extra-fields:
struct RSDP2Descriptor {
//v1 fields
uint32_t Length;
uint64_t XSDTAddress;
uint8_t ExtendedChecksum;
uint8_t Reserved[3];
};
Before proceeding, let’s explain a little bit better the validation. For both version what we need to check is that the sum of all bytes composing the descriptor structure have last byte equals to 0. How is possible to achieve that, and keep the same function for both? That is pretty easy, we just need cast the RSDP*Descriptor
to a char pointer, and pass the size of the correct struct. Once we have done that is just matter of cycling a byte array. Here the example code:
bool validate_RSDP(char *byte_array, size_t size) {
uint32_t sum = 0;
for(int i = 0; i < size; i++) {
sum += byte_array[i];
}
return (sum & 0xFF) == 0;
}
Having last byte means that result mod 0x100
is 0. Now there are two ways to test it:
mod
instruction, and check the result, if is 0 the structure is valid, otherwise it should be ignored.uint_8
if the content after casting is 0 the struct is valid, or use bitwise AND with 0xFF
value (0xFF
is equivalent to the 0b11111111
byte) sum & 0xFF
, if it is 0 the struct is valid otherwise it has to be ignored.The function above works perfectly with both versions of descriptors. In the XSDT, since it has more fields, the previous checksum field won’t offset them properly (because it doesn’t know about them), so this is why an extended checksum field is added.
RSDT (Root System Description Table) is a data structure used in the ACPI programming interface. This table contains pointers to many different table descriptor (SDTs). Explaining all the tables is beyond of the scope of these notes, and for our purpose we are going to need only one of those table (the APIC table that we will encounter later).
Since every SDT table contains different type of information, they are all different from each other, we can define an RSDT table by the composition of two parts:
struct ACPISDTHeader {
char Signature[4];
uint32_t Length;
uint8_t Revision;
uint8_t Checksum;
char OEMID[6];
char OEMTableID[8];
uint32_t OEMRevision;
uint32_t CreatorID;
uint32_t CreatorRevision;
};
It’s important to note that hte Length
field contains the size of the table, header included.
These 2 tables have the same purpose and are mutually exclusive. If the latter exists, the former is to be ignored, otherwise use the former.
The RSDT is an SDT header followed by an array of uint32_t
s, representing the address of another SDT header.
The XSDT is the same, except the array is of uint64_t
s.
struct RSDP {
ACPISDTHeader sdtHeader; //signature "RSDP"
uint32_t sdtAddresses[];
};
struct XSDT {
ACPISDTHeader sdtHeader; //signature "XSDT"
uint64_t sdtAddresses[];
};
This means that if we want to get the n-th SDT table we just need to acces the corresponding item in the *SDT array:
//to get the sdt header at n index
ACPISDTHeader* header = (ACPISDTHeader*)(use_xsdt ? xsdt->sdtAddresses[n] : (uint64_t)rsdt->sdtAddresses[n]);
Signature
the signature in any of the ACPI tables are not null-terminated. This means that if we try to print it, you will most likely end up in printing garbage in the best case scenario.uint32_t
addresses while the XSDT data is an array of uint64_t
addresses. The number of items in the RSDT and XSDT can be computed in the following way://for the RSDT
size_t number_of_items = (rsdt->sdtHeader.Length - sizeof(ACPISDTheader)) / 4;
//for the XSDT
size_t number_of_items = (xsdt->sdtHeader.Length - sizeof(ACPISDTHeader)) / 8;