Keyboard lights, TLB shootdowns, battery in sysfs, /proc/pid/maps

Caps, Num and Scroll Lock now light their LEDs on PS/2 and USB keyboards,
share one state between keyboards, and stop toggling on key repeat. X sets
them through evdev writes while it holds the keyboard.

Unmapping memory in a process with threads on other CPUs now flushes their
TLBs with an IPI. Without it a freed page stayed reachable through stale
translations, which corrupted Epiphany's heap on SMP.

ACPI batteries and AC adapters appear under /sys/class/power_supply.
/proc/<pid>/maps lists file mappings, and a process killed by a fault
names the file and offset it died in. r8169 gets a 256-entry RX ring.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S1giNvrHTrPMPw7V6x1kQ4
This commit is contained in:
Nexoniarz 2026-10-25 13:43:27 +01:00
commit 438e7af141
18 changed files with 945 additions and 9 deletions

View file

@ -581,3 +581,19 @@ void cmostime(struct rtcdate *r)
*r = t1;
r->year += 2000;
}
// A fixed interrupt to one processor, by APIC id. Waits for the local APIC
// to say it has sent the last one before writing the next: two ICR writes
// back to back lose the first.
void
lapic_send_ipi(int apicid, int vector)
{
if (!lapic)
return;
while (lapic[ICRLO] & DELIVS)
;
lapicw(ICRHI, apicid << 24);
lapicw(ICRLO, vector); // fixed, physical, edge, no shorthand
while (lapic[ICRLO] & DELIVS)
;
}

View file

@ -10,6 +10,8 @@
#include "core/spinlock.h"
#include "arch/vmap.h"
#include "ldls/kldls.h"
#include "core/irq.h"
#include "arch/traps.h"
int mappages(pml4e_t *pgdir, void *va, addr_t size, addr_t pa, int perm);
static pte_t * walkpgdir(pml4e_t *pml4, const void *va, int alloc);
@ -789,6 +791,90 @@ defer_free(uint64 pa)
}
}
// --- TLB shootdown ------------------------------------------------------------
//
// The deferred free above keeps a stale translation from reaching a page
// someone else now owns. It does not keep one address space coherent: when a
// thread unmaps a range and mmap hands the same addresses straight back, a
// thread on another processor goes on reading and writing the *old* pages
// through its TLB. Its stores vanish and its loads are stale. musl's malloc
// noticed first -- a group's metadata no longer pointing back at it -- and
// Epiphany, forty threads on four processors, died at a different address
// every run.
//
// So whoever unmaps from a shared address space interrupts the processors
// running it and waits until each has reloaded CR3. The vector is one of the
// MSI lines, which trap() already dispatches with an EOI.
static int tlb_irq = -1;
static volatile int tlb_busy;
static volatile int tlb_acks;
static void
tlb_ipi(void *arg)
{
(void)arg;
lcr3(rcr3()); // everything but global pages
__atomic_add_fetch(&tlb_acks, 1, __ATOMIC_RELEASE);
}
void
tlb_init(void)
{
tlb_irq = irq_alloc_msi();
if (tlb_irq < 0 || irq_register(tlb_irq, tlb_ipi, 0, "tlb", IRQ_EDGE_HIGH | IRQ_QUIET) < 0) {
cprintf("vm: no vector for TLB shootdown; threads on several CPUs are unsafe\n");
tlb_irq = -1;
}
}
// Every other processor now running pgdir forgets its translations.
static void
tlb_shootdown(pml4e_t *pgdir)
{
int targets = 0, me;
if (tlb_irq < 0 || ncpu_online < 2)
return;
// Only one shootdown at a time, taken without disabling interrupts: two
// processors each waiting with interrupts off for the other to answer
// would wait for ever.
while (__atomic_exchange_n(&tlb_busy, 1, __ATOMIC_ACQUIRE))
__builtin_ia32_pause();
__atomic_store_n(&tlb_acks, 0, __ATOMIC_RELAXED);
pushcli();
me = cpunum();
for (int c = 0; c < ncpu; c++) {
if (c == me || cpu_pgdir[c] != pgdir || !cpus[c].started)
continue;
lapic_send_ipi(cpus[c].apicid, T_IRQ0 + tlb_irq);
targets++;
}
popcli();
// A processor that switched away in the meantime still answers: the
// reload is harmless. One that never does is said once, not waited on.
uint64 spins = 0;
while (__atomic_load_n(&tlb_acks, __ATOMIC_ACQUIRE) < targets) {
__builtin_ia32_pause();
if (++spins == 200000000ULL) {
static int said;
if (!said++)
cprintf("vm: a processor did not answer a TLB shootdown\n");
break;
}
}
__atomic_store_n(&tlb_busy, 0, __ATOMIC_RELEASE);
}
// After unmapping from pgdir: shoot down if other threads may hold it.
static void
tlb_after_unmap(pml4e_t *pgdir)
{
struct proc *p = myproc();
if (p && p->pgdir == pgdir && mm_shared(p->mm))
tlb_shootdown(pgdir);
}
// Free a user page just unmapped from pgdir: now, or once no TLB can hold it.
static void
user_page_free(pml4e_t *pgdir, uint64 pa)
@ -836,6 +922,7 @@ vm_unmap_range(pml4e_t *pgdir, uint64 va, uint64 length)
{
uint64 a = PGROUNDDOWN(va);
uint64 end = PGROUNDUP(va + length);
int any = 0;
for (; a < end; a += PGSIZE) {
pte_t *pte = walkpgdir(pgdir, (void*)a, 0);
if (pte && (*pte & PTE_P)) {
@ -843,8 +930,11 @@ vm_unmap_range(pml4e_t *pgdir, uint64 va, uint64 length)
*pte = 0;
invlpg((void*)a);
user_page_free(pgdir, pa);
any = 1;
}
}
if (any)
tlb_after_unmap(pgdir);
}
// Tear down a mapping without freeing what is underneath it.
@ -860,13 +950,17 @@ vm_unmap_norelease(pml4e_t *pgdir, uint64 va, uint64 length)
{
uint64 a = PGROUNDDOWN(va);
uint64 end = PGROUNDUP(va + length);
int any = 0;
for (; a < end; a += PGSIZE) {
pte_t *pte = walkpgdir(pgdir, (void*)a, 0);
if (pte && (*pte & PTE_P)) {
*pte = 0;
invlpg((void*)a);
any = 1;
}
}
if (any)
tlb_after_unmap(pgdir);
}
// Duplicate an mmap()'d VMA region from src into dst for fork().
@ -996,6 +1090,7 @@ deallocuvm(pml4e_t *pgdir, uint64 oldsz, uint64 newsz)
user_page_free(pgdir, pa);
}
}
tlb_after_unmap(pgdir);
return newsz;
}