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

228
kernel/proc/mapname.c Normal file
View file

@ -0,0 +1,228 @@
// Which file each part of an address space came from.
//
// mmap of a file copies it into anonymous memory (see sys_mem.c), so the page
// tables and the vma records know nothing about files. This remembers the
// name beside them -- for /proc/<pid>/maps, and so that a process killed by a
// fault is reported with the library and offset it died in rather than a bare
// address. There is no ptrace here and so no debugger: Epiphany crashing at
// "rip 0x8a094bea" in a library loaded with dlopen was a dead end without it.
//
// A dynamic linker maps each library as one span from offset 0 and then
// places its segments inside with MAP_FIXED; those inner maps of the same
// file are absorbed, so a library is one entry. Only diagnostics read this,
// and it is allowed to be approximate at the edges: a span unmapped in part
// keeps its name.
#include "core/types.h"
#include "core/defs.h"
#include "core/param.h"
#include "arch/memlayout.h"
#include "arch/mmu.h"
#include "core/spinlock.h"
#include "core/sleeplock.h"
#include "core/proc.h"
#include "core/kmem.h"
struct mapname {
struct mapname *next;
uint64 start, end, off;
char path[96];
};
static struct spinlock mnlock;
static int mnlock_ready;
static void
mn_lock(void)
{
if (!mnlock_ready) {
initlock(&mnlock, "mapname");
mnlock_ready = 1;
}
acquire(&mnlock);
}
// Forget names wholly inside [start, end), and trim those hanging over an
// edge of it. A name the range falls inside stays whole.
static void
mn_cut(struct mm *m, uint64 start, uint64 end)
{
struct mapname **pp = &m->names;
while (*pp) {
struct mapname *n = *pp;
if (n->start >= start && n->end <= end) {
*pp = n->next;
kmfree(n);
continue;
}
if (n->start < start && n->end > start && n->end <= end)
n->end = start;
else if (n->start >= start && n->start < end && n->end > end) {
n->off += end - n->start;
n->start = end;
}
pp = &n->next;
}
}
void
mapname_add(struct mm *m, uint64 start, uint64 len, uint64 off, const char *path)
{
struct mapname *n;
if (!path || !*path)
return;
mn_lock();
for (n = m->names; n; n = n->next)
if (n->start <= start && start + len <= n->end && strncmp(n->path, path, sizeof(n->path) - 1) == 0) {
release(&mnlock);
return; // a segment of a library already named
}
mn_cut(m, start, start + len);
release(&mnlock);
if ((n = kmalloc(sizeof(*n))) == 0)
return;
n->start = start;
n->end = start + len;
n->off = off;
safestrcpy(n->path, path, sizeof(n->path));
mn_lock();
n->next = m->names;
m->names = n;
release(&mnlock);
}
void
mapname_unmap(struct mm *m, uint64 start, uint64 len)
{
mn_lock();
mn_cut(m, start, start + len);
release(&mnlock);
}
void
mapname_free(struct mm *m)
{
mn_lock();
struct mapname *n = m->names;
m->names = 0;
release(&mnlock);
while (n) {
struct mapname *next = n->next;
kmfree(n);
n = next;
}
}
// fork: the child's address space is the parent's, names and all.
void
mapname_copy(struct mm *dst, struct mm *src)
{
struct mapname *n;
mn_lock();
for (n = src->names; n; n = n->next) {
struct mapname *c = kmalloc(sizeof(*c));
if (!c)
break;
*c = *n;
c->next = dst->names;
dst->names = c;
}
release(&mnlock);
}
// The name addr falls in and its offset in the file, copied out. 0 if found.
int
mapname_find(struct mm *m, uint64 addr, char *out, int cap, uint64 *off)
{
int r = -1;
mn_lock();
for (struct mapname *n = m->names; n; n = n->next)
if (n->start <= addr && addr < n->end) {
safestrcpy(out, n->path, cap);
*off = n->off + (addr - n->start);
r = 0;
break;
}
release(&mnlock);
return r;
}
static int
put(char *buf, int n, int cap, const char *s)
{
while (*s && n < cap - 1)
buf[n++] = *s++;
return n;
}
static int
puthex(char *buf, int n, int cap, uint64 v, int width)
{
char t[17];
int i = 16;
t[16] = 0;
do { t[--i] = "0123456789abcdef"[v & 15]; v >>= 4; } while (v && i > 0);
while (16 - i < width && i > 0)
t[--i] = '0';
return put(buf, n, cap, t + i);
}
// /proc/<pid>/maps, in Linux's layout. Named spans and the anonymous records
// beside them; permissions are not tracked per page here, so every line
// says rw and private, and only the names are to be trusted.
int
mapname_gen(struct mm *m, char *buf, int cap)
{
int n = 0;
mn_lock();
for (struct mapname *e = m->names; e; e = e->next) {
n = puthex(buf, n, cap, e->start, 8);
n = put(buf, n, cap, "-");
n = puthex(buf, n, cap, e->end, 8);
n = put(buf, n, cap, " r-xp ");
n = puthex(buf, n, cap, e->off, 8);
n = put(buf, n, cap, " 00:00 0 ");
n = put(buf, n, cap, e->path);
n = put(buf, n, cap, "\n");
}
release(&mnlock);
for (int i = 0; i < NVMA; i++) {
struct vma *v = &m->vma[i];
if (!v->used)
continue;
n = puthex(buf, n, cap, v->addr, 8);
n = put(buf, n, cap, "-");
n = puthex(buf, n, cap, v->addr + v->length, 8);
n = put(buf, n, cap, v->shmid >= 0 ? " rw-s 00000000 00:00 0 [memfd]\n"
: " rw-p 00000000 00:00 0\n");
}
return n;
}
// Said when a process dies of a fault: where its instruction pointer was,
// and where the return address on top of its stack points, by file. The
// second is the caller, which is what is left to go on when the first is in
// generated code.
void
mapname_report(struct proc *p)
{
char path[96];
uint64 off, ret = 0;
if (!p->mm || !p->tf)
return;
if (mapname_find(p->mm, p->tf->rip, path, sizeof(path), &off) == 0)
cprintf("pid %d %s: rip %p is %s+0x%lx\n", p->pid, p->name,
(void *)p->tf->rip, path, off);
else
cprintf("pid %d %s: rip %p is in no file\n", p->pid, p->name,
(void *)p->tf->rip);
if (fetchaddr(p->tf->rsp, &ret) == 0 &&
mapname_find(p->mm, ret, path, sizeof(path), &off) == 0)
cprintf("pid %d %s: [rsp] %p is %s+0x%lx\n", p->pid, p->name,
(void *)ret, path, off);
}

