diff --git a/Makefile b/Makefile index a461daa..91ee2ac 100644 --- a/Makefile +++ b/Makefile @@ -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 \ diff --git a/include/arch/x86.h b/include/arch/x86.h index c695b9d..46933d9 100644 --- a/include/arch/x86.h +++ b/include/arch/x86.h @@ -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 diff --git a/include/core/defs.h b/include/core/defs.h index db5fbbd..9765fa8 100644 --- a/include/core/defs.h +++ b/include/core/defs.h @@ -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); diff --git a/include/core/proc.h b/include/core/proc.h index a8a6965..1812ab2 100644 --- a/include/core/proc.h +++ b/include/core/proc.h @@ -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. diff --git a/include/dev/acpi.h b/include/dev/acpi.h index 285cba2..de49970 100644 --- a/include/dev/acpi.h +++ b/include/dev/acpi.h @@ -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); diff --git a/kernel/acpi/aml.c b/kernel/acpi/aml.c index f027507..99a478b 100644 --- a/kernel/acpi/aml.c +++ b/kernel/acpi/aml.c @@ -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; +} diff --git a/kernel/arch/x86_64/lapic.c b/kernel/arch/x86_64/lapic.c index 2c06bd0..e42fd98 100644 --- a/kernel/arch/x86_64/lapic.c +++ b/kernel/arch/x86_64/lapic.c @@ -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) + ; +} diff --git a/kernel/arch/x86_64/vm.c b/kernel/arch/x86_64/vm.c index e61f784..72a9e21 100644 --- a/kernel/arch/x86_64/vm.c +++ b/kernel/arch/x86_64/vm.c @@ -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; } diff --git a/kernel/core/main.c b/kernel/core/main.c index 9d9dbfb..ce6fa03 100644 --- a/kernel/core/main.c +++ b/kernel/core/main.c @@ -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. diff --git a/kernel/dev/input.c b/kernel/dev/input.c index 228e6c2..a37c82e 100644 --- a/kernel/dev/input.c +++ b/kernel/dev/input.c @@ -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; } diff --git a/kernel/dev/kbd.c b/kernel/dev/kbd.c index 09a0f67..cb36b51 100644 --- a/kernel/dev/kbd.c +++ b/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 diff --git a/kernel/drivers/net/r8169/r8169.c b/kernel/drivers/net/r8169/r8169.c index 522625b..727d4f6 100644 --- a/kernel/drivers/net/r8169/r8169.c +++ b/kernel/drivers/net/r8169/r8169.c @@ -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 diff --git a/kernel/drivers/usb/hid/usb_hid.c b/kernel/drivers/usb/hid/usb_hid.c index 1f9a593..96e9cfc 100644 --- a/kernel/drivers/usb/hid/usb_hid.c +++ b/kernel/drivers/usb/hid/usb_hid.c @@ -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 diff --git a/kernel/fs/procfs.c b/kernel/fs/procfs.c index d80b1f1..bc718bf 100644 --- a/kernel/fs/procfs.c +++ b/kernel/fs/procfs.c @@ -71,7 +71,8 @@ #define PF_FD 7 // the directory /proc//fd #define PF_TASK 8 // the directory /proc//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, ' '); diff --git a/kernel/fs/sysfs.c b/kernel/fs/sysfs.c index 0910261..6718e28 100644 --- a/kernel/fs/sysfs.c +++ b/kernel/fs/sysfs.c @@ -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_=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; } diff --git a/kernel/proc/mapname.c b/kernel/proc/mapname.c new file mode 100644 index 0000000..a2a658b --- /dev/null +++ b/kernel/proc/mapname.c @@ -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//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//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); +} diff --git a/kernel/proc/proc.c b/kernel/proc/proc.c index ec448f5..9c15972 100644 --- a/kernel/proc/proc.c +++ b/kernel/proc/proc.c @@ -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//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; +} diff --git a/kernel/sys/sys_mem.c b/kernel/sys/sys_mem.c index b57e84e..5f4e0db 100644 --- a/kernel/sys/sys_mem.c +++ b/kernel/sys/sys_mem.c @@ -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