LAI, or Lightweight AML Interpreter, is a lightweight alternative to the bulky ACPICA. It is much, much smaller, and requires less functions in the OS interface layer be implemented. As well, not all of the functions need to be implemented (although it is optimal to do so) because LAI simply panics when a function is needed and undefined, rather than requiring them at link time. This means that you can use the simpler functions of LAI if you aren't ready to implement an entire OS interface layer just to power off the machine.
As an aside, LAI is permissively MIT-licensed, quite like ACPICA, which allows you to choose between GPLv2, a BSD License, and a non-OSI "Intel" License.
Official documentation can be found on GitHub.
LAI's core is just an AML interpreter which currently works on most hardware with sane AML. However, it includes drivers and helpers for:
- Embedded Controller
- Legacy PCI IRQ pin routing
- Power management (Setting sleep states S0-S5, reset)
- Discovering the RSDP in BIOS systems
- Enabling ACPI mode
LAI is under semi-active development, and the maintainers are relatively quick to reply to contributions.
In order to call into the kernel LAI has a few standardized "host" functions that the kernel needs to implement, most of these are weakly linked, and are optional. However that does not mean that only the few required host functions are needed, in practice, all of the Required and Integral functions are needed for smooth operation. You can find LAI's full host documentation here
These functions are 100% required for the proper functioning of the interpreter, it will not link without these functions
/* Logs a message. level can either be LAI_DEBUG_LOG for debugging info, or LAI_WARN_LOG for warnings */ void laihost_log(int level, const char *msg);
/* Reports a fatal error, and halts. */ __attribute__((noreturn)) void laihost_panic(const char *msg);
void *laihost_realloc(void *, size_t);
void *laihost_free(void *);
These functions, while not required to link with LAI, are practically integral to its operation
/* Maps count bytes from the given physical address and returns a virtual address that can be used to access the memory. */ void *laihost_map(size_t address, size_t count);
/* Unmaps count bytes from the given virtual address. LAI only calls this on memory that was previously mapped by laihost_map(). */ void laihost_unmap(void *pointer, size_t count);
/* Returns the (virtual) address of the n-th table that has the given signature, or NULL when no such table was found. */ void *laihost_scan(char *sig, size_t index);
/* Write a byte/word/dword to the given I/O port. */ void laihost_outb(uint16_t port, uint8_t val); void laihost_outw(uint16_t port, uint16_t val); void laihost_outd(uint16_t port, uint32_t val);
/* Read a byte/word/dword from the given I/O port. */ uint8_t laihost_inb(uint16_t port); uint16_t laihost_inw(uint16_t port); uint32_t laihost_ind(uint16_t port);
/* Read a byte/word/dword from the given device's PCI configuration space at the given offset. */ uint8_t laihost_pci_readb(uint16_t seg, uint8_t bus, uint8_t slot, uint8_t fun, uint16_t offset); uint16_t laihost_pci_readw(uint16_t seg, uint8_t bus, uint8_t slot, uint8_t fun, uint16_t offset); uint32_t laihost_pci_readd(uint16_t seg, uint8_t bus, uint8_t slot, uint8_t fun, uint16_t offset);
/* Write a byte/word/dword to the given device's PCI configuration space at the given offset. */ void laihost_pci_writeb(uint16_t seg, uint8_t bus, uint8_t slot, uint8_t fun, uint16_t offset, uint8_t val); void laihost_pci_writew(uint16_t seg, uint8_t bus, uint8_t slot, uint8_t fun, uint16_t offset, uint16_t val); void laihost_pci_writed(uint16_t seg, uint8_t bus, uint8_t slot, uint8_t fun, uint16_t offset, uint32_t val);
/* Sleeps for the given amount of milliseconds. Can be stubbed on emulators */ void laihost_sleep(uint64_t ms);
These functions are optional and LAI will not use them unless you try to use certain features. For example the sync functions will only be called when a lock is contended, this should not happen if you do not call LAI from 2 threads at once. (However it has been observed on 1 machine with buggy AML)
/* Blocks the current thread. The host ensures that calls to laihost_sync_wait() and laihost_sync_wake() on the same lai_sync_state are totally ordered. Before blocking, sync->val is compared against val. If these two values are non-equal, the function returns immediately. Returns a non-zero value if the timeout was exhausted and zero otherwise. */ int laihost_sync_wait(struct lai_sync_state *sync, unsigned int val, int64_t timeout);
/* Unblocks a blocked thread. The host ensures that calls to laihost_sync_wait() and laihost_sync_wake() on the same lai_sync_state are totally ordered. */ void laihost_sync_wake(struct lai_sync_state *sync);
/* Reports information about a variable. This function does not need to be implemented, lai has a default implementation that will print all the info needed for debugging */ void laihost_handle_amldebug(lai_variable_t *var);
Before you start using LAI's functions, you need to tell it the ACPI's revision and to let it enumerate the AML namespace.
LAI's full API documentation can be found on GitHub, however here are some simple examples for using some helpers
This is how you enter full ACPI mode in LAI, do not forget to install an SCI handler and handle ACPI events.
// From <lai/helpers/sci.h> lai_enable_acpi(0); // if you use legacy 8259 PIC lai_enable_acpi(1); // if you use the IOAPIC
This is how you shutdown in LAI:
lai_enter_sleep(5); // From <lai/helpers/pm.h>, requires you have enumerated the namespace and are in ACPI mode
This is how you reboot in LAI:
lai_acpi_reset(); // From <lai/helpers/pm.h>