// SPDX-License-Identifier: GPL-2.0-or-later // // What uACPI asks of the kernel it runs in: memory, mapping, port and PCI // configuration I/O, time, locks, the SCI, and somewhere to run deferred // work. Every function here is one uacpi_kernel_* entry point from // ; the interpreter calls nothing else. // // Two things make this harder than it looks. // // The namespace is loaded from kmain(), before the scheduler, with no // process to sleep in -- and AML sleeps, takes mutexes and waits on events // while it initializes devices. So every blocking primitive here checks // myproc() and spins when there is nothing to put to sleep, the same rule // the AHCI and NVMe drivers follow. // // And the SCI handler runs AML's notifications and GPE methods through // uacpi_kernel_schedule_work(), from interrupt context, where AML must not // run. That is the "acpi" kernel thread's whole job. #include "core/types.h" #include "core/defs.h" #include "core/param.h" #include "arch/memlayout.h" #include "arch/mmu.h" #include "arch/x86.h" #include "core/spinlock.h" #include "core/proc.h" #include "core/irq.h" #include "core/dma.h" #include "core/kmem.h" #include "dev/acpi.h" #include // --- memory ------------------------------------------------------------------ // // kmalloc stops at 2 KB and kalloc is one page, and uACPI's allocations range // from a few bytes to buffers AML sizes however it likes. So each allocation // carries a header saying where it came from: the slab caches, a page, or -- // for the rare one bigger than a page -- the contiguous DMA arena, which is // the only multi-page allocator the kernel has. #define HDR 16 enum { FROM_SLAB = 1, FROM_PAGE, FROM_DMA }; void * uacpi_kernel_alloc(uacpi_size size) { uint64 n = size + HDR; uint64 *h; int kind; if (n <= 2048) { h = kmalloc(n); kind = FROM_SLAB; } else if (n <= PGSIZE) { h = (uint64 *)kalloc(); kind = FROM_PAGE; } else { h = dma_alloc(n, 16); kind = FROM_DMA; } if (h == 0) return 0; h[0] = n; h[1] = kind; return (uchar *)h + HDR; } void uacpi_kernel_free(void *mem) { uint64 *h; if (mem == 0) return; h = (uint64 *)((uchar *)mem - HDR); switch (h[1]) { case FROM_SLAB: kmfree(h); break; case FROM_PAGE: kfree((char *)h); break; case FROM_DMA: dma_free(h, h[0]); break; default: panic("uacpi_kernel_free: not ours"); } } // Physical memory: tables, and AML's SystemMemory regions. The direct map // covers the first 4 GB; above that, ioremap(). void * uacpi_kernel_map(uacpi_phys_addr addr, uacpi_size len) { if (addr + len <= 4ULL * 1024 * 1024 * 1024) return P2V(addr); void *p = ioremap(addr, len); return p ? p : UACPI_MAP_FAILED; } void uacpi_kernel_unmap(void *addr, uacpi_size len) { // Nothing to undo: the direct map is permanent, and ioremap's 1 GB windows // are shared and never torn down. (void)addr; (void)len; } uacpi_status uacpi_kernel_get_rsdp(uacpi_phys_addr *out) { uint64 pa = acpi_rsdp_phys(); if (pa == 0) return UACPI_STATUS_NOT_FOUND; *out = pa; return UACPI_STATUS_OK; } // --- logging ----------------------------------------------------------------- void uacpi_kernel_log(uacpi_log_level level, const uacpi_char *msg) { switch (level) { case UACPI_LOG_ERROR: case UACPI_LOG_WARN: kinfo("acpi: %s", msg); break; case UACPI_LOG_INFO: kinfo("acpi: %s", msg); break; default: break; // trace and debug: per-opcode noise } } uacpi_status uacpi_kernel_handle_firmware_request(uacpi_firmware_request *req) { if (req->type == UACPI_FIRMWARE_REQUEST_TYPE_FATAL) cprintf("acpi: firmware reports a fatal error (type %x code %x arg %p)\n", req->fatal.type, req->fatal.code, req->fatal.arg); return UACPI_STATUS_OK; } // --- port I/O ---------------------------------------------------------------- // // The handle is the base port itself; there is nothing to map. uacpi_status uacpi_kernel_io_map(uacpi_io_addr base, uacpi_size len, uacpi_handle *out) { (void)len; *out = (uacpi_handle)(addr_t)base; return UACPI_STATUS_OK; } void uacpi_kernel_io_unmap(uacpi_handle h) { (void)h; } #define PORT(h, off) ((ushort)((addr_t)(h) + (off))) uacpi_status uacpi_kernel_io_read8(uacpi_handle h, uacpi_size o, uacpi_u8 *v) { *v = inb(PORT(h, o)); return UACPI_STATUS_OK; } uacpi_status uacpi_kernel_io_read16(uacpi_handle h, uacpi_size o, uacpi_u16 *v) { *v = inw(PORT(h, o)); return UACPI_STATUS_OK; } uacpi_status uacpi_kernel_io_read32(uacpi_handle h, uacpi_size o, uacpi_u32 *v) { *v = inl(PORT(h, o)); return UACPI_STATUS_OK; } uacpi_status uacpi_kernel_io_write8(uacpi_handle h, uacpi_size o, uacpi_u8 v) { outb(PORT(h, o), v); return UACPI_STATUS_OK; } uacpi_status uacpi_kernel_io_write16(uacpi_handle h, uacpi_size o, uacpi_u16 v) { outw(PORT(h, o), v); return UACPI_STATUS_OK; } uacpi_status uacpi_kernel_io_write32(uacpi_handle h, uacpi_size o, uacpi_u32 v) { outl(PORT(h, o), v); return UACPI_STATUS_OK; } // --- PCI configuration space ------------------------------------------------- // // Through the 0xCF8 mechanism, which reaches segment 0 and the first 256 // bytes of each function. AML that wants PCIe extended space is refused // rather than handed bytes from somewhere else. struct pcihandle { uint8 bus, dev, func; }; uacpi_status uacpi_kernel_pci_device_open(uacpi_pci_address a, uacpi_handle *out) { struct pcihandle *p; if (a.segment != 0) return UACPI_STATUS_UNIMPLEMENTED; p = uacpi_kernel_alloc(sizeof(*p)); if (p == 0) return UACPI_STATUS_OUT_OF_MEMORY; p->bus = a.bus; p->dev = a.device; p->func = a.function; *out = p; return UACPI_STATUS_OK; } void uacpi_kernel_pci_device_close(uacpi_handle h) { uacpi_kernel_free(h); } #define PCIH ((struct pcihandle *)h) #define CHECK(o, w) if ((o) + (w) > 256) return UACPI_STATUS_UNIMPLEMENTED uacpi_status uacpi_kernel_pci_read8(uacpi_handle h, uacpi_size o, uacpi_u8 *v) { CHECK(o, 1); *v = pci_read8(PCIH->bus, PCIH->dev, PCIH->func, o); return UACPI_STATUS_OK; } uacpi_status uacpi_kernel_pci_read16(uacpi_handle h, uacpi_size o, uacpi_u16 *v) { CHECK(o, 2); *v = pci_read16(PCIH->bus, PCIH->dev, PCIH->func, o); return UACPI_STATUS_OK; } uacpi_status uacpi_kernel_pci_read32(uacpi_handle h, uacpi_size o, uacpi_u32 *v) { CHECK(o, 4); *v = pci_read32(PCIH->bus, PCIH->dev, PCIH->func, o); return UACPI_STATUS_OK; } // Narrow writes as read-modify-write of the dword: the kernel has only a // 32-bit config write, which is all 0xCF8 strictly offers anyway. static void pci_write_part(uacpi_handle h, uacpi_size o, uint32 v, int width) { uint32 shift = (o & 3) * 8; uint32 mask = (width == 1 ? 0xFFu : 0xFFFFu) << shift; uint32 d = pci_read32(PCIH->bus, PCIH->dev, PCIH->func, o & ~3); d = (d & ~mask) | ((v << shift) & mask); pci_write32(PCIH->bus, PCIH->dev, PCIH->func, o & ~3, d); } uacpi_status uacpi_kernel_pci_write8(uacpi_handle h, uacpi_size o, uacpi_u8 v) { CHECK(o, 1); pci_write_part(h, o, v, 1); return UACPI_STATUS_OK; } uacpi_status uacpi_kernel_pci_write16(uacpi_handle h, uacpi_size o, uacpi_u16 v) { CHECK(o, 2); pci_write_part(h, o, v, 2); return UACPI_STATUS_OK; } uacpi_status uacpi_kernel_pci_write32(uacpi_handle h, uacpi_size o, uacpi_u32 v) { CHECK(o, 4); pci_write32(PCIH->bus, PCIH->dev, PCIH->func, o, v); return UACPI_STATUS_OK; } // --- time -------------------------------------------------------------------- uacpi_u64 uacpi_kernel_get_nanoseconds_since_boot(void) { uint64 t = rdtsc(), hz = tsc_hz ? tsc_hz : 1000000000ULL; // Split so that the multiplication cannot overflow after a few hours. return (t / hz) * 1000000000ULL + (t % hz) * 1000000000ULL / hz; } void uacpi_kernel_stall(uacpi_u8 usec) { microdelay(usec); } // Wait for up to `ms`, or until *cond is nonzero when cond is given, sleeping // a tick at a time when there is a process to sleep and spinning otherwise. // Caller holds lk, which is released while waiting. Returns 0 if cond became // true, -1 on timeout. 0xFFFF is "for ever", as uACPI spells it. static int wait_until(volatile int *cond, struct spinlock *lk, uint ms) { uint64 start = rdtsc(); uint64 limit = tsc_hz / 1000 * ms; uint t0 = ticks; while (!cond || !*cond) { if (ms != 0xFFFF) { if (myproc() ? (ticks - t0) * MSEC_PER_TICK >= ms : rdtsc() - start >= limit) return cond ? -1 : 0; } if (myproc()) { sleep(&ticks, lk); } else { release(lk); microdelay(10); acquire(lk); } } return 0; } void uacpi_kernel_sleep(uacpi_u64 msec) { static struct spinlock lk; static int init; if (!init) { initlock(&lk, "acpisleep"); init = 1; } if (msec >= 0xFFFF) msec = 0xFFFE; acquire(&lk); wait_until(0, &lk, msec); release(&lk); } // --- locks and events -------------------------------------------------------- struct kmutex { struct spinlock lk; volatile int free; // 1 when nobody holds it }; uacpi_handle uacpi_kernel_create_mutex(void) { struct kmutex *m = uacpi_kernel_alloc(sizeof(*m)); if (m) { initlock(&m->lk, "acpimutex"); m->free = 1; } return m; } void uacpi_kernel_free_mutex(uacpi_handle h) { uacpi_kernel_free(h); } uacpi_status uacpi_kernel_acquire_mutex(uacpi_handle h, uacpi_u16 timeout) { struct kmutex *m = h; int rc = 0; acquire(&m->lk); if (!m->free) rc = timeout == 0 ? -1 : wait_until(&m->free, &m->lk, timeout); if (rc == 0) m->free = 0; release(&m->lk); return rc == 0 ? UACPI_STATUS_OK : UACPI_STATUS_TIMEOUT; } void uacpi_kernel_release_mutex(uacpi_handle h) { struct kmutex *m = h; acquire(&m->lk); m->free = 1; release(&m->lk); } struct kevent { struct spinlock lk; volatile int count; }; uacpi_handle uacpi_kernel_create_event(void) { struct kevent *e = uacpi_kernel_alloc(sizeof(*e)); if (e) { initlock(&e->lk, "acpievent"); e->count = 0; } return e; } void uacpi_kernel_free_event(uacpi_handle h) { uacpi_kernel_free(h); } uacpi_bool uacpi_kernel_wait_for_event(uacpi_handle h, uacpi_u16 timeout) { struct kevent *e = h; int rc = 0; acquire(&e->lk); if (e->count == 0) rc = timeout == 0 ? -1 : wait_until(&e->count, &e->lk, timeout); if (rc == 0) e->count--; release(&e->lk); return rc == 0; } void uacpi_kernel_signal_event(uacpi_handle h) { struct kevent *e = h; acquire(&e->lk); e->count++; release(&e->lk); } void uacpi_kernel_reset_event(uacpi_handle h) { struct kevent *e = h; acquire(&e->lk); e->count = 0; release(&e->lk); } uacpi_thread_id uacpi_kernel_get_thread_id(void) { // A process, or -- before there are any -- the CPU, numbered from 1 so that // it can never look like a null pointer. struct proc *p = myproc(); return p ? (uacpi_thread_id)p : (uacpi_thread_id)(addr_t)(cpunum() + 1); } uacpi_interrupt_state uacpi_kernel_disable_interrupts(void) { uacpi_interrupt_state s = readeflags() & FL_IF; cli(); return s; } void uacpi_kernel_restore_interrupts(uacpi_interrupt_state s) { if (s) sti(); } uacpi_handle uacpi_kernel_create_spinlock(void) { struct spinlock *l = uacpi_kernel_alloc(sizeof(*l)); if (l) initlock(l, "acpispin"); return l; } void uacpi_kernel_free_spinlock(uacpi_handle h) { uacpi_kernel_free(h); } uacpi_cpu_flags uacpi_kernel_lock_spinlock(uacpi_handle h) { acquire(h); // which disables interrupts, as asked return 0; } void uacpi_kernel_unlock_spinlock(uacpi_handle h, uacpi_cpu_flags f) { (void)f; release(h); } // --- the SCI ----------------------------------------------------------------- struct sci { uacpi_interrupt_handler fn; uacpi_handle ctx; int irq; }; static void sci_trampoline(void *arg) { struct sci *s = arg; s->fn(s->ctx); } uacpi_status uacpi_kernel_install_interrupt_handler(uacpi_u32 irq, uacpi_interrupt_handler fn, uacpi_handle ctx, uacpi_handle *out) { struct sci *s; int mode, gsi; // The FADT names the SCI as an ISA IRQ. ACPI says it is level/low unless // an override says otherwise -- and on most machines one does, making it // level/high, which is the case that went wrong when only ISA's and PCI's // signalling could be expressed. gsi = acpi_isa_gsi(irq, IRQ_LEVEL_LOW, &mode); if (gsi < 0 || gsi >= NIRQ_IOAPIC) return UACPI_STATUS_UNIMPLEMENTED; s = uacpi_kernel_alloc(sizeof(*s)); if (s == 0) return UACPI_STATUS_OUT_OF_MEMORY; s->fn = fn; s->ctx = ctx; s->irq = gsi; if (irq_register(gsi, sci_trampoline, s, "acpi", mode | IRQ_QUIET) < 0) { uacpi_kernel_free(s); return UACPI_STATUS_INTERNAL_ERROR; } kinfo("acpi: SCI on irq %d (gsi %d, %s)\n", irq, gsi, IRQ_IS_LEVEL(mode) ? "level" : "edge"); *out = s; return UACPI_STATUS_OK; } uacpi_status uacpi_kernel_uninstall_interrupt_handler(uacpi_interrupt_handler fn, uacpi_handle h) { struct sci *s = h; (void)fn; irq_unregister(s->irq, sci_trampoline, s); uacpi_kernel_free(s); return UACPI_STATUS_OK; } // --- deferred work ----------------------------------------------------------- // // A ring of pending calls and one thread to make them. Work queued before the // thread exists waits for it; nothing is queued before interrupts are on. #define NWORK 64 static struct { struct spinlock lk; int init; struct { uacpi_work_handler fn; uacpi_handle ctx; } q[NWORK]; uint r, w; int busy; } work; static void work_init(void) { if (!work.init) { initlock(&work.lk, "acpiwork"); work.init = 1; } } uacpi_status uacpi_kernel_schedule_work(uacpi_work_type type, uacpi_work_handler fn, uacpi_handle ctx) { (void)type; // GPEs and notifications alike: one CPU's worth work_init(); acquire(&work.lk); if (work.w - work.r >= NWORK) { release(&work.lk); cprintf("acpi: work queue full; an event was dropped\n"); return UACPI_STATUS_OUT_OF_MEMORY; } work.q[work.w % NWORK].fn = fn; work.q[work.w % NWORK].ctx = ctx; work.w++; release(&work.lk); wakeup(&work); return UACPI_STATUS_OK; } uacpi_status uacpi_kernel_wait_for_work_completion(void) { work_init(); acquire(&work.lk); while (work.r != work.w || work.busy) { if (myproc()) { sleep(&ticks, &work.lk); } else { // No thread yet, so nothing would ever drain it: run it here. uacpi_work_handler fn = work.q[work.r % NWORK].fn; uacpi_handle ctx = work.q[work.r % NWORK].ctx; work.r++; release(&work.lk); fn(ctx); acquire(&work.lk); } } release(&work.lk); return UACPI_STATUS_OK; } static void acpi_worker(void) { work_init(); for (;;) { acquire(&work.lk); while (work.r == work.w) sleep(&work, &work.lk); uacpi_work_handler fn = work.q[work.r % NWORK].fn; uacpi_handle ctx = work.q[work.r % NWORK].ctx; work.r++; work.busy = 1; release(&work.lk); fn(ctx); acquire(&work.lk); work.busy = 0; release(&work.lk); } } void acpi_thread_start(void) { if (acpi_rsdp_phys() == 0) return; if (kthread_create(acpi_worker, "acpi") < 0) cprintf("acpi: no worker thread; the power button will not work\n"); }