#include #include #include "mm/memory.h" #include "arch/x86_64/cpu/io.h" #include "arch/x86_64/sys/irq.h" #include "arch/x86_64/sys/pit.h" #include "arch/x86_64/sys/tsc.h" #include #include #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