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

@ -71,7 +71,8 @@
#define PF_FD 7 // the directory /proc/<pid>/fd
#define PF_TASK 8 // the directory /proc/<pid>/task
#define PF_COMM 9
#define PF_MAX 9
#define PF_MAPS 10
#define PF_MAX 10
#define PF_FDENT 0x80 // | fd: one link in it
#define INO_PID(ino) ((int)((ino) >> 8))
@ -145,6 +146,9 @@ static const struct global_file pidfiles[] = {
// The name alone, a line of it. btop reads this and nothing else for the
// Program column.
{ "comm", PF_COMM },
// Which file each span of the address space came from. See
// kernel/proc/mapname.c; only the names and ranges are to be trusted.
{ "maps", PF_MAPS },
};
#define NPIDFILES ((int)(sizeof(pidfiles) / sizeof(pidfiles[0])))
@ -321,6 +325,7 @@ gen_file(uint64 ino, char *buf, int cap)
case PF_STATM: gen_pid_statm(&s, &pi); break;
case PF_EXE: sbputs(&s, pi.exe[0] ? pi.exe : pi.name); break;
case PF_COMM: sbputs(&s, pi.name); sbputc(&s, '\n'); break;
case PF_MAPS: return proc_maps(INO_PID(ino), buf, cap);
case PF_SCHEDSTAT:
sbputi(&s, (int64)pi.vruntime); sbputc(&s, ' ');
sbputi(&s, (int64)pi.nice); sbputc(&s, ' ');

View file

@ -13,6 +13,8 @@
// /sys/firmware/memmap/N/ start end type
// /sys/firmware/efi/ present when UEFI booted the kernel
// /sys/class/dmi/id/product_name ... the machine, as text
// /sys/class/power_supply/BAT0/ capacity status energy_* ... (ACPI)
// /sys/class/power_supply/AC/ type online
//
// Why Linux's layout rather than one of its own: everything that reads
// hardware information already reads it from these paths. toybox's lspci and
@ -42,6 +44,7 @@
#include "dev/usb.h"
#include "core/hwinfo.h"
#include "gfx/gfx.h"
#include "dev/acpi.h"
// ---------------------------------------------------------------------------
// Inode numbers: kind, index, file -- the whole identity, since iget gives
@ -55,6 +58,7 @@
#define K_MEMMAP 5ULL // idx an entry of e820_nth
#define K_DMIID 6ULL // file a DMI_* field + 1
#define K_DMITAB 7ULL // file 1 the entry point, 2 the table
#define K_PSU 8ULL // idx an acpi_psu_get() index; file a psufiles[] index
#define INO(k, idx, f) (((uint64)(k) << 40) | ((uint64)(idx) << 8) | (uint64)(f))
#define I_KIND(i) ((int)((i) >> 40))
@ -63,7 +67,8 @@
enum {
D_ROOT, D_BUS, D_PCI, D_PCIDEV, D_USB, D_USBDEV, D_FW, D_ACPI, D_ACPITAB,
D_DMI, D_DMITAB, D_MEMMAP, D_EFI, D_CLASS, D_CLASSDMI, D_DMIID, D_NDIRS
D_DMI, D_DMITAB, D_MEMMAP, D_EFI, D_CLASS, D_CLASSDMI, D_DMIID, D_PSU,
D_NDIRS
};
static const struct { const char *name; int parent; } dirs[D_NDIRS] = {
@ -83,6 +88,7 @@ static const struct { const char *name; int parent; } dirs[D_NDIRS] = {
[D_CLASS] = { "class", D_ROOT },
[D_CLASSDMI] = { "dmi", D_CLASS },
[D_DMIID] = { "id", D_CLASSDMI },
[D_PSU] = { "power_supply", D_CLASS },
};
#define SYSFS_ROOT INO(K_DIR, D_ROOT, 0)
@ -135,6 +141,23 @@ static const char *const usbfiles[] = {
static const char *const memfiles[] = { 0, "start", "end", "type" };
// Linux's power_supply attributes. Which of them a battery has depends on
// whether its firmware counts in mWh (energy_*, power_now) or mAh
// (charge_*, current_now); see psu_has().
enum {
PS_TYPE = 1, PS_PRESENT, PS_STATUS, PS_CAPACITY, PS_LEVEL, PS_TECH,
PS_CYCLES, PS_VNOW, PS_VMIN, PS_ENOW, PS_EFULL, PS_EDESIGN, PS_PNOW,
PS_CNOW, PS_CFULL, PS_CDESIGN, PS_INOW, PS_MODEL, PS_MAKER, PS_SERIAL,
PS_UEVENT, PS_ONLINE, NPSUFILES
};
static const char *const psufiles[NPSUFILES] = {
0, "type", "present", "status", "capacity", "capacity_level", "technology",
"cycle_count", "voltage_now", "voltage_min_design", "energy_now",
"energy_full", "energy_full_design", "power_now", "charge_now",
"charge_full", "charge_full_design", "current_now", "model_name",
"manufacturer", "serial_number", "uevent", "online",
};
// ---------------------------------------------------------------------------
// Text
// ---------------------------------------------------------------------------
@ -234,6 +257,136 @@ usb_by_idx(int idx, struct usb_info *u)
return -1;
}
// BAT0, BAT1 for batteries and AC, AC1 for adapters, numbered in the order
// ACPI lists them -- the names Linux gives most laptops.
static void
psu_name(int idx, char *name)
{
struct acpi_psu b;
int nth = 0;
acpi_psu_get(idx, &b);
for (int i = 0; i < idx; i++) {
struct acpi_psu c;
if (acpi_psu_get(i, &c) == 0 && c.battery == b.battery)
nth++;
}
struct out o = { name, 0, 15 };
if (b.battery) { puts_(&o, "BAT"); putu(&o, (uint64)nth); }
else { puts_(&o, "AC"); if (nth) putu(&o, (uint64)nth); }
name[o.n] = 0;
}
static uint32
psu_capacity(const struct acpi_psu *b)
{
if (b->remaining == ACPI_UNKNOWN || b->full == ACPI_UNKNOWN || b->full == 0)
return ACPI_UNKNOWN;
uint64 c = (uint64)b->remaining * 100 / b->full;
return c > 100 ? 100 : (uint32)c;
}
// Whether this supply has the attribute at all. A value the firmware
// reports as unknown is a missing file, not a zero.
static int
psu_has(const struct acpi_psu *b, int f)
{
int mw = b->unit == 0, live = b->present;
if (!b->battery)
return f == PS_TYPE || f == PS_ONLINE || f == PS_UEVENT;
switch (f) {
case PS_TYPE: case PS_PRESENT: case PS_UEVENT: return 1;
case PS_STATUS: case PS_TECH: return live;
case PS_CAPACITY: case PS_LEVEL: return live && psu_capacity(b) != ACPI_UNKNOWN;
case PS_CYCLES: return live && b->cycles != ACPI_UNKNOWN && b->cycles != 0;
case PS_VNOW: return live && b->voltage != ACPI_UNKNOWN;
case PS_VMIN: return live && b->design_voltage != ACPI_UNKNOWN;
case PS_ENOW: return live && mw && b->remaining != ACPI_UNKNOWN;
case PS_EFULL: return live && mw && b->full != ACPI_UNKNOWN;
case PS_EDESIGN: return live && mw && b->design != ACPI_UNKNOWN;
case PS_PNOW: return live && mw && b->rate != ACPI_UNKNOWN;
case PS_CNOW: return live && !mw && b->remaining != ACPI_UNKNOWN;
case PS_CFULL: return live && !mw && b->full != ACPI_UNKNOWN;
case PS_CDESIGN: return live && !mw && b->design != ACPI_UNKNOWN;
case PS_INOW: return live && !mw && b->rate != ACPI_UNKNOWN;
case PS_MODEL: return live && b->model[0];
case PS_MAKER: return live && b->oem[0];
case PS_SERIAL: return live && b->serial[0];
}
return 0;
}
static const char *
psu_status(const struct acpi_psu *b)
{
if (b->state & 2) return "Charging";
if (b->state & 1) return "Discharging";
uint32 c = psu_capacity(b);
return (c != ACPI_UNKNOWN && c >= 98) ? "Full" : "Not charging";
}
static const char *
psu_level(uint32 c)
{
if (c < 5) return "Critical";
if (c < 15) return "Low";
if (c >= 98) return "Full";
if (c > 80) return "High";
return "Normal";
}
static void
gen_psu_value(struct out *o, const struct acpi_psu *b, int f)
{
switch (f) {
case PS_TYPE: puts_(o, b->battery ? "Battery" : "Mains"); break;
case PS_PRESENT: putc_(o, b->present ? '1' : '0'); break;
case PS_ONLINE: putc_(o, b->online ? '1' : '0'); break;
case PS_STATUS: puts_(o, psu_status(b)); break;
case PS_CAPACITY: putu(o, psu_capacity(b)); break;
case PS_LEVEL: puts_(o, psu_level(psu_capacity(b))); break;
case PS_TECH: puts_(o, b->kind[0] == 'L' || b->kind[0] == 'l' ? "Li-ion"
: b->technology == 0 ? "Unknown" : "Li-ion"); break;
case PS_CYCLES: putu(o, b->cycles); break;
// Linux reports micro-units; ACPI's are milli.
case PS_VNOW: putu(o, (uint64)b->voltage * 1000); break;
case PS_VMIN: putu(o, (uint64)b->design_voltage * 1000); break;
case PS_ENOW: case PS_CNOW: putu(o, (uint64)b->remaining * 1000); break;
case PS_EFULL: case PS_CFULL: putu(o, (uint64)b->full * 1000); break;
case PS_EDESIGN: case PS_CDESIGN: putu(o, (uint64)b->design * 1000); break;
case PS_PNOW: case PS_INOW: putu(o, (uint64)b->rate * 1000); break;
case PS_MODEL: puts_(o, b->model); break;
case PS_MAKER: puts_(o, b->oem); break;
case PS_SERIAL: puts_(o, b->serial); break;
}
}
static void
gen_psu(struct out *o, int idx, int f)
{
struct acpi_psu b;
if (acpi_psu_get(idx, &b) < 0)
return;
if (f != PS_UEVENT) {
gen_psu_value(o, &b, f);
putc_(o, '\n');
return;
}
// uevent: every attribute this supply has, as POWER_SUPPLY_<NAME>=value.
char name[16];
psu_name(idx, name);
puts_(o, "POWER_SUPPLY_NAME="); puts_(o, name); putc_(o, '\n');
for (int k = 1; k < NPSUFILES; k++) {
if (k == PS_UEVENT || !psu_has(&b, k))
continue;
puts_(o, "POWER_SUPPLY_");
for (const char *s = psufiles[k]; *s; s++)
putc_(o, *s >= 'a' && *s <= 'z' ? *s - 32 : *s);
putc_(o, '=');
gen_psu_value(o, &b, k);
putc_(o, '\n');
}
}
static int
children(uint64 dino, child_fn cb, void *ctx)
{
@ -296,6 +449,13 @@ children(uint64 dino, child_fn cb, void *ctx)
}
break;
}
case D_PSU:
for (int i = 0; i < acpi_psu_count(); i++) {
psu_name(i, name);
if (cb(ctx, INO(K_PSU, i, 0), 1, name))
return 1;
}
break;
case D_DMIID:
for (int f = 0; f < DMI_NFIELDS; f++)
if (dmi_field(f, name, sizeof name) >= 0 &&
@ -320,6 +480,12 @@ children(uint64 dino, child_fn cb, void *ctx)
for (int f = 1; f < NUSBFILES; f++)
if (cb(ctx, INO(K_USB, idx, f), 0, usbfiles[f]))
return 1;
} else if (kind == K_PSU && I_FILE(dino) == 0) {
struct acpi_psu b;
if (acpi_psu_get(idx, &b) == 0)
for (int f = 1; f < NPSUFILES; f++)
if (psu_has(&b, f) && cb(ctx, INO(K_PSU, idx, f), 0, psufiles[f]))
return 1;
} else if (kind == K_MEMMAP && I_FILE(dino) == 0) {
for (int f = 1; f < 4; f++)
if (cb(ctx, INO(K_MEMMAP, idx, f), 0, memfiles[f]))
@ -339,6 +505,7 @@ parent_of(uint64 dino)
return INO(K_DIR, D_PCIDEV, 0);
case K_USB: return INO(K_DIR, D_USBDEV, 0);
case K_MEMMAP: return INO(K_DIR, D_MEMMAP, 0);
case K_PSU: return INO(K_DIR, D_PSU, 0);
}
return SYSFS_ROOT;
}
@ -505,6 +672,11 @@ text(uint64 ino, char *buf, int cap)
gen_usb(&o, &u, f);
return o.n;
}
case K_PSU:
if (idx >= acpi_psu_count() || f < 1 || f >= NPSUFILES)
return -1;
gen_psu(&o, idx, f);
return o.n;
case K_MEMMAP: {
struct e820_rec e;
if (e820_nth(idx, &e) < 0 || f < 1 || f > 3)
@ -548,6 +720,8 @@ exists_dir(uint64 ino)
struct e820_rec e;
return e820_nth(I_IDX(ino), &e) == 0;
}
case K_PSU:
return I_IDX(ino) < acpi_psu_count();
}
return 0;
}