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:
parent
73dcea7496
commit
438e7af141
18 changed files with 945 additions and 9 deletions
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue