diff --git a/Makefile b/Makefile index f38e32b..f509c03 100644 --- a/Makefile +++ b/Makefile @@ -88,6 +88,18 @@ KERN_NET += $(addprefix $(BUILD)/lwip/core/ipv4/, \ KERN_NET += $(addprefix $(BUILD)/lwip/netif/, ethernet.o) KERN_NET += $(addprefix $(BUILD)/kernel/net/lwip/, sys_arch.o netif_eth.o) +# uACPI: the AML interpreter, MIT, unpacked by ports/uacpi into +# build/ports/uacpi/src and compiled into the kernel like lwIP. kernel/acpi/ +# is the host side of it -- memory, locks, I/O, the SCI -- and what the kernel +# uses it for: PCI interrupt routing, the power button and S5. +UACPIDIR = $(BUILD)/ports/uacpi/src +KERN_ACPI = $(addprefix $(BUILD)/uacpi/, \ + default_handlers.o event.o interpreter.o io.o mutex.o \ + namespace.o notify.o opcodes.o opregion.o osi.o registers.o \ + resources.o shareable.o sleep.o stdlib.o tables.o types.o \ + uacpi.o utilities.o) +KERN_ACPI += $(addprefix $(BUILD)/kernel/acpi/, host.o aml.o) + KERN_PROC = $(addprefix $(BUILD)/kernel/proc/, \ proc.o) @@ -99,7 +111,7 @@ KERN_SYS = $(addprefix $(BUILD)/kernel/sys/, \ KERN_ARCH += $(BUILD)/kernel/arch/x86_64/vectors.o KERNOBJS = $(KERN_ARCH) $(KERN_CORE) $(KERN_DEV) $(KERN_DRIVERS) \ - $(KERN_FS) $(KERN_NET) $(KERN_PROC) $(KERN_SYS) + $(KERN_FS) $(KERN_NET) $(KERN_ACPI) $(KERN_PROC) $(KERN_SYS) # entry.o must be first in link order; remove it from KERNOBJS for the rule KERN_NOENTRY = $(filter-out $(BUILD)/kernel/arch/x86_64/entry.o, $(KERNOBJS)) @@ -942,6 +954,33 @@ $(BUILD)/lwip/%.o: $(LWIPDIR)/%.c | $(LWIPDIR) $(LWIPDIR): $(MAKE) -C ports lwip +# uACPI's headers for its own files and for kernel/acpi/, and the same +# configuration for both: a define seen by one side and not the other changes +# the size of structures they pass between them. Builtin string functions, +# because the kernel's memcpy and friends are not declared the way +# declares them. +UACPI_CFLAGS = -I$(UACPIDIR)/include -DUACPI_USE_BUILTIN_STRING +# Third-party, warned about differently from this tree; silenced for its files +# only. kernel/acpi/ is ours and is held to the usual warnings. +UACPI_QUIET = -Wno-unused-parameter -Wno-sign-compare -Wno-unused-function + +$(BUILD)/kernel/acpi/%.o: kernel/acpi/%.c | $(UACPIDIR) + @mkdir -p $(BUILD)/kernel/acpi + $(CC) $(CFLAGS) $(UACPI_CFLAGS) -c $< -o $@ + +$(BUILD)/uacpi/%.o: $(UACPIDIR)/source/%.c | $(UACPIDIR) + @mkdir -p $(dir $@) + $(CC) $(CFLAGS) $(UACPI_CFLAGS) $(UACPI_QUIET) -c $< -o $@ + +$(UACPIDIR): + $(MAKE) -C ports uacpi + +# The sources exist once the tarball is unpacked. Saying so is what lets the +# pattern rule above apply on a fresh tree, where make would otherwise look +# for $(UACPIDIR)/source/foo.c, find nothing, and report no rule for foo.o. +$(patsubst $(BUILD)/uacpi/%.o,$(UACPIDIR)/source/%.c,$(filter $(BUILD)/uacpi/%,$(KERN_ACPI))): | $(UACPIDIR) + @: + $(BUILD)/kernel/proc/%.o: kernel/proc/%.c @mkdir -p $(BUILD)/kernel/proc $(CC) $(CFLAGS) -c $< -o $@ diff --git a/NOTICE b/NOTICE index 2fed81a..a38b485 100644 --- a/NOTICE +++ b/NOTICE @@ -123,6 +123,34 @@ than in a binary under /bin. The port layer written for SchOS sources are unmodified. Clause 3 is a no-endorsement term, not the four-clause advertising requirement, so it combines with the GPL without difficulty. +-------------------------------------------------------------------------------- +uACPI 6.1.1 MIT +-------------------------------------------------------------------------------- +Copyright (c) 2022-2026 Daniil Tatianin + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +Fetched by ports/uacpi and compiled into the kernel: it is the ACPI AML +interpreter, used for PCI interrupt routing, the power button and power-off. +The host layer written for SchOS (kernel/acpi/) is GPL-2.0-or-later and is not +part of uACPI; uACPI's own sources are unmodified. + -------------------------------------------------------------------------------- Intel 8254x device identifiers BSD 3-Clause -------------------------------------------------------------------------------- diff --git a/docs/architecture/devices.md b/docs/architecture/devices.md index c496fc7..7c5f23c 100644 --- a/docs/architecture/devices.md +++ b/docs/architecture/devices.md @@ -276,16 +276,20 @@ does not choose how. In order of preference: 2. **MSI-X**, but only if the driver passes `PCI_IRQF_MSIX`. Some parts change how they report interrupt causes in that mode; the 82574 needs its IVAR programmed, which `e1000` does not do, so it does not ask. -3. **INTx**, on the line named by the device's `INTERRUPT_LINE` register. +3. **INTx**, on the pin ACPI's `_PRT` names for it. -MSI comes first because INTx on real hardware is **the wrong pin**. `INTERRUPT_LINE` holds the number the firmware gave the 8259. In APIC mode the -device is wired to an IOAPIC input that only ACPI's `_PRT` describes, and -reading `_PRT` takes an AML interpreter, which SchOS does not have. Under QEMU -the two numbers happen to agree, which is why this went unnoticed until the -drivers were read with a real board in mind. +device is wired to an IOAPIC input that only `_PRT` describes, and reading +`_PRT` takes an AML interpreter. That is uACPI (`ports/uacpi`, host side in +`kernel/acpi/`): `pci_init()` asks it for every device's pin between scanning +the bus and binding drivers, following link devices' `_CRS` and swizzling +through PCI-to-PCI bridges that have no `_PRT` of their own. The trigger mode +comes from there too -- q35's links are level/active-high, which +`irq_register()` could not express until it took a mode rather than a flag. -INTx is therefore backed by the poller in `kernel/dev/irq.c`: +MSI still comes first: it needs no routing at all, and a machine whose AML +does not load still has it. INTx is also backed by the poller in +`kernel/dev/irq.c`: - A level-triggered line that has had a handler for two seconds and has never fired gets its handlers called every tick, from a kernel thread with @@ -318,6 +322,17 @@ configurations: mounted. - The old INTx path, unchanged on i440fx. +And since `_PRT`: on q35 with MSI disabled, xHCI routed to GSI 22 level/high +and taking real interrupts, with nothing polled. + +### Power + +uACPI also handles the SCI, the power button and S5. The power button syncs +the filesystems and powers off, from the `acpi` kernel thread rather than the +interrupt; `shutdown()` and `reboot()` go through the FADT's PM1 and reset +registers first, and fall back to the emulators' fixed ports only if the AML +did not load. + --- ## `/dev/random` and `/dev/urandom` diff --git a/docs/licensing.md b/docs/licensing.md index 22ad20d..0a3fff9 100644 --- a/docs/licensing.md +++ b/docs/licensing.md @@ -172,6 +172,13 @@ Ethernet driver and no infrastructure to speak of. Its port layer is a handful of files, it has no dependency on the host kernel's allocator or locking, and it brings ARP, IPv4, ICMP, UDP, TCP, DHCP and DNS. +### uACPI + +The AML interpreter, `ports/uacpi`, compiled into the kernel like lwIP. +Verified 2026-10-22 from its `LICENSE`: MIT, Daniil Tatianin. Compatible, one +NOTICE entry. The choice over ACPICA was not about licensing but size: uACPI +asks a host for far less, and its whole host side is `kernel/acpi/host.c`. + ### Fonts Chosen 2026-08-09, both licences checked at source rather than assumed: diff --git a/include/acpi.h b/include/acpi.h new file mode 100644 index 0000000..0a28a56 --- /dev/null +++ b/include/acpi.h @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#pragma once + +// ACPI, in two layers. +// +// kernel/arch/x86_64/acpi.c reads the static tables at boot, before anything +// else can run: the MADT for the processors and the I/O APIC and its +// interrupt source overrides, the FADT for the PM timer. +// +// kernel/acpi/ is everything that needs AML, which is the firmware's own code +// and has to be interpreted: uACPI (ports/uacpi) runs it, and aml.c uses it +// for PCI interrupt routing, the power button, power-off and reset. Every +// function below that depends on it does nothing, and says so by returning +// -1, on a machine whose AML could not be loaded. + +// The RSDP's physical address, or 0 if there is none. +uint64 acpi_rsdp_phys(void); + +// The GSI an ISA IRQ is wired to, after the MADT's interrupt source +// overrides, and its trigger mode (an IRQ_* from irq.h) in *mode. `dflt` is +// the mode when the override says "conforms to the bus" or there is none: +// edge/high for an ISA device, level/low for the SCI. +int acpi_isa_gsi(int isa, int dflt, int *mode); + +// Load and initialize the namespace. After kinit, before pci_init. +void acpi_aml_init(void); +// The worker thread that runs AML on behalf of interrupts. After sinit. +void acpi_thread_start(void); +// Route every PCI device's INTx through _PRT. From pci_init, before binding. +void acpi_pci_route(void); + +int acpi_poweroff(void); // S5; returns only on failure +int acpi_reboot(void); // the FADT reset register; ditto diff --git a/include/irq.h b/include/irq.h index 5f64c2e..97a8c6d 100644 --- a/include/irq.h +++ b/include/irq.h @@ -41,6 +41,16 @@ typedef void (*irq_handler_t)(void *arg); // than ISA's (edge-triggered, active high). Getting this wrong does not fail // cleanly: an edge-configured level interrupt fires once and then wedges, // because the device keeps the line asserted and no further edge ever comes. +// +// It is a mode, not a flag. 0 and 1 are the two above, which is all a PCI or +// ISA device needs; the other two are what ACPI can say about a line -- q35's +// PCI links and most machines' SCI are level-triggered *active high*, and +// programming one active low leaves the pin reading as permanently asserted. +#define IRQ_EDGE_HIGH 0 // ISA +#define IRQ_LEVEL_LOW 1 // PCI INTx +#define IRQ_LEVEL_HIGH 2 +#define IRQ_EDGE_LOW 3 +#define IRQ_IS_LEVEL(m) ((m) == IRQ_LEVEL_LOW || (m) == IRQ_LEVEL_HIGH) int irq_register(int irq, irq_handler_t fn, void *arg, const char *name, int level_low); void irq_unregister(int irq, irq_handler_t fn, void *arg); diff --git a/include/pci.h b/include/pci.h index af2ba31..d3fa02a 100644 --- a/include/pci.h +++ b/include/pci.h @@ -68,6 +68,9 @@ struct pci_dev { uint16 vendor, device; uint8 class, subclass, prog_if, revision; uint8 irq; // interrupt line, 0xFF when unrouted + // How that line is signalled, an IRQ_* mode from irq.h. PCI's level/low + // unless ACPI's routing says otherwise -- see kernel/acpi/aml.c. + uint8 irq_mode; uint8 header_type; struct pci_bar bar[6]; const char *driver; // name of whatever claimed it, or 0 diff --git a/kernel/acpi/aml.c b/kernel/acpi/aml.c new file mode 100644 index 0000000..39be089 --- /dev/null +++ b/kernel/acpi/aml.c @@ -0,0 +1,334 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +// +// What the kernel uses AML for. See include/acpi.h. +// +// Interrupt routing is the reason this exists. A PCI device's config space +// names an interrupt line, but that number is the 8259's: the firmware +// programmed it for an OS running the legacy PICs, and with the I/O APIC in +// use the device is on some other pin entirely. Which pin is written down +// only in the namespace, as each PCI root bridge's _PRT -- a table from +// (device, INTx pin) to either a GSI or a "link device" whose _CRS holds the +// GSI. Without reading it, every INTx device on a real board waited on the +// wrong pin, and only MSI and the poller (kernel/dev/irq.c) kept anything +// working at all. +// +// The rest is what every machine expects of an OS once it has ACPI: the +// power button powers it off cleanly, and power-off and reset go through the +// firmware's own registers rather than an emulator's magic ports. + +#include "types.h" +#include "defs.h" +#include "param.h" +#include "x86.h" +#include "spinlock.h" +#include "pci.h" +#include "irq.h" +#include "acpi.h" + +#include +#include +#include +#include +#include +#include + +static int ready; + +static uacpi_interrupt_ret power_button(uacpi_handle ctx); + +void +acpi_aml_init(void) +{ + uacpi_status st; + + if (acpi_rsdp_phys() == 0) + return; + + st = uacpi_initialize(0); + if (uacpi_unlikely_error(st)) { + cprintf("acpi: tables not usable: %s\n", uacpi_status_to_string(st)); + return; + } + st = uacpi_namespace_load(); + if (uacpi_unlikely_error(st)) { + cprintf("acpi: AML would not load: %s\n", uacpi_status_to_string(st)); + return; + } + + // \_PIC(1): tell the firmware the I/O APIC is in use, so that _PRT answers + // with I/O APIC inputs rather than PIC lines. Before _INI runs, which on + // some boards reads the answer. + uacpi_set_interrupt_model(UACPI_INTERRUPT_MODEL_IOAPIC); + + st = uacpi_namespace_initialize(); + if (uacpi_unlikely_error(st)) { + cprintf("acpi: namespace would not initialize: %s\n", + uacpi_status_to_string(st)); + return; + } + + st = uacpi_install_fixed_event_handler(UACPI_FIXED_EVENT_POWER_BUTTON, + power_button, 0); + if (uacpi_unlikely_error(st) && st != UACPI_STATUS_COMPILED_OUT) + kinfo("acpi: no fixed power button: %s\n", uacpi_status_to_string(st)); + + uacpi_finalize_gpe_initialization(); + ready = 1; + kinfo("acpi: namespace loaded\n"); +} + +// --- power ------------------------------------------------------------------- + +// Run in the worker thread, not the SCI handler: syncing the filesystems +// sleeps, and S5 runs AML. +static void +power_off_work(uacpi_handle ctx) +{ + (void)ctx; + cprintf("acpi: power button pressed; syncing and powering off\n"); + schfs_ksync_all(); + acpi_poweroff(); + cprintf("acpi: power-off failed; it is safe to switch off now\n"); +} + +static uacpi_interrupt_ret +power_button(uacpi_handle ctx) +{ + static int once; + (void)ctx; + if (!once) { + once = 1; + uacpi_kernel_schedule_work(UACPI_WORK_NOTIFICATION, power_off_work, 0); + } + return UACPI_INTERRUPT_HANDLED; +} + +int +acpi_poweroff(void) +{ + if (!ready) + return -1; + if (uacpi_unlikely_error(uacpi_prepare_for_sleep_state(UACPI_SLEEP_STATE_S5))) + return -1; + cli(); + uacpi_enter_sleep_state(UACPI_SLEEP_STATE_S5); + sti(); + return -1; +} + +int +acpi_reboot(void) +{ + if (!ready) + return -1; + uacpi_reboot(); + return -1; +} + +// --- PCI interrupt routing --------------------------------------------------- + +#define MAXBUS 256 + +// The bridge leading to each secondary bus, from the devices pci_init found. +static struct pci_dev *upstream[MAXBUS]; + +static uacpi_namespace_node *root_bridge; + +static uacpi_iteration_decision +found_root(void *user, uacpi_namespace_node *node, uacpi_u32 depth) +{ + uacpi_u64 bbn = 0; + (void)user; + (void)depth; + // _BBN is the bridge's first bus. Absent means 0, and bus 0 is the one + // whose devices are all that most machines have. + uacpi_eval_simple_integer(node, "_BBN", &bbn); + if (bbn == 0 && root_bridge == 0) { + root_bridge = node; + return UACPI_ITERATION_DECISION_BREAK; + } + return UACPI_ITERATION_DECISION_CONTINUE; +} + +// The namespace node of the device at (slot, func) below `parent`: the child +// whose _ADR says so. +static uacpi_namespace_node * +child_at(uacpi_namespace_node *parent, int slot, int func) +{ + uacpi_namespace_node *child = 0; + uacpi_u64 adr; + + while (uacpi_namespace_node_next(parent, &child) == UACPI_STATUS_OK) { + if (uacpi_eval_simple_integer(child, "_ADR", &adr) == UACPI_STATUS_OK && + adr == (((uacpi_u64)slot << 16) | (uacpi_u64)func)) + return child; + } + return 0; +} + +struct irqfind { + uint32 index; + int gsi; + int mode; +}; + +static int +mode_of(int triggering, int polarity) +{ + int level = triggering == UACPI_TRIGGERING_LEVEL; + int low = polarity == UACPI_POLARITY_ACTIVE_LOW; + return level ? (low ? IRQ_LEVEL_LOW : IRQ_LEVEL_HIGH) + : (low ? IRQ_EDGE_LOW : IRQ_EDGE_HIGH); +} + +static uacpi_iteration_decision +link_irq(void *user, uacpi_resource *r) +{ + struct irqfind *f = user; + + if (r->type == UACPI_RESOURCE_TYPE_IRQ && r->irq.num_irqs > 0) { + f->gsi = r->irq.irqs[f->index < r->irq.num_irqs ? f->index : 0]; + f->mode = mode_of(r->irq.triggering, r->irq.polarity); + return UACPI_ITERATION_DECISION_BREAK; + } + if (r->type == UACPI_RESOURCE_TYPE_EXTENDED_IRQ && + r->extended_irq.num_irqs > 0) { + f->gsi = r->extended_irq.irqs[f->index < r->extended_irq.num_irqs + ? f->index : 0]; + f->mode = mode_of(r->extended_irq.triggering, r->extended_irq.polarity); + return UACPI_ITERATION_DECISION_BREAK; + } + return UACPI_ITERATION_DECISION_CONTINUE; +} + +// Look (slot, pin) up in a _PRT. Returns the GSI, or -1. A link device's +// current setting is used as it stands: firmware programs every link at +// boot, and one it left disabled is reported rather than reconfigured. +static int +prt_lookup(uacpi_pci_routing_table *t, int slot, int pin, int *mode) +{ + for (uacpi_size i = 0; i < t->num_entries; i++) { + uacpi_pci_routing_table_entry *e = &t->entries[i]; + if ((e->address >> 16) != (uint32)slot || e->pin != pin) + continue; + + if (e->source == 0) { + *mode = IRQ_LEVEL_LOW; // a bare GSI is PCI's signalling + return e->index; + } + + uacpi_resources *res; + struct irqfind f = { e->index, -1, IRQ_LEVEL_LOW }; + if (uacpi_get_current_resources(e->source, &res) != UACPI_STATUS_OK) + return -1; + uacpi_for_each_resource(res, link_irq, &f); + uacpi_free_resources(res); + if (f.gsi <= 0) { + uacpi_object_name nm = uacpi_namespace_node_name(e->source); + char name[5] = { nm.text[0], nm.text[1], nm.text[2], nm.text[3], 0 }; + cprintf("acpi: link %s for slot %d is disabled\n", name, slot); + return -1; + } + *mode = f.mode; + return f.gsi; + } + return -1; +} + +static int +route_one(struct pci_dev *d, uacpi_pci_routing_table *roottab, int *mode) +{ + // The chain of bridges from bus 0 down to this device's bus. + struct pci_dev *chain[8]; + int n = 0, bus = d->bus; + while (bus != 0 && n < 8 && upstream[bus]) { + chain[n++] = upstream[bus]; + bus = upstream[bus]->bus; + } + if (bus != 0) + return -1; // a bus not reached from bus 0 + + // Walk down the namespace alongside it, as far as the namespace describes + // the bridges. The deepest bridge with its own _PRT is where the lookup + // happens; below it, INTx pins are swizzled by the PCI-to-PCI bridge rule. + uacpi_namespace_node *node = root_bridge; + uacpi_pci_routing_table *tab = roottab, *owned = 0; + int at = n; // chain[at..] lies below tab's bus + for (int i = n - 1; i >= 0 && node; i--) { + node = child_at(node, chain[i]->slot, chain[i]->func); + uacpi_pci_routing_table *t; + if (node && uacpi_get_pci_routing_table(node, &t) == UACPI_STATUS_OK) { + if (owned) + uacpi_free_pci_routing_table(owned); + tab = owned = t; + at = i; + } + } + + int pin = pci_read8(d->bus, d->slot, d->func, 0x3D) - 1; // 0 = INTA + int slot = d->slot; + for (int i = 0; i < at; i++) { + pin = (pin + slot) % 4; + slot = chain[i]->slot; + } + // The device is now (slot, pin) on the bus `tab` describes: that bridge's + // secondary bus, or bus 0 for the root table. + + int gsi = prt_lookup(tab, slot, pin, mode); + if (owned) + uacpi_free_pci_routing_table(owned); + return gsi; +} + +void +acpi_pci_route(void) +{ + uacpi_pci_routing_table *roottab; + int i, routed = 0; + + if (!ready) + return; + uacpi_find_devices("PNP0A08", found_root, 0); // PCI Express + if (!root_bridge) + uacpi_find_devices("PNP0A03", found_root, 0); // conventional PCI + if (!root_bridge || + uacpi_get_pci_routing_table(root_bridge, &roottab) != UACPI_STATUS_OK) { + kinfo("acpi: no _PRT on the root bridge; PCI lines left as found\n"); + return; + } + + for (i = 0; i < pci_count(); i++) { + struct pci_dev *d = pci_get(i); + if ((d->header_type & 0x7F) == 1) { + int sec = pci_read8(d->bus, d->slot, d->func, 0x19); + if (sec > 0 && sec < MAXBUS) + upstream[sec] = d; + } + } + + for (i = 0; i < pci_count(); i++) { + struct pci_dev *d = pci_get(i); + int mode, gsi; + + if (pci_read8(d->bus, d->slot, d->func, 0x3D) == 0) + continue; // uses no INTx pin at all + gsi = route_one(d, roottab, &mode); + if (gsi < 0) + continue; + if (gsi >= NIRQ_IOAPIC) { + // A second I/O APIC's input. Not reachable here; MSI still is. + d->irq = 0xFF; + continue; + } + if (gsi != d->irq) + kinfo("acpi: %d:%d.%d INT%c is gsi %d, not %d\n", d->bus, d->slot, + d->func, 'A' + pci_read8(d->bus, d->slot, d->func, 0x3D) - 1, + gsi, d->irq); + d->irq = gsi; + d->irq_mode = mode; + routed++; + } + uacpi_free_pci_routing_table(roottab); + kinfo("acpi: %d PCI interrupt%s routed through _PRT\n", routed, + routed == 1 ? "" : "s"); +} diff --git a/kernel/acpi/host.c b/kernel/acpi/host.c new file mode 100644 index 0000000..1375dbd --- /dev/null +++ b/kernel/acpi/host.c @@ -0,0 +1,620 @@ +// 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 "types.h" +#include "defs.h" +#include "param.h" +#include "memlayout.h" +#include "mmu.h" +#include "x86.h" +#include "spinlock.h" +#include "proc.h" +#include "irq.h" +#include "dma.h" +#include "kmem.h" +#include "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: + 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"); +} diff --git a/kernel/arch/x86_64/acpi.c b/kernel/arch/x86_64/acpi.c index 0204ee1..1025ff4 100644 --- a/kernel/arch/x86_64/acpi.c +++ b/kernel/arch/x86_64/acpi.c @@ -24,6 +24,8 @@ #include "mmu.h" #include "proc.h" #include "x86.h" +#include "irq.h" +#include "acpi.h" extern struct cpu cpus[NCPU]; extern int ncpu; @@ -79,6 +81,7 @@ struct acpi_madt { // routing overrides that ioapic.c does not consult yet. #define MADT_LAPIC 0 #define MADT_IOAPIC 1 +#define MADT_ISO 2 #define MADT_LAPIC_OVERRIDE 5 #define MADT_X2APIC 9 @@ -123,6 +126,25 @@ struct madt_lapic_override { uint64 addr; } __attribute__((packed)); +// Interrupt source override: ISA IRQ `source` is really GSI `gsi`, signalled +// as `flags` says -- polarity in bits 1:0, trigger in 3:2, each 0 for "as the +// bus does it", 1 for high/edge and 3 for low/level. +struct madt_iso { + struct madt_entry e; + uint8 bus; + uint8 source; + uint32 gsi; + uint16 flags; +} __attribute__((packed)); + +static struct { + int set; + uint32 gsi; + uint16 flags; +} iso[16]; + +static uint64 rsdp_phys; + static int checksum_ok(const void *p, uint32 len) { @@ -267,6 +289,7 @@ acpi_init(void) struct acpi_rsdp *rsdp = find_rsdp(); if (!rsdp) return 0; + rsdp_phys = V2P(rsdp); // The FADT first, for the PM timer: PM_TMR_BLK at offset 76, and bit 8 of // the flags at 112 says whether it counts 32 bits or 24. A hardware-reduced @@ -326,6 +349,15 @@ acpi_init(void) } break; } + case MADT_ISO: { + struct madt_iso *o = (struct madt_iso *)p; + if (o->source < 16) { + iso[o->source].set = 1; + iso[o->source].gsi = o->gsi; + iso[o->source].flags = o->flags; + } + break; + } case MADT_LAPIC_OVERRIDE: { struct madt_lapic_override *o = (struct madt_lapic_override *)p; lapic = (uint *)P2V(o->addr); @@ -351,3 +383,33 @@ acpi_init(void) ncpu, ncpu == 1 ? "" : "s", lapic, ioapicid); return 1; } + +uint64 +acpi_rsdp_phys(void) +{ + return rsdp_phys; +} + +int +acpi_isa_gsi(int isa, int dflt, int *mode) +{ + int level, low; + + *mode = dflt; + if (isa < 0 || isa >= 16 || !iso[isa].set) + return isa; + + level = IRQ_IS_LEVEL(dflt); + low = dflt == IRQ_LEVEL_LOW || dflt == IRQ_EDGE_LOW; + switch (iso[isa].flags & 3) { + case 1: low = 0; break; + case 3: low = 1; break; + } + switch ((iso[isa].flags >> 2) & 3) { + case 1: level = 0; break; + case 3: level = 1; break; + } + *mode = level ? (low ? IRQ_LEVEL_LOW : IRQ_LEVEL_HIGH) + : (low ? IRQ_EDGE_LOW : IRQ_EDGE_HIGH); + return iso[isa].gsi; +} diff --git a/kernel/arch/x86_64/ioapic.c b/kernel/arch/x86_64/ioapic.c index 17be31c..e40652b 100644 --- a/kernel/arch/x86_64/ioapic.c +++ b/kernel/arch/x86_64/ioapic.c @@ -88,8 +88,12 @@ ioapicroute(int irq, int cpunum, int level_low) { uint lo = T_IRQ0 + irq; - if(level_low) - lo |= INT_LEVEL | INT_ACTIVELOW; + // A mode from include/irq.h; 1, the PCI case, is what `level_low` meant + // when it was a flag. + if(level_low == 1 || level_low == 2) + lo |= INT_LEVEL; + if(level_low == 1 || level_low == 3) + lo |= INT_ACTIVELOW; // Program the destination before unmasking, so the entry is never briefly // live pointing at CPU 0 when it was meant for another. diff --git a/kernel/core/main.c b/kernel/core/main.c index 029ae37..4a48058 100644 --- a/kernel/core/main.c +++ b/kernel/core/main.c @@ -11,6 +11,7 @@ #include "dma.h" #include "klog.h" #include "usb.h" +#include "acpi.h" static void *klog_base; // the persistent log, just above the kernel image @@ -294,6 +295,9 @@ kmain(uint32 fb_addr) // and change nothing. platform_register_devices(fb_addr); pciuser_init(); + // The firmware's AML, loaded before the bus is: pci_init() asks it where + // each device's interrupt really goes. See kernel/acpi/aml.c. + acpi_aml_init(); pci_init(); if (boot_verbose) cprintf("verbose boot: pci_init done\n"); @@ -369,6 +373,8 @@ kmain(uint32 fb_addr) // After the poller, which is what keeps a USB controller whose INTx // never arrives working, and after the scheduler, which it sleeps in. usb_hotplug_start(); + // Where AML runs for the SCI: the power button, GPEs, notifications. + acpi_thread_start(); // The debugger's own thread, for the same reason netinit's comes after // sinit: kthread_create needs a usable process table. From here on Ctrl-G // on the console answers, with no filesystem mounted and nothing in diff --git a/kernel/dev/irq.c b/kernel/dev/irq.c index 31e8955..587548d 100644 --- a/kernel/dev/irq.c +++ b/kernel/dev/irq.c @@ -300,7 +300,7 @@ irq_info(char *buf, int cap) if(line[irq].polled) off = emit(buf, cap, off, " [polled]"); off = emit(buf, cap, off, line[irq].msi ? " msi\n" : - line[irq].level_low ? " level\n" : " edge\n"); + IRQ_IS_LEVEL(line[irq].level_low) ? " level\n" : " edge\n"); } // How many timer interrupts were lost. @@ -424,7 +424,7 @@ irq_poll_thread(void) if(line[irq].n == 0 || line[irq].msi || reserved(irq)) continue; int poll = line[irq].polled || - (line[irq].level_low && line[irq].delivered == 0 && + (IRQ_IS_LEVEL(line[irq].level_low) && line[irq].delivered == 0 && now - line[irq].since > POLL_AFTER); if(!poll) continue; diff --git a/kernel/dev/pci.c b/kernel/dev/pci.c index 03af6b6..a10ba13 100644 --- a/kernel/dev/pci.c +++ b/kernel/dev/pci.c @@ -26,6 +26,7 @@ #include "device.h" #include "traps.h" #include "irq.h" +#include "acpi.h" #include "memlayout.h" #include "proc.h" @@ -275,7 +276,7 @@ pci_irq_attach(struct pci_dev *d, irq_handler_t fn, void *arg, if(d->irq == 0xFF || d->irq >= NIRQ_IOAPIC) return -1; - if(irq_register(d->irq, fn, arg, name, 1) < 0) + if(irq_register(d->irq, fn, arg, name, d->irq_mode) < 0) return -1; d->irq_kind = PCI_IRQ_INTX; d->irq_line = d->irq; @@ -394,6 +395,7 @@ add_func(uint8 bus, uint8 slot, uint8 func) d->class = pci_read8(bus, slot, func, PCI_CLASS); d->header_type = pci_read8(bus, slot, func, PCI_HEADER_TYPE); d->irq = pci_read8(bus, slot, func, PCI_INTERRUPT_LINE); + d->irq_mode = IRQ_LEVEL_LOW; // Only header type 0 has six BARs. A bridge (type 1) has two and a very // different layout, and reading it as though it had six would hand out @@ -692,5 +694,10 @@ pci_init(void) scan_bus(0); kinfo("pci: %d device%s\n", ndevs, ndevs == 1 ? "" : "s"); + // Before any driver binds: the line each device's config space names is + // the 8259's number, which is not the IOAPIC pin it is wired to. ACPI's + // _PRT says which pin it really is; see kernel/acpi/aml.c. + acpi_pci_route(); + pci_bind_all(); } diff --git a/kernel/sys/sys_proc.c b/kernel/sys/sys_proc.c index 92ee72b..c65df6d 100644 --- a/kernel/sys/sys_proc.c +++ b/kernel/sys/sys_proc.c @@ -9,6 +9,7 @@ #include "posix.h" #include "uaccess.h" #include "errno.h" +#include "acpi.h" addr_t sys_fork(void) { return fork(); } addr_t sys_wait(void) { return wait(); } @@ -152,6 +153,10 @@ sys_uptime(void) addr_t sys_reboot(void) { cprintf("System is rebooting...\n"); + // The FADT's reset register first: it is the one the board's designer + // wired up. Then the PCI reset port and the keyboard controller, which + // are what nearly every PC has had since there were PCs. + acpi_reboot(); outb(0xCF9, 0x06); outb(0x64, 0xFE); return 0; @@ -160,6 +165,12 @@ addr_t sys_reboot(void) addr_t sys_shutdown(void) { cprintf("System is shutting down...\n"); + // S5, through the firmware's own PM1 registers and its _PTS. The port + // writes after it are QEMU's, Bochs' and VirtualBox's fixed addresses, + // kept for a machine whose AML did not load; a real board answers none + // of them. + schfs_ksync_all(); + acpi_poweroff(); outw(0x604, 0x2000); outw(0xB004, 0x2000); outw(0x4004, 0x3400); diff --git a/ports/uacpi/Makefile b/ports/uacpi/Makefile new file mode 100644 index 0000000..c120edd --- /dev/null +++ b/ports/uacpi/Makefile @@ -0,0 +1,44 @@ +# SPDX-License-Identifier: GPL-2.0-or-later +# uACPI -- the ACPI AML interpreter. MIT; see LICENSE in the tarball and +# docs/licensing.md. +# +# Kernel code, like lwIP: nothing is installed into the sysroot. This +# directory fetches and unpacks a pinned release, and the top-level Makefile +# compiles its sources straight into the kernel against kernel/acpi/, which +# supplies everything uACPI asks of a host. That is why there is no build or +# install rule below and no dependency on musl. +# +# Chosen over ACPICA, which does the same job in roughly ten times the code +# and asks more of its host, and over writing an interpreter here: AML is a +# whole language, every firmware vendor's compiler emits its own dialect of +# it, and uACPI is tested against real machines' tables as a matter of course. + +include ../mk/port.mk + +UACPI_VERSION := 6.1.1 +UACPI_URL := https://github.com/uACPI/uACPI/archive/refs/tags/$(UACPI_VERSION).tar.gz +UACPI_SHA256 := 40f2e278b2080e69f4eaa3384404944874d430e50b72c1066cab2ba476e2b769 + +TARBALL := $(DISTDIR)/uacpi-$(UACPI_VERSION).tar.gz + +$(TARBALL): | $(DISTDIR) + curl -fsSL -o $@ $(UACPI_URL) + sh $(SHA256CHECK) "$(UACPI_SHA256)" "$@" + +download: $(TARBALL) + +$(SRCDIR): $(TARBALL) + mkdir -p $(PORTDIR) + cd $(PORTDIR) && tar xzf $(abspath $<) + mv $(PORTDIR)/uACPI-$(UACPI_VERSION) $@ + +# The kernel build depends on this directory existing, nothing more. +all: | $(SRCDIR) + +install: + +clean: + rm -rf $(PORTDIR) + +distclean: clean + rm -f $(TARBALL)