SchOS/kernel/acpi/host.c
Nexoniarz e659c6b187 bug pass: whole console lines, a quiet SCI, an honest reserve warning
- schinit, schinitd and vgpud build every message from pieces, one
  write() each, so a job starting on another CPU landed mid-line:
  "schinitd: starting vgpud: os.schos.resolvconfno virtio-gpu on this
  machine". They now collect a line and write it at the newline;
  resolvconf's verbose line is one write with its prefix.
- The ACPI SCI registers IRQ_QUIET. A desktop's SCI never fires, and the
  poller took two seconds of silence for a misrouted line ("irq 9:
  nothing has arrived on it") and called the handler every tick. A line
  is only exempt while every handler on it is quiet.
- The loader claims each segment from where the last one's claim ended,
  so two segments sharing a page no longer ask for it twice. When a
  claim is still refused, it names the memory-map entries in the way.
- Stale divesymlink() declaration gone; the kernel.md struct table
  re-measured (inode, buf and file pools grow from slabs now).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S1giNvrHTrPMPw7V6x1kQ4
2026-10-23 16:03:33 +02:00

620 lines
15 KiB
C

// 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
// <uacpi/kernel_api.h>; 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 <uacpi/kernel_api.h>
// --- 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:
cprintf("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");
}