bzt 4cd40ef726 Added partition UUIDs | 2 weeks ago | |
---|---|---|
.. | ||
00_device.md | 2 weeks ago | |
01_cpucore.md | 11 months ago | |
02_dma.md | 11 months ago | |
03_irq.md | 11 months ago | |
04_intc.md | 11 months ago | |
05_pins.md | 11 months ago | |
06_leds.md | 11 months ago | |
07_clocks.md | 11 months ago | |
08_sensors.md | 11 months ago | |
09_buttons.md | 11 months ago | |
0A_amper.md | 11 months ago | |
0B_volt.md | 11 months ago | |
0C_thermal.md | 11 months ago | |
0D_freq.md | 10 months ago | |
0E_l0cache.md | 11 months ago | |
0F_l1cache.md | 11 months ago | |
10_l2cache.md | 11 months ago | |
11_l3cache.md | 11 months ago | |
D5_boot.md | 2 weeks ago | |
D6_root.md | 2 weeks ago | |
D7_edid.md | 10 months ago | |
D8_fbptr.md | 2 weeks ago | |
D9_fbdim.md | 2 weeks ago | |
DA_module.md | 10 months ago | |
DB_cmdline.md | 10 months ago | |
DC_default.md | 11 months ago | |
DD_nvsmem.md | 11 months ago | |
DE_resvmem.md | 11 months ago | |
DF_ram.md | 11 months ago | |
E0_mmio.md | 11 months ago | |
E1_ioport.md | 11 months ago | |
E2_pci.md | 11 months ago | |
E3_ec.md | 11 months ago | |
E4_smb.md | 11 months ago | |
E5_nvram.md | 11 months ago | |
E6_pcibar.md | 11 months ago | |
E7_ipmi.md | 11 months ago | |
E8_gpio.md | 11 months ago | |
E9_gsb.md | 11 months ago | |
EA_pcc.md | 11 months ago | |
README.md | 10 months ago | |
flags.md | 11 months ago |
The goal of this file format is to provide a platofrm independent, standardized way of describing devices in a computer, which is more universal and a lot easier to use than the current overcomplicated solutions.
The GUDT blob comes in two flavours, depending on the use case. The first one is small and compact, the second one is easy to
manipulate. If you define GUDT_IMPLEMENTATION
in exactly one of your source files before you include gudt.h
then you'll get
a gudt_unpack()
function to convert from the former into the latter. This function does not use libc nor does it allocate
any memory, so it can be used even in freestanding mode on bare metal and it is endianness-agnostic. On big endian machines, you
should also define GUDT_BIGENDIAN
. In this case gudt_unpack()
will automatically convert all numbers in the blob to big endian
for you. The other function, gudt_find()
will look up and return a specific resource node for you.
The file itself consists of three parts: an 8 bytes long header (never compressed), a string table that's padded to be multiple of 8 bytes, and a node list with fixed sized elements (this two might be stored in a compressed payload).
Offset | Size | Description |
---|---|---|
0 | 4 | magic bytes 'G', 'U', 'D', 'T' |
4 | 2 | header size |
6 | 2 | number of nodes |
8 | 2 | magic bytes 0x78, 0xDA |
8 | x | zlib deflate (RFC 1950) compressed payload |
All numbers are little-endian. The uncompressed size is ((header size + 7) & ~7) + number of nodes * 16
and the payload contains
a string table followed by node records aligned on a 8 bytes boundary. This packed format is used on disk, in ROM and when the GUDT
blob is transmitted over the network.
Offset | Size | Description |
---|---|---|
0 | 4 | magic bytes 'G', 'U', 'D', 'T' |
4 | 2 | header size (H = header size rounded up to 8 bytes) |
6 | 2 | number of nodes (N) |
8 | H-8 | string table (guaranteed to not start with 0x78, 0xDA) |
H | N*16 | nodes |
This unpacked format is used in RAM and it might contain big endian numbers depending on the native endianness. On big endian machines this is indicated by the last byte of the magic being 'B' (instead of 'T'). This is only allowed in memory, on disk the magic must always end in 'T' and numbers must always be in little endian, even when the blob isn't compressed.
String table is a list of unique, zero terminated UTF-8 strings, header size - 8 bytes in total. The maximum size of the table is 65527 bytes. If the first two characters of the first string would happen to be 0x78 and 0xDA then the string table starts with a zero byte. The string table is padded with zero bytes so that the node list that follows always starts on a 8 byte aligned boundary (padding is not included in the header size field).
To help with the JSON format, strings are not allowed to have "
double quotes, those will be converted to '
single qoutes.
Unlike AML and FDT (both use a nested node list), here the nodes list is stored in a flattened one dimensional array of 16 bytes long elements with a parent pointer in them, so this format is extremely easy to work with. Parent nodes must always preceed their children nodes, and the very first node is the root of the tree, a "machine device" which describes the brand and model of the machine in general.
Depending on type
being zero or not a node can be either a device node or a resource node.
The fields have different meaning depending on type
, see the files above for a full list. If you feel that a certain
resource property is missing, please open an issue and let me know! The 0 zero type is
for device nodes, the last 32 types (from 0xe0 to 0xff) are reserved and must match the ACPI region space types. All the other
types are free to be allocated by this specification.
For resource nodes, the flags field tells if the node points to the resource, or has inlined items. If a resource needs
more than 12 inlined bytes or 6 words, 3 dwords or 1 qword, then simply more nodes are added to the list with the same type
and
parent
. When the lower tetrad of flags
is 0x0f, then size
field points to another resource node (indirect reference).
The human readable textual source is a simple RFC 8259 compliant JSON string in UTF-8 encoding. It must start with the following magic line, followed by a single array of non-nested objects:
/* Grand Unified Device Tree */
[
{
"type": (string) node type,
"parent": (string) parent device's name,
...
},
...
]
Within each object, only the type
and parent
fields are mandatory. Some 3rd party tools don't like comments in JSON files,
for those you might need to first remove the C style /* */
comments with the \/\*(.*?)\*\/
regexp, and then when you're done
with editing, add the first magic line back.
The category code as well as the first 256 device type codes in each category must match with PCI-SIG base class and sub-class codes in the pci.ids database, but type codes above can be freely assigned by GUDT. (With one notable exception, base class 0x40 only exists in pci.ids, but it's not in pcisig, so Universal Device Type doesn't have that either.)
If devids.txt can be found, then the tool uses it to read in EisaIDs and match those with
the pci.ids' data (based on best effort, you should always double check its results manually, use -vv
).
The gudt.ids
file is stored next to the pci.ids file, usually under /usr/share/hwdata/gudt.ids
. An up-to-date version of this
database can be freely downloaded from this repository: gudt.ids. It is a
simple plain text file with space separated coloumn list, where both NL and CRLF line endings are accepted.
Lines starting with #
hashmark are comments till the end of the line.
V (vendorid) (name)
Lines starting with an upper-case V
add a new vendor to the pci.ids list (currently not used). The name part might contain
spaces, but all double quotes "
in it will be replaced to single quotes '
. This is true to all name coloumns in this file.
M (vendorid) (deviceid) (name)
Lines starting with an upper-case M
add a new model, a vendorid + deviceid pair with a name to the list (currently not used).
C (class) (subclass) (name)
Lines starting with an upper-case C
add a new category (or class in PCI parlance) to the list. Currently only used to add some
missing CPU architectures.
(class) (sub-class) (vendorid) (deviceid) (driver/name)
All the other lines starting with a hexadecimal number have 5 coloumns. These assign a category, device, vendor and model id to
a particular driver string or device name string. When a device tree (in any format) is imported by the gudt
tool, this name
string is matched and if found, the corresponding coloumns in this line will be used for the node.
You'll need the following GUDT nodes:
PNP0C20
, type GUDT_T_IOPORT
is the SMI_CMD
port.PNP0C23
, type GUDT_T_IOPORT
is the PM1a_CNT
port.PNP0C24
, type GUDT_T_IOPORT
is the PM1b_CNT
port.PNP0C20
, type GUDT_T_DEFAULT
is inlined data, data[0]
is the ACPI enable value, data[1]
is the ACPI disable value.PNP0C23
, type GUDT_T_DEFAULT
is inlined data, stores S3, S4, S5 system state values (one node for each state).To enable ACPI, write out its first default value, data[0]
to the SMI_CMD
port.
For example to shutdown the computer, you'd need the S5
system state. So search the default values in PNP0C23
and look for the
one that starts with 5
(has 0x35 in its data[0]
). Write out data[1]
with bit 13 set to the PM1a_CNT
port, and if port b
exists, then data[2]
likewise with bit 13 set to the PM1b_CNT
port. That's all.
Here's a useful C code snippet to do all of this (error handling omited for clearity):
/* node list starts at header size rounded up to 8 bytes */
gudt_node_t *nodes = (gudt_node_t*)((uint8_t*)gudt + ((gudt->hdrsize + 7) & ~7));
gudt_device_t *parent;
/* iterate on GUDT nodes */
for(i = 0; i < gudt->numnodes; i++) {
/* we are looking for resource nodes, so their parents will be device nodes with the driver */
parent = (gudt_device_t*)&nodes[nodes[i].parent];
device_driver = (char*)gudt + parent->driver;
/* depending on the resource's type, we look for... */
switch(nodes[i].type) {
case GUDT_T_DEFAULT:
if(!strcmp(device_driver, "PNP0C20")) {
acpi_enable = nodes[i].r.b.data[0];
acpi_disable = nodes[i].r.b.data[1];
} else
if(!strcmp(device_driver, "PNP0C23") && nodes[i].r.w.data[0] == '5') {
slp_typa = nodes[i].r.w.data[1];
slp_typb = nodes[i].r.w.data[2];
}
break;
case GUDT_T_IOPORT:
if(!strcmp(device_driver, "PNP0C20")) smi_cmd = nodes[i].r.p.base; else
if(!strcmp(device_driver, "PNP0C23")) pm1a_cnt = nodes[i].r.p.base; else
if(!strcmp(device_driver, "PNP0C24")) pm1b_cnt = nodes[i].r.p.base; else
if(!strcmp(device_driver, "PNP0C26")) pm_tmr = nodes[i].r.p.base;
break;
/* these might be useful to have too */
case GUDT_T_MMIO:
if(!strcmp(device_driver, "PNP0C08")) ioapic_ptr = nodes[i].r.p.base; else
if(!strcmp(device_driver, "PNP0103")) hpet_ptr = nodes[i].r.p.base; else
if(!strcmp(device_driver, "PNP0003")) lapic_ptr = nodes[i].r.p.base;
break;
}
}
/* enable ACPI power management */
outb(smi_cmd, acpi_enable);
/* shutdown, power off machine */
outw(pm1a_cnt, slp_typa | (1 << 13));
if(pm1b_cnt) outw(pm1b_cnt, slp_typb | (1 << 13));
/* never reached */
To achieve the other system states (like sleep, hibernate etc.), consult the ACPI specification. All those cryptic PNP IDs (used to identify the devices) are as well defined by ACPI, do not hate GUDT for those!