332 lines
9.2 KiB
C
332 lines
9.2 KiB
C
// SPDX-License-Identifier: ISC
|
|
|
|
#include <zinnia/acpi.h>
|
|
#include <zinnia/alloc.h>
|
|
#include <zinnia/clock.h>
|
|
#include <zinnia/console.h>
|
|
#include <zinnia/init.h>
|
|
#include <zinnia/irq.h>
|
|
#include <zinnia/mmio.h>
|
|
#include <zinnia/pci.h>
|
|
#include <zinnia/percpu.h>
|
|
#include <zinnia/pmap.h>
|
|
#include <zinnia/print.h>
|
|
#include <zinnia/sched.h>
|
|
#include <zinnia/semaphore.h>
|
|
#include <zinnia/spin.h>
|
|
#include <zinnia/utils.h>
|
|
#include <zinnia/virt.h>
|
|
#include <zinnia/vma.h>
|
|
#include <zinnia/workqueue.h>
|
|
#include <arch/irq.h>
|
|
#include <arch/mem.h>
|
|
#include <stdarg.h>
|
|
#include <uacpi/kernel_api.h>
|
|
#include <uacpi/status.h>
|
|
#include <uacpi/types.h>
|
|
|
|
uacpi_status uacpi_kernel_get_rsdp(uacpi_phys_addr* out_rsdp_address) {
|
|
*out_rsdp_address = (uacpi_phys_addr)acpi_rsdp_addr;
|
|
return UACPI_STATUS_OK;
|
|
}
|
|
|
|
void* uacpi_kernel_map(uacpi_phys_addr addr, uacpi_size len) {
|
|
uintptr_t aligned_addr = ALIGN_DOWN(addr, arch_mem_page_size());
|
|
uintptr_t difference = addr - aligned_addr;
|
|
size_t aligned_len = ALIGN_UP(len + difference, arch_mem_page_size());
|
|
|
|
uintptr_t vaddr = vm_alloc(aligned_len);
|
|
if (!vaddr)
|
|
return nullptr;
|
|
|
|
for (size_t i = 0; i < aligned_len; i += arch_mem_page_size()) {
|
|
pmap_map(&kernel_vma.pmap, vaddr + i, aligned_addr + i, PTE_READ | PTE_WRITE, CACHE_ANY);
|
|
}
|
|
|
|
return (void*)(vaddr + difference);
|
|
}
|
|
|
|
void uacpi_kernel_unmap(void* addr, uacpi_size len) {
|
|
uintptr_t aligned_addr = ALIGN_DOWN((uintptr_t)addr, arch_mem_page_size());
|
|
uintptr_t difference = (uintptr_t)addr - aligned_addr;
|
|
size_t aligned_len = ALIGN_UP(len + difference, arch_mem_page_size());
|
|
|
|
for (size_t i = 0; i < aligned_len; i += arch_mem_page_size())
|
|
pmap_unmap(&kernel_vma.pmap, aligned_addr + i);
|
|
vm_free(aligned_addr, aligned_len);
|
|
}
|
|
|
|
static enum log_level acpi_log_level(uacpi_log_level lvl) {
|
|
switch (lvl) {
|
|
case UACPI_LOG_ERROR:
|
|
return LOG_ERR;
|
|
|
|
case UACPI_LOG_WARN:
|
|
return LOG_WARN;
|
|
|
|
case UACPI_LOG_INFO:
|
|
return LOG_INFO;
|
|
|
|
default:
|
|
return LOG_DEBUG;
|
|
}
|
|
}
|
|
|
|
void uacpi_kernel_log(uacpi_log_level lvl, const uacpi_char* msg, ...) {
|
|
va_list args;
|
|
va_start(args, msg);
|
|
log_vwrite_prefixed(acpi_log_level(lvl), "acpi: ", msg, args);
|
|
va_end(args);
|
|
}
|
|
|
|
void uacpi_kernel_vlog(uacpi_log_level lvl, const uacpi_char* msg, uacpi_va_list args) {
|
|
log_vwrite_prefixed(acpi_log_level(lvl), "acpi: ", msg, args);
|
|
}
|
|
|
|
uacpi_status uacpi_kernel_pci_device_open(uacpi_pci_address address, uacpi_handle* out_handle) {
|
|
#if CONFIG_PCI
|
|
struct pci_address addr = {
|
|
.domain = address.segment,
|
|
.bus = address.bus,
|
|
.device = address.device,
|
|
.function = address.function,
|
|
};
|
|
struct pci_access* access = pci_access_find(&addr);
|
|
if (!access)
|
|
return UACPI_STATUS_NOT_FOUND;
|
|
|
|
struct pci_device* dev = mem_alloc(sizeof(*dev), 0);
|
|
if (!dev)
|
|
return UACPI_STATUS_OUT_OF_MEMORY;
|
|
dev->address = addr;
|
|
dev->access = access;
|
|
*out_handle = dev;
|
|
return UACPI_STATUS_OK;
|
|
#else
|
|
return UACPI_STATUS_NOT_FOUND;
|
|
#endif
|
|
}
|
|
|
|
void uacpi_kernel_pci_device_close(uacpi_handle handle) {
|
|
mem_free(handle);
|
|
}
|
|
|
|
#if CONFIG_PCI
|
|
#define PCI_READ(width) \
|
|
uacpi_status uacpi_kernel_pci_read##width(uacpi_handle device, uacpi_size offset, uacpi_u##width* value) { \
|
|
*value = pci_read##width(device, offset); \
|
|
return UACPI_STATUS_OK; \
|
|
}
|
|
#define PCI_WRITE(width) \
|
|
uacpi_status uacpi_kernel_pci_write##width(uacpi_handle device, uacpi_size offset, uacpi_u##width value) { \
|
|
pci_write##width(device, offset, value); \
|
|
return UACPI_STATUS_OK; \
|
|
}
|
|
#else
|
|
#define PCI_READ(width) \
|
|
uacpi_status uacpi_kernel_pci_read##width(uacpi_handle device, uacpi_size offset, uacpi_u##width* value) { \
|
|
return UACPI_STATUS_UNIMPLEMENTED; \
|
|
}
|
|
#define PCI_WRITE(width) \
|
|
uacpi_status uacpi_kernel_pci_write##width(uacpi_handle device, uacpi_size offset, uacpi_u##width value) { \
|
|
return UACPI_STATUS_UNIMPLEMENTED; \
|
|
}
|
|
#endif
|
|
|
|
PCI_READ(8)
|
|
PCI_READ(16)
|
|
PCI_READ(32)
|
|
PCI_WRITE(8)
|
|
PCI_WRITE(16)
|
|
PCI_WRITE(32)
|
|
|
|
void* uacpi_kernel_alloc(uacpi_size size) {
|
|
return mem_alloc(size, ALLOC_NOZERO);
|
|
}
|
|
|
|
void uacpi_kernel_free(void* mem) {
|
|
if (mem)
|
|
mem_free(mem);
|
|
}
|
|
|
|
uacpi_u64 uacpi_kernel_get_nanoseconds_since_boot() {
|
|
return clock_get_elapsed_ns();
|
|
}
|
|
|
|
void uacpi_kernel_stall(uacpi_u8 usec) {
|
|
clock_spin_ns(usec * 1000ull);
|
|
}
|
|
|
|
void uacpi_kernel_sleep(uacpi_u64 msec) {
|
|
sched_sleep_until(clock_get_elapsed_ns() + msec * 1'000'000);
|
|
}
|
|
|
|
uacpi_handle uacpi_kernel_create_semaphore(uacpi_u32 initial_units) {
|
|
sem_t* sem = mem_alloc(sizeof(*sem), 0);
|
|
if (sem)
|
|
sem_init(sem, initial_units);
|
|
return sem;
|
|
}
|
|
|
|
void uacpi_kernel_free_semaphore(uacpi_handle handle) {
|
|
mem_free(handle);
|
|
}
|
|
|
|
uacpi_status uacpi_kernel_wait_for_semaphore(uacpi_handle handle, uacpi_u16 timeout) {
|
|
sem_t* sem = handle;
|
|
if (timeout == 0)
|
|
return sem_trywait(sem) ? UACPI_STATUS_OK : UACPI_STATUS_TIMEOUT;
|
|
if (timeout == 0xFFFF) {
|
|
sem_wait(sem);
|
|
return UACPI_STATUS_OK;
|
|
}
|
|
|
|
const uint64_t deadline = clock_get_elapsed_ns() + timeout * 1'000'000ull;
|
|
return sem_timedwait(sem, &deadline) ? UACPI_STATUS_OK : UACPI_STATUS_TIMEOUT;
|
|
}
|
|
|
|
void uacpi_kernel_signal_semaphore(uacpi_handle handle) {
|
|
sem_post(handle);
|
|
}
|
|
|
|
uacpi_thread_id uacpi_kernel_get_thread_id() {
|
|
return percpu_get()->sched.current;
|
|
}
|
|
|
|
uacpi_interrupt_state uacpi_kernel_disable_interrupts() {
|
|
irq_lock();
|
|
return 0;
|
|
}
|
|
|
|
void uacpi_kernel_restore_interrupts(uacpi_interrupt_state) {
|
|
irq_unlock();
|
|
}
|
|
|
|
uacpi_status uacpi_kernel_handle_firmware_request(uacpi_firmware_request* req) {
|
|
switch (req->type) {
|
|
case UACPI_FIRMWARE_REQUEST_TYPE_BREAKPOINT:
|
|
log_warn("acpi: Ignoring AML breakpoint\n");
|
|
break;
|
|
case UACPI_FIRMWARE_REQUEST_TYPE_FATAL:
|
|
log_err(
|
|
"acpi: Fatal firmware error: type %u, code %#x, arg %#llx\n",
|
|
req->fatal.type,
|
|
req->fatal.code,
|
|
(unsigned long long)req->fatal.arg
|
|
);
|
|
break;
|
|
}
|
|
return UACPI_STATUS_OK;
|
|
}
|
|
|
|
struct acpi_irq {
|
|
struct irq_handler handler;
|
|
uacpi_interrupt_handler func;
|
|
uacpi_handle ctx;
|
|
};
|
|
|
|
static enum irq_status acpi_irq_raise(struct irq_handler* self) {
|
|
struct acpi_irq* irq = containerof(self, struct acpi_irq, handler);
|
|
return irq->func(irq->ctx) == UACPI_INTERRUPT_HANDLED ? IRQ_STATUS_HANDLED : IRQ_STATUS_IGNORED;
|
|
}
|
|
|
|
uacpi_status uacpi_kernel_install_interrupt_handler(
|
|
uacpi_u32 irq,
|
|
uacpi_interrupt_handler handler,
|
|
uacpi_handle ctx,
|
|
uacpi_handle* out_irq_handle
|
|
) {
|
|
uint32_t gsi = irq;
|
|
enum irq_trigger_mode trigger = IRQ_TRIGGER_LEVEL;
|
|
enum irq_polarity polarity = IRQ_POLARITY_LOW;
|
|
if (irq <= UINT8_MAX)
|
|
arch_irq_isa_override(irq, &gsi, &trigger, &polarity);
|
|
|
|
struct irq_line* line;
|
|
errno_t status = arch_irq_get_gsi(gsi, &line);
|
|
if (status) {
|
|
log_err("acpi: Unable to get GSI %u for IRQ %u (%i)\n", gsi, irq, status);
|
|
return UACPI_STATUS_UNIMPLEMENTED;
|
|
}
|
|
|
|
struct acpi_irq* acpi_irq = mem_alloc(sizeof(*acpi_irq), 0);
|
|
if (!acpi_irq)
|
|
return UACPI_STATUS_OUT_OF_MEMORY;
|
|
acpi_irq->handler.raise = acpi_irq_raise;
|
|
acpi_irq->func = handler;
|
|
acpi_irq->ctx = ctx;
|
|
|
|
irq_line_attach(line, &acpi_irq->handler);
|
|
irq_line_program(line, trigger, polarity);
|
|
log("acpi: Installed handler for IRQ %u on GSI %u\n", irq, gsi);
|
|
|
|
*out_irq_handle = acpi_irq;
|
|
return UACPI_STATUS_OK;
|
|
}
|
|
|
|
uacpi_status uacpi_kernel_uninstall_interrupt_handler(uacpi_interrupt_handler handler, uacpi_handle irq_handle) {
|
|
struct acpi_irq* acpi_irq = irq_handle;
|
|
struct irq_line* line = acpi_irq->handler.line;
|
|
irq_line_detach(line, &acpi_irq->handler);
|
|
if (list_is_empty(&line->handlers) && line->mask)
|
|
line->mask(line);
|
|
mem_free(acpi_irq);
|
|
return UACPI_STATUS_OK;
|
|
}
|
|
|
|
uacpi_handle uacpi_kernel_create_spinlock() {
|
|
struct spinlock* lock = mem_alloc(sizeof(*lock), 0);
|
|
if (lock)
|
|
spin_init(lock);
|
|
return lock;
|
|
}
|
|
|
|
void uacpi_kernel_free_spinlock(uacpi_handle handle) {
|
|
mem_free(handle);
|
|
}
|
|
|
|
uacpi_cpu_flags uacpi_kernel_lock_spinlock(uacpi_handle handle) {
|
|
spin_lock(handle);
|
|
return 0;
|
|
}
|
|
|
|
void uacpi_kernel_unlock_spinlock(uacpi_handle handle, uacpi_cpu_flags) {
|
|
spin_unlock(handle);
|
|
}
|
|
|
|
struct acpi_work {
|
|
struct work work;
|
|
uacpi_work_handler handler;
|
|
uacpi_handle ctx;
|
|
};
|
|
|
|
static void acpi_work_fn(struct work* work) {
|
|
struct acpi_work* item = containerof(work, struct acpi_work, work);
|
|
const uacpi_work_handler handler = item->handler;
|
|
const uacpi_handle ctx = item->ctx;
|
|
handler(ctx);
|
|
}
|
|
|
|
uacpi_handle uacpi_kernel_create_work_item() {
|
|
struct acpi_work* item = mem_alloc(sizeof(*item), 0);
|
|
if (item)
|
|
work_init(&item->work, acpi_work_fn);
|
|
return item;
|
|
}
|
|
|
|
void uacpi_kernel_free_work_item(uacpi_handle handle) {
|
|
mem_free(handle);
|
|
}
|
|
|
|
void uacpi_kernel_schedule_work(uacpi_work_type, uacpi_handle work_item, uacpi_work_handler handler, uacpi_handle ctx) {
|
|
struct acpi_work* item = work_item;
|
|
item->handler = handler;
|
|
item->ctx = ctx;
|
|
work_queue(acpi_workqueue, &item->work);
|
|
}
|
|
|
|
uacpi_status uacpi_kernel_wait_for_work_completion() {
|
|
workqueue_flush(acpi_workqueue);
|
|
return UACPI_STATUS_OK;
|
|
}
|