View file

@ -334,6 +334,7 @@ mm_put(struct mm *m)
if (!last)
return;
mm_release_shared(m);
mapname_free(m);
if (m->pgdir)
freevm(m->pgdir);
m->pgdir = 0;
@ -720,6 +721,7 @@ fork(void)
shm_ref(np->mm->vma[i].shmid);
}
np->mm->mmap_next = myproc()->mm->mmap_next;
mapname_copy(np->mm, pm);
mm_unlock(pm);
memmove(np->tf, myproc()->tf, sizeof(struct trapframe));
@ -1360,6 +1362,17 @@ exit(int status)
struct proc *p;
int fd;
// Killed by a fault of its own -- the trapframe holds an exception, not a
// syscall -- so say which file it was executing. The register dump from
// trap() gives addresses; this gives the library they are in.
{
int sig = status & 0x7f;
struct trapframe *tf = myproc()->tf;
if ((sig == SIGSEGV || sig == SIGBUS || sig == SIGILL || sig == SIGFPE) &&
tf && (tf->cs & 3) == DPL_USER && tf->trapno < 32)
mapname_report(myproc());
}
if(myproc() == initproc) {
// Returning from exit() is the *success* path here. exec() does not fail
// to return -- it returns 0 having replaced the address space, and the new
@ -3006,3 +3019,24 @@ proc_cputime(uint64 *user, uint64 *sys)
*user = u;
*sys = s;
}
// /proc/<pid>/maps: see mapname_gen(). 0 bytes for a pid that is gone.
int
proc_maps(int pid, char *buf, int cap)
{
struct mm *m = 0;
acquire(&ptable.lock);
for (struct proc *p = ptable.proc; p < &ptable.proc[NPROC]; p++)
if (p->pid == pid && p->state != UNUSED && p->state != ZOMBIE && p->mm) {
m = p->mm;
mm_get(m);
break;
}
release(&ptable.lock);
if (!m)
return 0;
int n = mapname_gen(m, buf, cap);
mm_put(m);
return n;
}