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
2
Makefile
2
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_GMMOS = $(addprefix $(BUILD)/kernel/gmmos/, gmmos.o hidkeys.o ps2.o i2c.o xhci.o)
|
||||||
|
|
||||||
KERN_PROC = $(addprefix $(BUILD)/kernel/proc/, \
|
KERN_PROC = $(addprefix $(BUILD)/kernel/proc/, \
|
||||||
proc.o)
|
proc.o mapname.o)
|
||||||
|
|
||||||
KERN_SYS = $(addprefix $(BUILD)/kernel/sys/, \
|
KERN_SYS = $(addprefix $(BUILD)/kernel/sys/, \
|
||||||
syscall.o sys_proc.o sys_fs.o sys_mem.o sys_net.o \
|
syscall.o sys_proc.o sys_fs.o sys_mem.o sys_net.o \
|
||||||
|
|
|
||||||
|
|
@ -198,6 +198,14 @@ lcr3(uint64 val)
|
||||||
asm volatile("mov %0,%%cr3" : : "r" (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
|
// 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 —
|
// 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
|
// otherwise the CPU can keep translating through the stale entry until the
|
||||||
|
|
|
||||||
|
|
@ -736,6 +736,16 @@ int net_attach_late(struct device *);
|
||||||
int net_eth_poll(void);
|
int net_eth_poll(void);
|
||||||
void net_eth_link_check(void);
|
void net_eth_link_check(void);
|
||||||
uint poll_generation(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);
|
int net_eth_count(void);
|
||||||
void net_eth_counts(int i, uint64 *rx, uint64 *tx);
|
void net_eth_counts(int i, uint64 *rx, uint64 *tx);
|
||||||
struct netif *net_eth_netif(int i);
|
struct netif *net_eth_netif(int i);
|
||||||
|
|
@ -809,6 +819,13 @@ uint32 inotify_cookie(void);
|
||||||
int ev_fionread(struct evobj *o);
|
int ev_fionread(struct evobj *o);
|
||||||
int pipe_avail(struct pipe *p);
|
int pipe_avail(struct pipe *p);
|
||||||
int vm_fault(addr_t va);
|
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);
|
pte_t* walkpte(pml4e_t *pgdir, const void *va);
|
||||||
int path_identity(char *path, int follow, int onlydir, uint *dev, uint64 *inum);
|
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);
|
int memfd_rw(int id, int user, uint64 buf, uint64 off, int n, int writing);
|
||||||
|
|
|
||||||
|
|
@ -48,6 +48,7 @@ struct mm {
|
||||||
uint64 mmap_next; // next free mmap VA (bump allocator, see sys_mmap)
|
uint64 mmap_next; // next free mmap VA (bump allocator, see sys_mmap)
|
||||||
pml4e_t *pgdir;
|
pml4e_t *pgdir;
|
||||||
struct vma vma[NVMA]; // VMA tracking for mmap
|
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.
|
// The descriptor table, and FD_CLOEXEC beside it.
|
||||||
|
|
|
||||||
|
|
@ -32,3 +32,21 @@ void acpi_pci_route(void);
|
||||||
int acpi_poweroff(void); // S5; returns only on failure
|
int acpi_poweroff(void); // S5; returns only on failure
|
||||||
void acpi_poweroff_now(void); // S5 from a panic, no AML; 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
|
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);
|
||||||
|
|
|
||||||
|
|
@ -384,3 +384,145 @@ acpi_pci_route(void)
|
||||||
kinfo("acpi: %d more on a second I/O APIC, which is not driven; "
|
kinfo("acpi: %d more on a second I/O APIC, which is not driven; "
|
||||||
"those devices need MSI\n", beyond);
|
"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 = t1;
|
||||||
r->year += 2000;
|
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 "core/spinlock.h"
|
||||||
#include "arch/vmap.h"
|
#include "arch/vmap.h"
|
||||||
#include "ldls/kldls.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);
|
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);
|
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.
|
// Free a user page just unmapped from pgdir: now, or once no TLB can hold it.
|
||||||
static void
|
static void
|
||||||
user_page_free(pml4e_t *pgdir, uint64 pa)
|
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 a = PGROUNDDOWN(va);
|
||||||
uint64 end = PGROUNDUP(va + length);
|
uint64 end = PGROUNDUP(va + length);
|
||||||
|
int any = 0;
|
||||||
for (; a < end; a += PGSIZE) {
|
for (; a < end; a += PGSIZE) {
|
||||||
pte_t *pte = walkpgdir(pgdir, (void*)a, 0);
|
pte_t *pte = walkpgdir(pgdir, (void*)a, 0);
|
||||||
if (pte && (*pte & PTE_P)) {
|
if (pte && (*pte & PTE_P)) {
|
||||||
|
|
@ -843,8 +930,11 @@ vm_unmap_range(pml4e_t *pgdir, uint64 va, uint64 length)
|
||||||
*pte = 0;
|
*pte = 0;
|
||||||
invlpg((void*)a);
|
invlpg((void*)a);
|
||||||
user_page_free(pgdir, pa);
|
user_page_free(pgdir, pa);
|
||||||
|
any = 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (any)
|
||||||
|
tlb_after_unmap(pgdir);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tear down a mapping without freeing what is underneath it.
|
// 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 a = PGROUNDDOWN(va);
|
||||||
uint64 end = PGROUNDUP(va + length);
|
uint64 end = PGROUNDUP(va + length);
|
||||||
|
int any = 0;
|
||||||
for (; a < end; a += PGSIZE) {
|
for (; a < end; a += PGSIZE) {
|
||||||
pte_t *pte = walkpgdir(pgdir, (void*)a, 0);
|
pte_t *pte = walkpgdir(pgdir, (void*)a, 0);
|
||||||
if (pte && (*pte & PTE_P)) {
|
if (pte && (*pte & PTE_P)) {
|
||||||
*pte = 0;
|
*pte = 0;
|
||||||
invlpg((void*)a);
|
invlpg((void*)a);
|
||||||
|
any = 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (any)
|
||||||
|
tlb_after_unmap(pgdir);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Duplicate an mmap()'d VMA region from src into dst for fork().
|
// 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);
|
user_page_free(pgdir, pa);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
tlb_after_unmap(pgdir);
|
||||||
return newsz;
|
return newsz;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -266,6 +266,7 @@ kmain(uint64 fb_addr)
|
||||||
ioapicinit();
|
ioapicinit();
|
||||||
if (boot_verbose) cprintf("verbose boot: ioapicinit done\n");
|
if (boot_verbose) cprintf("verbose boot: ioapicinit done\n");
|
||||||
irq_init(); // must precede any driver init that registers a handler
|
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");
|
if (boot_verbose) cprintf("verbose boot: irq_init done\n");
|
||||||
// Before the keyboard and mouse, which start producing events the moment
|
// Before the keyboard and mouse, which start producing events the moment
|
||||||
// their handlers are registered.
|
// their handlers are registered.
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@
|
||||||
#include "fs/fs.h"
|
#include "fs/fs.h"
|
||||||
#include "fs/file.h"
|
#include "fs/file.h"
|
||||||
#include "dev/input.h"
|
#include "dev/input.h"
|
||||||
|
#include "dev/kbd.h"
|
||||||
#include "core/uaccess.h"
|
#include "core/uaccess.h"
|
||||||
#include "gfx/gfx.h"
|
#include "gfx/gfx.h"
|
||||||
#include "uapi/errno.h"
|
#include "uapi/errno.h"
|
||||||
|
|
@ -230,6 +231,9 @@ input_grab(int dev, int on)
|
||||||
// accident unless the kernel says which it was.
|
// accident unless the kernel says which it was.
|
||||||
cprintf("input: event%d %s by pid %d\n", dev,
|
cprintf("input: event%d %s by pid %d\n", dev,
|
||||||
on ? "grabbed" : "released", myproc() ? myproc()->pid : -1);
|
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;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
@ -433,11 +437,31 @@ inputread(struct inode *ip, uint64 off, char *dst, int n)
|
||||||
return got;
|
return got;
|
||||||
}
|
}
|
||||||
|
|
||||||
// evdev accepts writes to set keyboard LEDs. There are no LEDs to set here,
|
// evdev accepts writes to set keyboard LEDs, and X sets them this way: it
|
||||||
// and a program that writes one should not see an error for 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
|
int
|
||||||
inputwrite(struct inode *ip, uint64 off, char *src, int n)
|
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;
|
return n;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
110
kernel/dev/kbd.c
110
kernel/dev/kbd.c
|
|
@ -1,6 +1,7 @@
|
||||||
#include "core/types.h"
|
#include "core/types.h"
|
||||||
#include "arch/x86.h"
|
#include "arch/x86.h"
|
||||||
#include "core/defs.h"
|
#include "core/defs.h"
|
||||||
|
#include "core/spinlock.h"
|
||||||
#include "dev/kbd.h"
|
#include "dev/kbd.h"
|
||||||
#include "uapi/posix.h"
|
#include "uapi/posix.h"
|
||||||
#include "dev/vt.h" /* NVT -- how many terminals the chords reach */
|
#include "dev/vt.h" /* NVT -- how many terminals the chords reach */
|
||||||
|
|
@ -99,6 +100,104 @@ queue_utf8(uint32 cp)
|
||||||
return pend[pend_i++];
|
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
|
int
|
||||||
kbdgetc(void)
|
kbdgetc(void)
|
||||||
{
|
{
|
||||||
|
|
@ -119,6 +218,9 @@ kbdgetc(void)
|
||||||
|
|
||||||
data = inb(KBDATAP);
|
data = inb(KBDATAP);
|
||||||
|
|
||||||
|
if(ps2_led_answer(data))
|
||||||
|
return 0;
|
||||||
|
|
||||||
if(data == 0xE0){
|
if(data == 0xE0){
|
||||||
shift |= E0ESC;
|
shift |= E0ESC;
|
||||||
return 0;
|
return 0;
|
||||||
|
|
@ -137,9 +239,10 @@ kbdgetc(void)
|
||||||
if(data & 0x80){
|
if(data & 0x80){
|
||||||
data = (shift & E0ESC ? data : data & 0x7F);
|
data = (shift & E0ESC ? data : data & 0x7F);
|
||||||
shift &= ~(shiftcode[data] | E0ESC);
|
shift &= ~(shiftcode[data] | E0ESC);
|
||||||
|
if(togglecode[data]) kbd_lock_key(togglecode[data], 0);
|
||||||
} else {
|
} else {
|
||||||
shift |= shiftcode[data];
|
shift |= shiftcode[data];
|
||||||
shift ^= togglecode[data];
|
if(togglecode[data]) kbd_lock_key(togglecode[data], 1);
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
@ -147,6 +250,7 @@ kbdgetc(void)
|
||||||
if(data & 0x80){
|
if(data & 0x80){
|
||||||
data = (shift & E0ESC ? data : data & 0x7F);
|
data = (shift & E0ESC ? data : data & 0x7F);
|
||||||
shift &= ~(shiftcode[data] | E0ESC);
|
shift &= ~(shiftcode[data] | E0ESC);
|
||||||
|
if(togglecode[data]) kbd_lock_key(togglecode[data], 0);
|
||||||
return 0;
|
return 0;
|
||||||
} else if(shift & E0ESC){
|
} else if(shift & E0ESC){
|
||||||
data |= 0x80;
|
data |= 0x80;
|
||||||
|
|
@ -154,7 +258,9 @@ kbdgetc(void)
|
||||||
}
|
}
|
||||||
|
|
||||||
shift |= shiftcode[data];
|
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
|
// 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
|
// 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_LS 0x10000000 // last fragment
|
||||||
#define DESC_LEN 0x00003FFF
|
#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 NTX 32
|
||||||
#define BUFSZ 2048 // one frame, with room for a VLAN tag
|
#define BUFSZ 2048 // one frame, with room for a VLAN tag
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -43,6 +43,7 @@ struct hfield {
|
||||||
|
|
||||||
struct hid {
|
struct hid {
|
||||||
int in_use;
|
int in_use;
|
||||||
|
struct usb_dev *dev;
|
||||||
int proto; // USB_HID_PROTO_KEYBOARD, _MOUSE or _TABLET
|
int proto; // USB_HID_PROTO_KEYBOARD, _MOUSE or _TABLET
|
||||||
uchar prev[8]; // the previous report, for edge detection
|
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);
|
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
|
// Grabbed: the events above still went to /dev/input/event0, but nothing
|
||||||
// reaches the console. Note prev[] is updated before returning, or the next
|
// reaches the console. Note prev[] is updated before returning, or the next
|
||||||
// report would look like every held key had just been pressed again.
|
// 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))
|
if(was_down(h->prev, usage))
|
||||||
continue; // still held from last time, not a new press
|
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(usage >= 0x39){
|
||||||
if((c = hid_special(usage, mods)) == 0)
|
if((c = hid_special(usage, mods)) == 0)
|
||||||
continue;
|
continue;
|
||||||
} else {
|
} 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)
|
if(c == 0)
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
@ -305,6 +321,39 @@ keyboard_report(struct hid *h, const uchar *rep, int len)
|
||||||
consoleintr(usb_kbd_getc);
|
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
|
void
|
||||||
usb_hid_tick(void)
|
usb_hid_tick(void)
|
||||||
{
|
{
|
||||||
|
|
@ -312,6 +361,11 @@ usb_hid_tick(void)
|
||||||
|
|
||||||
if(!kqinit)
|
if(!kqinit)
|
||||||
return;
|
return;
|
||||||
|
int want = leds_want;
|
||||||
|
if(want >= 0 && want != leds_sent){
|
||||||
|
leds_sent = want;
|
||||||
|
hid_send_leds(want);
|
||||||
|
}
|
||||||
acquire(&kqlock);
|
acquire(&kqlock);
|
||||||
if(krep.c && (int)(ticks - krep.at) >= 0){
|
if(krep.c && (int)(ticks - krep.at) >= 0){
|
||||||
c = krep.c;
|
c = krep.c;
|
||||||
|
|
@ -870,7 +924,9 @@ hid_probe(struct usb_dev *dev)
|
||||||
return -1;
|
return -1;
|
||||||
|
|
||||||
h->in_use = 1;
|
h->in_use = 1;
|
||||||
|
h->dev = dev;
|
||||||
dev->drvdata = h;
|
dev->drvdata = h;
|
||||||
|
leds_sent = -1; // a new keyboard gets the current lights
|
||||||
dev->complete = hid_complete;
|
dev->complete = hid_complete;
|
||||||
|
|
||||||
// Arm the first transfer. Until a TRB is waiting on the endpoint the device
|
// 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_FD 7 // the directory /proc/<pid>/fd
|
||||||
#define PF_TASK 8 // the directory /proc/<pid>/task
|
#define PF_TASK 8 // the directory /proc/<pid>/task
|
||||||
#define PF_COMM 9
|
#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 PF_FDENT 0x80 // | fd: one link in it
|
||||||
|
|
||||||
#define INO_PID(ino) ((int)((ino) >> 8))
|
#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
|
// The name alone, a line of it. btop reads this and nothing else for the
|
||||||
// Program column.
|
// Program column.
|
||||||
{ "comm", PF_COMM },
|
{ "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])))
|
#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_STATM: gen_pid_statm(&s, &pi); break;
|
||||||
case PF_EXE: sbputs(&s, pi.exe[0] ? pi.exe : pi.name); 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_COMM: sbputs(&s, pi.name); sbputc(&s, '\n'); break;
|
||||||
|
case PF_MAPS: return proc_maps(INO_PID(ino), buf, cap);
|
||||||
case PF_SCHEDSTAT:
|
case PF_SCHEDSTAT:
|
||||||
sbputi(&s, (int64)pi.vruntime); sbputc(&s, ' ');
|
sbputi(&s, (int64)pi.vruntime); sbputc(&s, ' ');
|
||||||
sbputi(&s, (int64)pi.nice); sbputc(&s, ' ');
|
sbputi(&s, (int64)pi.nice); sbputc(&s, ' ');
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,8 @@
|
||||||
// /sys/firmware/memmap/N/ start end type
|
// /sys/firmware/memmap/N/ start end type
|
||||||
// /sys/firmware/efi/ present when UEFI booted the kernel
|
// /sys/firmware/efi/ present when UEFI booted the kernel
|
||||||
// /sys/class/dmi/id/product_name ... the machine, as text
|
// /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
|
// 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
|
// hardware information already reads it from these paths. toybox's lspci and
|
||||||
|
|
@ -42,6 +44,7 @@
|
||||||
#include "dev/usb.h"
|
#include "dev/usb.h"
|
||||||
#include "core/hwinfo.h"
|
#include "core/hwinfo.h"
|
||||||
#include "gfx/gfx.h"
|
#include "gfx/gfx.h"
|
||||||
|
#include "dev/acpi.h"
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// Inode numbers: kind, index, file -- the whole identity, since iget gives
|
// 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_MEMMAP 5ULL // idx an entry of e820_nth
|
||||||
#define K_DMIID 6ULL // file a DMI_* field + 1
|
#define K_DMIID 6ULL // file a DMI_* field + 1
|
||||||
#define K_DMITAB 7ULL // file 1 the entry point, 2 the table
|
#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 INO(k, idx, f) (((uint64)(k) << 40) | ((uint64)(idx) << 8) | (uint64)(f))
|
||||||
#define I_KIND(i) ((int)((i) >> 40))
|
#define I_KIND(i) ((int)((i) >> 40))
|
||||||
|
|
@ -63,7 +67,8 @@
|
||||||
|
|
||||||
enum {
|
enum {
|
||||||
D_ROOT, D_BUS, D_PCI, D_PCIDEV, D_USB, D_USBDEV, D_FW, D_ACPI, D_ACPITAB,
|
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] = {
|
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_CLASS] = { "class", D_ROOT },
|
||||||
[D_CLASSDMI] = { "dmi", D_CLASS },
|
[D_CLASSDMI] = { "dmi", D_CLASS },
|
||||||
[D_DMIID] = { "id", D_CLASSDMI },
|
[D_DMIID] = { "id", D_CLASSDMI },
|
||||||
|
[D_PSU] = { "power_supply", D_CLASS },
|
||||||
};
|
};
|
||||||
|
|
||||||
#define SYSFS_ROOT INO(K_DIR, D_ROOT, 0)
|
#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" };
|
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
|
// Text
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
@ -234,6 +257,136 @@ usb_by_idx(int idx, struct usb_info *u)
|
||||||
return -1;
|
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
|
static int
|
||||||
children(uint64 dino, child_fn cb, void *ctx)
|
children(uint64 dino, child_fn cb, void *ctx)
|
||||||
{
|
{
|
||||||
|
|
@ -296,6 +449,13 @@ children(uint64 dino, child_fn cb, void *ctx)
|
||||||
}
|
}
|
||||||
break;
|
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:
|
case D_DMIID:
|
||||||
for (int f = 0; f < DMI_NFIELDS; f++)
|
for (int f = 0; f < DMI_NFIELDS; f++)
|
||||||
if (dmi_field(f, name, sizeof name) >= 0 &&
|
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++)
|
for (int f = 1; f < NUSBFILES; f++)
|
||||||
if (cb(ctx, INO(K_USB, idx, f), 0, usbfiles[f]))
|
if (cb(ctx, INO(K_USB, idx, f), 0, usbfiles[f]))
|
||||||
return 1;
|
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) {
|
} else if (kind == K_MEMMAP && I_FILE(dino) == 0) {
|
||||||
for (int f = 1; f < 4; f++)
|
for (int f = 1; f < 4; f++)
|
||||||
if (cb(ctx, INO(K_MEMMAP, idx, f), 0, memfiles[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);
|
return INO(K_DIR, D_PCIDEV, 0);
|
||||||
case K_USB: return INO(K_DIR, D_USBDEV, 0);
|
case K_USB: return INO(K_DIR, D_USBDEV, 0);
|
||||||
case K_MEMMAP: return INO(K_DIR, D_MEMMAP, 0);
|
case K_MEMMAP: return INO(K_DIR, D_MEMMAP, 0);
|
||||||
|
case K_PSU: return INO(K_DIR, D_PSU, 0);
|
||||||
}
|
}
|
||||||
return SYSFS_ROOT;
|
return SYSFS_ROOT;
|
||||||
}
|
}
|
||||||
|
|
@ -505,6 +672,11 @@ text(uint64 ino, char *buf, int cap)
|
||||||
gen_usb(&o, &u, f);
|
gen_usb(&o, &u, f);
|
||||||
return o.n;
|
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: {
|
case K_MEMMAP: {
|
||||||
struct e820_rec e;
|
struct e820_rec e;
|
||||||
if (e820_nth(idx, &e) < 0 || f < 1 || f > 3)
|
if (e820_nth(idx, &e) < 0 || f < 1 || f > 3)
|
||||||
|
|
@ -548,6 +720,8 @@ exists_dir(uint64 ino)
|
||||||
struct e820_rec e;
|
struct e820_rec e;
|
||||||
return e820_nth(I_IDX(ino), &e) == 0;
|
return e820_nth(I_IDX(ino), &e) == 0;
|
||||||
}
|
}
|
||||||
|
case K_PSU:
|
||||||
|
return I_IDX(ino) < acpi_psu_count();
|
||||||
}
|
}
|
||||||
return 0;
|
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)
|
if (!last)
|
||||||
return;
|
return;
|
||||||
mm_release_shared(m);
|
mm_release_shared(m);
|
||||||
|
mapname_free(m);
|
||||||
if (m->pgdir)
|
if (m->pgdir)
|
||||||
freevm(m->pgdir);
|
freevm(m->pgdir);
|
||||||
m->pgdir = 0;
|
m->pgdir = 0;
|
||||||
|
|
@ -720,6 +721,7 @@ fork(void)
|
||||||
shm_ref(np->mm->vma[i].shmid);
|
shm_ref(np->mm->vma[i].shmid);
|
||||||
}
|
}
|
||||||
np->mm->mmap_next = myproc()->mm->mmap_next;
|
np->mm->mmap_next = myproc()->mm->mmap_next;
|
||||||
|
mapname_copy(np->mm, pm);
|
||||||
mm_unlock(pm);
|
mm_unlock(pm);
|
||||||
|
|
||||||
memmove(np->tf, myproc()->tf, sizeof(struct trapframe));
|
memmove(np->tf, myproc()->tf, sizeof(struct trapframe));
|
||||||
|
|
@ -1360,6 +1362,17 @@ exit(int status)
|
||||||
struct proc *p;
|
struct proc *p;
|
||||||
int fd;
|
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) {
|
if(myproc() == initproc) {
|
||||||
// Returning from exit() is the *success* path here. exec() does not fail
|
// 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
|
// 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;
|
*user = u;
|
||||||
*sys = s;
|
*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);
|
vm_unmap_range(myproc()->pgdir, va, length);
|
||||||
int fixed_file = f && f->type == FD_INODE && f->ip->type != T_DEV &&
|
int fixed_file = f && f->type == FD_INODE && f->ip->type != T_DEV &&
|
||||||
!(flags & MAP_ANONYMOUS);
|
!(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
|
// Reserved, not backed. PROT_NONE is recorded too: mprotect() does not
|
||||||
// change pages here, so a range reserved PROT_NONE and opened up later
|
// change pages here, so a range reserved PROT_NONE and opened up later
|
||||||
// has to be memory that can appear when it is touched.
|
// 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);
|
vm_unmap_range(myproc()->pgdir, va, length);
|
||||||
return -ENOMEM;
|
return -ENOMEM;
|
||||||
}
|
}
|
||||||
|
mapname_add(myproc()->mm, va, length, offset, f->path);
|
||||||
return va;
|
return va;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -531,6 +536,7 @@ munmap_locked(void)
|
||||||
if (bad_user_range(addr, length) || (addr % PGSIZE))
|
if (bad_user_range(addr, length) || (addr % PGSIZE))
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
uint64 end = addr + length;
|
uint64 end = addr + length;
|
||||||
|
mapname_unmap(p->mm, addr, length);
|
||||||
|
|
||||||
// A segment or device mapping goes only as a whole, by its own address.
|
// 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
|
// Part of one is refused: unmapping it the anonymous way below would kfree
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue