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
|
|
@ -384,3 +384,145 @@ acpi_pci_route(void)
|
|||
kinfo("acpi: %d more on a second I/O APIC, which is not driven; "
|
||||
"those devices need MSI\n", beyond);
|
||||
}
|
||||
|
||||
// --- batteries and adapters --------------------------------------------------
|
||||
//
|
||||
// A laptop's battery is an ACPI device, PNP0C0A, with methods that answer in
|
||||
// packages: _BIX (or the older _BIF) for what it is -- capacities, units,
|
||||
// names -- and _BST for how it is now. The adapter is ACPI0003 and _PSR says
|
||||
// whether it is plugged in. Read on demand, from /sys/class/power_supply,
|
||||
// which is where fastfetch and every battery widget look.
|
||||
|
||||
#define MAXPSU 4
|
||||
static uacpi_namespace_node *psu_node[MAXPSU];
|
||||
static int psu_isbat[MAXPSU];
|
||||
static int npsu;
|
||||
|
||||
static uacpi_iteration_decision
|
||||
found_psu(void *user, uacpi_namespace_node *node, uacpi_u32 depth)
|
||||
{
|
||||
(void)depth;
|
||||
if (npsu < MAXPSU) {
|
||||
psu_node[npsu] = node;
|
||||
psu_isbat[npsu] = user != 0;
|
||||
npsu++;
|
||||
}
|
||||
return UACPI_ITERATION_DECISION_CONTINUE;
|
||||
}
|
||||
|
||||
static int psu_found;
|
||||
|
||||
static void
|
||||
psu_find(void)
|
||||
{
|
||||
if (psu_found || !ready)
|
||||
return;
|
||||
psu_found = 1;
|
||||
uacpi_find_devices("PNP0C0A", found_psu, (void *)1);
|
||||
uacpi_find_devices("ACPI0003", found_psu, 0);
|
||||
}
|
||||
|
||||
int
|
||||
acpi_psu_count(void)
|
||||
{
|
||||
psu_find();
|
||||
return npsu;
|
||||
}
|
||||
|
||||
static uint32
|
||||
pkg_int(uacpi_object_array *p, uacpi_size i)
|
||||
{
|
||||
uacpi_u64 v;
|
||||
if (i >= p->count || uacpi_object_get_integer(p->objects[i], &v) != UACPI_STATUS_OK)
|
||||
return ACPI_UNKNOWN;
|
||||
return (uint32)v;
|
||||
}
|
||||
|
||||
static void
|
||||
pkg_str(uacpi_object_array *p, uacpi_size i, char *out, int cap)
|
||||
{
|
||||
uacpi_data_view d;
|
||||
out[0] = 0;
|
||||
if (i >= p->count ||
|
||||
uacpi_object_get_string_or_buffer(p->objects[i], &d) != UACPI_STATUS_OK)
|
||||
return;
|
||||
int n = 0;
|
||||
for (uacpi_size k = 0; k < d.length && n < cap - 1 && d.const_text[k]; k++)
|
||||
if (d.const_text[k] >= ' ' && d.const_text[k] < 127)
|
||||
out[n++] = d.const_text[k];
|
||||
// Firmware pads with spaces.
|
||||
while (n > 0 && out[n - 1] == ' ')
|
||||
n--;
|
||||
out[n] = 0;
|
||||
}
|
||||
|
||||
int
|
||||
acpi_psu_get(int i, struct acpi_psu *b)
|
||||
{
|
||||
uacpi_object *o;
|
||||
uacpi_object_array p;
|
||||
uacpi_u32 sta = 0xF;
|
||||
|
||||
psu_find();
|
||||
if (i < 0 || i >= npsu)
|
||||
return -1;
|
||||
memset(b, 0, sizeof(*b));
|
||||
b->battery = psu_isbat[i];
|
||||
|
||||
if (!b->battery) {
|
||||
uacpi_u64 v = 0;
|
||||
uacpi_eval_simple_integer(psu_node[i], "_PSR", &v);
|
||||
b->online = v != 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Bit 4 of _STA: a battery is in the bay. A missing _STA means present.
|
||||
uacpi_eval_sta(psu_node[i], &sta);
|
||||
b->present = (sta & 0x10) != 0;
|
||||
b->rate = b->remaining = b->voltage = ACPI_UNKNOWN;
|
||||
b->design = b->full = b->design_voltage = b->cycles = ACPI_UNKNOWN;
|
||||
if (!b->present)
|
||||
return 0;
|
||||
|
||||
// _BIX: a revision first, then _BIF's fields shifted by one, with the
|
||||
// cycle count after the design voltage's neighbours.
|
||||
if (uacpi_eval_package(psu_node[i], "_BIX", 0, &o) == UACPI_STATUS_OK) {
|
||||
if (uacpi_object_get_package(o, &p) == UACPI_STATUS_OK) {
|
||||
b->unit = (int)pkg_int(&p, 1);
|
||||
b->design = pkg_int(&p, 2);
|
||||
b->full = pkg_int(&p, 3);
|
||||
b->technology = (int)pkg_int(&p, 4);
|
||||
b->design_voltage = pkg_int(&p, 5);
|
||||
b->cycles = pkg_int(&p, 8);
|
||||
pkg_str(&p, 16, b->model, sizeof(b->model));
|
||||
pkg_str(&p, 17, b->serial, sizeof(b->serial));
|
||||
pkg_str(&p, 18, b->kind, sizeof(b->kind));
|
||||
pkg_str(&p, 19, b->oem, sizeof(b->oem));
|
||||
}
|
||||
uacpi_object_unref(o);
|
||||
} else if (uacpi_eval_package(psu_node[i], "_BIF", 0, &o) == UACPI_STATUS_OK) {
|
||||
if (uacpi_object_get_package(o, &p) == UACPI_STATUS_OK) {
|
||||
b->unit = (int)pkg_int(&p, 0);
|
||||
b->design = pkg_int(&p, 1);
|
||||
b->full = pkg_int(&p, 2);
|
||||
b->technology = (int)pkg_int(&p, 3);
|
||||
b->design_voltage = pkg_int(&p, 4);
|
||||
pkg_str(&p, 9, b->model, sizeof(b->model));
|
||||
pkg_str(&p, 10, b->serial, sizeof(b->serial));
|
||||
pkg_str(&p, 11, b->kind, sizeof(b->kind));
|
||||
pkg_str(&p, 12, b->oem, sizeof(b->oem));
|
||||
}
|
||||
uacpi_object_unref(o);
|
||||
}
|
||||
|
||||
if (uacpi_eval_package(psu_node[i], "_BST", 0, &o) == UACPI_STATUS_OK) {
|
||||
if (uacpi_object_get_package(o, &p) == UACPI_STATUS_OK) {
|
||||
b->state = pkg_int(&p, 0);
|
||||
b->rate = pkg_int(&p, 1);
|
||||
b->remaining = pkg_int(&p, 2);
|
||||
b->voltage = pkg_int(&p, 3);
|
||||
}
|
||||
uacpi_object_unref(o);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -266,6 +266,7 @@ kmain(uint64 fb_addr)
|
|||
ioapicinit();
|
||||
if (boot_verbose) cprintf("verbose boot: ioapicinit done\n");
|
||||
irq_init(); // must precede any driver init that registers a handler
|
||||
tlb_init(); // the shootdown vector, before any second CPU runs threads
|
||||
if (boot_verbose) cprintf("verbose boot: irq_init done\n");
|
||||
// Before the keyboard and mouse, which start producing events the moment
|
||||
// their handlers are registered.
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@
|
|||
#include "fs/fs.h"
|
||||
#include "fs/file.h"
|
||||
#include "dev/input.h"
|
||||
#include "dev/kbd.h"
|
||||
#include "core/uaccess.h"
|
||||
#include "gfx/gfx.h"
|
||||
#include "uapi/errno.h"
|
||||
|
|
@ -230,6 +231,9 @@ input_grab(int dev, int on)
|
|||
// accident unless the kernel says which it was.
|
||||
cprintf("input: event%d %s by pid %d\n", dev,
|
||||
on ? "grabbed" : "released", myproc() ? myproc()->pid : -1);
|
||||
// The holder kept its own lights; the console's come back with it.
|
||||
if(!on && dev == INPUT_KBD)
|
||||
kbd_set_leds(kbd_lock_state());
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -433,11 +437,31 @@ inputread(struct inode *ip, uint64 off, char *dst, int n)
|
|||
return got;
|
||||
}
|
||||
|
||||
// evdev accepts writes to set keyboard LEDs. There are no LEDs to set here,
|
||||
// and a program that writes one should not see an error for it.
|
||||
// evdev accepts writes to set keyboard LEDs, and X sets them this way: it
|
||||
// keeps its own lock state while it holds the keyboard. Each write is
|
||||
// struct input_event -- a 16-byte time, type, code, value -- and EV_LED's
|
||||
// codes 0, 1 and 2 are Num, Caps and Scroll Lock. Anything else is accepted
|
||||
// and ignored.
|
||||
int
|
||||
inputwrite(struct inode *ip, uint64 off, char *src, int n)
|
||||
{
|
||||
static uint xleds;
|
||||
int changed = 0;
|
||||
|
||||
for (int k = 0; k + 24 <= n; k += 24) {
|
||||
uint16 type, code;
|
||||
int32 value;
|
||||
memmove(&type, src + k + 16, 2);
|
||||
memmove(&code, src + k + 18, 2);
|
||||
memmove(&value, src + k + 20, 4);
|
||||
if (type != 0x11 || code > 2) // EV_LED
|
||||
continue;
|
||||
uint bit = code == 0 ? NUMLOCK : code == 1 ? CAPSLOCK : SCROLLLOCK;
|
||||
xleds = value ? (xleds | bit) : (xleds & ~bit);
|
||||
changed = 1;
|
||||
}
|
||||
if (changed)
|
||||
kbd_set_leds(xleds);
|
||||
return n;
|
||||
}
|
||||
|
||||
|
|
|
|||
110
kernel/dev/kbd.c
110
kernel/dev/kbd.c
|
|
@ -1,6 +1,7 @@
|
|||
#include "core/types.h"
|
||||
#include "arch/x86.h"
|
||||
#include "core/defs.h"
|
||||
#include "core/spinlock.h"
|
||||
#include "dev/kbd.h"
|
||||
#include "uapi/posix.h"
|
||||
#include "dev/vt.h" /* NVT -- how many terminals the chords reach */
|
||||
|
|
@ -99,6 +100,104 @@ queue_utf8(uint32 cp)
|
|||
return pend[pend_i++];
|
||||
}
|
||||
|
||||
// --- the lock keys and their lights -------------------------------------------
|
||||
//
|
||||
// Caps, Num and Scroll Lock are one state for the machine, whichever keyboard
|
||||
// pressed them -- the laptop's own and a USB one agree -- and the lights on
|
||||
// every keyboard show it. They never did: caps lock worked and its light
|
||||
// stayed dark. Under X the server keeps its own lock state and sets the
|
||||
// lights through /dev/input (see inputwrite), so these are left to it while
|
||||
// it holds the keyboard.
|
||||
|
||||
static uint kbd_locks; // CAPSLOCK | NUMLOCK | SCROLLLOCK
|
||||
static uint lock_held; // lock keys down now: a held key repeats
|
||||
static struct spinlock ledlock;
|
||||
static int ledlock_ready;
|
||||
|
||||
// The PS/2 side is a two-byte command, 0xED and the lights, and each byte
|
||||
// must be acknowledged (0xFA) before the next is sent. That cannot be waited
|
||||
// for here -- this runs in the keyboard's interrupt, and the acknowledgement
|
||||
// *is* a keyboard interrupt -- so the second byte goes out when kbdgetc sees
|
||||
// the first one acknowledged.
|
||||
static int led_want = -1; // PS/2 light byte still to send
|
||||
static int led_stage; // 0 idle, 1 0xED sent, 2 the byte sent
|
||||
static uint led_since;
|
||||
|
||||
static void
|
||||
ps2_led_kick(void)
|
||||
{
|
||||
if (led_stage != 0 && ticks - led_since > HZ)
|
||||
led_stage = 0; // no keyboard answered: start over
|
||||
if (led_stage == 0 && led_want >= 0 && !(inb(KBSTATP) & 0x02)) {
|
||||
outb(KBDATAP, 0xED);
|
||||
led_stage = 1;
|
||||
led_since = ticks;
|
||||
}
|
||||
}
|
||||
|
||||
// Show `locks` on every keyboard.
|
||||
void
|
||||
kbd_set_leds(uint locks)
|
||||
{
|
||||
if (!ledlock_ready) {
|
||||
initlock(&ledlock, "kbdled");
|
||||
ledlock_ready = 1;
|
||||
}
|
||||
acquire(&ledlock);
|
||||
led_want = ((locks & SCROLLLOCK) ? 1 : 0) | ((locks & NUMLOCK) ? 2 : 0) |
|
||||
((locks & CAPSLOCK) ? 4 : 0);
|
||||
ps2_led_kick();
|
||||
release(&ledlock);
|
||||
// USB numbers them the other way round: Num, Caps, Scroll.
|
||||
hid_leds_want(((locks & NUMLOCK) ? 1 : 0) | ((locks & CAPSLOCK) ? 2 : 0) |
|
||||
((locks & SCROLLLOCK) ? 4 : 0));
|
||||
}
|
||||
|
||||
// A lock key pressed, on any keyboard. `down` 0 is its release.
|
||||
void
|
||||
kbd_lock_key(uint lock, int down)
|
||||
{
|
||||
if (!down) {
|
||||
lock_held &= ~lock;
|
||||
return;
|
||||
}
|
||||
if (lock_held & lock)
|
||||
return; // the keyboard's repeat, not a new press
|
||||
lock_held |= lock;
|
||||
kbd_locks ^= lock;
|
||||
if (!input_is_grabbed(INPUT_KBD))
|
||||
kbd_set_leds(kbd_locks);
|
||||
}
|
||||
|
||||
uint
|
||||
kbd_lock_state(void)
|
||||
{
|
||||
return kbd_locks;
|
||||
}
|
||||
|
||||
// 0xFA and 0xFE from the keyboard: answers to a light command, not keys.
|
||||
static int
|
||||
ps2_led_answer(uint data)
|
||||
{
|
||||
if (led_stage == 0 || (data != 0xFA && data != 0xFE))
|
||||
return 0;
|
||||
if (!ledlock_ready)
|
||||
return 0;
|
||||
acquire(&ledlock);
|
||||
if (data == 0xFE) {
|
||||
led_stage = 0; // "resend": try again with the next change
|
||||
} else if (led_stage == 1 && led_want >= 0) {
|
||||
outb(KBDATAP, (uchar)led_want);
|
||||
led_want = -1;
|
||||
led_stage = 2;
|
||||
} else {
|
||||
led_stage = 0;
|
||||
ps2_led_kick(); // changed again meanwhile
|
||||
}
|
||||
release(&ledlock);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int
|
||||
kbdgetc(void)
|
||||
{
|
||||
|
|
@ -119,6 +218,9 @@ kbdgetc(void)
|
|||
|
||||
data = inb(KBDATAP);
|
||||
|
||||
if(ps2_led_answer(data))
|
||||
return 0;
|
||||
|
||||
if(data == 0xE0){
|
||||
shift |= E0ESC;
|
||||
return 0;
|
||||
|
|
@ -137,9 +239,10 @@ kbdgetc(void)
|
|||
if(data & 0x80){
|
||||
data = (shift & E0ESC ? data : data & 0x7F);
|
||||
shift &= ~(shiftcode[data] | E0ESC);
|
||||
if(togglecode[data]) kbd_lock_key(togglecode[data], 0);
|
||||
} else {
|
||||
shift |= shiftcode[data];
|
||||
shift ^= togglecode[data];
|
||||
if(togglecode[data]) kbd_lock_key(togglecode[data], 1);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -147,6 +250,7 @@ kbdgetc(void)
|
|||
if(data & 0x80){
|
||||
data = (shift & E0ESC ? data : data & 0x7F);
|
||||
shift &= ~(shiftcode[data] | E0ESC);
|
||||
if(togglecode[data]) kbd_lock_key(togglecode[data], 0);
|
||||
return 0;
|
||||
} else if(shift & E0ESC){
|
||||
data |= 0x80;
|
||||
|
|
@ -154,7 +258,9 @@ kbdgetc(void)
|
|||
}
|
||||
|
||||
shift |= shiftcode[data];
|
||||
shift ^= togglecode[data];
|
||||
if(togglecode[data]) kbd_lock_key(togglecode[data], 1);
|
||||
// The locks are the machine's, not this keyboard's: see kbd_lock_key().
|
||||
shift = (shift & ~(CAPSLOCK | NUMLOCK | SCROLLLOCK)) | kbd_locks;
|
||||
|
||||
// The switch chord, before the character tables, because there is no
|
||||
// character to look up: it is a combination that means "show me a different
|
||||
|
|
|
|||
|
|
@ -128,7 +128,12 @@ struct rtl_desc {
|
|||
#define DESC_LS 0x10000000 // last fragment
|
||||
#define DESC_LEN 0x00003FFF
|
||||
|
||||
#define NRX 32
|
||||
// 256, as the e1000 has. It was 32, and TCP advertises a 256 KB window --
|
||||
// some 180 full frames in flight -- which a gigabit link fills 32 slots with
|
||||
// in under half a millisecond. Everything past them was dropped, TCP read the
|
||||
// drops as congestion, and a download on the laptop crawled: apk sat on its
|
||||
// first index for minutes. The chip takes up to 1024.
|
||||
#define NRX 256
|
||||
#define NTX 32
|
||||
#define BUFSZ 2048 // one frame, with room for a VLAN tag
|
||||
|
||||
|
|
|
|||
|
|
@ -43,6 +43,7 @@ struct hfield {
|
|||
|
||||
struct hid {
|
||||
int in_use;
|
||||
struct usb_dev *dev;
|
||||
int proto; // USB_HID_PROTO_KEYBOARD, _MOUSE or _TABLET
|
||||
uchar prev[8]; // the previous report, for edge detection
|
||||
|
||||
|
|
@ -248,6 +249,13 @@ keyboard_report(struct hid *h, const uchar *rep, int len)
|
|||
|
||||
keyboard_events(h->prev, rep);
|
||||
|
||||
// A lock key let go: the next press toggles again.
|
||||
for(i = 2; i < 8; i++){
|
||||
uchar was = h->prev[i];
|
||||
if((was == 0x39 || was == 0x53 || was == 0x47) && !was_down(rep, was))
|
||||
kbd_lock_key(was == 0x39 ? CAPSLOCK : was == 0x53 ? NUMLOCK : SCROLLLOCK, 0);
|
||||
}
|
||||
|
||||
// Grabbed: the events above still went to /dev/input/event0, but nothing
|
||||
// reaches the console. Note prev[] is updated before returning, or the next
|
||||
// report would look like every held key had just been pressed again.
|
||||
|
|
@ -264,11 +272,19 @@ keyboard_report(struct hid *h, const uchar *rep, int len)
|
|||
if(was_down(h->prev, usage))
|
||||
continue; // still held from last time, not a new press
|
||||
|
||||
if(usage == 0x39 || usage == 0x53 || usage == 0x47){
|
||||
kbd_lock_key(usage == 0x39 ? CAPSLOCK : usage == 0x53 ? NUMLOCK : SCROLLLOCK, 1);
|
||||
continue;
|
||||
}
|
||||
if(usage >= 0x39){
|
||||
if((c = hid_special(usage, mods)) == 0)
|
||||
continue;
|
||||
} else {
|
||||
c = (mods & HID_MOD_SHIFT) ? hid_shifted[usage] : hid_unshifted[usage];
|
||||
// Caps lock shifts letters only, as on every keyboard.
|
||||
int shifted = (mods & HID_MOD_SHIFT) != 0;
|
||||
if((kbd_lock_state() & CAPSLOCK) && usage >= 0x04 && usage <= 0x1D)
|
||||
shifted = !shifted;
|
||||
c = shifted ? hid_shifted[usage] : hid_unshifted[usage];
|
||||
if(c == 0)
|
||||
continue;
|
||||
}
|
||||
|
|
@ -305,6 +321,39 @@ keyboard_report(struct hid *h, const uchar *rep, int len)
|
|||
consoleintr(usb_kbd_getc);
|
||||
}
|
||||
|
||||
// The lights every USB keyboard should show, in HID's order (Num, Caps,
|
||||
// Scroll), and what was last sent. Set from interrupt context; sent from
|
||||
// usb_hid_tick, which runs on a thread and may wait for a control transfer.
|
||||
static volatile int leds_want = -1;
|
||||
static int leds_sent = -1;
|
||||
|
||||
void
|
||||
hid_leds_want(int bits)
|
||||
{
|
||||
leds_want = bits;
|
||||
}
|
||||
|
||||
static void
|
||||
hid_send_leds(int bits)
|
||||
{
|
||||
for(int i = 0; i < NHID; i++){
|
||||
struct hid *h = &hids[i];
|
||||
if(!h->in_use || !h->dev ||
|
||||
!(h->rkbd || h->proto == USB_HID_PROTO_KEYBOARD))
|
||||
continue;
|
||||
// SET_REPORT, output report: one byte of lights, under the keyboard's
|
||||
// report ID when its reports carry one.
|
||||
struct usb_setup s;
|
||||
uchar b = (uchar)bits;
|
||||
s.bmRequestType = USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE;
|
||||
s.bRequest = 0x09;
|
||||
s.wValue = (2 << 8) | (h->rkbd ? h->kbd_id : 0);
|
||||
s.wIndex = h->dev->iface.bInterfaceNumber;
|
||||
s.wLength = 1;
|
||||
h->dev->ops->control(h->dev, &s, &b, 1);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
usb_hid_tick(void)
|
||||
{
|
||||
|
|
@ -312,6 +361,11 @@ usb_hid_tick(void)
|
|||
|
||||
if(!kqinit)
|
||||
return;
|
||||
int want = leds_want;
|
||||
if(want >= 0 && want != leds_sent){
|
||||
leds_sent = want;
|
||||
hid_send_leds(want);
|
||||
}
|
||||
acquire(&kqlock);
|
||||
if(krep.c && (int)(ticks - krep.at) >= 0){
|
||||
c = krep.c;
|
||||
|
|
@ -870,7 +924,9 @@ hid_probe(struct usb_dev *dev)
|
|||
return -1;
|
||||
|
||||
h->in_use = 1;
|
||||
h->dev = dev;
|
||||
dev->drvdata = h;
|
||||
leds_sent = -1; // a new keyboard gets the current lights
|
||||
dev->complete = hid_complete;
|
||||
|
||||
// Arm the first transfer. Until a TRB is waiting on the endpoint the device
|
||||
|
|
|
|||
|
|
@ -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, ' ');
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
228
kernel/proc/mapname.c
Normal file
228
kernel/proc/mapname.c
Normal 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);
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -282,6 +282,10 @@ mmap_locked(void)
|
|||
vm_unmap_range(myproc()->pgdir, va, length);
|
||||
int fixed_file = f && f->type == FD_INODE && f->ip->type != T_DEV &&
|
||||
!(flags & MAP_ANONYMOUS);
|
||||
if (fixed_file)
|
||||
mapname_add(myproc()->mm, va, length, offset, f->path);
|
||||
else
|
||||
mapname_unmap(myproc()->mm, va, length);
|
||||
// Reserved, not backed. PROT_NONE is recorded too: mprotect() does not
|
||||
// change pages here, so a range reserved PROT_NONE and opened up later
|
||||
// has to be memory that can appear when it is touched.
|
||||
|
|
@ -440,6 +444,7 @@ mmap_locked(void)
|
|||
vm_unmap_range(myproc()->pgdir, va, length);
|
||||
return -ENOMEM;
|
||||
}
|
||||
mapname_add(myproc()->mm, va, length, offset, f->path);
|
||||
return va;
|
||||
}
|
||||
|
||||
|
|
@ -531,6 +536,7 @@ munmap_locked(void)
|
|||
if (bad_user_range(addr, length) || (addr % PGSIZE))
|
||||
return -EINVAL;
|
||||
uint64 end = addr + length;
|
||||
mapname_unmap(p->mm, addr, length);
|
||||
|
||||
// A segment or device mapping goes only as a whole, by its own address.
|
||||
// Part of one is refused: unmapping it the anonymous way below would kfree
|
||||
|
|
|
|||
Loading…
Reference in a new issue