acpi: remerge uacpi as inhouse

Signed-off-by: kaguya <vpshinomiya@protonmail.com>
This commit is contained in:
kaguya
2026-04-15 13:42:16 -04:00
parent d66e042ec1
commit 4f0480fa84
155 changed files with 43506 additions and 0 deletions
+705
View File
@@ -0,0 +1,705 @@
#include <uacpi/types.h>
#include <uacpi/platform/arch_helpers.h>
#include "mm/memory.h"
#include "arch/x86_64/io.h"
#include "arch/x86_64/irq.h"
#include "arch/x86_64/pit.h"
#include "time/time.h"
#include <stdatomic.h>
#include <stdarg.h>
#include "mm/vmm.h"
extern uint64_t g_rsdp_phys;
extern volatile uint64_t tsc_cycles_per_us;
// Returns the PHYSICAL address of the RSDP structure via *out_rsdp_address.
uacpi_status uacpi_kernel_get_rsdp(uacpi_phys_addr *out_rsdp_address)
{
if (g_rsdp_phys == INVALID_PHYS)
return UACPI_STATUS_NOT_FOUND;
*out_rsdp_address = g_rsdp_phys;
return UACPI_STATUS_OK;
}
/**
* Map a physical memory range starting at 'addr' with length 'len', and return
* a virtual address that can be used to access it.
*
* NOTE: 'addr' may be misaligned, in this case the host is expected to round it
* down to the nearest page-aligned boundary and map that, while making
* sure that at least 'len' bytes are still mapped starting at 'addr'. The
* return value preserves the misaligned offset.
*
* Example for uacpi_kernel_map(0x1ABC, 0xF00):
* 1. Round down the 'addr' we got to the nearest page boundary.
* Considering a PAGE_SIZE of 4096 (or 0x1000), 0x1ABC rounded down
* is 0x1000, offset within the page is 0x1ABC - 0x1000 => 0xABC
* 2. Requested 'len' is 0xF00 bytes, but we just rounded the address
* down by 0xABC bytes, so add those on top. 0xF00 + 0xABC => 0x19BC
* 3. Round up the final 'len' to the nearest PAGE_SIZE boundary, in
* this case 0x19BC is 0x2000 bytes (2 pages if PAGE_SIZE is 4096)
* 4. Call the VMM to map the aligned address 0x1000 (from step 1)
* with length 0x2000 (from step 3). Let's assume the returned
* virtual address for the mapping is 0xF000.
* 5. Add the original offset within page 0xABC (from step 1) to the
* resulting virtual address 0xF000 + 0xABC => 0xFABC. Return it
* to uACPI.
*/
void *uacpi_kernel_map(uacpi_phys_addr addr, uacpi_size len)
{
(void)len;
return (void *)((uintptr_t)addr + MEM_PHYS_OFFSET);
}
/**
* Unmap a virtual memory range at 'addr' with a length of 'len' bytes.
*
* NOTE: 'addr' may be misaligned, see the comment above 'uacpi_kernel_map'.
* Similar steps to uacpi_kernel_map can be taken to retrieve the
* virtual address originally returned by the VMM for this mapping
* as well as its true length.
*/
void uacpi_kernel_unmap(void *addr, uacpi_size len)
{
(void)addr;
(void)len;
/* HHDM mappings are permanent — nothing to unmap */
}
static const char *uacpi_log_prefix(uacpi_log_level level)
{
switch (level) {
case UACPI_LOG_DEBUG: return "[uACPI DBG ] ";
case UACPI_LOG_TRACE: return "[uACPI TRC ] ";
case UACPI_LOG_INFO: return "[uACPI INFO] ";
case UACPI_LOG_WARN: return "[uACPI WARN] ";
case UACPI_LOG_ERROR: return "[uACPI ERR ] ";
default: return "[uACPI ] ";
}
}
#ifndef UACPI_FORMATTED_LOGGING
void uacpi_kernel_log(uacpi_log_level level, const uacpi_char *msg)
{
printf("%s%s", uacpi_log_prefix(level), msg);
}
#else
UACPI_PRINTF_DECL(2, 3)
void uacpi_kernel_log(uacpi_log_level level, const uacpi_char *fmt, ...)
{
va_list va;
va_start(va, fmt);
uacpi_kernel_vlog(level, fmt, va);
va_end(va);
}
void uacpi_kernel_vlog(uacpi_log_level level, const uacpi_char *fmt,
uacpi_va_list va)
{
printf("%s", uacpi_log_prefix(level));
vfprintf(fmt, va);
}
#endif
/**
* Only the above ^^^ API may be used by early table access and
* UACPI_BAREBONES_MODE.
*/
#ifndef UACPI_BAREBONES_MODE
/**
* Convenience initialization/deinitialization hooks that will be called by
* uACPI automatically when appropriate if compiled-in.
*/
#ifdef UACPI_KERNEL_INITIALIZATION
/**
* This API is invoked for each initialization level so that appropriate parts
* of the host kernel and/or glue code can be initialized at different stages.
*
* uACPI API that triggers calls to uacpi_kernel_initialize and the respective
* 'current_init_lvl' passed to the hook at that stage:
* 1. uacpi_initialize() -> UACPI_INIT_LEVEL_EARLY
* 2. uacpi_namespace_load() -> UACPI_INIT_LEVEL_SUBSYSTEM_INITIALIZED
* 3. (start of) uacpi_namespace_initialize() -> UACPI_INIT_LEVEL_NAMESPACE_LOADED
* 4. (end of) uacpi_namespace_initialize() -> UACPI_INIT_LEVEL_NAMESPACE_INITIALIZED
*/
uacpi_status uacpi_kernel_initialize(uacpi_init_level current_init_lvl);
void uacpi_kernel_deinitialize(void);
#endif
/**
* Open a PCI device at 'address' for reading & writing.
*
* The device at 'address' might not actually exist on the system, in this case
* the api is allowed to return UACPI_STATUS_NOT_FOUND to indicate that, this
* error is handled gracefully by creating a dummy device internally that always
* returns 0xFF on reads and is no-op for writes. This is to support a common
* pattern in AML that probes for 0xFF reads to detect whether a device exists.
*
* The handle returned via 'out_handle' is used to perform IO on the
* configuration space of the device.
*/
#define PCI_CFG_ADDR_PORT 0x0CF8u
#define PCI_CFG_DATA_PORT 0x0CFCu
typedef struct { uint8_t bus, dev, func; } pci_dev_impl_t;
static uint32_t pci_make_addr(pci_dev_impl_t *d, uacpi_size offset)
{
return (1u << 31)
| ((uint32_t)d->bus << 16)
| ((uint32_t)d->dev << 11)
| ((uint32_t)d->func << 8)
| (uint32_t)(offset & 0xFCu);
}
static uint32_t pci_read32(pci_dev_impl_t *d, uacpi_size offset)
{
x86_64_outl(PCI_CFG_ADDR_PORT, pci_make_addr(d, offset));
return x86_64_inl(PCI_CFG_DATA_PORT);
}
static void pci_write32_masked(pci_dev_impl_t *d, uacpi_size offset,
uint32_t val, uint32_t mask)
{
x86_64_outl(PCI_CFG_ADDR_PORT, pci_make_addr(d, offset));
uint32_t old = x86_64_inl(PCI_CFG_DATA_PORT);
x86_64_outl(PCI_CFG_DATA_PORT, (old & ~mask) | (val & mask));
}
uacpi_status uacpi_kernel_pci_device_open(uacpi_pci_address addr,
uacpi_handle *out_handle)
{
pci_dev_impl_t *d = kmalloc(sizeof(*d));
if (!d) return UACPI_STATUS_OUT_OF_MEMORY;
d->bus = addr.bus;
d->dev = addr.device;
d->func = addr.function;
*out_handle = (uacpi_handle)d;
return UACPI_STATUS_OK;
}
void uacpi_kernel_pci_device_close(uacpi_handle handle) { kfree(handle); }
uacpi_status uacpi_kernel_pci_read8(uacpi_handle h, uacpi_size off,
uacpi_u8 *v)
{
uint8_t shift = (off & 3u) * 8u;
*v = (pci_read32(h, off) >> shift) & 0xFFu;
return UACPI_STATUS_OK;
}
uacpi_status uacpi_kernel_pci_read16(uacpi_handle h, uacpi_size off,
uacpi_u16 *v)
{
uint8_t shift = (off & 3u) * 8u;
*v = (pci_read32(h, off) >> shift) & 0xFFFFu;
return UACPI_STATUS_OK;
}
uacpi_status uacpi_kernel_pci_read32(uacpi_handle h, uacpi_size off,
uacpi_u32 *v)
{
*v = pci_read32(h, off);
return UACPI_STATUS_OK;
}
uacpi_status uacpi_kernel_pci_write8(uacpi_handle h, uacpi_size off,
uacpi_u8 v)
{
uint8_t shift = (off & 3u) * 8u;
pci_write32_masked(h, off, (uint32_t)v << shift, 0xFFu << shift);
return UACPI_STATUS_OK;
}
uacpi_status uacpi_kernel_pci_write16(uacpi_handle h, uacpi_size off,
uacpi_u16 v)
{
uint8_t shift = (off & 3u) * 8u;
pci_write32_masked(h, off, (uint32_t)v << shift, 0xFFFFu << shift);
return UACPI_STATUS_OK;
}
uacpi_status uacpi_kernel_pci_write32(uacpi_handle h, uacpi_size off,
uacpi_u32 v)
{
x86_64_outl(PCI_CFG_ADDR_PORT, pci_make_addr(h, off));
x86_64_outl(PCI_CFG_DATA_PORT, v);
return UACPI_STATUS_OK;
}
/**
* Map a SystemIO address at [base, base + len) and return a kernel-implemented
* handle that can be used for reading and writing the IO range.
*
* NOTE: The x86 architecture uses the in/out family of instructions
* to access the SystemIO address space.
*/
uacpi_status uacpi_kernel_io_map(uacpi_io_addr base, uacpi_size len,
uacpi_handle *out_handle)
{
(void)len;
*out_handle = (uacpi_handle)(uintptr_t)base;
return UACPI_STATUS_OK;
}
void uacpi_kernel_io_unmap(uacpi_handle handle) { (void)handle; }
/**
* Read/Write the IO range mapped via uacpi_kernel_io_map
* at a 0-based 'offset' within the range.
*
* NOTE:
* The x86 architecture uses the in/out family of instructions
* to access the SystemIO address space.
*
* You are NOT allowed to break e.g. a 4-byte access into four 1-byte accesses.
* Hardware ALWAYS expects accesses to be of the exact width.
*/
static inline uint16_t io_port_of(uacpi_handle h, uacpi_size offset)
{
return (uint16_t)((uintptr_t)h + offset);
}
uacpi_status uacpi_kernel_io_read8(uacpi_handle h, uacpi_size off,
uacpi_u8 *v)
{
*v = x86_64_inb(io_port_of(h, off));
return UACPI_STATUS_OK;
}
uacpi_status uacpi_kernel_io_read16(uacpi_handle h, uacpi_size off,
uacpi_u16 *v)
{
*v = x86_64_inw(io_port_of(h, off));
return UACPI_STATUS_OK;
}
uacpi_status uacpi_kernel_io_read32(uacpi_handle h, uacpi_size off,
uacpi_u32 *v)
{
*v = x86_64_inl(io_port_of(h, off));
return UACPI_STATUS_OK;
}
uacpi_status uacpi_kernel_io_write8(uacpi_handle h, uacpi_size off,
uacpi_u8 v)
{
x86_64_outb(io_port_of(h, off), v);
return UACPI_STATUS_OK;
}
uacpi_status uacpi_kernel_io_write16(uacpi_handle h, uacpi_size off,
uacpi_u16 v)
{
x86_64_outw(io_port_of(h, off), v);
return UACPI_STATUS_OK;
}
uacpi_status uacpi_kernel_io_write32(uacpi_handle h, uacpi_size off,
uacpi_u32 v)
{
x86_64_outl(io_port_of(h, off), v);
return UACPI_STATUS_OK;
}
/**
* Allocate a block of memory of 'size' bytes.
* The contents of the allocated memory are unspecified.
*/
void *uacpi_kernel_alloc(uacpi_size size)
{
return kmalloc(size);
}
#ifdef UACPI_NATIVE_ALLOC_ZEROED
/**
* Allocate a block of memory of 'size' bytes.
* The returned memory block is expected to be zero-filled.
*/
void *uacpi_kernel_alloc_zeroed(uacpi_size size)
{
void *p = kmalloc(size);
if (p) memset(p, 0, size);
return p;
}
#endif
/**
* Free a previously allocated memory block.
*
* 'mem' might be a NULL pointer. In this case, the call is assumed to be a
* no-op.
*
* An optionally enabled 'size_hint' parameter contains the size of the original
* allocation. Note that in some scenarios this incurs additional cost to
* calculate the object size.
*/
#ifndef UACPI_SIZED_FREES
void uacpi_kernel_free(void *mem)
{
kfree(mem);
}
#else
void uacpi_kernel_free(void *mem, uacpi_size size_hint)
{
(void)size_hint;
kfree(mem);
}
#endif
/**
* Returns the number of nanosecond ticks elapsed since boot,
* strictly monotonic.
*/
uacpi_u64 uacpi_kernel_get_nanoseconds_since_boot(void)
{
return rdtsc() / (tsc_cycles_per_us / 1000); // rough ns
}
/**
* Spin for N microseconds.
*/
void uacpi_kernel_stall(uacpi_u8 usec)
{
uint64_t start = rdtsc();
uint64_t target = start + ((uint64_t)usec * tsc_cycles_per_us);
while (rdtsc() < target)
__asm__ volatile("pause");
}
/**
* Sleep for N milliseconds.
*/
void uacpi_kernel_sleep(uacpi_u64 msec)
{
uint64_t start = g_Ticks;
uint64_t target = start + msec;
while (g_Ticks < target)
{
__asm__ volatile("hlt");
}
}
typedef struct { atomic_flag flag; } uacpi_mutex_impl_t;
/**
* Create/free an opaque non-recursive kernel mutex object.
*/
uacpi_handle uacpi_kernel_create_mutex(void)
{
uacpi_mutex_impl_t *m = kmalloc(sizeof(*m));
if (!m) return UACPI_NULL;
atomic_flag_clear(&m->flag);
return (uacpi_handle)m;
}
void uacpi_kernel_free_mutex(uacpi_handle handle)
{
kfree(handle);
}
/**
* Create/free an opaque kernel (semaphore-like) event object.
*/
typedef struct { atomic_uint counter; } uacpi_event_impl_t;
uacpi_handle uacpi_kernel_create_event(void)
{
uacpi_event_impl_t *e = kmalloc(sizeof(*e));
if (!e) return UACPI_NULL;
atomic_store_explicit(&e->counter, 0u, memory_order_relaxed);
return (uacpi_handle)e;
}
void uacpi_kernel_free_event(uacpi_handle handle)
{
kfree(handle);
}
/**
* Disable interrupts and return an kernel-defined value representing the
* "before" state. This value is used in the subsequent call to restore the
* prior state.
*
* Note that this is talking about ALL interrupts on the current CPU, not just
* those installed by uACPI. This is typically achieved by executing the 'cli'
* instruction on x86, 'msr daifset, #3' on aarch64 etc.
*/
uacpi_interrupt_state uacpi_kernel_disable_interrupts(void)
{
uint64_t flags;
asm volatile("pushfq; pop %0; cli" : "=r"(flags) :: "memory");
return (uacpi_interrupt_state)flags;
}
void uacpi_kernel_restore_interrupts(uacpi_interrupt_state state)
{
asm volatile("push %0; popfq" :: "r"((uint64_t)state) : "memory", "cc");
}
/**
* Restore the state of the interrupt flags to the kernel-defined value provided
* in 'state'.
*/
uacpi_thread_id uacpi_kernel_get_thread_id(void)
{
/* Always "thread 1" — update when you add SMP */
return (uacpi_thread_id)1;
}
/**
* Try to acquire the mutex with a millisecond timeout.
*
* The timeout value has the following meanings:
* 0x0000 - Attempt to acquire the mutex once, in a non-blocking manner
* 0x0001...0xFFFE - Attempt to acquire the mutex for at least 'timeout'
* milliseconds
* 0xFFFF - Infinite wait, block until the mutex is acquired
*
* The following are possible return values:
* 1. UACPI_STATUS_OK - successful acquire operation
* 2. UACPI_STATUS_TIMEOUT - timeout reached while attempting to acquire (or the
* single attempt to acquire was not successful for
* calls with timeout=0)
* 3. Any other value - signifies a host internal error and is treated as such
*/
uacpi_status uacpi_kernel_acquire_mutex(uacpi_handle handle, uacpi_u16 timeout)
{
uacpi_mutex_impl_t *m = handle;
if (timeout == 0) {
if (atomic_flag_test_and_set_explicit(&m->flag, memory_order_acquire))
return UACPI_STATUS_TIMEOUT;
return UACPI_STATUS_OK;
}
while (atomic_flag_test_and_set_explicit(&m->flag, memory_order_acquire))
asm volatile("pause" ::: "memory");
return UACPI_STATUS_OK;
}
void uacpi_kernel_release_mutex(uacpi_handle handle)
{
uacpi_mutex_impl_t *m = handle;
atomic_flag_clear_explicit(&m->flag, memory_order_release);
}
/**
* Try to wait for an event (counter > 0) with a millisecond timeout.
* A timeout value of 0xFFFF implies infinite wait.
*
* The internal counter is decremented by 1 if wait was successful.
*
* A successful wait is indicated by returning UACPI_TRUE.
*/
uacpi_bool uacpi_kernel_wait_for_event(uacpi_handle handle, uacpi_u16 timeout)
{
uacpi_event_impl_t *e = handle;
if (timeout == 0xFFFF) {
while (atomic_load_explicit(&e->counter, memory_order_acquire) == 0)
asm volatile("pause" ::: "memory");
} else {
if (atomic_load_explicit(&e->counter, memory_order_acquire) == 0)
return UACPI_FALSE;
}
atomic_fetch_sub_explicit(&e->counter, 1u, memory_order_acq_rel);
return UACPI_TRUE;
}
/**
* Signal the event object by incrementing its internal counter by 1.
*
* This function may be used in interrupt contexts.
*/
void uacpi_kernel_signal_event(uacpi_handle handle)
{
uacpi_event_impl_t *e = handle;
atomic_fetch_add_explicit(&e->counter, 1u, memory_order_release);
}
/**
* Reset the event counter to 0.
*/
void uacpi_kernel_reset_event(uacpi_handle handle)
{
uacpi_event_impl_t *e = handle;
atomic_store_explicit(&e->counter, 0u, memory_order_release);
}
/**
* Handle a firmware request.
*
* Currently either a Breakpoint or Fatal operators.
*/
uacpi_status uacpi_kernel_handle_firmware_request(uacpi_firmware_request *req)
{
switch (req->type) {
case UACPI_FIRMWARE_REQUEST_TYPE_BREAKPOINT:
printf("[uACPI] AML Breakpoint\n");
return UACPI_STATUS_OK;
case UACPI_FIRMWARE_REQUEST_TYPE_FATAL:
printf("[uACPI] AML Fatal! type=0x%x code=0x%x arg=0x%lx\n",
req->fatal.type, req->fatal.code,
(unsigned long)req->fatal.arg);
for (;;) asm volatile("hlt");
default:
return UACPI_STATUS_UNIMPLEMENTED;
}
}
/**
* Install an interrupt handler at 'irq', 'ctx' is passed to the provided
* handler for every invocation.
*
* 'out_irq_handle' is set to a kernel-implemented value that can be used to
* refer to this handler from other API.
*/
#define UACPI_MAX_IRQS 16
typedef struct {
uacpi_interrupt_handler fn;
uacpi_handle ctx;
} uacpi_irq_entry_t;
static uacpi_irq_entry_t uacpi_irq_table[UACPI_MAX_IRQS];
static void uacpi_irq_shim(Registers *regs)
{
int irq = (int)regs->interrupt - 0x20; /* PIC_REMAP_OFFSET */
if ((unsigned)irq < UACPI_MAX_IRQS && uacpi_irq_table[irq].fn)
uacpi_irq_table[irq].fn(uacpi_irq_table[irq].ctx);
}
uacpi_status uacpi_kernel_install_interrupt_handler(
uacpi_u32 irq, uacpi_interrupt_handler handler,
uacpi_handle ctx, uacpi_handle *out_irq_handle)
{
if (irq >= UACPI_MAX_IRQS)
return UACPI_STATUS_INVALID_ARGUMENT;
uacpi_irq_table[irq].fn = handler;
uacpi_irq_table[irq].ctx = ctx;
x86_64_IRQ_RegisterHandler((int)irq, uacpi_irq_shim);
/* Return a non-NULL token the caller can pass to uninstall */
*out_irq_handle = (uacpi_handle)(uintptr_t)(irq + 1);
return UACPI_STATUS_OK;
}
/**
* Uninstall an interrupt handler. 'irq_handle' is the value returned via
* 'out_irq_handle' during installation.
*/
uacpi_status uacpi_kernel_uninstall_interrupt_handler(
uacpi_interrupt_handler handler, uacpi_handle irq_handle)
{
(void)handler;
uacpi_u32 irq = (uacpi_u32)(uintptr_t)irq_handle - 1;
if (irq >= UACPI_MAX_IRQS)
return UACPI_STATUS_INVALID_ARGUMENT;
uacpi_irq_table[irq].fn = NULL;
uacpi_irq_table[irq].ctx = NULL;
x86_64_IRQ_RegisterHandler((int)irq, NULL);
return UACPI_STATUS_OK;
}
typedef struct { atomic_flag flag; } uacpi_spinlock_impl_t;
/**
* Create/free a kernel spinlock object.
*
* Unlike other types of locks, spinlocks may be used in interrupt contexts.
*/
uacpi_handle uacpi_kernel_create_spinlock(void)
{
uacpi_spinlock_impl_t *sl = kmalloc(sizeof(*sl));
if (!sl) return UACPI_NULL;
atomic_flag_clear(&sl->flag);
return (uacpi_handle)sl;
}
void uacpi_kernel_free_spinlock(uacpi_handle handle)
{
kfree(handle);
}
/**
* Lock/unlock helpers for spinlocks.
*
* These are expected to disable interrupts, returning the previous state of cpu
* flags, that can be used to possibly re-enable interrupts if they were enabled
* before.
*
* Note that lock is infalliable.
*/
uacpi_cpu_flags uacpi_kernel_lock_spinlock(uacpi_handle handle)
{
uint64_t flags;
asm volatile("pushfq; pop %0; cli" : "=r"(flags) :: "memory");
uacpi_spinlock_impl_t *sl = handle;
while (atomic_flag_test_and_set_explicit(&sl->flag, memory_order_acquire))
asm volatile("pause" ::: "memory");
return (uacpi_cpu_flags)flags;
}
void uacpi_kernel_unlock_spinlock(uacpi_handle handle, uacpi_cpu_flags saved)
{
uacpi_spinlock_impl_t *sl = handle;
atomic_flag_clear_explicit(&sl->flag, memory_order_release);
asm volatile("push %0; popfq" :: "r"((uint64_t)saved) : "memory", "cc");
}
typedef enum uacpi_work_type {
/**
* Schedule a GPE handler method for execution.
* This should be scheduled to run on CPU0 to avoid potential SMI-related
* firmware bugs.
*/
UACPI_WORK_GPE_EXECUTION,
/**
* Schedule a Notify(device) firmware request for execution.
* This can run on any CPU.
*/
UACPI_WORK_NOTIFICATION,
} uacpi_work_type;
typedef void (*uacpi_work_handler)(uacpi_handle);
/**
* Schedules deferred work for execution.
* Might be invoked from an interrupt context.
*/
uacpi_status uacpi_kernel_schedule_work(uacpi_work_type type,
uacpi_work_handler handler,
uacpi_handle ctx)
{
(void)type;
handler(ctx);
return UACPI_STATUS_OK;
}
/**
* Waits for two types of work to finish:
* 1. All in-flight interrupts installed via uacpi_kernel_install_interrupt_handler
* 2. All work scheduled via uacpi_kernel_schedule_work
*
* Note that the waits must be done in this order specifically.
*/
uacpi_status uacpi_kernel_wait_for_work_completion(void)
{
return UACPI_STATUS_OK; /* all work ran synchronously above */
}
#endif // !UACPI_BAREBONES_MODE