Sixty-five headers sat side by side in include/, so `#include "irq.h"` said nothing about where irq.h was or what it belonged to, and errno.h, stat.h and fcntl.h at the top shadowed the system's for any host tool built with -I./include. They are in folders now -- arch/ core/ fs/ dev/ gfx/ net/ uapi/ (and ldls/ for the Linux driver layer) -- and every include names the folder, as Linux's do: "core/proc.h". Done mechanically and checked the hard way: the kernel built from this tree disassembles identically to the one built from the tree before it. The full image boots, and userland is the same set of programs. Paths in docs and comments follow; docs/guides/layout.md has a table of what is where, and tools/fix-includes.py rewrites old-style includes from the folders in the tree, for code written before this. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S1giNvrHTrPMPw7V6x1kQ4
620 lines
15 KiB
C
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) < 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");
|
|
}
|