4f0480fa84
Signed-off-by: kaguya <vpshinomiya@protonmail.com>
705 lines
21 KiB
C
705 lines
21 KiB
C
#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
|