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

@ -119,7 +119,7 @@ KERN_ZSTD += $(BUILD)/kernel/scff/scff.o
KERN_GMMOS = $(addprefix $(BUILD)/kernel/gmmos/, gmmos.o hidkeys.o ps2.o i2c.o xhci.o)
KERN_PROC = $(addprefix $(BUILD)/kernel/proc/, \
proc.o)
proc.o mapname.o)
KERN_SYS = $(addprefix $(BUILD)/kernel/sys/, \
syscall.o sys_proc.o sys_fs.o sys_mem.o sys_net.o \

View file

@ -198,6 +198,14 @@ lcr3(uint64 val)
asm volatile("mov %0,%%cr3" : : "r" (val));
}
static inline uint64
rcr3(void)
{
uint64 val;
asm volatile("mov %%cr3,%0" : "=r" (val));
return val;
}
// Invalidate the TLB entry for one page. Needed any time a PTE is changed
// or cleared for an address space that may still be the active CR3 —
// otherwise the CPU can keep translating through the stale entry until the

View file

@ -736,6 +736,16 @@ int net_attach_late(struct device *);
int net_eth_poll(void);
void net_eth_link_check(void);
uint poll_generation(void);
// kernel/proc/mapname.c
struct mm;
void mapname_add(struct mm*, uint64 start, uint64 len, uint64 off, const char *path);
void mapname_unmap(struct mm*, uint64 start, uint64 len);
void mapname_free(struct mm*);
void mapname_copy(struct mm *dst, struct mm *src);
int mapname_find(struct mm*, uint64 addr, char *out, int cap, uint64 *off);
int mapname_gen(struct mm*, char *buf, int cap);
void mapname_report(struct proc*);
int proc_maps(int pid, char *buf, int cap);
int net_eth_count(void);
void net_eth_counts(int i, uint64 *rx, uint64 *tx);
struct netif *net_eth_netif(int i);
@ -809,6 +819,13 @@ uint32 inotify_cookie(void);
int ev_fionread(struct evobj *o);
int pipe_avail(struct pipe *p);
int vm_fault(addr_t va);
void tlb_init(void);
// keyboard lock keys and their lights: kernel/dev/kbd.c
void kbd_set_leds(uint locks);
void kbd_lock_key(uint lock, int down);
uint kbd_lock_state(void);
void hid_leds_want(int bits);
void lapic_send_ipi(int apicid, int vector);
pte_t* walkpte(pml4e_t *pgdir, const void *va);
int path_identity(char *path, int follow, int onlydir, uint *dev, uint64 *inum);
int memfd_rw(int id, int user, uint64 buf, uint64 off, int n, int writing);

View file

@ -48,6 +48,7 @@ struct mm {
uint64 mmap_next; // next free mmap VA (bump allocator, see sys_mmap)
pml4e_t *pgdir;
struct vma vma[NVMA]; // VMA tracking for mmap
struct mapname *names; // which file each span came from; mapname.c
};
// The descriptor table, and FD_CLOEXEC beside it.

View file

@ -32,3 +32,21 @@ void acpi_pci_route(void);
int acpi_poweroff(void); // S5; returns only on failure
void acpi_poweroff_now(void); // S5 from a panic, no AML; returns only on failure
int acpi_reboot(void); // the FADT reset register; ditto
// Batteries and AC adapters, for /sys/class/power_supply. Units are the
// battery's own: unit 0 is mW/mWh, 1 is mA/mAh; ACPI_UNKNOWN where the
// firmware does not say.
#define ACPI_UNKNOWN 0xFFFFFFFFu
struct acpi_psu {
int battery; // else an AC adapter
int present; // battery: in the bay
int online; // adapter: plugged in
int unit;
uint32 state; // _BST: 1 discharging, 2 charging, 4 critical
uint32 rate, remaining, voltage; // _BST, voltage in mV
uint32 design, full, design_voltage, cycles;
int technology; // 0 primary, 1 rechargeable
char model[32], serial[32], kind[16], oem[32];
};
int acpi_psu_count(void);
int acpi_psu_get(int i, struct acpi_psu *out);

View file

@ -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;
}

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;
}

View file

@ -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.

View file

@ -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;
}

View file

@ -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

View file

@ -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

View file

@ -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

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;
}

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;
}

View file

@ -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