Initial commit: Schunix as it stands before the SchOS rename
An x86-64 operating system built from xv6, now largely rewritten: SMP, a copy-on-write root filesystem (SchFS), PCI/USB/virtio drivers, a TTF rasteriser, and a Linux-numbered syscall ABI. No history before this point -- the tree was never under version control. This commit exists so the rename that follows has something to undo to.
This commit is contained in:
commit
d0f7acf9eb
275 changed files with 51417 additions and 0 deletions
264
kernel/arch/x86_64/acpi.c
Normal file
264
kernel/arch/x86_64/acpi.c
Normal file
|
|
@ -0,0 +1,264 @@
|
|||
// Find the processors, the way firmware from this century describes them.
|
||||
//
|
||||
// mpinit() walks the Intel MP table, which is from 1994. Modern firmware fills
|
||||
// it in minimally or omits it: measured 2026-08-08, QEMU/SeaBIOS with -smp 4
|
||||
// publishes a 208-byte MP table containing exactly *one* processor entry, then
|
||||
// buses and interrupt assignments. Everything since has expected the operating
|
||||
// system to read the ACPI MADT instead.
|
||||
//
|
||||
// The consequence was not that SMP was slow, it was that ncpu was 1 and SMP was
|
||||
// silently never exercised. Machines whose firmware does publish a full MP
|
||||
// table -- VMware, older hardware -- took the other path, started their APs,
|
||||
// and triple faulted. So this file is a prerequisite for debugging that: until
|
||||
// the CPUs are visible under QEMU there is no way to reproduce the crash except
|
||||
// on the hardware it happens on.
|
||||
//
|
||||
// Written from the ACPI specification. No table layout here is copied from
|
||||
// another kernel; they are all in chapter 5 of the published standard.
|
||||
|
||||
#include "types.h"
|
||||
#include "defs.h"
|
||||
#include "param.h"
|
||||
#include "memlayout.h"
|
||||
#include "mmu.h"
|
||||
#include "proc.h"
|
||||
#include "x86.h"
|
||||
|
||||
extern struct cpu cpus[NCPU];
|
||||
extern int ncpu;
|
||||
extern uchar ioapicid;
|
||||
extern volatile uint *lapic;
|
||||
|
||||
// The Root System Description Pointer. Found by signature, not by being told
|
||||
// where it is -- it sits either in the Extended BIOS Data Area or in the BIOS
|
||||
// read-only region, on a 16-byte boundary.
|
||||
struct acpi_rsdp {
|
||||
char sig[8]; // "RSD PTR "
|
||||
uint8 checksum; // over the first 20 bytes
|
||||
char oemid[6];
|
||||
uint8 revision; // 0 = ACPI 1.0 (RSDT), 2+ = 2.0 (XSDT)
|
||||
uint32 rsdt_addr;
|
||||
// ACPI 2.0 and later only, and only valid when revision >= 2.
|
||||
uint32 length;
|
||||
uint64 xsdt_addr;
|
||||
uint8 ext_checksum; // over `length` bytes
|
||||
uint8 reserved[3];
|
||||
} __attribute__((packed));
|
||||
|
||||
// Every ACPI table starts with this.
|
||||
struct acpi_sdt {
|
||||
char sig[4];
|
||||
uint32 length;
|
||||
uint8 revision;
|
||||
uint8 checksum;
|
||||
char oemid[6];
|
||||
char oem_table_id[8];
|
||||
uint32 oem_revision;
|
||||
uint32 creator_id;
|
||||
uint32 creator_revision;
|
||||
} __attribute__((packed));
|
||||
|
||||
// The Multiple APIC Description Table: signature "APIC".
|
||||
struct acpi_madt {
|
||||
struct acpi_sdt h;
|
||||
uint32 lapic_addr;
|
||||
uint32 flags; // bit 0: legacy 8259 PICs are present
|
||||
} __attribute__((packed));
|
||||
|
||||
#define MADT_PCAT_COMPAT 1
|
||||
|
||||
// MADT entry types. Only these three are read; the rest are interrupt
|
||||
// routing overrides that ioapic.c does not consult yet.
|
||||
#define MADT_LAPIC 0
|
||||
#define MADT_IOAPIC 1
|
||||
#define MADT_LAPIC_OVERRIDE 5
|
||||
|
||||
struct madt_entry {
|
||||
uint8 type;
|
||||
uint8 length;
|
||||
} __attribute__((packed));
|
||||
|
||||
struct madt_lapic {
|
||||
struct madt_entry e;
|
||||
uint8 acpi_id;
|
||||
uint8 apic_id;
|
||||
uint32 flags; // bit 0: enabled, bit 1: online-capable
|
||||
} __attribute__((packed));
|
||||
|
||||
#define LAPIC_ENABLED 1
|
||||
#define LAPIC_ONLINE_CAPABLE 2
|
||||
|
||||
struct madt_ioapic {
|
||||
struct madt_entry e;
|
||||
uint8 id;
|
||||
uint8 reserved;
|
||||
uint32 addr;
|
||||
uint32 gsi_base;
|
||||
} __attribute__((packed));
|
||||
|
||||
struct madt_lapic_override {
|
||||
struct madt_entry e;
|
||||
uint16 reserved;
|
||||
uint64 addr;
|
||||
} __attribute__((packed));
|
||||
|
||||
static int
|
||||
checksum_ok(const void *p, uint32 len)
|
||||
{
|
||||
const uint8 *b = p;
|
||||
uint8 sum = 0;
|
||||
for (uint32 i = 0; i < len; i++)
|
||||
sum += b[i];
|
||||
return sum == 0;
|
||||
}
|
||||
|
||||
// Scan a physical range for the RSDP signature. It is 16-byte aligned by
|
||||
// specification, which makes this cheap enough to do twice.
|
||||
static struct acpi_rsdp *
|
||||
scan_rsdp(uint64 base, uint64 len)
|
||||
{
|
||||
for (uint64 off = 0; off + sizeof(struct acpi_rsdp) <= len; off += 16) {
|
||||
struct acpi_rsdp *r = (struct acpi_rsdp *)P2V(base + off);
|
||||
if (memcmp(r->sig, "RSD PTR ", 8) != 0)
|
||||
continue;
|
||||
// Signature alone is not enough: the string can appear in unrelated data,
|
||||
// and following a bad pointer here means faulting before there is anything
|
||||
// to report the fault with.
|
||||
if (!checksum_ok(r, 20))
|
||||
continue;
|
||||
return r;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct acpi_rsdp *
|
||||
find_rsdp(void)
|
||||
{
|
||||
// The EBDA, whose segment address the BIOS leaves at 0x40E.
|
||||
uint16 ebda_seg = *(uint16 *)P2V(0x40E);
|
||||
if (ebda_seg) {
|
||||
struct acpi_rsdp *r = scan_rsdp((uint64)ebda_seg << 4, 1024);
|
||||
if (r)
|
||||
return r;
|
||||
}
|
||||
return scan_rsdp(0xE0000, 0x20000);
|
||||
}
|
||||
|
||||
// Walk the RSDT or XSDT looking for a table by signature.
|
||||
//
|
||||
// Two nearly identical loops rather than one clever one: the only difference is
|
||||
// the pointer width -- 32-bit entries in the RSDT, 64-bit in the XSDT -- and
|
||||
// reading a 64-bit pointer out of a 32-bit array is exactly the sort of bug
|
||||
// that produces a plausible address pointing at nothing.
|
||||
static struct acpi_sdt *
|
||||
find_table(struct acpi_rsdp *rsdp, const char *sig)
|
||||
{
|
||||
struct acpi_sdt *root;
|
||||
int wide;
|
||||
|
||||
if (rsdp->revision >= 2 && rsdp->xsdt_addr) {
|
||||
root = (struct acpi_sdt *)P2V(rsdp->xsdt_addr);
|
||||
wide = 1;
|
||||
} else {
|
||||
root = (struct acpi_sdt *)P2V((uint64)rsdp->rsdt_addr);
|
||||
wide = 0;
|
||||
}
|
||||
|
||||
if (!checksum_ok(root, root->length))
|
||||
return 0;
|
||||
|
||||
uint32 n = (root->length - sizeof(struct acpi_sdt)) / (wide ? 8 : 4);
|
||||
for (uint32 i = 0; i < n; i++) {
|
||||
uint64 pa;
|
||||
if (wide) {
|
||||
// Byte-wise, because the array is packed and 8-byte entries in it are
|
||||
// not guaranteed to be 8-byte aligned.
|
||||
uint8 *p = (uint8 *)(root + 1) + i * 8;
|
||||
pa = 0;
|
||||
for (int b = 7; b >= 0; b--)
|
||||
pa = (pa << 8) | p[b];
|
||||
} else {
|
||||
pa = ((uint32 *)(root + 1))[i];
|
||||
}
|
||||
struct acpi_sdt *t = (struct acpi_sdt *)P2V(pa);
|
||||
if (memcmp(t->sig, sig, 4) == 0)
|
||||
return t;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Returns 1 if the processors were enumerated, 0 to fall back to mpinit().
|
||||
int
|
||||
acpi_init(void)
|
||||
{
|
||||
struct acpi_rsdp *rsdp = find_rsdp();
|
||||
if (!rsdp)
|
||||
return 0;
|
||||
|
||||
struct acpi_madt *madt = (struct acpi_madt *)find_table(rsdp, "APIC");
|
||||
if (!madt || !checksum_ok(madt, madt->h.length))
|
||||
return 0;
|
||||
|
||||
lapic = (uint *)P2V((uint64)madt->lapic_addr);
|
||||
|
||||
uint8 *p = (uint8 *)madt + sizeof(struct acpi_madt);
|
||||
uint8 *e = (uint8 *)madt + madt->h.length;
|
||||
int found_ioapic = 0;
|
||||
|
||||
while (p + sizeof(struct madt_entry) <= e) {
|
||||
struct madt_entry *ent = (struct madt_entry *)p;
|
||||
// A zero length would spin here forever. Firmware tables are not always
|
||||
// well formed, and an infinite loop this early is indistinguishable from a
|
||||
// hang with no output at all.
|
||||
if (ent->length < sizeof(struct madt_entry))
|
||||
break;
|
||||
|
||||
switch (ent->type) {
|
||||
case MADT_LAPIC: {
|
||||
struct madt_lapic *l = (struct madt_lapic *)p;
|
||||
// Skip processors that are neither enabled nor online-capable: those are
|
||||
// sockets the firmware is describing but not offering.
|
||||
if (!(l->flags & (LAPIC_ENABLED | LAPIC_ONLINE_CAPABLE)))
|
||||
break;
|
||||
if (ncpu < NCPU) {
|
||||
cpus[ncpu].apicid = l->apic_id;
|
||||
ncpu++;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case MADT_IOAPIC: {
|
||||
struct madt_ioapic *io = (struct madt_ioapic *)p;
|
||||
// The first one only. Multi-I/O-APIC routing needs the interrupt source
|
||||
// overrides too, which nothing here reads yet, so claiming to support it
|
||||
// would be worse than not.
|
||||
if (!found_ioapic) {
|
||||
ioapicid = io->id;
|
||||
found_ioapic = 1;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case MADT_LAPIC_OVERRIDE: {
|
||||
struct madt_lapic_override *o = (struct madt_lapic_override *)p;
|
||||
lapic = (uint *)P2V(o->addr);
|
||||
break;
|
||||
}
|
||||
}
|
||||
p += ent->length;
|
||||
}
|
||||
|
||||
if (ncpu == 0)
|
||||
return 0;
|
||||
|
||||
// The 8259s are still there and still wired to the CPU. On a system with an
|
||||
// IMCR, this switches interrupt delivery from the PICs to the APIC; on one
|
||||
// without, the ports are unclaimed and the writes go nowhere. mpinit() does
|
||||
// the same thing guarded by the MP table's imcrp flag.
|
||||
if (madt->flags & MADT_PCAT_COMPAT) {
|
||||
outb(0x22, 0x70);
|
||||
outb(0x23, inb(0x23) | 1);
|
||||
}
|
||||
|
||||
cprintf("acpi: %d cpu%s, lapic at %p, ioapic id %d\n",
|
||||
ncpu, ncpu == 1 ? "" : "s", lapic, ioapicid);
|
||||
return 1;
|
||||
}
|
||||
105
kernel/arch/x86_64/e820.c
Normal file
105
kernel/arch/x86_64/e820.c
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
// Find out how much memory this machine actually has.
|
||||
//
|
||||
// PHYSTOP used to be a constant: 0xE000000, 224 MB, inherited from xv6 and
|
||||
// never revisited. That is not a limit on how much memory the kernel *uses* --
|
||||
// it is an assertion that exactly that much exists. Boot a machine with less
|
||||
// and kinit1() walks off the end of RAM into nothing; boot one with more and
|
||||
// every byte above 224 MB is invisible. Measured before this file existed:
|
||||
// Schunix booted at 224 MB and triple faulted at 192 MB, for no reason at all.
|
||||
//
|
||||
// The firmware has known the answer the whole time. boot/stage2.S asks it with
|
||||
// INT 0x15 / E820 in real mode -- the same interface Linux's boot stub and GRUB
|
||||
// use -- and leaves the result in low memory. stage2main.c already parses it to
|
||||
// print detected RAM on the splash. The kernel simply never read it.
|
||||
|
||||
#include "types.h"
|
||||
#include "defs.h"
|
||||
#include "memlayout.h"
|
||||
#include "mmu.h"
|
||||
|
||||
// Set before kinit1() and never again. Starts at the old constant so that a
|
||||
// crash between entry and e820_init() behaves the way it always did rather
|
||||
// than in some new way.
|
||||
uint64 phystop = PHYSTOP_FALLBACK;
|
||||
|
||||
// Where stage2 left the map. Must match E820_COUNT_ADDR / E820_MAP_ADDR in
|
||||
// boot/stage2main.c, which are themselves relative to VBE_INFO_ADDR.
|
||||
#define E820_COUNT_ADDR (0x30000 + 0x200)
|
||||
#define E820_MAP_ADDR (0x30000 + 0x208)
|
||||
|
||||
#define E820_USABLE 1
|
||||
#define E820_MAXENTRIES 32
|
||||
|
||||
struct e820entry {
|
||||
uint64 base;
|
||||
uint64 length;
|
||||
uint32 type;
|
||||
uint32 acpi_ext;
|
||||
} __attribute__((packed));
|
||||
|
||||
void
|
||||
e820_init(void)
|
||||
{
|
||||
uint32 count = *(uint32 *)P2V(E820_COUNT_ADDR);
|
||||
struct e820entry *e = (struct e820entry *)P2V(E820_MAP_ADDR);
|
||||
|
||||
// A count of zero means the firmware declined; anything absurd means we are
|
||||
// reading something that is not a memory map. Either way the constant is the
|
||||
// only honest answer left.
|
||||
if (count == 0 || count > E820_MAXENTRIES) {
|
||||
cprintf("e820: no usable map (count %d), assuming %d MB\n",
|
||||
count, (int)(PHYSTOP_FALLBACK / (1024 * 1024)));
|
||||
return;
|
||||
}
|
||||
|
||||
// The end of the usable region that *contains the kernel*, not the highest
|
||||
// usable address anywhere.
|
||||
//
|
||||
// This distinction matters and is easy to get wrong. A memory map is not one
|
||||
// run of RAM: there are reserved holes in it -- ACPI tables, firmware
|
||||
// scratch, the legacy hole below 1 MB -- and kalloc's freerange() walks
|
||||
// straight from the end of the kernel image to phystop handing out every page
|
||||
// between. Taking the global maximum would step over a reserved region and
|
||||
// put firmware tables on the free list, which fails later, elsewhere, and
|
||||
// looks like anything but a memory map problem.
|
||||
uint64 top = 0;
|
||||
for (uint32 i = 0; i < count; i++) {
|
||||
if (e[i].type != E820_USABLE)
|
||||
continue;
|
||||
uint64 base = e[i].base, end = base + e[i].length;
|
||||
if (base <= EXTMEM && EXTMEM < end) {
|
||||
top = end;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (top == 0) {
|
||||
cprintf("e820: no usable region covers 0x%x, assuming %d MB\n",
|
||||
EXTMEM, (int)(PHYSTOP_FALLBACK / (1024 * 1024)));
|
||||
return;
|
||||
}
|
||||
|
||||
// kvmalloc() maps physical 0-4 GB with four 1 GB pages, and marks the top one
|
||||
// uncached because that is where PCI BARs and the framebuffer live. So 3 GB
|
||||
// is the ceiling for anything the kernel will treat as RAM: above it the
|
||||
// direct map either does not exist or is device memory that would be
|
||||
// catastrophically slow to allocate from and is not ours to hand out.
|
||||
if (top > PHYSTOP_MAX)
|
||||
top = PHYSTOP_MAX;
|
||||
|
||||
top &= ~(uint64)(PGSIZE - 1);
|
||||
|
||||
// Below this the kernel image, its page tables and the DMA arena do not fit,
|
||||
// and continuing produces a failure much harder to read than this line.
|
||||
if (top < PHYSTOP_MIN) {
|
||||
cprintf("e820: only %d MB usable; Schunix needs at least %d MB\n",
|
||||
(int)(top / (1024 * 1024)), (int)(PHYSTOP_MIN / (1024 * 1024)));
|
||||
// Left at the fallback deliberately rather than lowered: a machine this
|
||||
// small is going to fail, and it should fail having said why.
|
||||
return;
|
||||
}
|
||||
|
||||
phystop = top;
|
||||
cprintf("e820: %d MB usable (%d entries)\n",
|
||||
(int)(phystop / (1024 * 1024)), count);
|
||||
}
|
||||
174
kernel/arch/x86_64/entry.S
Normal file
174
kernel/arch/x86_64/entry.S
Normal file
|
|
@ -0,0 +1,174 @@
|
|||
/* entry.S
|
||||
*
|
||||
* Copyright (c) 2013 Brian Swetland
|
||||
* Modified by Nexoniarz
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining
|
||||
* a copy of this software and associated documentation files (the
|
||||
* "Software"), to deal in the Software without restriction, including
|
||||
* without limitation the rights to use, copy, modify, merge, publish,
|
||||
* distribute, sublicense, and/or sell copies of the Software, and to
|
||||
* permit persons to whom the Software is furnished to do so, subject to
|
||||
* the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*
|
||||
*/
|
||||
|
||||
#define mboot_magic 0x1badb002
|
||||
#define mboot_flags 0x00010000
|
||||
#include "mmu.h"
|
||||
|
||||
.code64
|
||||
.global mboot_header
|
||||
.global mboot_entry
|
||||
|
||||
mboot_header:
|
||||
.long mboot_magic
|
||||
.long mboot_flags
|
||||
.long (-mboot_magic -mboot_flags)
|
||||
.long mboot_load_addr
|
||||
.long mboot_load_addr
|
||||
.long mboot_load_end
|
||||
.long mboot_bss_end
|
||||
.long mboot_entry_addr
|
||||
|
||||
.code32
|
||||
mboot_entry:
|
||||
mov %eax, %ebp
|
||||
|
||||
# bootstrap page tables (0x1000 - 0x3000)
|
||||
xor %eax, %eax
|
||||
mov $0x1000, %edi
|
||||
mov $0x2000, %ecx
|
||||
rep stosb
|
||||
|
||||
# Bootstrap page tables (Identity mapping 4GB)
|
||||
# PML4T[0] i PML4T[256] -> PDPT pod 0x2000
|
||||
mov $(0x2000 | PTE_P | PTE_W), %edx
|
||||
mov %edx, 0x1000
|
||||
mov %edx, 0x1800
|
||||
|
||||
# PDPT mapping 4GB using 1GB huge pages
|
||||
mov $(0x00000000 | PTE_P | PTE_PS | PTE_W), %edx
|
||||
mov %edx, 0x2000
|
||||
mov $(0x40000000 | PTE_P | PTE_PS | PTE_W), %edx
|
||||
mov %edx, 0x2008
|
||||
mov $(0x80000000 | PTE_P | PTE_PS | PTE_W), %edx
|
||||
mov %edx, 0x2010
|
||||
mov $(0xC0000000 | PTE_P | PTE_PS | PTE_W), %edx
|
||||
mov %edx, 0x2018
|
||||
|
||||
mov %ebp, %ebx
|
||||
|
||||
.global entry32mp
|
||||
entry32mp:
|
||||
mov $0x1000, %eax
|
||||
mov %eax, %cr3
|
||||
|
||||
lgdt (gdtr64 - mboot_header + mboot_load_addr)
|
||||
|
||||
mov %cr4, %eax
|
||||
bts $5, %eax
|
||||
mov %eax, %cr4
|
||||
|
||||
mov $MSR_EFER, %ecx
|
||||
rdmsr
|
||||
bts $0, %eax
|
||||
bts $8, %eax
|
||||
wrmsr
|
||||
|
||||
mov %cr0, %eax
|
||||
orl $(CR0_PG | CR0_WP | CR0_MP), %eax
|
||||
mov %eax, %cr0
|
||||
|
||||
ljmp $8, $(entry64low - mboot_header + mboot_load_addr)
|
||||
|
||||
.align 16
|
||||
gdtr64:
|
||||
.word gdt64_end - gdt64_begin - 1;
|
||||
.quad gdt64_begin - mboot_header + mboot_load_addr
|
||||
|
||||
.align 16
|
||||
gdt64_begin:
|
||||
.long 0x00000000
|
||||
.long 0x00000000
|
||||
.long 0x00000000 # 64-bit code
|
||||
.long 0x00209800
|
||||
.long 0x00000000 # 64-bit data
|
||||
.long 0x00009000
|
||||
gdt64_end:
|
||||
|
||||
.align 16
|
||||
.code64
|
||||
entry64low:
|
||||
movabs $entry64high, %rax
|
||||
jmp *%rax
|
||||
|
||||
.global _start
|
||||
_start:
|
||||
entry64high:
|
||||
xor %rax, %rax
|
||||
mov %ax, %ss
|
||||
mov %ax, %ds
|
||||
mov %ax, %es
|
||||
mov %ax, %fs
|
||||
mov %ax, %gs
|
||||
|
||||
# Is this an application processor, or the boot processor?
|
||||
#
|
||||
# This check used to be a comment saying it had not been written yet, and
|
||||
# every AP fell straight through into the BSP path below: it set RSP to the
|
||||
# *boot processor's* stack, passed its own flag to kmain() as a framebuffer
|
||||
# address, and re-ran the entire bootstrap -- e820_init, kinit1, kvmalloc --
|
||||
# on a second core sharing one stack with the first. That is the SMP triple
|
||||
# fault. QEMU hid it for years by never reporting more than one CPU (see
|
||||
# kernel/arch/x86_64/acpi.c); VMware did not.
|
||||
#
|
||||
# EBX is the discriminator, and entryother.S sets it to 1 immediately before
|
||||
# entering long mode for exactly this purpose. It is safe: on the boot
|
||||
# processor EBX carries the VESA framebuffer address from the loader, which is
|
||||
# either zero or a physical MMIO address, and can never be 1.
|
||||
cmp $1, %ebx
|
||||
je entry64mp
|
||||
|
||||
# Setup initial stack
|
||||
movabs $0xFFFF800000010000, %rax
|
||||
mov %rax, %rsp
|
||||
|
||||
# Pass VESA address to main (1st argument in RDI)
|
||||
mov %rbx, %rdi
|
||||
|
||||
call kmain
|
||||
|
||||
.global __deadloop
|
||||
__deadloop:
|
||||
jmp .
|
||||
|
||||
entry64mp:
|
||||
mov $0x7000, %rax
|
||||
mov -16(%rax), %rsp
|
||||
jmp mpenter
|
||||
|
||||
.global wrmsr
|
||||
wrmsr:
|
||||
mov %rdi, %rcx
|
||||
mov %rsi, %rax
|
||||
shr $32, %rsi
|
||||
mov %rsi, %rdx
|
||||
wrmsr
|
||||
retq
|
||||
|
||||
.global ignore_sysret
|
||||
ignore_sysret:
|
||||
mov $-38, %rax
|
||||
sysretq
|
||||
78
kernel/arch/x86_64/entryother.S
Normal file
78
kernel/arch/x86_64/entryother.S
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
#include "asm.h"
|
||||
#include "memlayout.h"
|
||||
#include "mmu.h"
|
||||
|
||||
# Each non-boot CPU ("AP") is started up in response to a STARTUP
|
||||
# IPI from the boot CPU. Section B.4.2 of the Multi-Processor
|
||||
# Specification says that the AP will start in real mode with CS:IP
|
||||
# set to XY00:0000, where XY is an 8-bit value sent with the
|
||||
# STARTUP. Thus this code must start at a 4096-byte boundary.
|
||||
#
|
||||
# Because this code sets DS to zero, it must sit
|
||||
# at an address in the low 2^16 bytes.
|
||||
#
|
||||
# Startothers (in main.c) sends the STARTUPs one at a time.
|
||||
# It copies this code (start) at 0x7000. It puts the address of
|
||||
# a newly allocated per-core stack in start-4,the address of the
|
||||
# place to jump to (mpenter) in start-8, and the physical address
|
||||
# of entrypgdir in start-12.
|
||||
#
|
||||
# This code is identical to bootasm.S except:
|
||||
# - it does not need to enable A20
|
||||
# - it uses the address at start-4, start-8, and start-12
|
||||
|
||||
.code16
|
||||
.global start
|
||||
start:
|
||||
cli
|
||||
|
||||
xorw %ax,%ax
|
||||
movw %ax,%ds
|
||||
movw %ax,%es
|
||||
movw %ax,%ss
|
||||
|
||||
lgdt gdtdesc
|
||||
movl %cr0, %eax
|
||||
orl $CR0_PE, %eax
|
||||
movl %eax, %cr0
|
||||
|
||||
ljmpl $(SEG_KCODE<<3), $(start32)
|
||||
//PAGEBREAK!
|
||||
|
||||
.code32
|
||||
start32:
|
||||
movw $(SEG_KDATA<<3), %ax
|
||||
movw %ax, %ds
|
||||
movw %ax, %es
|
||||
movw %ax, %ss
|
||||
movw $0, %ax
|
||||
movw %ax, %fs
|
||||
movw %ax, %gs
|
||||
|
||||
# defer paging until we switch to 64bit mode
|
||||
# set ebx=1 so shared boot code knows we're booting a secondary core
|
||||
mov $1, %ebx
|
||||
|
||||
# Switch to the stack allocated by startothers()
|
||||
movl (start-4), %esp
|
||||
# Call mpenter()
|
||||
call *(start-8)
|
||||
|
||||
movw $0x8a00, %ax
|
||||
movw %ax, %dx
|
||||
outw %ax, %dx
|
||||
movw $0x8ae0, %ax
|
||||
outw %ax, %dx
|
||||
spin:
|
||||
jmp spin
|
||||
|
||||
.p2align 2
|
||||
gdt:
|
||||
SEG_NULLASM
|
||||
SEG_ASM(STA_X|STA_R, 0, 0xffffffff)
|
||||
SEG_ASM(STA_W, 0, 0xffffffff)
|
||||
|
||||
|
||||
gdtdesc:
|
||||
.word (gdtdesc - gdt - 1)
|
||||
.long gdt
|
||||
102
kernel/arch/x86_64/ioapic.c
Normal file
102
kernel/arch/x86_64/ioapic.c
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
// The I/O APIC manages hardware interrupts for an SMP system.
|
||||
// http://www.intel.com/design/chipsets/datashts/29056601.pdf
|
||||
// See also picirq.c.
|
||||
|
||||
#include "types.h"
|
||||
#include "defs.h"
|
||||
#include "traps.h"
|
||||
#include "memlayout.h"
|
||||
|
||||
#define IOAPIC 0xFEC00000 // Default physical address of IO APIC
|
||||
|
||||
#define REG_ID 0x00 // Register index: ID
|
||||
#define REG_VER 0x01 // Register index: version
|
||||
#define REG_TABLE 0x10 // Redirection table base
|
||||
|
||||
// The redirection table starts at REG_TABLE and uses
|
||||
// two registers to configure each interrupt.
|
||||
// The first (low) register in a pair contains configuration bits.
|
||||
// The second (high) register contains a bitmask telling which
|
||||
// CPUs can serve that interrupt.
|
||||
#define INT_DISABLED 0x00010000 // Interrupt disabled
|
||||
#define INT_LEVEL 0x00008000 // Level-triggered (vs edge-)
|
||||
#define INT_ACTIVELOW 0x00002000 // Active low (vs high)
|
||||
#define INT_LOGICAL 0x00000800 // Destination is CPU id (vs APIC ID)
|
||||
|
||||
volatile struct ioapic *ioapic;
|
||||
|
||||
// IO APIC MMIO structure: write reg, then read or write data.
|
||||
struct ioapic {
|
||||
uint reg;
|
||||
uint pad[3];
|
||||
uint data;
|
||||
};
|
||||
|
||||
static uint
|
||||
ioapicread(int reg)
|
||||
{
|
||||
ioapic->reg = reg;
|
||||
return ioapic->data;
|
||||
}
|
||||
|
||||
static void
|
||||
ioapicwrite(int reg, uint data)
|
||||
{
|
||||
ioapic->reg = reg;
|
||||
ioapic->data = data;
|
||||
}
|
||||
|
||||
void
|
||||
ioapicinit(void)
|
||||
{
|
||||
int i, id, maxintr;
|
||||
|
||||
ioapic = P2V((volatile struct ioapic*)IOAPIC);
|
||||
maxintr = (ioapicread(REG_VER) >> 16) & 0xFF;
|
||||
id = ioapicread(REG_ID) >> 24;
|
||||
if(id != ioapicid)
|
||||
cprintf("ioapicinit: id isn't equal to ioapicid; not a MP\n");
|
||||
|
||||
// Mark all interrupts edge-triggered, active high, disabled,
|
||||
// and not routed to any CPUs.
|
||||
for(i = 0; i <= maxintr; i++){
|
||||
ioapicwrite(REG_TABLE+2*i, INT_DISABLED | (T_IRQ0 + i));
|
||||
ioapicwrite(REG_TABLE+2*i+1, 0);
|
||||
}
|
||||
}
|
||||
|
||||
// Route an interrupt to a CPU and unmask it.
|
||||
//
|
||||
// `level_low` picks PCI's signalling instead of ISA's. This distinction did
|
||||
// not matter while every interrupt in the kernel came from an ISA-era device
|
||||
// at a fixed pin, all of which are edge-triggered and active high -- which is
|
||||
// why the original ioapicenable() hardcoded exactly that. A PCI device wired
|
||||
// through an INTx pin is the opposite on both counts, and configuring it as
|
||||
// edge/high does not fail cleanly: the device raises the line, we take one
|
||||
// interrupt, and because the level never goes away there is no second edge
|
||||
// and the device is never heard from again.
|
||||
void
|
||||
ioapicroute(int irq, int cpunum, int level_low)
|
||||
{
|
||||
uint lo = T_IRQ0 + irq;
|
||||
|
||||
if(level_low)
|
||||
lo |= INT_LEVEL | INT_ACTIVELOW;
|
||||
|
||||
// Program the destination before unmasking, so the entry is never briefly
|
||||
// live pointing at CPU 0 when it was meant for another.
|
||||
ioapicwrite(REG_TABLE+2*irq+1, cpunum << 24);
|
||||
ioapicwrite(REG_TABLE+2*irq, lo);
|
||||
}
|
||||
|
||||
void
|
||||
ioapicmask(int irq)
|
||||
{
|
||||
ioapicwrite(REG_TABLE+2*irq, INT_DISABLED | (T_IRQ0 + irq));
|
||||
}
|
||||
|
||||
void
|
||||
ioapicenable(int irq, int cpunum)
|
||||
{
|
||||
ioapicroute(irq, cpunum, 0);
|
||||
}
|
||||
245
kernel/arch/x86_64/lapic.c
Normal file
245
kernel/arch/x86_64/lapic.c
Normal file
|
|
@ -0,0 +1,245 @@
|
|||
// The local APIC manages internal (non-I/O) interrupts.
|
||||
// See Chapter 8 & Appendix C of Intel processor manual volume 3.
|
||||
|
||||
#include "param.h"
|
||||
#include "types.h"
|
||||
#include "defs.h"
|
||||
#include "date.h"
|
||||
#include "memlayout.h"
|
||||
#include "traps.h"
|
||||
#include "mmu.h"
|
||||
#include "x86.h"
|
||||
#include "proc.h" // ncpu
|
||||
|
||||
// Local APIC registers, divided by 4 for use as uint[] indices.
|
||||
#define ID (0x0020/4) // ID
|
||||
#define VER (0x0030/4) // Version
|
||||
#define TPR (0x0080/4) // Task Priority
|
||||
#define EOI (0x00B0/4) // EOI
|
||||
#define SVR (0x00F0/4) // Spurious Interrupt Vector
|
||||
#define ENABLE 0x00000100 // Unit Enable
|
||||
#define ESR (0x0280/4) // Error Status
|
||||
#define ICRLO (0x0300/4) // Interrupt Command
|
||||
#define SINIT 0x00000500 // INIT/RESET
|
||||
#define STARTUP 0x00000600 // Startup IPI
|
||||
#define DELIVS 0x00001000 // Delivery status
|
||||
#define ASSERT 0x00004000 // Assert interrupt (vs deassert)
|
||||
#define LEVEL 0x00008000 // Level triggered
|
||||
#define BCAST 0x00080000 // Send to all APICs, including self.
|
||||
#define BUSY 0x00001000
|
||||
#define ICRHI (0x0310/4) // Interrupt Command [63:32]
|
||||
#define TIMER (0x0320/4) // Local Vector Table 0 (TIMER)
|
||||
#define X1 0x0000000B // divide counts by 1
|
||||
#define PERIODIC 0x00020000 // Periodic
|
||||
#define PCINT (0x0340/4) // Performance Counter LVT
|
||||
#define LINT0 (0x0350/4) // Local Vector Table 1 (LINT0)
|
||||
#define LINT1 (0x0360/4) // Local Vector Table 2 (LINT1)
|
||||
#define ERROR (0x0370/4) // Local Vector Table 3 (ERROR)
|
||||
#define MASKED 0x00010000 // Interrupt masked
|
||||
#define TICR (0x0380/4) // Timer Initial Count
|
||||
#define TCCR (0x0390/4) // Timer Current Count
|
||||
#define TDCR (0x03E0/4) // Timer Divide Configuration
|
||||
|
||||
volatile uint *lapic; // Initialized in mp.c
|
||||
|
||||
static void
|
||||
lapicw(int index, int value)
|
||||
{
|
||||
lapic[index] = value;
|
||||
lapic[ID]; // wait for write to finish, by reading
|
||||
}
|
||||
|
||||
void
|
||||
lapicinit(void)
|
||||
{
|
||||
if(!lapic)
|
||||
return;
|
||||
|
||||
// Enable local APIC; set spurious interrupt vector.
|
||||
lapicw(SVR, ENABLE | (T_IRQ0 + IRQ_SPURIOUS));
|
||||
|
||||
// The timer repeatedly counts down at bus frequency
|
||||
// from lapic[TICR] and then issues an interrupt.
|
||||
// If xv6 cared more about precise timekeeping,
|
||||
// TICR would be calibrated using an external time source.
|
||||
lapicw(TDCR, X1);
|
||||
lapicw(TIMER, PERIODIC | (T_IRQ0 + IRQ_TIMER));
|
||||
lapicw(TICR, 10000000);
|
||||
|
||||
// Disable logical interrupt lines.
|
||||
lapicw(LINT0, MASKED);
|
||||
lapicw(LINT1, MASKED);
|
||||
|
||||
// Disable performance counter overflow interrupts
|
||||
// on machines that provide that interrupt entry.
|
||||
if(((lapic[VER]>>16) & 0xFF) >= 4)
|
||||
lapicw(PCINT, MASKED);
|
||||
|
||||
// Map error interrupt to IRQ_ERROR.
|
||||
lapicw(ERROR, T_IRQ0 + IRQ_ERROR);
|
||||
|
||||
// Clear error status register (requires back-to-back writes).
|
||||
lapicw(ESR, 0);
|
||||
lapicw(ESR, 0);
|
||||
|
||||
// Ack any outstanding interrupts.
|
||||
lapicw(EOI, 0);
|
||||
|
||||
// Send an Init Level De-Assert to synchronise arbitration ID's.
|
||||
lapicw(ICRHI, 0);
|
||||
lapicw(ICRLO, BCAST | SINIT | LEVEL);
|
||||
while(lapic[ICRLO] & DELIVS)
|
||||
;
|
||||
|
||||
// Enable interrupts on the APIC (but not on the processor).
|
||||
lapicw(TPR, 0);
|
||||
}
|
||||
|
||||
int
|
||||
cpunum(void)
|
||||
{
|
||||
int apicid, i;
|
||||
|
||||
// Cannot call cpu when interrupts are enabled:
|
||||
// result not guaranteed to last long enough to be used!
|
||||
// Would prefer to panic but even printing is chancy here:
|
||||
// almost everything, including cprintf and panic, calls cpu,
|
||||
// often indirectly through acquire and release.
|
||||
if(readeflags()&FL_IF){
|
||||
static int n;
|
||||
if(n++ == 0)
|
||||
cprintf("cpu called from %x with interrupts enabled\n",
|
||||
__builtin_return_address(0));
|
||||
}
|
||||
|
||||
if (!lapic)
|
||||
return 0;
|
||||
|
||||
apicid = lapic[ID] >> 24;
|
||||
for (i = 0; i < ncpu; ++i) {
|
||||
if (cpus[i].apicid == apicid)
|
||||
return i;
|
||||
}
|
||||
panic("unknown apicid\n");
|
||||
}
|
||||
|
||||
// Acknowledge interrupt.
|
||||
void
|
||||
lapiceoi(void)
|
||||
{
|
||||
if(lapic)
|
||||
lapicw(EOI, 0);
|
||||
}
|
||||
|
||||
// Spin for a given number of microseconds.
|
||||
// On real hardware would want to tune this dynamically.
|
||||
void
|
||||
microdelay(int us)
|
||||
{
|
||||
}
|
||||
|
||||
#define CMOS_PORT 0x70
|
||||
#define CMOS_RETURN 0x71
|
||||
|
||||
// Start additional processor running entry code at addr.
|
||||
// See Appendix B of MultiProcessor Specification.
|
||||
void
|
||||
lapicstartap(uchar apicid, uint addr)
|
||||
{
|
||||
int i;
|
||||
ushort *wrv;
|
||||
|
||||
// "The BSP must initialize CMOS shutdown code to 0AH
|
||||
// and the warm reset vector (DWORD based at 40:67) to point at
|
||||
// the AP startup code prior to the [universal startup algorithm]."
|
||||
outb(CMOS_PORT, 0xF); // offset 0xF is shutdown code
|
||||
outb(CMOS_PORT+1, 0x0A);
|
||||
wrv = (ushort*)P2V((0x40<<4 | 0x67)); // Warm reset vector
|
||||
wrv[0] = 0;
|
||||
wrv[1] = addr >> 4;
|
||||
|
||||
// "Universal startup algorithm."
|
||||
// Send INIT (level-triggered) interrupt to reset other CPU.
|
||||
lapicw(ICRHI, apicid<<24);
|
||||
lapicw(ICRLO, SINIT | LEVEL | ASSERT);
|
||||
microdelay(200);
|
||||
lapicw(ICRLO, SINIT | LEVEL);
|
||||
microdelay(100); // should be 10ms, but too slow in Bochs!
|
||||
|
||||
// Send startup IPI (twice!) to enter code.
|
||||
// Regular hardware is supposed to only accept a STARTUP
|
||||
// when it is in the halted state due to an INIT. So the second
|
||||
// should be ignored, but it is part of the official Intel algorithm.
|
||||
// Bochs complains about the second one. Too bad for Bochs.
|
||||
for(i = 0; i < 2; i++){
|
||||
lapicw(ICRHI, apicid<<24);
|
||||
lapicw(ICRLO, STARTUP | (addr>>12));
|
||||
microdelay(200);
|
||||
}
|
||||
}
|
||||
|
||||
#define CMOS_STATA 0x0a
|
||||
#define CMOS_STATB 0x0b
|
||||
#define CMOS_UIP (1 << 7) // RTC update in progress
|
||||
|
||||
#define SECS 0x00
|
||||
#define MINS 0x02
|
||||
#define HOURS 0x04
|
||||
#define DAY 0x07
|
||||
#define MONTH 0x08
|
||||
#define YEAR 0x09
|
||||
|
||||
static uint cmos_read(uint reg)
|
||||
{
|
||||
outb(CMOS_PORT, reg);
|
||||
microdelay(200);
|
||||
|
||||
return inb(CMOS_RETURN);
|
||||
}
|
||||
|
||||
static void fill_rtcdate(struct rtcdate *r)
|
||||
{
|
||||
r->second = cmos_read(SECS);
|
||||
r->minute = cmos_read(MINS);
|
||||
r->hour = cmos_read(HOURS);
|
||||
r->day = cmos_read(DAY);
|
||||
r->month = cmos_read(MONTH);
|
||||
r->year = cmos_read(YEAR);
|
||||
}
|
||||
//PAGEBREAK!
|
||||
|
||||
// qemu seems to use 24-hour GWT and the values are BCD encoded
|
||||
void cmostime(struct rtcdate *r)
|
||||
{
|
||||
struct rtcdate t1, t2;
|
||||
int sb, bcd;
|
||||
|
||||
sb = cmos_read(CMOS_STATB);
|
||||
|
||||
bcd = (sb & (1 << 2)) == 0;
|
||||
|
||||
// make sure CMOS doesn't modify time while we read it
|
||||
for(;;) {
|
||||
fill_rtcdate(&t1);
|
||||
if(cmos_read(CMOS_STATA) & CMOS_UIP)
|
||||
continue;
|
||||
fill_rtcdate(&t2);
|
||||
if(memcmp(&t1, &t2, sizeof(t1)) == 0)
|
||||
break;
|
||||
}
|
||||
|
||||
// convert
|
||||
if(bcd) {
|
||||
#define CONV(x) (t1.x = ((t1.x >> 4) * 10) + (t1.x & 0xf))
|
||||
CONV(second);
|
||||
CONV(minute);
|
||||
CONV(hour );
|
||||
CONV(day );
|
||||
CONV(month );
|
||||
CONV(year );
|
||||
#undef CONV
|
||||
}
|
||||
|
||||
*r = t1;
|
||||
r->year += 2000;
|
||||
}
|
||||
159
kernel/arch/x86_64/mp.c
Normal file
159
kernel/arch/x86_64/mp.c
Normal file
|
|
@ -0,0 +1,159 @@
|
|||
// Multiprocessor support
|
||||
// Search memory for MP description structures.
|
||||
// http://developer.intel.com/design/pentium/datashts/24201606.pdf
|
||||
|
||||
#include "types.h"
|
||||
#include "defs.h"
|
||||
#include "param.h"
|
||||
#include "memlayout.h"
|
||||
#include "mp.h"
|
||||
#include "x86.h"
|
||||
#include "mmu.h"
|
||||
#include "proc.h"
|
||||
|
||||
struct cpu cpus[NCPU];
|
||||
// Processors the firmware says exist.
|
||||
int ncpu;
|
||||
|
||||
// Processors actually running, which is not the same number and must not be
|
||||
// confused with it. Enumeration is cheap and startup is where the difficulty
|
||||
// is: right now the APs are found and deliberately not started, so this stays
|
||||
// at 1 while ncpu is 2, 4 or 16.
|
||||
//
|
||||
// /proc/cpuinfo reports *this* one, matching Linux, where cpuinfo lists online
|
||||
// CPUs. Reporting ncpu there told a VM with four processors that it had four
|
||||
// working ones, which was a comfortable thing to read and false.
|
||||
int ncpu_online = 1;
|
||||
uchar ioapicid;
|
||||
|
||||
static uchar
|
||||
sum(uchar *addr, int len)
|
||||
{
|
||||
int i, sum;
|
||||
|
||||
sum = 0;
|
||||
for(i=0; i<len; i++)
|
||||
sum += addr[i];
|
||||
return sum;
|
||||
}
|
||||
|
||||
// Look for an MP structure in the len bytes at addr.
|
||||
static struct mp*
|
||||
mpsearch1(addr_t a, int len)
|
||||
{
|
||||
uchar *e, *p, *addr;
|
||||
addr = P2V(a);
|
||||
e = addr+len;
|
||||
for(p = addr; p < e; p += sizeof(struct mp))
|
||||
if(memcmp(p, "_MP_", 4) == 0 && sum(p, sizeof(struct mp)) == 0)
|
||||
return (struct mp*)p;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Search for the MP Floating Pointer Structure, which according to the
|
||||
// spec is in one of the following three locations:
|
||||
// 1) in the first KB of the EBDA;
|
||||
// 2) in the last KB of system base memory;
|
||||
// 3) in the BIOS ROM between 0xE0000 and 0xFFFFF.
|
||||
static struct mp*
|
||||
mpsearch(void)
|
||||
{
|
||||
uchar *bda;
|
||||
uint p;
|
||||
struct mp *mp;
|
||||
|
||||
bda = (uchar *) P2V(0x400);
|
||||
if((p = ((bda[0x0F]<<8)| bda[0x0E]) << 4)){
|
||||
if((mp = mpsearch1(p, 1024)))
|
||||
return mp;
|
||||
} else {
|
||||
p = ((bda[0x14]<<8)|bda[0x13])*1024;
|
||||
if((mp = mpsearch1(p-1024, 1024)))
|
||||
return mp;
|
||||
}
|
||||
return mpsearch1(0xF0000, 0x10000);
|
||||
}
|
||||
|
||||
// Search for an MP configuration table. For now,
|
||||
// don't accept the default configurations (physaddr == 0).
|
||||
// Check for correct signature, calculate the checksum and,
|
||||
// if correct, check the version.
|
||||
// To do: check extended table checksum.
|
||||
static struct mpconf*
|
||||
mpconfig(struct mp **pmp)
|
||||
{
|
||||
struct mpconf *conf;
|
||||
struct mp *mp;
|
||||
|
||||
if((mp = mpsearch()) == 0 || mp->physaddr == 0)
|
||||
return 0;
|
||||
conf = (struct mpconf*) P2V((addr_t) mp->physaddr);
|
||||
if(memcmp(conf, "PCMP", 4) != 0)
|
||||
return 0;
|
||||
if(conf->version != 1 && conf->version != 4)
|
||||
return 0;
|
||||
if(sum((uchar*)conf, conf->length) != 0)
|
||||
return 0;
|
||||
*pmp = mp;
|
||||
return conf;
|
||||
}
|
||||
|
||||
void
|
||||
mpinit(void)
|
||||
{
|
||||
uchar *p, *e;
|
||||
struct mp *mp;
|
||||
struct mpconf *conf;
|
||||
struct mpproc *mproc;
|
||||
struct mpioapic *ioapic;
|
||||
|
||||
if((conf = mpconfig(&mp)) == 0) {
|
||||
cprintf("No other CPUs found.\n");
|
||||
return;
|
||||
}
|
||||
lapic = (uint*)P2V(conf->lapicaddr_p);
|
||||
for(p=(uchar*)(conf+1), e=(uchar*)conf+conf->length; p<e; ){
|
||||
switch(*p){
|
||||
case MPPROC:
|
||||
mproc = (struct mpproc*)p;
|
||||
if(ncpu < NCPU) {
|
||||
cpus[ncpu].apicid = mproc->apicid; // apicid may differ from ncpu
|
||||
ncpu++;
|
||||
}
|
||||
p += sizeof(struct mpproc);
|
||||
continue;
|
||||
case MPIOAPIC:
|
||||
ioapic = (struct mpioapic*)p;
|
||||
ioapicid = ioapic->apicno;
|
||||
p += sizeof(struct mpioapic);
|
||||
continue;
|
||||
case MPBUS:
|
||||
case MPIOINTR:
|
||||
case MPLINTR:
|
||||
p += 8;
|
||||
continue;
|
||||
default:
|
||||
panic("Major problem parsing mp config.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
// "Seems we are" is doing a lot of work here, and the number is usually
|
||||
// wrong. This walks the *legacy Intel MP table* (1994), which modern firmware
|
||||
// fills in minimally or not at all -- QEMU/SeaBIOS emits exactly one
|
||||
// processor entry regardless of -smp, because it expects the OS to enumerate
|
||||
// CPUs from the ACPI MADT instead. Verified 2026-08-08: with -smp 4 the table
|
||||
// is 208 bytes and contains one type-0 entry, then buses, an I/O APIC and
|
||||
// interrupt assignments. So ncpu is 1 on QEMU no matter what, and SMP is
|
||||
// silently never exercised there.
|
||||
//
|
||||
// See docs/roadmap.md "Known broken" -- this is half of the SMP bug, and the
|
||||
// reason the other half (AP startup triple-faulting) cannot be reproduced
|
||||
// under QEMU until the MADT is parsed.
|
||||
cprintf("Seems we are SMP, ncpu = %d\n",ncpu);
|
||||
if(mp->imcrp){
|
||||
// Bochs doesn't support IMCR, so this doesn't run on Bochs.
|
||||
// But it would on real hardware.
|
||||
outb(0x22, 0x70); // Select IMCR
|
||||
outb(0x23, inb(0x23) | 1); // Mask external interrupts.
|
||||
}
|
||||
}
|
||||
30
kernel/arch/x86_64/swtch.S
Normal file
30
kernel/arch/x86_64/swtch.S
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
# Context switch
|
||||
#
|
||||
# void swtch(struct context **old, struct context *new);
|
||||
#
|
||||
# Save current register context in old
|
||||
# and then load register context from new.
|
||||
|
||||
.global swtch
|
||||
swtch:
|
||||
# Save old callee-save registers
|
||||
pushq %rbp
|
||||
pushq %rbx
|
||||
pushq %r12
|
||||
pushq %r13
|
||||
pushq %r14
|
||||
pushq %r15
|
||||
|
||||
# Switch stacks
|
||||
movq %rsp, (%rdi)
|
||||
movq %rsi, %rsp
|
||||
|
||||
# Load new callee-save registers
|
||||
popq %r15
|
||||
popq %r14
|
||||
popq %r13
|
||||
popq %r12
|
||||
popq %rbx
|
||||
popq %rbp
|
||||
|
||||
retq #??
|
||||
159
kernel/arch/x86_64/trap.c
Normal file
159
kernel/arch/x86_64/trap.c
Normal file
|
|
@ -0,0 +1,159 @@
|
|||
#include "types.h"
|
||||
#include "defs.h"
|
||||
#include "irq.h"
|
||||
#include "param.h"
|
||||
#include "memlayout.h"
|
||||
#include "mmu.h"
|
||||
#include "proc.h"
|
||||
#include "x86.h"
|
||||
#include "traps.h"
|
||||
#include "spinlock.h"
|
||||
#include "posix.h"
|
||||
|
||||
uint *idt;
|
||||
extern addr_t vectors[];
|
||||
struct spinlock tickslock;
|
||||
uint ticks;
|
||||
|
||||
static void
|
||||
mkgate(uint *idt, uint n, addr_t kva, uint pl)
|
||||
{
|
||||
uint64 addr = (uint64) kva;
|
||||
|
||||
n *= 4;
|
||||
idt[n+0] = (addr & 0xFFFF) | (KERNEL_CS << 16);
|
||||
idt[n+1] = (addr & 0xFFFF0000) | 0x8E00 | ((pl & 3) << 13);
|
||||
idt[n+2] = addr >> 32;
|
||||
idt[n+3] = 0;
|
||||
}
|
||||
|
||||
void idtinit(void)
|
||||
{
|
||||
struct {
|
||||
ushort limit;
|
||||
uint64 base;
|
||||
} __attribute__((packed)) idtdesc;
|
||||
|
||||
idtdesc.limit = PGSIZE - 1;
|
||||
idtdesc.base = (uint64)idt;
|
||||
|
||||
asm volatile("lidt %0" : : "m" (idtdesc));
|
||||
}
|
||||
|
||||
void tvinit(void)
|
||||
{
|
||||
int n;
|
||||
idt = (uint*) kalloc();
|
||||
memset(idt, 0, PGSIZE);
|
||||
for (n = 0; n < 256; n++)
|
||||
mkgate(idt, n, vectors[n], 0);
|
||||
mkgate(idt, T_SYSCALL, vectors[T_SYSCALL], 3);
|
||||
}
|
||||
|
||||
uint
|
||||
uptime(void)
|
||||
{
|
||||
uint xticks;
|
||||
|
||||
acquire(&tickslock);
|
||||
xticks = ticks;
|
||||
release(&tickslock);
|
||||
return xticks;
|
||||
}
|
||||
|
||||
// Which signal a CPU fault corresponds to, so a process killed by a fault is
|
||||
// reported to wait() as died-by-signal with a sensible cause rather than a
|
||||
// blanket SIGKILL.
|
||||
static int
|
||||
trapsig(uint64 trapno)
|
||||
{
|
||||
switch(trapno){
|
||||
case T_DIVIDE: return SIGFPE;
|
||||
case T_ILLOP: return SIGILL;
|
||||
case T_GPFLT: return SIGSEGV;
|
||||
case T_PGFLT: return SIGSEGV;
|
||||
case T_BRKPT: return SIGTRAP;
|
||||
case T_DEBUG: return SIGTRAP;
|
||||
default: return SIGSEGV;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
trap(struct trapframe *tf)
|
||||
{
|
||||
kstack_check_canary("trap entry");
|
||||
switch(tf->trapno){
|
||||
case T_IRQ0 + IRQ_TIMER:
|
||||
if(cpunum() == 0){
|
||||
acquire(&tickslock);
|
||||
ticks++;
|
||||
wakeup(&ticks);
|
||||
release(&tickslock);
|
||||
}
|
||||
lapiceoi();
|
||||
break;
|
||||
case T_IRQ0 + IRQ_SPURIOUS:
|
||||
cprintf("cpu%d: spurious interrupt at %p:%p\n",
|
||||
cpunum(), tf->cs, tf->rip);
|
||||
lapiceoi();
|
||||
break;
|
||||
|
||||
default:
|
||||
// Every other IOAPIC pin goes through the registration table. The device
|
||||
// interrupts used to be listed here one case at a time, which cannot work
|
||||
// for anything found on the PCI bus: a driver does not know its line
|
||||
// until it reads the device's config space at probe time, so there is no
|
||||
// constant to write a case label for. See include/irq.h.
|
||||
if(tf->trapno >= T_IRQ0 && tf->trapno < T_IRQ0 + NIRQ){
|
||||
irq_dispatch(tf->trapno - T_IRQ0);
|
||||
lapiceoi();
|
||||
break;
|
||||
}
|
||||
|
||||
if(myproc() == 0 || (tf->cs&3) == 0){
|
||||
cprintf("unexpected trap %d from cpu %d rip %p (cr2=0x%p) err=0x%p\n",
|
||||
tf->trapno, cpunum(), tf->rip, rcr2(), tf->err);
|
||||
if (myproc())
|
||||
cprintf("proc id: %d\n", myproc()->pid);
|
||||
if (tf->trapno == T_GPFLT) {
|
||||
uint64 *iretframe = (uint64*)tf->rsp;
|
||||
cprintf("iretq frame @ %p: rip=%p cs=%p rflags=%p rsp=%p ss=%p\n",
|
||||
iretframe, iretframe[0], iretframe[1], iretframe[2],
|
||||
iretframe[3], iretframe[4]);
|
||||
if (myproc())
|
||||
cprintf("this tf=%p myproc()->tf=%p myproc()->kstack=%p (tf-kstack=%p)\n",
|
||||
tf, myproc()->tf, myproc()->kstack, (uint64)tf - (uint64)myproc()->kstack);
|
||||
}
|
||||
panic("trap");
|
||||
}
|
||||
cprintf("pid %d %s: trap %d err %d on cpu %d "
|
||||
"rip 0x%p addr 0x%p--kill proc\n",
|
||||
myproc()->pid, myproc()->name, tf->trapno, tf->err, cpunum(), tf->rip,
|
||||
rcr2());
|
||||
// General-purpose register dump. This used to be followed by ~25 lines
|
||||
// that walked musl's mallocng group/meta structures out of rax/rdi and
|
||||
// dumped the user stack -- scaffolding for the boot-crash investigation
|
||||
// recorded in docs/architecture/kernel-history.md section 1. That
|
||||
// investigation is closed and the underlying kernel bugs are fixed, so
|
||||
// the allocator-specific part is gone; the registers stay, since they are
|
||||
// useful for any fault.
|
||||
if (tf->trapno == T_GPFLT) {
|
||||
cprintf(" rax=%p rbx=%p rcx=%p rdx=%p rsi=%p rdi=%p rbp=%p rsp=%p\n",
|
||||
tf->rax, tf->rbx, tf->rcx, tf->rdx, tf->rsi, tf->rdi, tf->rbp, tf->rsp);
|
||||
}
|
||||
myproc()->killed = 1;
|
||||
if(myproc()->killsig == 0)
|
||||
myproc()->killsig = trapsig(tf->trapno);
|
||||
}
|
||||
|
||||
if(myproc() && myproc()->killed && (tf->cs&3) == DPL_USER)
|
||||
exit(W_EXITCODE(0, myproc()->killsig ? myproc()->killsig : SIGKILL));
|
||||
|
||||
if(myproc() && myproc()->state == RUNNING && tf->trapno == T_IRQ0+IRQ_TIMER)
|
||||
yield();
|
||||
|
||||
if(myproc() && myproc()->killed && (tf->cs&3) == DPL_USER)
|
||||
exit(W_EXITCODE(0, myproc()->killsig ? myproc()->killsig : SIGKILL));
|
||||
|
||||
kstack_check_canary("trap exit");
|
||||
}
|
||||
257
kernel/arch/x86_64/trapasm.S
Normal file
257
kernel/arch/x86_64/trapasm.S
Normal file
|
|
@ -0,0 +1,257 @@
|
|||
# trapasm.S — kernel/user entry/exit with FS_BASE swapping for TLS
|
||||
#
|
||||
# The kernel uses __thread struct cpu *cpu and __thread struct proc *proc,
|
||||
# accessed via %fs (IA32_FS_BASE MSR, 0xC0000100). seginit() sets this to
|
||||
# kernel_fs_base. User-space TLS (musl) uses the same MSR via arch_prctl
|
||||
# ARCH_SET_FS (saved in proc->fs_base).
|
||||
#
|
||||
# Rule: on ENTRY from user (syscall or interrupt), restore kernel FS_BASE.
|
||||
# on EXIT to user (sysretq or iretq), write proc->fs_base to MSR.
|
||||
#
|
||||
# proc->fs_base is at byte offset 0x18 (24) inside struct proc.
|
||||
# proc pointer is at %gs:24 (PERCPU_PROC in include/proc.h); cpu is at %gs:16.
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
# Helper macro: write kernel_fs_base to IA32_FS_BASE.
|
||||
# Clobbers %rax, %rcx, %rdx. Caller must save them if needed.
|
||||
# -----------------------------------------------------------------------
|
||||
# Read from %gs, not from a global.
|
||||
#
|
||||
# This used to be `movq kernel_fs_base(%rip), %rax`, and kernel_fs_base was a
|
||||
# single global that every CPU wrote during seginit(). With one processor that
|
||||
# is merely untidy. With two it is fatal: whichever CPU ran seginit() last wins,
|
||||
# and from the first trap onward *every* CPU restores that one CPU's per-CPU
|
||||
# block -- so `cpu` and `proc` are shared, pushcli/popcli nesting counts collide,
|
||||
# and the kernel dies in panic("sched locks") shortly after the APs start.
|
||||
#
|
||||
# GS base is set per CPU by seginit() to the same value as FS base, and %gs:0
|
||||
# holds the block's self-pointer. So this reads *this* CPU's kernel FS base with
|
||||
# no global involved.
|
||||
#
|
||||
# No swapgs, deliberately. The usual reason for it is that userspace owns GS;
|
||||
# here it does not -- sys_arch_prctl() implements only ARCH_SET_FS/ARCH_GET_FS
|
||||
# and rejects everything else, musl uses FS for TLS on x86-64, and the only
|
||||
# other writes to %gs are selector loads in entry.S and entryother.S that happen
|
||||
# before seginit(). GS is the kernel's alone, so it can simply stay pointing at
|
||||
# the per-CPU block. If ARCH_SET_GS is ever implemented, this must become a
|
||||
# proper swapgs pair.
|
||||
.macro RESTORE_KERNEL_FS
|
||||
movq %gs:0, %rax
|
||||
movq %rax, %rdx
|
||||
shrq $32, %rdx
|
||||
movl $0xC0000100, %ecx
|
||||
wrmsr
|
||||
.endm
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
# Helper macro: write proc->fs_base to IA32_FS_BASE.
|
||||
# Requires %fs to still hold the kernel base.
|
||||
# Clobbers %rax, %rcx, %rdx. Caller must save them if needed.
|
||||
# -----------------------------------------------------------------------
|
||||
.macro WRITE_USER_FS
|
||||
# proc, from the per-CPU block at %gs:24 (PERCPU_PROC in include/proc.h).
|
||||
# This used to be %fs:(-8), the __thread slot, which no longer exists --
|
||||
# see the comment in include/proc.h for why those stopped being __thread.
|
||||
movq %gs:24, %rax # proc pointer
|
||||
movq 0x18(%rax), %rax # proc->fs_base (offset 24 = 0x18)
|
||||
movq %rax, %rdx
|
||||
shrq $32, %rdx
|
||||
movl $0xC0000100, %ecx
|
||||
wrmsr
|
||||
.endm
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
# alltraps — IDT interrupt/fault entry
|
||||
# Stack on entry (from user mode, CPU has done privilege switch):
|
||||
# [rsp+0] trapno (pushed by vectors.S)
|
||||
# [rsp+8] errorcode
|
||||
# [rsp+16] rip
|
||||
# [rsp+24] cs ← bit 1:0 == 3 means user mode
|
||||
# [rsp+32] rflags
|
||||
# [rsp+40] user_rsp
|
||||
# [rsp+48] user_ss
|
||||
# -----------------------------------------------------------------------
|
||||
.global alltraps
|
||||
alltraps:
|
||||
# Always restore kernel FS_BASE before touching %fs:, even when the
|
||||
# saved cs says we were already in kernel mode. A fault/interrupt can
|
||||
# land in that narrow kernel-mode window after syscall_trapret/trapret
|
||||
# have already done WRITE_USER_FS (cs is still 0x8 there, but FS_BASE
|
||||
# is the *user* value) — the old cs-based skip trusted that combination
|
||||
# never happens and read %fs:(-8) as garbage (proc/cpu == NULL) in that
|
||||
# case. RESTORE_KERNEL_FS is just a wrmsr of an already-known value when
|
||||
# we truly were in the kernel, so doing it unconditionally is cheap and
|
||||
# always correct.
|
||||
pushq %rax
|
||||
pushq %rcx
|
||||
pushq %rdx
|
||||
RESTORE_KERNEL_FS
|
||||
popq %rdx
|
||||
popq %rcx
|
||||
popq %rax
|
||||
|
||||
# Build trap frame.
|
||||
pushq %r15
|
||||
pushq %r14
|
||||
pushq %r13
|
||||
pushq %r12
|
||||
pushq %r11
|
||||
pushq %r10
|
||||
pushq %r9
|
||||
pushq %r8
|
||||
pushq %rdi
|
||||
pushq %rsi
|
||||
pushq %rbp
|
||||
pushq %rdx
|
||||
pushq %rcx
|
||||
pushq %rbx
|
||||
pushq %rax
|
||||
|
||||
movq %rsp, %rdi # frame in arg1
|
||||
callq trap
|
||||
# Return falls through to trapret...
|
||||
|
||||
.global trapret
|
||||
trapret:
|
||||
popq %rax
|
||||
popq %rbx
|
||||
popq %rcx
|
||||
popq %rdx
|
||||
popq %rbp
|
||||
popq %rsi
|
||||
popq %rdi
|
||||
popq %r8
|
||||
popq %r9
|
||||
popq %r10
|
||||
popq %r11
|
||||
popq %r12
|
||||
popq %r13
|
||||
popq %r14
|
||||
popq %r15
|
||||
|
||||
addq $16, %rsp # discard trapno and errorcode
|
||||
|
||||
# If returning to user mode, write proc->fs_base to FS_BASE MSR.
|
||||
# Stack now: [rsp+0]=rip, [rsp+8]=cs, [rsp+16]=rflags, [rsp+24]=rsp, [rsp+32]=ss
|
||||
testb $3, 8(%rsp)
|
||||
jz .Ltrapret_kret
|
||||
|
||||
# cli before WRITE_USER_FS: close the same race as in syscall_trapret.
|
||||
# iretq will atomically restore RFLAGS (IF=1) when switching to user mode.
|
||||
pushq %rax
|
||||
pushq %rcx
|
||||
pushq %rdx
|
||||
cli
|
||||
WRITE_USER_FS
|
||||
popq %rdx
|
||||
popq %rcx
|
||||
popq %rax
|
||||
|
||||
.Ltrapret_kret:
|
||||
iretq
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
# syscall_entry — SYSCALL instruction entry point
|
||||
# On entry: rcx=user rip, r11=user rflags, rsp=user stack, IF cleared.
|
||||
# FS_BASE may be the user's TLS value (set at last sysretq).
|
||||
# -----------------------------------------------------------------------
|
||||
.global syscall_entry
|
||||
syscall_entry:
|
||||
# Swap FS_BASE to kernel value BEFORE any %fs: access.
|
||||
# Save rax/rcx/rdx to single-CPU scratch globals (IF is off, no race).
|
||||
movq %rax, _sc_rax(%rip)
|
||||
movq %rcx, _sc_rcx(%rip) # user rip (SYSCALL stores it here)
|
||||
movq %rdx, _sc_rdx(%rip)
|
||||
RESTORE_KERNEL_FS # clobbers rax, rcx, rdx — that's OK
|
||||
movq _sc_rax(%rip), %rax
|
||||
movq _sc_rcx(%rip), %rcx
|
||||
movq _sc_rdx(%rip), %rdx
|
||||
|
||||
# Switch to kernel stack: store user rsp at top of proc->kstack.
|
||||
#
|
||||
# The scratch slot is %fs:(8), NOT %fs:(0). %fs:(0) is the TCB
|
||||
# self-pointer -- the x86-64 TLS ABI reserves the word at the thread
|
||||
# pointer to hold the thread pointer itself, and a compiler is entitled to
|
||||
# read it to materialise the TP before applying a variable's offset.
|
||||
#
|
||||
# It used to be %fs:(0), and that survived for years only because GCC
|
||||
# compiles `proc`/`cpu` into direct `%fs:-8` segment references and never
|
||||
# touches the word. clang at -O0 does `movq %fs:0, %rax; addq $-8, %rax;
|
||||
# (%rax)` instead -- so it read the syscall number this line had just
|
||||
# stored (7, SYS_exec, from initcode), computed 7-8 = -1, and page-faulted
|
||||
# on the very first syscall of the very first process.
|
||||
#
|
||||
# Positive offsets are safe: the TLS block occupies negative offsets
|
||||
# (-16 = cpu, -8 = proc) and seginit puts the thread pointer 2048 bytes
|
||||
# into a 4096-byte page, so %fs:(8) onward is 2040 bytes we own outright.
|
||||
movq %rax, %fs:(8) # save rax in per-CPU scratch slot
|
||||
movq %gs:24, %rax # proc pointer (PERCPU_PROC)
|
||||
movq 0x10(%rax), %rax # proc->kstack (offset 16)
|
||||
addq $(4096-16), %rax # point to tf->rsp slot
|
||||
movq %rsp, (%rax) # save user rsp
|
||||
movq %rax, %rsp # switch to kernel stack
|
||||
movq %fs:(8), %rax # restore rax
|
||||
|
||||
pushq %r11 # rflags
|
||||
pushq $0 # cs (ignored)
|
||||
pushq %rcx # rip (next user instruction)
|
||||
|
||||
pushq $0 # err
|
||||
pushq $0 # trapno (ignored)
|
||||
|
||||
pushq %r15
|
||||
pushq %r14
|
||||
pushq %r13
|
||||
pushq %r12
|
||||
pushq %r11
|
||||
pushq %r10
|
||||
pushq %r9
|
||||
pushq %r8
|
||||
pushq %rdi
|
||||
pushq %rsi
|
||||
pushq %rbp
|
||||
pushq %rdx
|
||||
pushq %rcx
|
||||
pushq %rbx
|
||||
pushq %rax
|
||||
|
||||
movq %rsp, %rdi # frame in arg1
|
||||
callq syscall
|
||||
# Return falls through to syscall_trapret...
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
# syscall_trapret — return path for SYSCALL-based syscalls
|
||||
# -----------------------------------------------------------------------
|
||||
.global syscall_trapret
|
||||
syscall_trapret:
|
||||
# Disable interrupts BEFORE writing user FS_BASE to the MSR.
|
||||
# Without cli here, a timer IRQ between WRITE_USER_FS and the cli below
|
||||
# would arrive in ring-0 with user FS_BASE set, causing %fs:(-8) to return
|
||||
# garbage (proc=NULL) → panic in the interrupt handler.
|
||||
# rax/rcx/rdx are clobbered by WRITE_USER_FS then restored by the pops.
|
||||
cli
|
||||
WRITE_USER_FS
|
||||
|
||||
popq %rax
|
||||
popq %rbx
|
||||
popq %rcx
|
||||
popq %rdx
|
||||
popq %rbp
|
||||
popq %rsi
|
||||
popq %rdi
|
||||
popq %r8
|
||||
popq %r9
|
||||
popq %r10
|
||||
popq %r11
|
||||
popq %r12
|
||||
popq %r13
|
||||
popq %r14
|
||||
popq %r15
|
||||
|
||||
addq $40, %rsp # discard trapno, errorcode, rip, cs, rflags
|
||||
|
||||
# Mask interrupts: sysretq is unsafe with IF set on non-canonical RSP.
|
||||
# See https://www.felixcloutier.com/x86/sysret
|
||||
cli
|
||||
movq (%rsp), %rsp # restore the user stack
|
||||
sysretq
|
||||
1281
kernel/arch/x86_64/vectors.S
Normal file
1281
kernel/arch/x86_64/vectors.S
Normal file
File diff suppressed because it is too large
Load diff
631
kernel/arch/x86_64/vm.c
Normal file
631
kernel/arch/x86_64/vm.c
Normal file
|
|
@ -0,0 +1,631 @@
|
|||
#include "param.h"
|
||||
#include "types.h"
|
||||
#include "defs.h"
|
||||
#include "x86.h"
|
||||
#include "memlayout.h"
|
||||
#include "mmu.h"
|
||||
#include "proc.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);
|
||||
|
||||
|
||||
// `cpu` and `proc` are no longer __thread variables; they live at fixed offsets
|
||||
// in the per-CPU block and are read through %gs at each use. See the long
|
||||
// comment in include/proc.h for why -- the short version is that __thread means
|
||||
// per *thread*, the compiler caches the thread pointer across calls on that
|
||||
// basis, and a kernel thread changes CPU at every sched().
|
||||
|
||||
// Kernel FS_BASE set by seginit(); read by trapasm.S to restore after user TLS
|
||||
uint64 kernel_fs_base;
|
||||
// Single-CPU scratch for syscall_entry FS swap (safe while IF cleared by SYSCALL/SFMASK)
|
||||
uint64 _sc_rax, _sc_rcx, _sc_rdx;
|
||||
|
||||
static pml4e_t *kpml4;
|
||||
static pdpe_t *kpdpt;
|
||||
|
||||
struct tassdesc {
|
||||
uint lim_15_0 : 16;
|
||||
uint base_15_0 : 16;
|
||||
uint base_23_16 : 8;
|
||||
uint type : 4;
|
||||
uint s : 1;
|
||||
uint dpl : 2;
|
||||
uint p : 1;
|
||||
uint lim_19_16 : 4;
|
||||
uint avl : 1;
|
||||
uint l : 1;
|
||||
uint d : 1;
|
||||
uint g : 1;
|
||||
uint base_31_24 : 8;
|
||||
uint32 base_63_32;
|
||||
uint32 res;
|
||||
};
|
||||
|
||||
void
|
||||
syscallinit(void)
|
||||
{
|
||||
// SYSRETQ does NOT use USER_CS/USER_DS directly. Per the AMD64/Intel
|
||||
// convention, it computes CS = STAR[63:48]+16 and SS = STAR[63:48]+8
|
||||
// (RPL forced to 3), so STAR[63:48] must hold the *compat* 32-bit user
|
||||
// code selector (SEG_UCODE32), relying on the GDT's consecutive
|
||||
// [UCODE32, UDATA, UCODE] layout — NOT USER_CS itself. Using USER_CS
|
||||
// here made every sysretq load CS=USER_CS+16 (an invalid selector,
|
||||
// pointing past UCODE into the TSS descriptor) and SS=USER_CS+8. QEMU's
|
||||
// TCG apparently doesn't validate segment descriptors on SYSRET, so this
|
||||
// went unnoticed until an interrupt forced a strictly-validated iretq
|
||||
// with that same bogus CS still loaded, which correctly #GP'd.
|
||||
wrmsr(MSR_STAR,
|
||||
((((uint64)((SEG_UCODE32 << 3) | DPL_USER) << 16) | (uint64)KERNEL_CS) << 32));
|
||||
wrmsr(MSR_LSTAR, (addr_t)syscall_entry);
|
||||
wrmsr(MSR_CSTAR, (addr_t)ignore_sysret);
|
||||
wrmsr(MSR_SFMASK, FL_TF|FL_DF|FL_IF|FL_IOPL_MASK|FL_AC|FL_NT);
|
||||
}
|
||||
|
||||
void
|
||||
seginit(void)
|
||||
{
|
||||
struct segdesc *gdt;
|
||||
uint *tss;
|
||||
uint64 addr;
|
||||
void *local;
|
||||
struct cpu *c;
|
||||
|
||||
// The per-CPU page, preallocated by the boot processor for every AP.
|
||||
//
|
||||
// It must not be kalloc()'d here on an application processor. kalloc takes a
|
||||
// spinlock; acquire() calls holding(); holding() reads `cpu`, which is a
|
||||
// __thread variable reached through %fs -- and %fs is set up four lines
|
||||
// below. So an AP allocating its own page reads the per-CPU pointer before
|
||||
// the per-CPU pointer exists, and the kernel dies in panic("acquire") with
|
||||
// every lock appearing to be held.
|
||||
//
|
||||
// startothers() fills c->local in before starting each AP, on the boot
|
||||
// processor, where locking works.
|
||||
c = &cpus[cpunum()];
|
||||
local = c->local;
|
||||
if (local == 0) {
|
||||
local = kalloc(); // the boot processor's own, taken normally
|
||||
memset(local, 0, PGSIZE);
|
||||
}
|
||||
|
||||
gdt = (struct segdesc*) local;
|
||||
tss = (uint*) (((char*) local) + 1024);
|
||||
tss[16] = 0x00680000;
|
||||
|
||||
kernel_fs_base = ((uint64) local) + 2048;
|
||||
wrmsr(0xC0000100, kernel_fs_base);
|
||||
|
||||
// The TCB self-pointer: the word at %fs:0 must hold the thread pointer.
|
||||
//
|
||||
// This is part of the x86-64 TLS ABI and it was missing, which worked only
|
||||
// by accident. GCC with -mtls-direct-seg-refs compiles `cpu = c` into a
|
||||
// single `movq %rcx, %fs:-0x10` and never needs the thread pointer as a
|
||||
// value, so nothing ever read this word. clang materialises it instead --
|
||||
// `movq %fs:0x0, %rax; addq $-0x10, %rax; movq %rcx, (%rax)` -- which read
|
||||
// the zero left by the memset above and stored through address
|
||||
// 0xfffffffffffffff0, three instructions later.
|
||||
//
|
||||
// Writing it costs one store per CPU and removes the dependency on a
|
||||
// particular compiler's TLS optimisation.
|
||||
*(uint64 *) kernel_fs_base = kernel_fs_base;
|
||||
|
||||
// Clear the two per-CPU slots before anything can read them. Their offsets
|
||||
// are PERCPU_CPU and PERCPU_PROC in include/proc.h and are shared with
|
||||
// trapasm.S, which reads %gs:16 for proc on the way out to user mode.
|
||||
((uint64 *) kernel_fs_base)[2] = 0; // struct cpu * (PERCPU_CPU)
|
||||
((uint64 *) kernel_fs_base)[3] = 0; // struct proc * (PERCPU_PROC)
|
||||
|
||||
// The same address in GS base, per CPU.
|
||||
//
|
||||
// trapasm.S needs to find *this* CPU's kernel FS base on every entry from
|
||||
// user mode, and it has no register to work from at that instant -- which is
|
||||
// why it used to read the kernel_fs_base global. That global is written by
|
||||
// every CPU during its own seginit(), so with more than one processor the
|
||||
// last writer wins and every CPU restores one CPU's per-CPU block. GS base is
|
||||
// per-CPU in hardware, so %gs:0 answers the question with no global at all.
|
||||
//
|
||||
// Safe because GS belongs entirely to the kernel here: sys_arch_prctl()
|
||||
// implements ARCH_SET_FS and ARCH_GET_FS only, and musl uses FS for TLS on
|
||||
// x86-64. See the comment on RESTORE_KERNEL_FS.
|
||||
wrmsr(0xC0000101, kernel_fs_base); // IA32_GS_BASE
|
||||
|
||||
c->local = local;
|
||||
|
||||
cpu_set(c);
|
||||
proc_set(0);
|
||||
|
||||
// Two of the entries below are deliberately null descriptors, and neither is
|
||||
// dead code even though both look it:
|
||||
//
|
||||
// SEG_UCODE32 is never loaded, but SYSRETQ computes CS from STAR[63:48]+16
|
||||
// and SS from +8, so the *slot* must exist to keep the
|
||||
// [UCODE32, UDATA, UCODE] triple consecutive. See syscallinit.
|
||||
// SEG_KCPU is a leftover slot number from 32-bit xv6, where %gs held the
|
||||
// per-CPU pointer. Schunix uses FS_BASE/TLS instead, but the
|
||||
// index has to stay occupied or SEG_TSS stops landing at 7.
|
||||
//
|
||||
// Deleting either moves every descriptor after it and breaks sysret.
|
||||
addr = (uint64) tss;
|
||||
gdt[0] = (struct segdesc) {0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
|
||||
gdt[SEG_KCODE] = SEG(STA_X|STA_R, 0, 0, 0);
|
||||
gdt[SEG_KDATA] = SEG(STA_W, 0, 0, 0);
|
||||
gdt[SEG_UCODE32] = (struct segdesc) {0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
|
||||
gdt[SEG_UDATA] = SEG(STA_W, 0, 0, DPL_USER);
|
||||
gdt[SEG_UCODE] = SEG(STA_X|STA_R, 0, 0, DPL_USER);
|
||||
gdt[SEG_KCPU] = (struct segdesc) {0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
|
||||
|
||||
struct tassdesc *td = (struct tassdesc*)(gdt + SEG_TSS);
|
||||
td->lim_15_0 = 0x67;
|
||||
td->base_15_0 = addr & 0xffff;
|
||||
td->base_23_16 = (addr >> 16) & 0xff;
|
||||
td->type = STS_T64A;
|
||||
td->s = 0;
|
||||
td->dpl = 0;
|
||||
td->p = 1;
|
||||
td->lim_19_16 = 0;
|
||||
td->avl = 0;
|
||||
td->l = 0;
|
||||
td->d = 0;
|
||||
td->g = 0;
|
||||
td->base_31_24 = (addr >> 24) & 0xff;
|
||||
td->base_63_32 = (addr >> 32) & 0xffffffff;
|
||||
td->res = 0;
|
||||
|
||||
lgdt((void*) gdt, (NSEGS+1) * sizeof(struct segdesc));
|
||||
ltr(SEG_TSS << 3);
|
||||
}
|
||||
|
||||
pml4e_t*
|
||||
setupkvm(void)
|
||||
{
|
||||
pml4e_t *pml4 = (pml4e_t*) kalloc();
|
||||
if(pml4 == 0) return 0;
|
||||
memset(pml4, 0, PGSIZE);
|
||||
|
||||
pml4[PMX(KERNBASE)] = v2p(kpdpt) | PTE_P | PTE_W;
|
||||
|
||||
// The framebuffer is deliberately NOT mapped here.
|
||||
//
|
||||
// This used to identity-map 0xfd000000 for 800*600*4 bytes, user-writable,
|
||||
// into every process that ever existed. Three things were wrong with that.
|
||||
// The address and the size were both guesses that only hold on QEMU's
|
||||
// stdvga at one resolution; setupkvm runs before vga_init, so on the first
|
||||
// call nothing had even read the VBE block yet; and every process could
|
||||
// scribble on the screen without asking anyone.
|
||||
//
|
||||
// Nothing used it -- userspace reaches the framebuffer through
|
||||
// mmap("/dev/fb0"), which maps the real base at the real size. So it is
|
||||
// gone, and a process gets the framebuffer exactly when it asks for it.
|
||||
|
||||
return pml4;
|
||||
}
|
||||
|
||||
void
|
||||
kvmalloc(void)
|
||||
{
|
||||
kpml4 = (pml4e_t*) kalloc();
|
||||
memset(kpml4, 0, PGSIZE);
|
||||
|
||||
kpdpt = (pdpe_t*) kalloc();
|
||||
memset(kpdpt, 0, PGSIZE);
|
||||
|
||||
kpml4[0] = v2p(kpdpt) | PTE_P | PTE_W;
|
||||
kpml4[PMX(KERNBASE)] = v2p(kpdpt) | PTE_P | PTE_W;
|
||||
|
||||
for(uint64 i = 0; i < 4; i++) {
|
||||
uint64 flags = PTE_PS | PTE_P | PTE_W;
|
||||
if(i >= 3) {
|
||||
flags |= PTE_PCD | PTE_PWT;
|
||||
}
|
||||
kpdpt[i] = (i << 30) | flags;
|
||||
}
|
||||
|
||||
switchkvm();
|
||||
}
|
||||
|
||||
void
|
||||
switchuvm(struct proc *p)
|
||||
{
|
||||
pushcli();
|
||||
if(p->pgdir == 0)
|
||||
panic("switchuvm: no pgdir");
|
||||
uint *tss = (uint*) (((char*) mycpu()->local) + 1024);
|
||||
const addr_t stktop = (addr_t)p->kstack + KSTACKSIZE;
|
||||
tss[1] = (uint)stktop;
|
||||
tss[2] = (uint)(stktop >> 32);
|
||||
lcr3(v2p(p->pgdir));
|
||||
popcli();
|
||||
}
|
||||
|
||||
static pte_t *
|
||||
walkpgdir(pml4e_t *pml4, const void *va, int alloc)
|
||||
{
|
||||
pml4e_t *pml4e;
|
||||
pdpe_t *pdp, *pdpe;
|
||||
pde_t *pd, *pde;
|
||||
pte_t *pgtab;
|
||||
|
||||
pml4e = &pml4[PMX(va)];
|
||||
if(*pml4e & PTE_P)
|
||||
pdp = (pdpe_t*)P2V(PTE_ADDR(*pml4e));
|
||||
else {
|
||||
if(!alloc || (pdp = (pdpe_t*)kalloc()) == 0)
|
||||
return 0;
|
||||
memset(pdp, 0, PGSIZE);
|
||||
*pml4e = V2P(pdp) | PTE_P | PTE_W | PTE_U;
|
||||
}
|
||||
|
||||
pdpe = &pdp[PDPX(va)];
|
||||
if(*pdpe & PTE_P)
|
||||
pd = (pde_t*)P2V(PTE_ADDR(*pdpe));
|
||||
else {
|
||||
if(!alloc || (pd = (pde_t*)kalloc()) == 0)
|
||||
return 0;
|
||||
memset(pd, 0, PGSIZE);
|
||||
*pdpe = V2P(pd) | PTE_P | PTE_W | PTE_U;
|
||||
}
|
||||
|
||||
pde = &pd[PDX(va)];
|
||||
if(*pde & PTE_P)
|
||||
pgtab = (pte_t*)P2V(PTE_ADDR(*pde));
|
||||
else {
|
||||
if(!alloc || (pgtab = (pte_t*)kalloc()) == 0)
|
||||
return 0;
|
||||
memset(pgtab, 0, PGSIZE);
|
||||
*pde = V2P(pgtab) | PTE_P | PTE_W | PTE_U;
|
||||
}
|
||||
|
||||
return (pte_t*)&pgtab[PTX(va)];
|
||||
}
|
||||
|
||||
void
|
||||
switchkvm(void)
|
||||
{
|
||||
lcr3(v2p(kpml4));
|
||||
}
|
||||
|
||||
int
|
||||
mappages(pml4e_t *pgdir, void *va, addr_t size, addr_t pa, int perm)
|
||||
{
|
||||
char *a, *last;
|
||||
pte_t *pte;
|
||||
|
||||
a = (char*)PGROUNDDOWN((addr_t)va);
|
||||
last = (char*)PGROUNDDOWN(((addr_t)va) + size - 1);
|
||||
for(;;){
|
||||
if((pte = walkpgdir(pgdir, a, 1)) == 0)
|
||||
return -1;
|
||||
if(*pte & PTE_P)
|
||||
panic("remap");
|
||||
*pte = pa | perm | PTE_P | PTE_U;
|
||||
invlpg(a);
|
||||
if(a == last)
|
||||
break;
|
||||
a += PGSIZE;
|
||||
pa += PGSIZE;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void
|
||||
vm_unmap_range(pml4e_t *pgdir, uint64 va, uint64 length)
|
||||
{
|
||||
uint64 a = PGROUNDDOWN(va);
|
||||
uint64 end = PGROUNDUP(va + length);
|
||||
for (; a < end; a += PGSIZE) {
|
||||
pte_t *pte = walkpgdir(pgdir, (void*)a, 0);
|
||||
if (pte && (*pte & PTE_P)) {
|
||||
uint64 pa = PTE_ADDR(*pte);
|
||||
kfree((char*)P2V(pa));
|
||||
*pte = 0;
|
||||
invlpg((void*)a);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Tear down a mapping without freeing what is underneath it.
|
||||
//
|
||||
// vm_unmap_range() kfree()s every page it unmaps, which is right for an
|
||||
// anonymous mapping and catastrophic for anything shared: the framebuffer is
|
||||
// device memory that was never allocated, and a shared memory segment's pages
|
||||
// belong to the segment, not to whichever process happens to be detaching.
|
||||
// Handing either of those to kfree puts them on the free list while another
|
||||
// process is still reading and writing them.
|
||||
void
|
||||
vm_unmap_norelease(pml4e_t *pgdir, uint64 va, uint64 length)
|
||||
{
|
||||
uint64 a = PGROUNDDOWN(va);
|
||||
uint64 end = PGROUNDUP(va + length);
|
||||
for (; a < end; a += PGSIZE) {
|
||||
pte_t *pte = walkpgdir(pgdir, (void*)a, 0);
|
||||
if (pte && (*pte & PTE_P)) {
|
||||
*pte = 0;
|
||||
invlpg((void*)a);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Duplicate an mmap()'d VMA region from src into dst for fork().
|
||||
// Framebuffer mappings are re-pointed at the same physical MMIO range
|
||||
// (shared, never copied/freed); anonymous mappings get fresh pages with
|
||||
// the parent's contents copied in — mirrors what copyuvm() does for the
|
||||
// [0, sz) heap range, which never covers mmap()'d addresses.
|
||||
int
|
||||
copy_vma_range(pml4e_t *dstpgdir, pml4e_t *srcpgdir, uint64 va, uint64 length, int share)
|
||||
{
|
||||
uint64 a = PGROUNDDOWN(va);
|
||||
uint64 end = PGROUNDUP(va + length);
|
||||
for (; a < end; a += PGSIZE) {
|
||||
if (share) {
|
||||
// Point the child at the very same physical pages, whatever they are.
|
||||
//
|
||||
// This used to special-case the framebuffer and map vga_fb_phys()
|
||||
// explicitly. Reading the parent's own PTE instead covers device memory
|
||||
// and shared memory segments with one rule, and cannot disagree with
|
||||
// what the parent actually has mapped -- which the explicit version
|
||||
// could, and did, when the address was hardcoded.
|
||||
pte_t *spte = walkpgdir(srcpgdir, (void*)a, 0);
|
||||
if (!spte || !(*spte & PTE_P))
|
||||
continue;
|
||||
if (mappages(dstpgdir, (void*)a, PGSIZE, PTE_ADDR(*spte), PTE_FLAGS(*spte)) < 0)
|
||||
return -1;
|
||||
continue;
|
||||
}
|
||||
pte_t *spte = walkpgdir(srcpgdir, (void*)a, 0);
|
||||
if (!spte || !(*spte & PTE_P))
|
||||
continue;
|
||||
char *mem = kalloc();
|
||||
if (!mem)
|
||||
return -1;
|
||||
memmove(mem, (char*)P2V(PTE_ADDR(*spte)), PGSIZE);
|
||||
if (mappages(dstpgdir, (void*)a, PGSIZE, V2P(mem), PTE_FLAGS(*spte)) < 0) {
|
||||
kfree(mem);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void
|
||||
inituvm(pde_t *pgdir, char *sinit, uint64 sz)
|
||||
{
|
||||
char *mem;
|
||||
|
||||
if(sz >= PGSIZE)
|
||||
panic("inituvm: more than a page");
|
||||
mem = kalloc();
|
||||
memset(mem, 0, PGSIZE);
|
||||
mappages(pgdir, 0, PGSIZE, v2p(mem), PTE_W|PTE_U);
|
||||
memmove(mem, sinit, sz);
|
||||
}
|
||||
|
||||
int
|
||||
loaduvm(pml4e_t *pgdir, char *addr, struct inode *ip, uint64 offset, uint64 sz)
|
||||
{
|
||||
uint64 done, n, off_in_page;
|
||||
addr_t pa;
|
||||
pte_t *pte;
|
||||
char *cur_page;
|
||||
|
||||
for(done = 0; done < sz; done += n){
|
||||
cur_page = (char*)PGROUNDDOWN((uint64)(addr + done));
|
||||
off_in_page = (uint64)(addr + done) - (uint64)cur_page;
|
||||
if((pte = walkpgdir(pgdir, cur_page, 0)) == 0)
|
||||
panic("loaduvm: address should exist");
|
||||
pa = PTE_ADDR(*pte);
|
||||
n = PGSIZE - off_in_page;
|
||||
if(n > sz - done)
|
||||
n = sz - done;
|
||||
if(readi(ip, 0, (uint64)(P2V(pa) + off_in_page), offset + done, n) != (int)n)
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint64
|
||||
allocuvm(pml4e_t *pgdir, uint64 oldsz, uint64 newsz)
|
||||
{
|
||||
char *mem;
|
||||
addr_t a;
|
||||
|
||||
if(newsz >= KERNBASE)
|
||||
return 0;
|
||||
if(newsz < oldsz)
|
||||
return oldsz;
|
||||
|
||||
a = PGROUNDUP(oldsz);
|
||||
for(; a < newsz; a += PGSIZE){
|
||||
mem = kalloc();
|
||||
if(mem == 0){
|
||||
deallocuvm(pgdir, newsz, oldsz);
|
||||
return 0;
|
||||
}
|
||||
memset(mem, 0, PGSIZE);
|
||||
if(mappages(pgdir, (char*)a, PGSIZE, V2P(mem), PTE_W|PTE_U) < 0){
|
||||
deallocuvm(pgdir, newsz, oldsz);
|
||||
kfree(mem);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return newsz;
|
||||
}
|
||||
|
||||
uint64
|
||||
deallocuvm(pml4e_t *pgdir, uint64 oldsz, uint64 newsz)
|
||||
{
|
||||
pte_t *pte;
|
||||
addr_t a, pa;
|
||||
|
||||
if(newsz >= oldsz)
|
||||
return oldsz;
|
||||
|
||||
a = PGROUNDUP(newsz);
|
||||
for(; a < oldsz; a += PGSIZE){
|
||||
pte = walkpgdir(pgdir, (char*)a, 0);
|
||||
if(pte && (*pte & PTE_P) != 0){
|
||||
pa = PTE_ADDR(*pte);
|
||||
if(pa == 0)
|
||||
panic("kfree");
|
||||
if(pa < PHYSTOP) {
|
||||
char *v = P2V(pa);
|
||||
kfree(v);
|
||||
}
|
||||
*pte = 0;
|
||||
}
|
||||
}
|
||||
return newsz;
|
||||
}
|
||||
|
||||
void
|
||||
freevm(pml4e_t *pml4)
|
||||
{
|
||||
uint i, j, k, l;
|
||||
pdpe_t *pdp;
|
||||
pde_t *pd;
|
||||
pte_t *pt;
|
||||
|
||||
if(pml4 == 0)
|
||||
panic("freevm: no pgdir");
|
||||
|
||||
for(i = 0; i < (512/2); i++){
|
||||
if(pml4[i] & PTE_P){
|
||||
pdp = (pdpe_t*)P2V(PTE_ADDR(pml4[i]));
|
||||
for(j = 0; j < 512; j++){
|
||||
if(pdp[j] & PTE_P){
|
||||
pd = (pde_t*)P2V(PTE_ADDR(pdp[j]));
|
||||
for(k = 0; k < 512; k++){
|
||||
if(pd[k] & PTE_P) {
|
||||
pt = (pte_t*)P2V(PTE_ADDR(pd[k]));
|
||||
for(l = 0; l < 512; l++){
|
||||
if(pt[l] & PTE_P) {
|
||||
addr_t pa = PTE_ADDR(pt[l]);
|
||||
if(pa < PHYSTOP && pa != 0) {
|
||||
kfree(P2V(pa));
|
||||
}
|
||||
}
|
||||
}
|
||||
kfree((char*)pt);
|
||||
}
|
||||
}
|
||||
kfree((char*)pd);
|
||||
}
|
||||
}
|
||||
kfree((char*)pdp);
|
||||
}
|
||||
}
|
||||
kfree((char*)pml4);
|
||||
}
|
||||
|
||||
void
|
||||
clearpteu(pml4e_t *pgdir, char *uva)
|
||||
{
|
||||
pte_t *pte;
|
||||
pte = walkpgdir(pgdir, uva, 0);
|
||||
if(pte == 0)
|
||||
panic("clearpteu");
|
||||
*pte &= ~PTE_U;
|
||||
}
|
||||
|
||||
pml4e_t*
|
||||
copyuvm(pml4e_t *pgdir, uint64 sz)
|
||||
{
|
||||
pml4e_t *d;
|
||||
pte_t *pte;
|
||||
addr_t pa, i, flags;
|
||||
char *mem;
|
||||
|
||||
if((d = setupkvm()) == 0)
|
||||
return 0;
|
||||
for(i = 0; i < sz; i += PGSIZE){
|
||||
if((pte = walkpgdir(pgdir, (void*)i, 0)) == 0)
|
||||
continue;
|
||||
if(!(*pte & PTE_P))
|
||||
continue;
|
||||
pa = PTE_ADDR(*pte);
|
||||
flags = PTE_FLAGS(*pte);
|
||||
|
||||
if(pa < PHYSTOP) {
|
||||
if((mem = kalloc()) == 0)
|
||||
goto bad;
|
||||
memmove(mem, (char*)P2V(pa), PGSIZE);
|
||||
if(mappages(d, (void*)i, PGSIZE, V2P(mem), flags) < 0) {
|
||||
kfree(mem);
|
||||
goto bad;
|
||||
}
|
||||
}
|
||||
}
|
||||
return d;
|
||||
|
||||
bad:
|
||||
freevm(d);
|
||||
return 0;
|
||||
}
|
||||
|
||||
char*
|
||||
uva2ka(pml4e_t *pgdir, char *uva)
|
||||
{
|
||||
if((addr_t)uva >= KERNBASE)
|
||||
return 0;
|
||||
|
||||
pte_t *pte;
|
||||
pte = walkpgdir(pgdir, uva, 0);
|
||||
if(pte == 0 || (*pte & PTE_P) == 0)
|
||||
return 0;
|
||||
if((*pte & PTE_U) == 0)
|
||||
return 0;
|
||||
addr_t pa = (*pte) & 0x000FFFFFFFFFF000ull;
|
||||
// Include uva's in-page offset — callers dereference the returned pointer
|
||||
// directly at uva's address, not just somewhere within its page.
|
||||
return (char*)P2V(pa) + ((addr_t)uva & 0xFFF);
|
||||
}
|
||||
|
||||
int
|
||||
copyout(pml4e_t *pgdir, addr_t va, void *p, uint64 len)
|
||||
{
|
||||
char *buf, *pa0;
|
||||
addr_t n, va0;
|
||||
|
||||
buf = (char*)p;
|
||||
while(len > 0){
|
||||
va0 = PGROUNDDOWN(va);
|
||||
pa0 = uva2ka(pgdir, (char*)va0);
|
||||
|
||||
if(pa0 == 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
n = PGSIZE - (va - va0);
|
||||
if(n > len)
|
||||
n = len;
|
||||
memmove(pa0 + (va - va0), buf, n);
|
||||
|
||||
len -= n;
|
||||
buf += n;
|
||||
va += n;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
copyin(pml4e_t *pgdir, addr_t srcva, void *dst, uint64 len)
|
||||
{
|
||||
uint64 n, va0;
|
||||
char *pa0;
|
||||
char *dst_buf = (char*)dst;
|
||||
|
||||
while(len > 0){
|
||||
va0 = PGROUNDDOWN(srcva);
|
||||
pa0 = uva2ka(pgdir, (char*)va0);
|
||||
if(pa0 == 0)
|
||||
return -1;
|
||||
n = PGSIZE - (srcva - va0);
|
||||
if(n > len)
|
||||
n = len;
|
||||
memmove(dst_buf, pa0 + (srcva - va0), n);
|
||||
len -= n;
|
||||
dst_buf += n;
|
||||
srcva += n;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
810
kernel/core/console.c
Normal file
810
kernel/core/console.c
Normal file
|
|
@ -0,0 +1,810 @@
|
|||
#include <stdarg.h>
|
||||
#include "types.h"
|
||||
#include "defs.h"
|
||||
#include "theme.h"
|
||||
#include "irq.h"
|
||||
#include "param.h"
|
||||
#include "traps.h"
|
||||
#include "spinlock.h"
|
||||
#include "sleeplock.h"
|
||||
#include "fs.h"
|
||||
#include "file.h"
|
||||
#include "memlayout.h"
|
||||
#include "mmu.h"
|
||||
#include "proc.h"
|
||||
#include "posix.h"
|
||||
#include "x86.h"
|
||||
|
||||
void consputc(int);
|
||||
// Set by panic() once the crash screen has been painted: after that, consputc
|
||||
// spins instead of writing, so nothing else can scribble over the report.
|
||||
int panicked = 0;
|
||||
|
||||
// When set, console output goes to the serial line only and the framebuffer
|
||||
// is left alone.
|
||||
//
|
||||
// **Starts set, on purpose.** The display is the product; the serial line is
|
||||
// the diagnostics. A machine that boots by printing "pci: 6 devices" and
|
||||
// "vga: display controller 1234:1111, aperture fd000000" to the screen the
|
||||
// user is looking at is showing them a developer's log, and that single fact
|
||||
// is most of what makes an OS look like a hobby project.
|
||||
//
|
||||
// Nothing is lost: every one of those lines still goes out the UART, which is
|
||||
// where tools/bootcheck.ps1 reads them from and where you want them when
|
||||
// something goes wrong. console_takeover() clears this once userspace is up
|
||||
// and the display genuinely belongs to the console. The F8 verbose boot never
|
||||
// sets it, so "show me everything" still works.
|
||||
int cons_serial_only = 1;
|
||||
extern void vga_screen_reset(void);
|
||||
|
||||
// Text colour used when drawing to the framebuffer. Changed by SGR escapes
|
||||
// (see sgr() below); reset to the default by ESC[0m and ESC c.
|
||||
//
|
||||
// This was initialised to 7, a leftover from when it was a VGA text-mode
|
||||
// attribute byte. vga_putc takes a 32-bit RGB value, so 7 meant "very dark
|
||||
// blue" -- it looked black against the cream background purely by accident.
|
||||
static uint32 cons_fg = SCH_FG;
|
||||
|
||||
static struct {
|
||||
struct spinlock lock;
|
||||
int locking;
|
||||
} cons;
|
||||
|
||||
static char digits[] = "0123456789abcdef";
|
||||
|
||||
static void
|
||||
print_int(int64 xx, int base, int sign)
|
||||
{
|
||||
char buf[64];
|
||||
int i;
|
||||
uint64 x;
|
||||
|
||||
if(sign && (sign = (xx < 0)))
|
||||
x = -xx;
|
||||
else
|
||||
x = xx;
|
||||
|
||||
i = 0;
|
||||
do {
|
||||
buf[i++] = digits[x % base];
|
||||
} while((x /= base) != 0);
|
||||
|
||||
if(sign)
|
||||
buf[i++] = '-';
|
||||
|
||||
while(--i >= 0)
|
||||
consputc(buf[i]);
|
||||
}
|
||||
|
||||
void
|
||||
cprintf(char *fmt, ...)
|
||||
{
|
||||
va_list ap;
|
||||
int i, c, locking;
|
||||
char *s;
|
||||
|
||||
va_start(ap, fmt);
|
||||
|
||||
locking = cons.locking;
|
||||
if (locking)
|
||||
acquire(&cons.lock);
|
||||
|
||||
if (fmt == 0)
|
||||
panic("null fmt");
|
||||
|
||||
for (i = 0; (c = fmt[i] & 0xff) != 0; i++) {
|
||||
if (c != '%') {
|
||||
consputc(c);
|
||||
continue;
|
||||
}
|
||||
c = fmt[++i] & 0xff;
|
||||
if (c == 0)
|
||||
break;
|
||||
switch(c) {
|
||||
case 'd':
|
||||
print_int(va_arg(ap, int), 10, 1);
|
||||
break;
|
||||
case 'u':
|
||||
print_int(va_arg(ap, uint32), 10, 0);
|
||||
break;
|
||||
case 'l':
|
||||
print_int(va_arg(ap, int64), 10, 1);
|
||||
break;
|
||||
case 'x':
|
||||
print_int(va_arg(ap, uint32), 16, 0);
|
||||
break;
|
||||
case 'p':
|
||||
print_int(va_arg(ap, uint64), 16, 0);
|
||||
break;
|
||||
case 'b':
|
||||
print_int(va_arg(ap, uint32), 2, 0);
|
||||
break;
|
||||
case 's':
|
||||
if ((s = va_arg(ap, char*)) == 0)
|
||||
s = "(null)";
|
||||
while (*s)
|
||||
consputc(*(s++));
|
||||
break;
|
||||
case '%':
|
||||
consputc('%');
|
||||
break;
|
||||
default:
|
||||
consputc('%');
|
||||
consputc(c);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (locking)
|
||||
release(&cons.lock);
|
||||
}
|
||||
|
||||
// Hand the console over to panic().
|
||||
//
|
||||
// Two things change. The lock stops being taken, so a panic can report itself
|
||||
// from anywhere — including from inside a routine that already holds it. And
|
||||
// output stops going to the framebuffer, because from here on the framebuffer
|
||||
// belongs to the crash screen: without this the report would scroll straight
|
||||
// over the panel that is trying to display it.
|
||||
void
|
||||
cons_panic_mode(void)
|
||||
{
|
||||
cons.locking = 0;
|
||||
cons_serial_only = 1;
|
||||
}
|
||||
|
||||
// Hand the display over to the text console.
|
||||
//
|
||||
// Called once, when userspace is far enough along that the screen should stop
|
||||
// being a boot splash and start being a terminal. Until this runs the kernel
|
||||
// has been talking to the serial line only (see cons_serial_only), so this is
|
||||
// the moment the framebuffer changes owner -- which is why it clears first
|
||||
// rather than scrolling the shell prompt in underneath the splash.
|
||||
void
|
||||
console_takeover(void)
|
||||
{
|
||||
if (!cons_serial_only)
|
||||
return; // verbose boot: the console never left
|
||||
cons_serial_only = 0;
|
||||
vga_screen_reset();
|
||||
}
|
||||
|
||||
// Foreground process group of the console, i.e. the controlling terminal.
|
||||
// tcgetpgrp()/tcsetpgrp() read and write this through TIOCGPGRP/TIOCSPGRP in
|
||||
// sys_fs.c. Initialised to 1 -- init's process group -- so the first shell,
|
||||
// which inherits that group, already sees itself as the foreground job and
|
||||
// does not immediately stop itself with SIGTTIN.
|
||||
int tty_fg_pgrp = 1;
|
||||
|
||||
#define BACKSPACE 0x100
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Output: an ANSI escape parser, and UTF-8
|
||||
//
|
||||
// The console used to print every byte it was given, which meant every escape
|
||||
// sequence any program emitted appeared on screen as literal text -- `clear`
|
||||
// drew "^[[2J^[[H" and left the screen exactly as it found it. Colour, cursor
|
||||
// addressing, and anything full-screen were all equally impossible.
|
||||
//
|
||||
// The serial line does NOT go through any of this. On the other end of it is
|
||||
// a real terminal emulator that already understands ANSI far better than this
|
||||
// ever will, so escapes are passed through to it verbatim and interpreted
|
||||
// only for the framebuffer. Two consumers, two right answers.
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// The sixteen ANSI colours as RGB, for a linear framebuffer.
|
||||
//
|
||||
// consoleinit() used to program these into the VGA DAC with set_vga_palette(),
|
||||
// which does nothing at all in a 32bpp linear mode -- there is no palette to
|
||||
// index. The colours had to live somewhere real, and this is it.
|
||||
static const uint32 ansi_rgb[16] = {
|
||||
0x000000, 0xAA0000, 0x00AA00, 0xAA5500, // black red green yellow(brown)
|
||||
0x0000AA, 0xAA00AA, 0x00AAAA, 0xAAAAAA, // blue magenta cyan white
|
||||
0x555555, 0xFF5555, 0x55FF55, 0xFFFF55, // bright versions
|
||||
0x5555FF, 0xFF55FF, 0x55FFFF, 0xFFFFFF,
|
||||
};
|
||||
|
||||
#define CONS_DEFAULT_FG SCH_FG // see include/theme.h
|
||||
#define CONS_DEFAULT_BG SCH_BG
|
||||
|
||||
static uint32 cons_bg = CONS_DEFAULT_BG;
|
||||
static int cons_bold = 0;
|
||||
|
||||
// Escape parser state.
|
||||
enum { E_NORMAL, E_ESC, E_CSI };
|
||||
static int esc_state = E_NORMAL;
|
||||
static int esc_parm[8];
|
||||
static int esc_nparm;
|
||||
static int esc_private; // saw '?' after CSI
|
||||
static int saved_col, saved_row;
|
||||
|
||||
// UTF-8 decoder state. A codepoint arrives as up to four bytes and has to be
|
||||
// reassembled before it can be turned into a glyph.
|
||||
static uint32 u8_cp;
|
||||
static int u8_need;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Unicode -> glyph
|
||||
//
|
||||
// The font's upper half is CP437, not Latin-1, so a codepoint cannot be used
|
||||
// as an index. It looks like it can -- font.h's own comments say so, labelling
|
||||
// each glyph with the Unicode character of the same number -- but the bitmaps
|
||||
// disagree: index 0xE9 draws a capital theta, and rendering U+00E9 there turns
|
||||
// "cafe" into "caf" followed by a theta. Every accented character came out as
|
||||
// an unrelated symbol, consistently enough to look almost deliberate.
|
||||
//
|
||||
// So: a table. Entry i covers codepoint 0x80 + i, and 0 means "this font has
|
||||
// no glyph for that". The C1 range (0x80-0x9F) has no printable characters at
|
||||
// all, hence the leading run of zeros.
|
||||
// ---------------------------------------------------------------------------
|
||||
static const uchar latin1_to_cp437[128] = {
|
||||
/* 0x80-0x9F: C1 controls, nothing to draw */
|
||||
0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,
|
||||
/* 0xA0 */ 0x20, 0xAD, 0x9B, 0x9C, 0, 0x9D, 0, 0x15,
|
||||
/* 0xA8 */ 0, 0, 0xA6, 0xAE, 0xAA, 0x2D, 0, 0,
|
||||
/* 0xB0 */ 0xF8, 0xF1, 0xFD, 0, 0, 0xE6, 0x14, 0xFA,
|
||||
/* 0xB8 */ 0, 0, 0xA7, 0xAF, 0xAC, 0xAB, 0, 0xA8,
|
||||
/* 0xC0 */ 0, 0, 0, 0, 0x8E, 0x8F, 0x92, 0x80,
|
||||
/* 0xC8 */ 0, 0x90, 0, 0, 0, 0, 0, 0,
|
||||
/* 0xD0 */ 0, 0xA5, 0, 0, 0, 0, 0x99, 0,
|
||||
/* 0xD8 */ 0, 0, 0, 0, 0x9A, 0, 0, 0xE1,
|
||||
/* 0xE0 */ 0x85, 0xA0, 0x83, 0, 0x84, 0x86, 0x91, 0x87,
|
||||
/* 0xE8 */ 0x8A, 0x82, 0x88, 0x89, 0x8D, 0xA1, 0x8C, 0x8B,
|
||||
/* 0xF0 */ 0, 0xA4, 0x95, 0xA2, 0x93, 0, 0x94, 0xF6,
|
||||
/* 0xF8 */ 0, 0x97, 0xA3, 0x96, 0x81, 0, 0, 0x98,
|
||||
};
|
||||
|
||||
// The handful of characters above U+00FF that CP437 does have. Box drawing
|
||||
// and block elements matter because they are what every text-mode program
|
||||
// draws frames and progress bars out of.
|
||||
struct cp_pair { uint32 cp; uchar glyph; };
|
||||
static const struct cp_pair cp437_extra[] = {
|
||||
{ 0x2022, 0x07 }, /* bullet */
|
||||
{ 0x2500, 0xC4 }, { 0x2502, 0xB3 }, /* light horizontal/vertical */
|
||||
{ 0x250C, 0xDA }, { 0x2510, 0xBF },
|
||||
{ 0x2514, 0xC0 }, { 0x2518, 0xD9 },
|
||||
{ 0x251C, 0xC3 }, { 0x2524, 0xB4 },
|
||||
{ 0x252C, 0xC2 }, { 0x2534, 0xC1 }, { 0x253C, 0xC5 },
|
||||
{ 0x2550, 0xCD }, { 0x2551, 0xBA }, /* double */
|
||||
{ 0x2554, 0xC9 }, { 0x2557, 0xBB },
|
||||
{ 0x255A, 0xC8 }, { 0x255D, 0xBC },
|
||||
{ 0x2580, 0xDF }, { 0x2584, 0xDC }, { 0x2588, 0xDB },
|
||||
{ 0x258C, 0xDD }, { 0x2590, 0xDE },
|
||||
{ 0x2591, 0xB0 }, { 0x2592, 0xB1 }, { 0x2593, 0xB2 },
|
||||
{ 0x25A0, 0xFE }, /* black square */
|
||||
{ 0x221A, 0xFB }, { 0x221E, 0xEC }, /* root, infinity */
|
||||
{ 0x2260, 0, }, { 0x2264, 0xF3 }, { 0x2265, 0xF2 },
|
||||
{ 0x2248, 0xF7 }, { 0x2261, 0xF0 },
|
||||
};
|
||||
|
||||
// Returns the glyph to draw for a codepoint, or 0 if there is none.
|
||||
static int
|
||||
glyph_for(uint32 cp)
|
||||
{
|
||||
if (cp < 0x80)
|
||||
return (int)cp; // ASCII is CP437's lower half
|
||||
if (cp < 0x100)
|
||||
return latin1_to_cp437[cp - 0x80];
|
||||
|
||||
for (unsigned i = 0; i < NELEM(cp437_extra); i++)
|
||||
if (cp437_extra[i].cp == cp)
|
||||
return cp437_extra[i].glyph;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void
|
||||
sgr(void)
|
||||
{
|
||||
for (int i = 0; i < esc_nparm; i++) {
|
||||
int p = esc_parm[i];
|
||||
|
||||
if (p == 0) {
|
||||
cons_fg = CONS_DEFAULT_FG;
|
||||
cons_bg = CONS_DEFAULT_BG;
|
||||
cons_bold = 0;
|
||||
} else if (p == 1) {
|
||||
cons_bold = 1;
|
||||
// Re-apply brightness to a colour already chosen.
|
||||
for (int j = 0; j < 8; j++)
|
||||
if (cons_fg == ansi_rgb[j]) { cons_fg = ansi_rgb[j + 8]; break; }
|
||||
} else if (p == 22) {
|
||||
cons_bold = 0;
|
||||
} else if (p >= 30 && p <= 37) {
|
||||
cons_fg = ansi_rgb[(p - 30) + (cons_bold ? 8 : 0)];
|
||||
} else if (p == 39) {
|
||||
cons_fg = CONS_DEFAULT_FG;
|
||||
} else if (p >= 40 && p <= 47) {
|
||||
cons_bg = ansi_rgb[p - 40];
|
||||
} else if (p == 49) {
|
||||
cons_bg = CONS_DEFAULT_BG;
|
||||
} else if (p >= 90 && p <= 97) {
|
||||
cons_fg = ansi_rgb[(p - 90) + 8];
|
||||
} else if (p >= 100 && p <= 107) {
|
||||
cons_bg = ansi_rgb[(p - 100) + 8];
|
||||
}
|
||||
}
|
||||
vga_set_bg(cons_bg);
|
||||
}
|
||||
|
||||
// One final byte of a CSI sequence, with esc_parm[] already filled in.
|
||||
static void
|
||||
csi_dispatch(int final)
|
||||
{
|
||||
int col, row;
|
||||
int n = esc_nparm > 0 ? esc_parm[0] : 0;
|
||||
int n1 = n ? n : 1; // most sequences default to 1
|
||||
|
||||
vga_cursor_get(&col, &row);
|
||||
|
||||
switch (final) {
|
||||
case 'A': vga_cursor_set(col, row - n1); break;
|
||||
case 'B': vga_cursor_set(col, row + n1); break;
|
||||
case 'C': vga_cursor_set(col + n1, row); break;
|
||||
case 'D': vga_cursor_set(col - n1, row); break;
|
||||
case 'E': vga_cursor_set(0, row + n1); break;
|
||||
case 'F': vga_cursor_set(0, row - n1); break;
|
||||
case 'G': vga_cursor_set(n1 - 1, row); break;
|
||||
case 'd': vga_cursor_set(col, n1 - 1); break;
|
||||
|
||||
case 'H': case 'f': {
|
||||
int r = esc_nparm > 0 && esc_parm[0] ? esc_parm[0] : 1;
|
||||
int c = esc_nparm > 1 && esc_parm[1] ? esc_parm[1] : 1;
|
||||
vga_cursor_set(c - 1, r - 1); // ANSI counts from 1
|
||||
break;
|
||||
}
|
||||
|
||||
case 'J': vga_erase_display(n); break;
|
||||
case 'K': vga_erase_line(n); break;
|
||||
|
||||
case 'm': sgr(); break;
|
||||
|
||||
case 's': saved_col = col; saved_row = row; break;
|
||||
case 'u': vga_cursor_set(saved_col, saved_row); break;
|
||||
|
||||
// Cursor visibility (DECTCEM) and the other private modes. There is no
|
||||
// hardware cursor being drawn, so there is nothing to show or hide --
|
||||
// swallowing them silently is the correct behaviour, and far better than
|
||||
// printing "?25l" into the middle of someone's output.
|
||||
case 'h': case 'l': break;
|
||||
|
||||
default: break; // unknown: drop it, do not print it
|
||||
}
|
||||
}
|
||||
|
||||
// Feed one output byte to the framebuffer side. Returns nothing; the serial
|
||||
// side has already had the raw byte.
|
||||
static void
|
||||
vga_feed(int c)
|
||||
{
|
||||
switch (esc_state) {
|
||||
case E_ESC:
|
||||
if (c == '[') {
|
||||
esc_state = E_CSI;
|
||||
esc_nparm = 0;
|
||||
esc_private = 0;
|
||||
for (int i = 0; i < 8; i++) esc_parm[i] = 0;
|
||||
return;
|
||||
}
|
||||
esc_state = E_NORMAL;
|
||||
if (c == 'c') { // RIS -- full reset
|
||||
cons_fg = CONS_DEFAULT_FG;
|
||||
cons_bg = CONS_DEFAULT_BG;
|
||||
vga_screen_reset();
|
||||
}
|
||||
return; // other two-byte escapes: ignored
|
||||
|
||||
case E_CSI:
|
||||
if (c == '?') { esc_private = 1; return; }
|
||||
if (c >= '0' && c <= '9') {
|
||||
if (esc_nparm == 0) esc_nparm = 1;
|
||||
if (esc_nparm <= 8)
|
||||
esc_parm[esc_nparm - 1] = esc_parm[esc_nparm - 1] * 10 + (c - '0');
|
||||
return;
|
||||
}
|
||||
if (c == ';') {
|
||||
if (esc_nparm < 8) esc_nparm++;
|
||||
return;
|
||||
}
|
||||
esc_state = E_NORMAL;
|
||||
if (!esc_private)
|
||||
csi_dispatch(c);
|
||||
return;
|
||||
}
|
||||
|
||||
// E_NORMAL
|
||||
if (c == '\x1b') { esc_state = E_ESC; return; }
|
||||
|
||||
// UTF-8 continuation. A codepoint the font cannot draw becomes '?' -- the
|
||||
// one thing it must not become is a *different* character, which is exactly
|
||||
// what indexing the font by codepoint did.
|
||||
if (u8_need > 0) {
|
||||
if ((c & 0xC0) != 0x80) { // malformed: abandon the sequence
|
||||
u8_need = 0;
|
||||
vga_putc('?', cons_fg);
|
||||
return;
|
||||
}
|
||||
u8_cp = (u8_cp << 6) | (c & 0x3F);
|
||||
if (--u8_need > 0)
|
||||
return;
|
||||
int g = glyph_for(u8_cp);
|
||||
vga_putc(g ? g : '?', cons_fg);
|
||||
return;
|
||||
}
|
||||
|
||||
if (c >= 0xF0) { u8_cp = c & 0x07; u8_need = 3; return; }
|
||||
else if (c >= 0xE0) { u8_cp = c & 0x0F; u8_need = 2; return; }
|
||||
else if (c >= 0xC0) { u8_cp = c & 0x1F; u8_need = 1; return; }
|
||||
else if (c >= 0x80) { vga_putc('?', cons_fg); return; } // stray continuation
|
||||
|
||||
vga_putc(c, cons_fg);
|
||||
}
|
||||
|
||||
void
|
||||
consputc(int c)
|
||||
{
|
||||
if (panicked) {
|
||||
cli();
|
||||
for(;;) hlt();
|
||||
}
|
||||
|
||||
if (c == BACKSPACE) {
|
||||
uartputc('\b'); uartputc(' '); uartputc('\b');
|
||||
if (!cons_serial_only)
|
||||
vga_putc(c, cons_fg);
|
||||
return;
|
||||
}
|
||||
|
||||
uartputc(c);
|
||||
if (!cons_serial_only)
|
||||
vga_feed(c & 0xff);
|
||||
}
|
||||
|
||||
#define INPUT_BUF 256
|
||||
struct {
|
||||
struct spinlock lock;
|
||||
char buf[INPUT_BUF];
|
||||
uint r;
|
||||
uint w;
|
||||
uint e;
|
||||
uint abort_read;
|
||||
// Set by EOF (Ctrl-D) on an empty line. Canonical-mode end of input is not
|
||||
// a character -- it is the *absence* of one -- so it cannot be signalled by
|
||||
// putting something in the buffer. The old code pushed a literal ^D in and
|
||||
// had consoleread() pick it back out, which meant a ^D typed mid-line was
|
||||
// indistinguishable from one typed alone.
|
||||
uint eof;
|
||||
} input;
|
||||
|
||||
#define C(x) ((x)-'@')
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// The line discipline
|
||||
//
|
||||
// TCGETS used to return a hardcoded set of flags and TCSETS threw its argument
|
||||
// away, so the terminal was permanently in one mode: line-at-a-time, always
|
||||
// echoing. Anything wanting a keypress at a time -- a pager, an editor, a
|
||||
// shell doing its own line editing, which is most of what zsh is -- had no way
|
||||
// to ask for one. This is the real thing: a struct programs can write, that
|
||||
// changes what the kernel actually does.
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static struct termios tio = {
|
||||
.c_iflag = ICRNL,
|
||||
.c_oflag = OPOST | ONLCR,
|
||||
.c_cflag = CS8 | CREAD | CLOCAL,
|
||||
.c_lflag = ISIG | ICANON | ECHO | ECHOE,
|
||||
.c_cc = {
|
||||
[VINTR] = C('C'), [VQUIT] = C('\\'), [VERASE] = '\x7f', [VKILL] = C('U'),
|
||||
[VEOF] = C('D'), [VSUSP] = C('Z'), [VWERASE] = C('W'), [VMIN] = 1,
|
||||
},
|
||||
.c_ispeed = 15, .c_ospeed = 15,
|
||||
};
|
||||
|
||||
void
|
||||
cons_get_termios(struct termios *out)
|
||||
{
|
||||
acquire(&input.lock);
|
||||
*out = tio;
|
||||
release(&input.lock);
|
||||
}
|
||||
|
||||
void
|
||||
cons_set_termios(const struct termios *in)
|
||||
{
|
||||
acquire(&input.lock);
|
||||
tio = *in;
|
||||
// Switching out of canonical mode has to hand over whatever has been typed
|
||||
// but not yet submitted, or a shell that turns off ICANON mid-line silently
|
||||
// eats the characters already in the edit buffer.
|
||||
if (!(tio.c_lflag & ICANON))
|
||||
input.w = input.e;
|
||||
release(&input.lock);
|
||||
wakeup(&input.r);
|
||||
}
|
||||
|
||||
static void
|
||||
echo(int c)
|
||||
{
|
||||
if (c == '\n' || c == '\r') {
|
||||
if ((tio.c_lflag & ECHO) || (tio.c_lflag & ECHONL))
|
||||
consputc('\n');
|
||||
return;
|
||||
}
|
||||
if (!(tio.c_lflag & ECHO))
|
||||
return;
|
||||
// Control characters echo as ^X, the way every other terminal shows them.
|
||||
if (c < 32 && c != '\t') {
|
||||
consputc('^');
|
||||
consputc(c + '@');
|
||||
} else {
|
||||
consputc(c);
|
||||
}
|
||||
}
|
||||
|
||||
// Rub out the last character of the current line.
|
||||
static void
|
||||
erase_one(void)
|
||||
{
|
||||
if (input.e == input.w)
|
||||
return;
|
||||
input.e--;
|
||||
if (tio.c_lflag & ECHOE)
|
||||
consputc(BACKSPACE);
|
||||
}
|
||||
|
||||
void
|
||||
consoleintr(int (*getc)(void))
|
||||
{
|
||||
int c;
|
||||
|
||||
acquire(&input.lock);
|
||||
while((c = getc()) >= 0){
|
||||
if (c == 0)
|
||||
continue;
|
||||
|
||||
// Input mapping, before anything looks at the character.
|
||||
if (tio.c_iflag & ISTRIP) c &= 0x7f;
|
||||
if (c == '\r') {
|
||||
if (tio.c_iflag & IGNCR) continue;
|
||||
if (tio.c_iflag & ICRNL) c = '\n';
|
||||
} else if (c == '\n' && (tio.c_iflag & INLCR)) {
|
||||
c = '\r';
|
||||
}
|
||||
|
||||
// Signals. These are the characters that must not reach the reader.
|
||||
//
|
||||
// Ctrl-C used to do `myproc()->killed = 1` -- and `proc` in an interrupt
|
||||
// handler is whatever happened to be running on this CPU when the key was
|
||||
// pressed, which is almost never the program the user is looking at. It
|
||||
// also set a kill flag rather than raising a signal, so nothing could
|
||||
// catch it and the shell never learned the job had died. Both wrong;
|
||||
// both fixed by sending a real signal to the foreground process group,
|
||||
// which the kernel has been tracking in tty_fg_pgrp all along.
|
||||
if (tio.c_lflag & ISIG) {
|
||||
int sig = 0;
|
||||
if (c == tio.c_cc[VINTR]) sig = SIGINT;
|
||||
else if (c == tio.c_cc[VQUIT]) sig = SIGQUIT;
|
||||
else if (c == tio.c_cc[VSUSP]) sig = SIGTSTP;
|
||||
if (sig) {
|
||||
echo(c);
|
||||
input.e = input.w = input.r; // discard the partial line
|
||||
input.abort_read = 1;
|
||||
release(&input.lock);
|
||||
sigsend_pgrp(tty_fg_pgrp, sig);
|
||||
acquire(&input.lock);
|
||||
wakeup(&input.r);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Ctrl-P: dump the process table. Not POSIX, but it is the one debugging
|
||||
// aid that works when everything else has stopped responding.
|
||||
if (c == C('P')) {
|
||||
procdump();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (tio.c_lflag & ICANON) {
|
||||
if (c == tio.c_cc[VERASE] || c == C('H')) {
|
||||
erase_one();
|
||||
continue;
|
||||
}
|
||||
if (c == tio.c_cc[VKILL]) {
|
||||
while (input.e != input.w &&
|
||||
input.buf[(input.e-1) % INPUT_BUF] != '\n')
|
||||
erase_one();
|
||||
continue;
|
||||
}
|
||||
if (c == tio.c_cc[VWERASE]) {
|
||||
while (input.e != input.w &&
|
||||
input.buf[(input.e-1) % INPUT_BUF] == ' ')
|
||||
erase_one();
|
||||
while (input.e != input.w &&
|
||||
input.buf[(input.e-1) % INPUT_BUF] != ' ')
|
||||
erase_one();
|
||||
continue;
|
||||
}
|
||||
if (c == tio.c_cc[VEOF]) {
|
||||
if (input.e == input.w)
|
||||
input.eof = 1; // empty line: end of input
|
||||
input.w = input.e; // otherwise just submit what is there
|
||||
wakeup(&input.r);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (input.e - input.r >= INPUT_BUF)
|
||||
continue; // buffer full: drop it
|
||||
|
||||
// Everything else goes in, including bytes above 127 -- those are UTF-8
|
||||
// continuation bytes, and the old `c >= 32 && c <= 126` test silently
|
||||
// refused every non-ASCII character anyone tried to type.
|
||||
input.buf[input.e++ % INPUT_BUF] = c;
|
||||
echo(c);
|
||||
|
||||
// Canonical mode hands over a line at a time; raw mode hands over
|
||||
// everything as it arrives.
|
||||
if (!(tio.c_lflag & ICANON) ||
|
||||
c == '\n' || input.e == input.r + INPUT_BUF) {
|
||||
input.w = input.e;
|
||||
wakeup(&input.r);
|
||||
}
|
||||
}
|
||||
release(&input.lock);
|
||||
}
|
||||
|
||||
int
|
||||
consoleread(struct inode *ip, uint off, char *dst, int n)
|
||||
{
|
||||
uint target;
|
||||
int c;
|
||||
static uint32 prng_seed = 0x9A7B3C1D;
|
||||
|
||||
if (ip->minor == 2) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (ip->minor == 4) {
|
||||
for (int i = 0; i < n; i++) {
|
||||
prng_seed ^= prng_seed << 13;
|
||||
prng_seed ^= prng_seed >> 17;
|
||||
prng_seed ^= prng_seed << 5;
|
||||
dst[i] = prng_seed & 0xFF;
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
iunlock(ip);
|
||||
target = n;
|
||||
acquire(&input.lock);
|
||||
while(n > 0){
|
||||
while(input.r == input.w){
|
||||
if (input.eof) {
|
||||
input.eof = 0;
|
||||
release(&input.lock);
|
||||
ilock(ip);
|
||||
return 0; // end of input
|
||||
}
|
||||
if (myproc()->killed || input.abort_read) {
|
||||
input.abort_read = 0;
|
||||
release(&input.lock);
|
||||
ilock(ip);
|
||||
return -1;
|
||||
}
|
||||
sleep(&input.r, &input.lock);
|
||||
}
|
||||
c = input.buf[input.r++ % INPUT_BUF];
|
||||
*dst++ = c;
|
||||
--n;
|
||||
|
||||
// Canonical mode returns a line at a time. Raw mode returns whatever has
|
||||
// arrived: a program that asked for a keypress must not be made to wait
|
||||
// for a newline that is never coming.
|
||||
if (tio.c_lflag & ICANON) {
|
||||
if (c == '\n')
|
||||
break;
|
||||
} else {
|
||||
if (target - n >= (uint)(tio.c_cc[VMIN] ? tio.c_cc[VMIN] : 1))
|
||||
break;
|
||||
}
|
||||
}
|
||||
release(&input.lock);
|
||||
ilock(ip);
|
||||
|
||||
return target - n;
|
||||
}
|
||||
|
||||
// Nonzero if a completed input line is waiting — poll(2)'s POLLIN answer
|
||||
// for the console tty. Mirrors the r/w cursors consoleread() blocks on.
|
||||
int
|
||||
console_input_ready(void)
|
||||
{
|
||||
int ready;
|
||||
|
||||
acquire(&input.lock);
|
||||
ready = input.r != input.w;
|
||||
release(&input.lock);
|
||||
return ready;
|
||||
}
|
||||
|
||||
int
|
||||
consolewrite(struct inode *ip, uint off, char *buf, int n)
|
||||
{
|
||||
int i;
|
||||
|
||||
if (ip->minor == 2 || ip->minor == 3 || ip->minor == 4) {
|
||||
return n;
|
||||
}
|
||||
|
||||
iunlock(ip);
|
||||
acquire(&cons.lock);
|
||||
for(i = 0; i < n; i++)
|
||||
consputc(buf[i] & 0xff);
|
||||
release(&cons.lock);
|
||||
ilock(ip);
|
||||
|
||||
return n;
|
||||
}
|
||||
|
||||
void
|
||||
disable_blink(void)
|
||||
{
|
||||
inb(0x3DA);
|
||||
outb(0x3C0, 0x10 | 0x20);
|
||||
uchar dummy = inb(0x3C1);
|
||||
outb(0x3C0, dummy & ~0x08);
|
||||
}
|
||||
|
||||
static void
|
||||
set_vga_palette(uchar index, uchar r, uchar g, uchar b)
|
||||
{
|
||||
outb(0x3C6, 0xFF);
|
||||
outb(0x3C8, index);
|
||||
outb(0x3C9, r & 0x3F);
|
||||
outb(0x3C9, g & 0x3F);
|
||||
outb(0x3C9, b & 0x3F);
|
||||
}
|
||||
|
||||
// The keyboard handler lives in kbd.c, but the console is what owns the
|
||||
// keyboard as an input source, so this is where the line gets claimed.
|
||||
static void
|
||||
kbd_irq(void *arg)
|
||||
{
|
||||
(void)arg;
|
||||
kbdintr();
|
||||
}
|
||||
|
||||
void
|
||||
consoleinit(void)
|
||||
{
|
||||
initlock(&cons.lock, "console");
|
||||
initlock(&input.lock, "input");
|
||||
|
||||
memset(&input, 0, sizeof(input));
|
||||
|
||||
devsw[CONSOLE].write = consolewrite;
|
||||
devsw[CONSOLE].read = consoleread;
|
||||
cons.locking = 1;
|
||||
|
||||
disable_blink();
|
||||
|
||||
set_vga_palette(0, 0, 0, 0); // Black
|
||||
set_vga_palette(1, 0, 0, 42); // Blue
|
||||
set_vga_palette(2, 0, 42, 0); // Green
|
||||
set_vga_palette(3, 0, 42, 42); // Cyan
|
||||
set_vga_palette(4, 42, 0, 0); // Red
|
||||
set_vga_palette(5, 42, 0, 42); // Magenta
|
||||
set_vga_palette(6, 42, 21, 0); // Brown
|
||||
set_vga_palette(7, 42, 42, 42); // Light Gray
|
||||
set_vga_palette(8, 21, 21, 21); // Dark Gray
|
||||
set_vga_palette(9, 21, 21, 63); // Light Blue
|
||||
set_vga_palette(10, 21, 63, 21); // Light Green
|
||||
set_vga_palette(11, 21, 63, 63); // Light Cyan
|
||||
set_vga_palette(12, 63, 21, 21); // Light Red
|
||||
set_vga_palette(13, 21, 21, 63); // Light Magenta
|
||||
set_vga_palette(14, 63, 63, 21); // Yellow
|
||||
set_vga_palette(15, 63, 63, 63); // White
|
||||
|
||||
irq_register(IRQ_KBD, kbd_irq, 0, "ps2-kbd", 0);
|
||||
}
|
||||
191
kernel/core/dma.c
Normal file
191
kernel/core/dma.c
Normal file
|
|
@ -0,0 +1,191 @@
|
|||
// Physically contiguous memory for devices. See include/dma.h.
|
||||
|
||||
#include "types.h"
|
||||
#include "defs.h"
|
||||
#include "param.h"
|
||||
#include "memlayout.h"
|
||||
#include "mmu.h"
|
||||
#include "spinlock.h"
|
||||
#include "dma.h"
|
||||
|
||||
#define DMA_GRAIN 64 // one cache line; also xHCI's ring alignment
|
||||
#define NEXTENT 64 // free extents we can track
|
||||
|
||||
struct extent {
|
||||
addr_t base; // kernel virtual address
|
||||
uint64 len;
|
||||
};
|
||||
|
||||
static struct {
|
||||
struct spinlock lock;
|
||||
struct extent free[NEXTENT];
|
||||
int n; // free extents, kept sorted by base
|
||||
addr_t lo, hi;
|
||||
uint64 total;
|
||||
} arena;
|
||||
|
||||
static uint64
|
||||
roundup(uint64 v, uint64 to)
|
||||
{
|
||||
return (v + to - 1) & ~(to - 1);
|
||||
}
|
||||
|
||||
void
|
||||
dma_init(void *vstart, void *vend)
|
||||
{
|
||||
initlock(&arena.lock, "dma");
|
||||
arena.lo = roundup((addr_t)vstart, PGSIZE);
|
||||
arena.hi = (addr_t)vend & ~(addr_t)(PGSIZE - 1);
|
||||
if(arena.hi <= arena.lo)
|
||||
panic("dma_init: empty arena");
|
||||
arena.free[0].base = arena.lo;
|
||||
arena.free[0].len = arena.hi - arena.lo;
|
||||
arena.n = 1;
|
||||
arena.total = arena.free[0].len;
|
||||
}
|
||||
|
||||
// Remove free[i], shifting the tail down.
|
||||
static void
|
||||
drop(int i)
|
||||
{
|
||||
int j;
|
||||
for(j = i; j + 1 < arena.n; j++)
|
||||
arena.free[j] = arena.free[j+1];
|
||||
arena.n--;
|
||||
}
|
||||
|
||||
// Insert a free extent at index i, shifting the tail up. Returns 0 if the
|
||||
// table is full, in which case the memory is leaked rather than corrupting
|
||||
// the list -- a leak is recoverable by rebooting, a corrupt free list hands
|
||||
// two drivers the same ring.
|
||||
static int
|
||||
insert(int i, addr_t base, uint64 len)
|
||||
{
|
||||
int j;
|
||||
|
||||
if(arena.n >= NEXTENT)
|
||||
return 0;
|
||||
for(j = arena.n; j > i; j--)
|
||||
arena.free[j] = arena.free[j-1];
|
||||
arena.free[i].base = base;
|
||||
arena.free[i].len = len;
|
||||
arena.n++;
|
||||
return 1;
|
||||
}
|
||||
|
||||
void *
|
||||
dma_alloc(uint64 size, uint64 align)
|
||||
{
|
||||
int i;
|
||||
addr_t start;
|
||||
|
||||
if(size == 0)
|
||||
return 0;
|
||||
if(align < DMA_GRAIN)
|
||||
align = DMA_GRAIN;
|
||||
if(align & (align - 1))
|
||||
panic("dma_alloc: alignment not a power of two");
|
||||
size = roundup(size, DMA_GRAIN);
|
||||
|
||||
acquire(&arena.lock);
|
||||
|
||||
for(i = 0; i < arena.n; i++){
|
||||
addr_t base = arena.free[i].base;
|
||||
uint64 len = arena.free[i].len;
|
||||
uint64 lead;
|
||||
|
||||
start = roundup(base, align);
|
||||
lead = start - base;
|
||||
if(lead + size > len)
|
||||
continue;
|
||||
|
||||
// Three cases: the request consumes the extent, sits at its head, sits
|
||||
// at its tail, or sits in the middle and splits it in two.
|
||||
if(lead == 0 && size == len){
|
||||
drop(i);
|
||||
} else if(lead == 0){
|
||||
arena.free[i].base += size;
|
||||
arena.free[i].len -= size;
|
||||
} else if(lead + size == len){
|
||||
arena.free[i].len = lead;
|
||||
} else {
|
||||
uint64 tail_len = len - lead - size;
|
||||
arena.free[i].len = lead;
|
||||
if(!insert(i + 1, start + size, tail_len)){
|
||||
// No room to record the tail. Give the whole extent to the caller
|
||||
// rather than lose track of where the tail was.
|
||||
arena.free[i].len = len;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
release(&arena.lock);
|
||||
memset((void*)start, 0, size);
|
||||
return (void*)start;
|
||||
}
|
||||
|
||||
release(&arena.lock);
|
||||
cprintf("dma_alloc: out of DMA memory (wanted %d bytes, %d free)\n",
|
||||
(int)size, (int)dma_free_bytes());
|
||||
return 0;
|
||||
}
|
||||
|
||||
void
|
||||
dma_free(void *p, uint64 size)
|
||||
{
|
||||
addr_t base = (addr_t)p;
|
||||
int i;
|
||||
|
||||
if(p == 0)
|
||||
return;
|
||||
size = roundup(size, DMA_GRAIN);
|
||||
if(base < arena.lo || base + size > arena.hi)
|
||||
panic("dma_free: not DMA memory");
|
||||
|
||||
acquire(&arena.lock);
|
||||
|
||||
// Find the first extent that starts after this block, so the list stays
|
||||
// sorted and neighbours end up adjacent for coalescing.
|
||||
for(i = 0; i < arena.n && arena.free[i].base < base; i++)
|
||||
;
|
||||
|
||||
if(i > 0 && arena.free[i-1].base + arena.free[i-1].len == base){
|
||||
arena.free[i-1].len += size; // merge with the block before
|
||||
i--;
|
||||
} else if(!insert(i, base, size)){
|
||||
release(&arena.lock);
|
||||
cprintf("dma_free: extent table full, leaking %d bytes\n", (int)size);
|
||||
return;
|
||||
}
|
||||
|
||||
if(i + 1 < arena.n &&
|
||||
arena.free[i].base + arena.free[i].len == arena.free[i+1].base){
|
||||
arena.free[i].len += arena.free[i+1].len; // and with the block after
|
||||
drop(i + 1);
|
||||
}
|
||||
|
||||
release(&arena.lock);
|
||||
}
|
||||
|
||||
addr_t
|
||||
dma_phys(const void *p)
|
||||
{
|
||||
return V2P((addr_t)p);
|
||||
}
|
||||
|
||||
uint64
|
||||
dma_free_bytes(void)
|
||||
{
|
||||
uint64 n = 0;
|
||||
int i;
|
||||
|
||||
for(i = 0; i < arena.n; i++)
|
||||
n += arena.free[i].len;
|
||||
return n;
|
||||
}
|
||||
|
||||
uint64
|
||||
dma_total_bytes(void)
|
||||
{
|
||||
return arena.total;
|
||||
}
|
||||
124
kernel/core/kalloc.c
Normal file
124
kernel/core/kalloc.c
Normal file
|
|
@ -0,0 +1,124 @@
|
|||
// Physical memory allocator, intended to allocate
|
||||
// memory for user processes, kernel stacks, page table pages,
|
||||
// and pipe buffers. Allocates 4096-byte pages.
|
||||
|
||||
#include "types.h"
|
||||
#include "defs.h"
|
||||
#include "param.h"
|
||||
#include "memlayout.h"
|
||||
#include "mmu.h"
|
||||
#include "spinlock.h"
|
||||
|
||||
void freerange(void *vstart, void *vend);
|
||||
extern char end[]; // first address after kernel loaded from ELF file
|
||||
|
||||
|
||||
struct run {
|
||||
struct run *next;
|
||||
};
|
||||
|
||||
struct {
|
||||
struct spinlock lock;
|
||||
int use_lock;
|
||||
struct run *freelist;
|
||||
} kmem;
|
||||
|
||||
// Catch kmem.freelist corruption at the point it's touched, rather than
|
||||
// letting a bad pointer be silently returned by kalloc() and crash whatever
|
||||
// code dereferences it minutes later on an unrelated call stack.
|
||||
static void
|
||||
check_run(struct run *r, char *where)
|
||||
{
|
||||
if(r == 0)
|
||||
return;
|
||||
if((addr_t)r % PGSIZE || (char*)r < end || V2P(r) >= PHYSTOP){
|
||||
cprintf("kalloc: corrupt freelist entry %p at %s\n", r, where);
|
||||
panic("kalloc: corrupt freelist");
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
kinit1(void *vstart, void *vend)
|
||||
{
|
||||
initlock(&kmem.lock, "kmem");
|
||||
kmem.use_lock = 0;
|
||||
kmem.freelist = 0; // empty
|
||||
freerange(vstart, vend);
|
||||
}
|
||||
|
||||
void
|
||||
kinit2()
|
||||
{
|
||||
kmem.use_lock = 1;
|
||||
}
|
||||
|
||||
void
|
||||
freerange(void *vstart, void *vend)
|
||||
{
|
||||
char *p;
|
||||
p = (char*)PGROUNDUP((addr_t)vstart);
|
||||
for(; p + PGSIZE <= (char*)vend; p += PGSIZE)
|
||||
kfree(p);
|
||||
}
|
||||
|
||||
// Free the page of physical memory pointed at by v,
|
||||
// which normally should have been returned by a
|
||||
// call to kalloc(). (The exception is when
|
||||
// initializing the allocator; see kinit above.)
|
||||
void
|
||||
kfree(char *v)
|
||||
{
|
||||
struct run *r;
|
||||
|
||||
if((addr_t)v % PGSIZE || v < end || V2P(v) >= PHYSTOP)
|
||||
panic("kfree");
|
||||
|
||||
// Fill with junk to catch dangling refs.
|
||||
memset(v, 1, PGSIZE);
|
||||
|
||||
if(kmem.use_lock)
|
||||
acquire(&kmem.lock);
|
||||
check_run(kmem.freelist, "kfree entry");
|
||||
r = (struct run*)v;
|
||||
r->next = kmem.freelist;
|
||||
kmem.freelist = r;
|
||||
if(kmem.use_lock)
|
||||
release(&kmem.lock);
|
||||
}
|
||||
|
||||
// Allocate one 4096-byte page of physical memory.
|
||||
// Returns a pointer that the kernel can use.
|
||||
// Returns 0 if the memory cannot be allocated.
|
||||
char*
|
||||
kalloc(void)
|
||||
{
|
||||
struct run *r;
|
||||
|
||||
if(kmem.use_lock)
|
||||
acquire(&kmem.lock);
|
||||
check_run(kmem.freelist, "kalloc entry");
|
||||
r = kmem.freelist;
|
||||
if(r)
|
||||
kmem.freelist = r->next;
|
||||
|
||||
if(kmem.use_lock)
|
||||
release(&kmem.lock);
|
||||
return (char*)r;
|
||||
}
|
||||
|
||||
// Total free physical memory, in bytes. Walks the free list; only /proc
|
||||
// readers call it, so the O(n) scan is not on any hot path.
|
||||
uint64
|
||||
kalloc_free_bytes(void)
|
||||
{
|
||||
struct run *r;
|
||||
uint64 n = 0;
|
||||
|
||||
if(kmem.use_lock)
|
||||
acquire(&kmem.lock);
|
||||
for(r = kmem.freelist; r; r = r->next)
|
||||
n += PGSIZE;
|
||||
if(kmem.use_lock)
|
||||
release(&kmem.lock);
|
||||
return n;
|
||||
}
|
||||
231
kernel/core/main.c
Normal file
231
kernel/core/main.c
Normal file
|
|
@ -0,0 +1,231 @@
|
|||
#include "types.h"
|
||||
#include "defs.h"
|
||||
#include "param.h"
|
||||
#include "memlayout.h"
|
||||
#include "mmu.h"
|
||||
#include "proc.h"
|
||||
#include "x86.h"
|
||||
#include "irq.h"
|
||||
#include "dma.h"
|
||||
|
||||
static void startothers(void);
|
||||
static void mpmain(void) __attribute__((noreturn));
|
||||
extern char end[]; // first address after kernel loaded from ELF file
|
||||
|
||||
// Set by stage2main.c (boot/stage2main.c, BOOT_VERBOSE_FLAG_ADDR) when the
|
||||
// user picks "Boot Schunix (verbose)" from the F8 boot menu; read once here,
|
||||
// before anything else, so subsystem init can log its own steps.
|
||||
int boot_verbose;
|
||||
|
||||
// Must match BOOT_VERBOSE_FLAG_ADDR in boot/stage2main.c exactly: just past
|
||||
// the VBE info block, not 0x7000 (startothers() below uses that address for
|
||||
// the AP boot trampoline).
|
||||
#define BOOT_VERBOSE_FLAG_ADDR (0x30000 + 0x100)
|
||||
|
||||
// Bootstrap processor starts running C code here.
|
||||
// Allocate a real stack and switch to it, first
|
||||
// doing some setup required for memory allocator to work.
|
||||
int
|
||||
kmain(uint32 fb_addr)
|
||||
{
|
||||
boot_verbose = *(uchar*)P2V(BOOT_VERBOSE_FLAG_ADDR);
|
||||
|
||||
uartearlyinit();
|
||||
if (boot_verbose) cprintf("verbose boot: uartearlyinit done\n");
|
||||
// The DMA arena is carved off the top of physical memory *before* kalloc is
|
||||
// given the rest, because a device needs physically contiguous memory and
|
||||
// kalloc's free list makes no such promise -- two pages from it are adjacent
|
||||
// only by chance. Taking it first is the only way to be sure the region
|
||||
// exists; asking for it later would mean asking kalloc for something it
|
||||
// fundamentally cannot provide. See include/dma.h.
|
||||
// Before anything touches PHYSTOP, which is now a variable read from the
|
||||
// firmware's memory map rather than a constant asserting 224 MB exists.
|
||||
e820_init();
|
||||
kinit1(end, P2V(PHYSTOP - DMA_ARENA_BYTES));
|
||||
if (boot_verbose) cprintf("verbose boot: kinit1 done\n");
|
||||
dma_init(P2V(PHYSTOP - DMA_ARENA_BYTES), P2V(PHYSTOP));
|
||||
if (boot_verbose) cprintf("verbose boot: dma_init done\n");
|
||||
kvmalloc();
|
||||
if (boot_verbose) cprintf("verbose boot: kvmalloc done\n");
|
||||
// ACPI first, the legacy MP table only if it finds nothing. Modern firmware
|
||||
// publishes a complete MADT and an MP table with one processor in it, so
|
||||
// asking the older interface first means never seeing more than one CPU.
|
||||
// See kernel/arch/x86_64/acpi.c.
|
||||
if (!acpi_init())
|
||||
mpinit();
|
||||
if (boot_verbose) cprintf("verbose boot: cpu enumeration done\n");
|
||||
lapicinit();
|
||||
if (boot_verbose) cprintf("verbose boot: lapicinit done\n");
|
||||
tvinit();
|
||||
if (boot_verbose) cprintf("verbose boot: tvinit done\n");
|
||||
seginit();
|
||||
if (boot_verbose) cprintf("verbose boot: seginit done\n");
|
||||
ioapicinit();
|
||||
if (boot_verbose) cprintf("verbose boot: ioapicinit done\n");
|
||||
irq_init(); // must precede any driver init that registers a handler
|
||||
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.
|
||||
inputinit();
|
||||
if (boot_verbose) cprintf("verbose boot: inputinit done\n");
|
||||
consoleinit();
|
||||
if (boot_verbose) cprintf("verbose boot: consoleinit done\n");
|
||||
uartinit();
|
||||
if (boot_verbose) cprintf("verbose boot: uartinit done\n");
|
||||
mouseinit();
|
||||
if (boot_verbose) cprintf("verbose boot: mouseinit done\n");
|
||||
vga_init(fb_addr);
|
||||
if (boot_verbose) cprintf("verbose boot: vga_init done (fb=0x%x)\n", fb_addr);
|
||||
// No splash drawing here on purpose. The loader owns the splash, logo and
|
||||
// all, and vga_init() deliberately does not clear -- so it is still on the
|
||||
// display right through to console_takeover(). Painting it again from here
|
||||
// would land in the last few hundred milliseconds of boot and change nothing.
|
||||
// After vga_init, because the display driver cross-checks the framebuffer
|
||||
// address it finds on the bus against the one VBE reported -- and it can
|
||||
// only do that once vga.c has read the VBE block.
|
||||
// The devices that are simply known to be on a PC rather than enumerated by
|
||||
// any bus. Registered before pci_init() so that /proc/devices lists them
|
||||
// first, which puts the machine's fixed furniture above whatever happened to
|
||||
// be plugged into it.
|
||||
platform_register_devices();
|
||||
pci_init();
|
||||
if (boot_verbose) cprintf("verbose boot: pci_init done\n");
|
||||
pinit();
|
||||
if (boot_verbose) cprintf("verbose boot: pinit done\n");
|
||||
binit();
|
||||
if (boot_verbose) cprintf("verbose boot: binit done\n");
|
||||
fileinit();
|
||||
if (boot_verbose) cprintf("verbose boot: fileinit done\n");
|
||||
sockinit();
|
||||
if (boot_verbose) cprintf("verbose boot: sockinit done\n");
|
||||
// After the timer is running, so boot_epoch can subtract the uptime so far,
|
||||
// and before userspace, so the very first timestamp any program takes is
|
||||
// already real. Reading the RTC is slow, which is exactly why it happens once
|
||||
// here rather than on every gettimeofday().
|
||||
time_init();
|
||||
ideinit();
|
||||
if (boot_verbose) cprintf("verbose boot: ideinit done\n");
|
||||
ramdiskinit();
|
||||
if (boot_verbose) cprintf("verbose boot: ramdiskinit done\n");
|
||||
// Start the application processors.
|
||||
//
|
||||
// This was broken from the beginning of the project and is fixed as of
|
||||
// 2026-08-08. Five distinct bugs, each hidden behind the one before it, and
|
||||
// all of them invisible under QEMU because it publishes only one processor in
|
||||
// the legacy MP table -- see kernel/arch/x86_64/acpi.c. VMware published the
|
||||
// real count, which is why the triple fault only ever appeared there.
|
||||
//
|
||||
// 1. entry64high had no AP branch at all; the check was a comment saying it
|
||||
// had never been written. Every AP fell into the boot processor's path,
|
||||
// took the BSP's stack, and ran kmain() a second time.
|
||||
// 2. seginit() called kalloc() for its per-CPU page. kalloc locks, locking
|
||||
// reads `cpu`, and `cpu` is what seginit() was about to set up.
|
||||
// 3. kernel_fs_base was one global for all CPUs, and trapasm.S read it on
|
||||
// every entry from user mode, so both CPUs shared one per-CPU block.
|
||||
// 4. `cpu` and `proc` were __thread. That means per *thread*, so the compiler
|
||||
// cached the thread pointer across calls -- but %fs here is per *CPU*, and
|
||||
// a kernel thread changes CPU at every sched(). See include/proc.h.
|
||||
// 5. The new per-CPU slots collided with syscall_entry's scratch word at
|
||||
// %fs:(8), which is the same memory as %gs:8.
|
||||
//
|
||||
int smp_start_aps = 1;
|
||||
if (smp_start_aps) {
|
||||
startothers();
|
||||
if (boot_verbose) cprintf("verbose boot: startothers done\n");
|
||||
} else if (ncpu > 1) {
|
||||
cprintf("smp: %d cpus found, running on 1 (AP startup is broken)\n", ncpu);
|
||||
}
|
||||
kinit2();
|
||||
if (boot_verbose) cprintf("verbose boot: kinit2 done\n");
|
||||
sinit();
|
||||
if (boot_verbose) cprintf("verbose boot: sinit done\n");
|
||||
mpmain();
|
||||
}
|
||||
|
||||
// Other CPUs jump here from entryother.S.
|
||||
void
|
||||
mpenter(void)
|
||||
{
|
||||
switchkvm();
|
||||
seginit();
|
||||
lapicinit();
|
||||
mpmain();
|
||||
}
|
||||
|
||||
// Enable SSE/FXSAVE so user binaries can use XMM registers.
|
||||
static void
|
||||
enable_sse(void)
|
||||
{
|
||||
uint64 cr0, cr4;
|
||||
asm volatile("mov %%cr0, %0" : "=r"(cr0));
|
||||
cr0 &= ~(1UL << 2); // clear CR0.EM (no FP emulation)
|
||||
cr0 |= (1UL << 1); // set CR0.MP (monitor coprocessor)
|
||||
asm volatile("mov %0, %%cr0" :: "r"(cr0));
|
||||
asm volatile("mov %%cr4, %0" : "=r"(cr4));
|
||||
cr4 |= (1UL << 9); // CR4.OSFXSR — OS handles FXSAVE/FXRSTOR
|
||||
cr4 |= (1UL << 10); // CR4.OSXMMEXCPT — OS handles SSE exceptions
|
||||
asm volatile("mov %0, %%cr4" :: "r"(cr4));
|
||||
}
|
||||
|
||||
// Common CPU setup code.
|
||||
static void
|
||||
mpmain(void)
|
||||
{
|
||||
cprintf("cpu%d: starting\n", cpunum());
|
||||
enable_sse(); // allow user XMM/SSE instructions
|
||||
idtinit(); // load idt register
|
||||
syscallinit(); // syscall set up
|
||||
xchg(&mycpu()->started, 1); // tell startothers() we're up
|
||||
scheduler(); // start running processes
|
||||
}
|
||||
|
||||
void entry32mp(void);
|
||||
|
||||
// Start the non-boot (AP) processors.
|
||||
static void
|
||||
startothers(void)
|
||||
{
|
||||
extern uchar _binary_entryother_start[], _binary_entryother_size[];
|
||||
uchar *code;
|
||||
struct cpu *c;
|
||||
char *stack;
|
||||
|
||||
// Write entry code to unused memory at 0x7000.
|
||||
// The linker has placed the image of entryother.S in
|
||||
// _binary_entryother_start.
|
||||
code = P2V(0x7000);
|
||||
memmove(code, _binary_entryother_start,
|
||||
(addr_t)_binary_entryother_size);
|
||||
|
||||
for(c = cpus; c < cpus+ncpu; c++){
|
||||
if(c == cpus+cpunum()) // We've started already.
|
||||
continue;
|
||||
|
||||
// Tell entryother.S what stack to use, where to enter, and what
|
||||
// pgdir to use. We cannot use kpgdir yet, because the AP processor
|
||||
// is running in low memory, so we use entrypgdir for the APs too.
|
||||
// The AP's per-CPU page, allocated here rather than by the AP itself.
|
||||
// seginit() explains why: an AP cannot call kalloc() before %fs is set up,
|
||||
// because kalloc locks and locking reads the per-CPU pointer.
|
||||
if (c->local == 0) {
|
||||
c->local = kalloc();
|
||||
memset(c->local, 0, PGSIZE);
|
||||
}
|
||||
|
||||
stack = kalloc();
|
||||
*(uint32*)(code-4) = 0x8000; // enough stack to get us to entry64mp
|
||||
*(uint32*)(code-8) = v2p(entry32mp);
|
||||
*(uint64*)(code-16) = (uint64) (stack + KSTACKSIZE);
|
||||
|
||||
lapicstartap(c->apicid, V2P(code));
|
||||
|
||||
// wait for cpu to finish mpmain()
|
||||
while(c->started == 0)
|
||||
;
|
||||
|
||||
// Counted here, on the boot processor, after the AP has actually reported
|
||||
// in -- not by the AP itself. This is serialised by the loop, so it needs
|
||||
// no atomic, and it cannot overcount a processor that started and died.
|
||||
ncpu_online++;
|
||||
}
|
||||
}
|
||||
543
kernel/core/panic.c
Normal file
543
kernel/core/panic.c
Normal file
|
|
@ -0,0 +1,543 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
// PSOGM — the Schunix crash screen.
|
||||
//
|
||||
// The name is a nod to the Amiga's "Guru Meditation", but nothing else about
|
||||
// this is a nod to 1985. Three things it is built around:
|
||||
//
|
||||
// *It does not clear the screen.* Whatever was being displayed stays there,
|
||||
// dimmed and pushed back, with the report floating over it. A crash screen that
|
||||
// wipes the display tells you the machine is gone; one that leaves your work
|
||||
// visible underneath tells you it stopped, which is both truer and much less
|
||||
// alarming.
|
||||
//
|
||||
// *It is not frightening.* No red, no capitals, no "FATAL". A calm sentence, a
|
||||
// face that is obviously a cartoon, and then the facts. Whoever is reading this
|
||||
// is already having a bad time.
|
||||
//
|
||||
// *It says considerably more than it needs to.* The screen is the only artefact
|
||||
// of a kernel panic — there is no core dump and no log to go back to — so
|
||||
// everything cheap to collect is collected: which CPU, which process, how far
|
||||
// into the boot, where the kernel is loaded, and a call chain. Anything left
|
||||
// out here is gone.
|
||||
//
|
||||
// Everything is also written to the serial console, in the same order. That is
|
||||
// what tools/bootcheck.ps1 reads, and it is what you get when the crash happens
|
||||
// before there is a framebuffer at all.
|
||||
|
||||
#include "types.h"
|
||||
#include "defs.h"
|
||||
#include "param.h"
|
||||
#include "memlayout.h"
|
||||
#include "mmu.h"
|
||||
#include "x86.h"
|
||||
#include "proc.h"
|
||||
#include "spinlock.h"
|
||||
// Purple, because the name says so, and white on it because nothing else is
|
||||
// legible at every brightness a monitor might be set to. The palette lives in
|
||||
// theme.h with everything else; this file used to keep its own copy under the
|
||||
// same names but with different values, which meant theme.h described a crash
|
||||
// screen nobody had ever seen.
|
||||
#include "theme.h"
|
||||
#include "qr.h"
|
||||
|
||||
extern uint ticks;
|
||||
extern int panicked;
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Small formatting helpers */
|
||||
/* */
|
||||
/* cprintf goes to the console, which during a panic is also the screen */
|
||||
/* being painted over -- so the panel builds its strings itself and */
|
||||
/* places them with vga_text_at. */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
static char *
|
||||
put_str(char *p, char *end, const char *s)
|
||||
{
|
||||
while (*s && p < end - 1)
|
||||
*p++ = *s++;
|
||||
*p = '\0';
|
||||
return p;
|
||||
}
|
||||
|
||||
static char *
|
||||
put_dec(char *p, char *end, uint64 v)
|
||||
{
|
||||
char tmp[24];
|
||||
int i = 0;
|
||||
if (v == 0)
|
||||
tmp[i++] = '0';
|
||||
while (v && i < (int)sizeof(tmp)) { tmp[i++] = '0' + (v % 10); v /= 10; }
|
||||
while (i-- && p < end - 1)
|
||||
*p++ = tmp[i];
|
||||
*p = '\0';
|
||||
return p;
|
||||
}
|
||||
|
||||
static char *
|
||||
put_hex(char *p, char *end, uint64 v, int digits)
|
||||
{
|
||||
const char *d = "0123456789abcdef";
|
||||
if (p < end - 2) { *p++ = '0'; *p++ = 'x'; }
|
||||
for (int i = digits - 1; i >= 0 && p < end - 1; i--)
|
||||
*p++ = d[(v >> (i * 4)) & 0xF];
|
||||
*p = '\0';
|
||||
return p;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* The face */
|
||||
/* */
|
||||
/* ":(" — two dots and a parenthesis, drawn large. It is a sad face */
|
||||
/* only if you tilt your head, which is exactly why it works: it is */
|
||||
/* legible as a mood without being a picture of anything, and it does */
|
||||
/* not pretend the machine has feelings about what just happened. */
|
||||
/* */
|
||||
/* Drawn from primitives rather than stored as a bitmap, so it scales */
|
||||
/* with the display and costs no space in a kernel image that stage2 */
|
||||
/* loads one sector at a time by PIO. */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
static int
|
||||
isqrt(int v)
|
||||
{
|
||||
if (v <= 0)
|
||||
return 0;
|
||||
|
||||
// Newton's method, iterated only while the estimate is still *decreasing*.
|
||||
//
|
||||
// The obvious "stop when it stops changing" version does not terminate: on a
|
||||
// non-square it settles into a two-cycle rather than a fixed point, and
|
||||
// isqrt(3) alternates between 1 and 2 forever. In a panic handler that is a
|
||||
// hang with the screen half drawn and no way to find out why.
|
||||
int x = v;
|
||||
int y = (x + 1) / 2;
|
||||
while (y < x) {
|
||||
x = y;
|
||||
y = (x + v / x) / 2;
|
||||
}
|
||||
return x;
|
||||
}
|
||||
|
||||
// The "(" of ":(" — the left half of a squashed circle, so the tips point right
|
||||
// and the middle bulges left, the way the glyph does.
|
||||
static void
|
||||
draw_paren(int cx, int cy, int h, int thick, uint32 color)
|
||||
{
|
||||
int r = h / 2;
|
||||
for (int dy = -r; dy <= r; dy++) {
|
||||
int dx = isqrt(r * r - dy * dy);
|
||||
// Narrow it: a true half-circle reads as a bracket, not a parenthesis.
|
||||
int x = cx - (dx * 45) / 100;
|
||||
vga_fill_circle(x, cy + dy, thick / 2, color);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
draw_sad_face(int x, int y, int h, uint32 color)
|
||||
{
|
||||
int dot = h / 12;
|
||||
int gap = h / 4;
|
||||
|
||||
// The colon.
|
||||
vga_fill_circle(x + dot, y + h / 2 - gap, dot, color);
|
||||
vga_fill_circle(x + dot, y + h / 2 + gap, dot, color);
|
||||
|
||||
// The parenthesis, a little to the right of it.
|
||||
draw_paren(x + dot * 2 + h / 2, y + h / 2, h, dot * 2, color);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* The report */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
struct field {
|
||||
const char *label;
|
||||
char value[72];
|
||||
};
|
||||
|
||||
#define NFIELD 10
|
||||
|
||||
static struct field fields[NFIELD];
|
||||
static int nfields;
|
||||
|
||||
static struct field *
|
||||
field(const char *label)
|
||||
{
|
||||
if (nfields >= NFIELD)
|
||||
return &fields[NFIELD - 1];
|
||||
struct field *f = &fields[nfields++];
|
||||
f->label = label;
|
||||
f->value[0] = '\0';
|
||||
return f;
|
||||
}
|
||||
|
||||
// Collect everything worth knowing while the machine is still in the state
|
||||
// that produced the fault. Interrupts are already off and no lock is taken:
|
||||
// a panic that deadlocks trying to report itself tells you nothing at all.
|
||||
// A short, shoutable name for the failure, derived from the panic message
|
||||
// rather than declared at every call site.
|
||||
//
|
||||
// Windows stop codes are genuinely useful: they are the thing you search for,
|
||||
// and they stay the same across builds while the prose around them changes.
|
||||
// Taking the message up to its first colon (or its first few words) gets that
|
||||
// for free -- "sysrq: crash requested" becomes SYSRQ, "short filewrite" becomes
|
||||
// SHORT_FILEWRITE -- with no risk of a call site forgetting to supply one.
|
||||
static void
|
||||
stop_code(char *out, int cap, const char *reason)
|
||||
{
|
||||
int n = 0;
|
||||
for (int i = 0; reason[i] && n < cap - 1; i++) {
|
||||
char c = reason[i];
|
||||
if (c == ':')
|
||||
break;
|
||||
if (c == ' ' || c == '\t') {
|
||||
if (n == 0 || out[n-1] == '_')
|
||||
continue;
|
||||
out[n++] = '_';
|
||||
continue;
|
||||
}
|
||||
if (c >= 'a' && c <= 'z') c = c - 'a' + 'A';
|
||||
if ((c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_')
|
||||
out[n++] = c;
|
||||
}
|
||||
while (n > 0 && out[n-1] == '_')
|
||||
n--;
|
||||
if (n == 0)
|
||||
{ out[n++] = 'P'; out[n++] = 'A'; out[n++] = 'N'; out[n++] = 'I'; out[n++] = 'C'; }
|
||||
out[n] = '\0';
|
||||
}
|
||||
|
||||
static void
|
||||
gather(char *reason, addr_t *pcs, int npcs)
|
||||
{
|
||||
char *e;
|
||||
struct field *f;
|
||||
|
||||
nfields = 0;
|
||||
|
||||
f = field("Stop code");
|
||||
stop_code(f->value, sizeof(f->value), reason);
|
||||
|
||||
f = field("Reason");
|
||||
put_str(f->value, f->value + sizeof(f->value), reason);
|
||||
|
||||
f = field("CPU");
|
||||
e = f->value + sizeof(f->value);
|
||||
put_dec(f->value, e, mycpu() ? (uint64)mycpu()->id : 0);
|
||||
if (!mycpu())
|
||||
put_str(f->value, e, "unknown (very early boot)");
|
||||
|
||||
f = field("Process");
|
||||
e = f->value + sizeof(f->value);
|
||||
if (myproc()) {
|
||||
char *p = put_str(f->value, e, myproc()->name);
|
||||
p = put_str(p, e, " pid ");
|
||||
put_dec(p, e, (uint64)myproc()->pid);
|
||||
} else {
|
||||
put_str(f->value, e, "none -- in the kernel, not on anybody's behalf");
|
||||
}
|
||||
|
||||
f = field("Uptime");
|
||||
e = f->value + sizeof(f->value);
|
||||
{
|
||||
char *p = put_dec(f->value, e, (uint64)ticks);
|
||||
p = put_str(p, e, " ticks (about ");
|
||||
p = put_dec(p, e, (uint64)ticks / 100);
|
||||
put_str(p, e, " s)");
|
||||
}
|
||||
|
||||
// Every address below is a link-time address. Printing where the kernel
|
||||
// actually sits is what lets addr2line resolve them afterwards, and it is
|
||||
// the single most useful line here when the trace is all you have.
|
||||
f = field("Kernel base");
|
||||
e = f->value + sizeof(f->value);
|
||||
put_hex(f->value, e, (uint64)KERNBASE, 16);
|
||||
|
||||
f = field("Fault PC");
|
||||
e = f->value + sizeof(f->value);
|
||||
if (npcs > 0 && pcs[0])
|
||||
put_hex(f->value, e, (uint64)pcs[0], 16);
|
||||
else
|
||||
put_str(f->value, e, "unavailable");
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* The QR code */
|
||||
/* */
|
||||
/* A panic report is only worth what can be carried away from it, and */
|
||||
/* at this point there is no network, no log to write to, and often no */
|
||||
/* second machine on the desk. Photographing the screen works but */
|
||||
/* turns a call chain into something that has to be retyped by hand, */
|
||||
/* with sixteen hex digits per frame and no way to tell 8 from B. */
|
||||
/* */
|
||||
/* A phone camera is a working out-of-band channel that needs no */
|
||||
/* driver on our side at all. See kernel/core/qr.c. */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
// Deliberately not a URL. This encodes the report itself, so it goes wherever
|
||||
// the person holding the phone decides -- and reaches nothing on its own.
|
||||
//
|
||||
// Compact because the budget is 134 bytes for the largest symbol this encoder
|
||||
// builds, and a crash with a deep call chain is exactly when the budget is
|
||||
// tightest. Two decisions buy most of the room: frames are stored as offsets
|
||||
// from the kernel base rather than absolute addresses, which halves 16 hex
|
||||
// digits to 8, and the fields are single letters. The base is included, so
|
||||
// nothing is lost -- addr2line wants the link-time address and can be given it.
|
||||
//
|
||||
// Fields are appended in priority order and the loop simply stops when the
|
||||
// buffer is full, so a long reason string costs call-chain depth rather than
|
||||
// producing a report that will not encode.
|
||||
static int
|
||||
build_qr_payload(char *buf, int cap, char *reason, addr_t *pcs, int npcs)
|
||||
{
|
||||
char *p = buf, *end = buf + cap;
|
||||
|
||||
// Magic and format version, so whatever reads this later can tell what it is
|
||||
// looking at and can change the layout without ambiguity.
|
||||
p = put_str(p, end, "SXPSOGM1\n");
|
||||
|
||||
// The reason, not the stop code: the stop code is derived from the reason by
|
||||
// stop_code() and carries strictly less, so encoding both would spend bytes
|
||||
// on something the reader can recompute.
|
||||
//
|
||||
// Held back from the end of the buffer so that a long message costs some of
|
||||
// the call chain but never all of it. A report with a sentence and no
|
||||
// addresses is much harder to act on than one with addresses and a truncated
|
||||
// sentence.
|
||||
{
|
||||
char *reason_end = end - 34; // three frames and their separators
|
||||
if (reason_end > p)
|
||||
p = put_str(p, reason_end, reason);
|
||||
}
|
||||
|
||||
p = put_str(p, end, "\nc");
|
||||
p = put_dec(p, end, mycpu() ? (uint64)mycpu()->id : 0);
|
||||
p = put_str(p, end, " p");
|
||||
p = put_dec(p, end, myproc() ? (uint64)myproc()->pid : 0);
|
||||
p = put_str(p, end, " t");
|
||||
p = put_dec(p, end, (uint64)ticks);
|
||||
if (myproc()) {
|
||||
p = put_str(p, end, " ");
|
||||
p = put_str(p, end, myproc()->name);
|
||||
}
|
||||
|
||||
p = put_str(p, end, "\nb");
|
||||
p = put_hex(p, end, (uint64)KERNBASE, 16);
|
||||
|
||||
p = put_str(p, end, "\n");
|
||||
for (int i = 0; i < npcs && pcs[i]; i++) {
|
||||
// Nine bytes: eight hex digits and the separator. Stopping here rather than
|
||||
// letting put_hex truncate matters -- half an address in a crash report is
|
||||
// worse than one fewer address, because it looks like a real one.
|
||||
if (end - p < 10)
|
||||
break;
|
||||
if (i)
|
||||
p = put_str(p, end, " ");
|
||||
// put_hex prefixes "0x", which is 14 wasted bytes across seven frames.
|
||||
{
|
||||
const char *d = "0123456789abcdef";
|
||||
uint64 off = (uint64)pcs[i] - (uint64)KERNBASE;
|
||||
for (int k = 7; k >= 0; k--)
|
||||
*p++ = d[(off >> (k * 4)) & 0xF];
|
||||
*p = '\0';
|
||||
}
|
||||
}
|
||||
|
||||
return (int)(p - buf);
|
||||
}
|
||||
|
||||
// One static symbol rather than a local: struct qr is about 3.4 KB and the
|
||||
// panic path runs on whatever stack the fault left behind, which may not have
|
||||
// that much room. It is only ever touched here, after everything else has
|
||||
// stopped.
|
||||
static struct qr panic_qr;
|
||||
|
||||
// Returns the width used, so the text column knows what it may not overlap.
|
||||
static int
|
||||
draw_qr_panel(int sw, int sh, int ch, char *reason, addr_t *pcs, int npcs)
|
||||
{
|
||||
char payload[QR_MAX_BYTES];
|
||||
int n = build_qr_payload(payload, sizeof(payload), reason, pcs, npcs);
|
||||
|
||||
if (n <= 0 || qr_encode(&panic_qr, (unsigned char *)payload, n) != 0)
|
||||
return 0;
|
||||
|
||||
// Four modules of quiet zone, which the standard requires and which decoders
|
||||
// genuinely need -- without it many will not find the symbol at all.
|
||||
const int quiet = 4;
|
||||
int cells = panic_qr.size + 2 * quiet;
|
||||
|
||||
// Scale to about a quarter of the display, then clamp. The lower bound is
|
||||
// what a phone can still resolve from a photograph of a monitor; the upper
|
||||
// bound stops the thing dominating a screen whose actual job is the text.
|
||||
int scale = (sw / 4) / cells;
|
||||
if (scale < 3) scale = 3;
|
||||
if (scale > 8) scale = 8;
|
||||
|
||||
int card = cells * scale;
|
||||
int x0 = sw - card - sw / 24;
|
||||
int y0 = sh / 8;
|
||||
|
||||
vga_fill_rect(x0, y0, card, card, PSOGM_CARD);
|
||||
for (int y = 0; y < panic_qr.size; y++)
|
||||
for (int x = 0; x < panic_qr.size; x++)
|
||||
if (panic_qr.mod[y][x])
|
||||
vga_fill_rect(x0 + (x + quiet) * scale, y0 + (y + quiet) * scale,
|
||||
scale, scale, PSOGM_INK);
|
||||
|
||||
vga_text_at(x0, y0 + card + ch, "Scan for a text copy", PSOGM_DIM);
|
||||
vga_text_at(x0, y0 + card + ch * 2 + 2, "of this report.", PSOGM_DIM);
|
||||
|
||||
return card + sw / 24;
|
||||
}
|
||||
|
||||
// The frame pointers, one per line, as one string per row.
|
||||
static void
|
||||
trace_line(char *buf, int cap, int i, addr_t pc)
|
||||
{
|
||||
char *e = buf + cap;
|
||||
char *p = put_str(buf, e, " #");
|
||||
p = put_dec(p, e, (uint64)i);
|
||||
p = put_str(p, e, i < 10 ? " " : " ");
|
||||
put_hex(p, e, (uint64)pc, 16);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
static void
|
||||
paint(char *reason, addr_t *pcs, int npcs)
|
||||
{
|
||||
int sw, sh;
|
||||
vga_dims(&sw, &sh);
|
||||
|
||||
int ch = vga_char_h();
|
||||
int cw = vga_char_w();
|
||||
|
||||
// The whole display, one colour. An earlier version dimmed the previous
|
||||
// screen and floated a panel over it, which looked good and was wrong: the
|
||||
// thing is called a Purple Screen, and half a screen of somebody's abandoned
|
||||
// work behind the report is a distraction at the worst possible moment.
|
||||
vga_clear(PSOGM_BG);
|
||||
// Glyphs are drawn with their background filled, so the console's cream has
|
||||
// to become the background or every line of text arrives in its own box.
|
||||
vga_set_bg(PSOGM_BG);
|
||||
|
||||
// The symbol goes first, because it decides how much width the text has. It
|
||||
// is drawn before anything can fail: if the encoder refuses the payload the
|
||||
// screen simply has no card and the report is unchanged.
|
||||
draw_qr_panel(sw, sh, ch, reason, pcs, npcs);
|
||||
|
||||
int left = sw / 8;
|
||||
if (left > 110) left = 110;
|
||||
int y = sh / 8;
|
||||
|
||||
int face_h = 96;
|
||||
draw_sad_face(left, y, face_h, PSOGM_FG);
|
||||
y += face_h + ch * 2;
|
||||
|
||||
// Left-aligned, not centred. Centred text reads as a poster; this is a
|
||||
// report, and a report is read down the left edge.
|
||||
vga_text_at(left, y, "Schunix ran into a problem and needs to restart.",
|
||||
PSOGM_FG);
|
||||
y += ch + ch / 2;
|
||||
vga_text_at(left, y, "Nothing on your disks was touched by this.",
|
||||
PSOGM_DIM);
|
||||
|
||||
// A rule in the boot splash's accent, the same width as the one under the
|
||||
// wordmark there, tying this screen to the one the machine started with.
|
||||
//
|
||||
// Equal space above and below on purpose: at any less it sits close enough to
|
||||
// the line above to read as an underline of that sentence rather than as a
|
||||
// divider before the facts.
|
||||
y += ch * 2;
|
||||
vga_fill_rect(left, y, 120, 2, SCH_ACCENT);
|
||||
y += ch;
|
||||
|
||||
// Facts: quiet labels down the left, bright values in a second column.
|
||||
int vx = left + 14 * cw;
|
||||
for (int i = 0; i < nfields; i++) {
|
||||
vga_text_at(left, y, fields[i].label, PSOGM_DIM);
|
||||
vga_text_at(vx, y, fields[i].value, PSOGM_FG);
|
||||
y += ch + 2;
|
||||
}
|
||||
|
||||
y += ch;
|
||||
vga_text_at(left, y, "Call chain", PSOGM_DIM);
|
||||
y += ch + 2;
|
||||
|
||||
for (int i = 0; i < npcs && pcs[i]; i++) {
|
||||
char line[48];
|
||||
trace_line(line, sizeof(line), i, pcs[i]);
|
||||
vga_text_at(vx - 3 * cw, y, line, PSOGM_FG);
|
||||
y += ch;
|
||||
if (y > sh - ch * 5)
|
||||
break;
|
||||
}
|
||||
|
||||
// Two closing lines, bottom left: what to do with the numbers above, and
|
||||
// what this screen is called, so it can be searched for by name. Shortened
|
||||
// from the full command line, which at 62 characters ran under the QR card.
|
||||
vga_text_at(left, sh - ch * 3,
|
||||
"Resolve: addr2line -e kernel <address>", PSOGM_DIM);
|
||||
vga_text_at(left, sh - ch * 3 + ch + 4,
|
||||
"PSOGM -- Purple Screen of Guru Meditation", PSOGM_DIM);
|
||||
}
|
||||
|
||||
// Same report, down the serial line. This is the copy that survives: it is what
|
||||
// bootcheck reads, and the only one that exists when the framebuffer does not.
|
||||
static void
|
||||
report_serial(char *reason, addr_t *pcs, int npcs)
|
||||
{
|
||||
cprintf("\n");
|
||||
cprintf(" :(\n\n");
|
||||
cprintf(" Schunix ran into a problem and needs to restart.\n");
|
||||
cprintf(" PSOGM -- Purple Screen of Guru Meditation\n\n");
|
||||
|
||||
for (int i = 0; i < nfields; i++) {
|
||||
// Pad the label out so the values line up, without a printf width
|
||||
// specifier -- cprintf has no such thing.
|
||||
int n = 0;
|
||||
while (fields[i].label[n]) n++;
|
||||
cprintf(" %s", fields[i].label);
|
||||
for (int pad = n; pad < 14; pad++)
|
||||
cprintf(" ");
|
||||
cprintf("%s\n", fields[i].value);
|
||||
}
|
||||
|
||||
cprintf("\n Call chain\n");
|
||||
for (int i = 0; i < npcs && pcs[i]; i++)
|
||||
cprintf(" #%d %p\n", i, pcs[i]);
|
||||
|
||||
cprintf("\n Resolve with: addr2line -e build/kernel/kernel <address>\n");
|
||||
cprintf(" Reason: %s\n", reason);
|
||||
cprintf(" System halted.\n");
|
||||
}
|
||||
|
||||
__attribute__((noreturn))
|
||||
void
|
||||
panic(char *s)
|
||||
{
|
||||
addr_t pcs[10];
|
||||
|
||||
cli();
|
||||
// Stop every other path from writing to the console. consputc checks this
|
||||
// and spins, which is what keeps another CPU from painting over the report
|
||||
// while it is being drawn.
|
||||
cons_panic_mode();
|
||||
|
||||
for (int i = 0; i < 10; i++)
|
||||
pcs[i] = 0;
|
||||
getcallerpcs(&s, pcs);
|
||||
|
||||
gather(s, pcs, 10);
|
||||
report_serial(s, pcs, 10);
|
||||
|
||||
if (vga_ready())
|
||||
paint(s, pcs, 10);
|
||||
|
||||
panicked = 1;
|
||||
for (;;)
|
||||
hlt();
|
||||
}
|
||||
456
kernel/core/qr.c
Normal file
456
kernel/core/qr.c
Normal file
|
|
@ -0,0 +1,456 @@
|
|||
// QR encoder. See include/qr.h for why it exists and why it is this small.
|
||||
//
|
||||
// Freestanding on purpose: no allocation, no floating point, no libc beyond
|
||||
// memset/memmove. It has to run in panic context, where the heap may be exactly
|
||||
// what is broken. Every buffer here is either on the caller's struct or a
|
||||
// bounded local.
|
||||
//
|
||||
// The structure follows ISO/IEC 18004 in the order the standard describes it,
|
||||
// which is also the only order in which any of it can be checked:
|
||||
//
|
||||
// 1. encode the payload into data codewords
|
||||
// 2. append Reed-Solomon error correction, per block
|
||||
// 3. interleave the blocks
|
||||
// 4. draw the function patterns (finders, timing, alignment, format area)
|
||||
// 5. lay the codeword bits into the remaining modules in a zigzag
|
||||
// 6. try all 8 masks, score each, keep the best
|
||||
//
|
||||
// Verified by scanning the output with a phone, which is the only test that
|
||||
// matters and is also literally the feature.
|
||||
|
||||
#ifdef QR_HOST
|
||||
#include <string.h>
|
||||
#else
|
||||
#include "types.h"
|
||||
#include "defs.h"
|
||||
#endif
|
||||
|
||||
#include "qr.h"
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Version table
|
||||
// ---------------------------------------------------------------------------
|
||||
//
|
||||
// Per version, at error correction level L: total codewords, error correction
|
||||
// codewords per block, and number of blocks. These are from the standard's
|
||||
// tables and cannot be derived, so they are the one part of this file that has
|
||||
// to be right by transcription rather than by reasoning.
|
||||
//
|
||||
// A useful check on them: total - ecc*blocks must be divisible by blocks for
|
||||
// every row below, because levels 1..6 at L happen to have equally sized
|
||||
// blocks. qr_encode() asserts exactly that at runtime, which is what turns a
|
||||
// mistyped number here into a visible failure instead of an unscannable symbol.
|
||||
static const struct {
|
||||
short total; // total codewords in the symbol
|
||||
unsigned char ecc; // EC codewords per block
|
||||
unsigned char blocks; // number of RS blocks
|
||||
} vinfo[QR_MAX_VERSION + 1] = {
|
||||
{ 0, 0, 0 }, // version 0 does not exist; keeps indexing natural
|
||||
{ 26, 7, 1 },
|
||||
{ 44, 10, 1 },
|
||||
{ 70, 15, 1 },
|
||||
{ 100, 20, 1 },
|
||||
{ 134, 26, 1 },
|
||||
{ 172, 18, 2 },
|
||||
};
|
||||
|
||||
int
|
||||
qr_capacity(int version)
|
||||
{
|
||||
if (version < 1 || version > QR_MAX_VERSION)
|
||||
return 0;
|
||||
// Data codewords, less the two bytes of framing: a 4-bit mode indicator, an
|
||||
// 8-bit character count (versions 1-9), and a 4-bit terminator come to
|
||||
// exactly two codewords' worth of overhead.
|
||||
int data = vinfo[version].total - vinfo[version].ecc * vinfo[version].blocks;
|
||||
return data - 2;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// GF(256) and Reed-Solomon
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// Multiply in GF(256) modulo x^8 + x^4 + x^3 + x^2 + 1 (0x11D).
|
||||
//
|
||||
// Done by shift-and-add rather than log/exp tables. Two 256-byte tables plus
|
||||
// their initialisation is more code than this loop, and this needs no init step
|
||||
// at all -- which matters when the caller is a panic handler that cannot assume
|
||||
// any particular earlier function ran.
|
||||
static unsigned char
|
||||
gmul(unsigned char x, unsigned char y)
|
||||
{
|
||||
unsigned char z = 0;
|
||||
for (int i = 7; i >= 0; i--) {
|
||||
z = (unsigned char)((z << 1) ^ ((z >> 7) * 0x1D));
|
||||
z ^= (unsigned char)(((y >> i) & 1) * x);
|
||||
}
|
||||
return z;
|
||||
}
|
||||
|
||||
// Coefficients of the degree-`degree` generator polynomial, highest power
|
||||
// first, with the leading 1 left implicit.
|
||||
static void
|
||||
rs_divisor(int degree, unsigned char *result)
|
||||
{
|
||||
memset(result, 0, degree);
|
||||
result[degree - 1] = 1; // start at the monomial x^0
|
||||
|
||||
// Multiply by (x - r) for r = 1, a, a^2, ... Each pass shifts the polynomial
|
||||
// up one degree and folds in the new root.
|
||||
unsigned char root = 1;
|
||||
for (int i = 0; i < degree; i++) {
|
||||
for (int j = 0; j < degree; j++) {
|
||||
result[j] = gmul(result[j], root);
|
||||
if (j + 1 < degree)
|
||||
result[j] ^= result[j + 1];
|
||||
}
|
||||
root = gmul(root, 0x02);
|
||||
}
|
||||
}
|
||||
|
||||
// The remainder of data[] divided by the generator: the EC codewords.
|
||||
static void
|
||||
rs_remainder(const unsigned char *data, int len,
|
||||
const unsigned char *gen, int degree, unsigned char *result)
|
||||
{
|
||||
memset(result, 0, degree);
|
||||
for (int i = 0; i < len; i++) {
|
||||
unsigned char factor = data[i] ^ result[0];
|
||||
memmove(&result[0], &result[1], degree - 1);
|
||||
result[degree - 1] = 0;
|
||||
for (int j = 0; j < degree; j++)
|
||||
result[j] ^= gmul(gen[j], factor);
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Module access
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static void
|
||||
set_fn(struct qr *q, int x, int y, int dark)
|
||||
{
|
||||
if (x < 0 || y < 0 || x >= q->size || y >= q->size)
|
||||
return;
|
||||
q->mod[y][x] = (unsigned char)(dark != 0);
|
||||
q->fn[y][x] = 1;
|
||||
}
|
||||
|
||||
static int
|
||||
absi(int v)
|
||||
{
|
||||
return v < 0 ? -v : v;
|
||||
}
|
||||
|
||||
static int
|
||||
cheb(int dx, int dy)
|
||||
{
|
||||
int ax = absi(dx), ay = absi(dy);
|
||||
return ax > ay ? ax : ay;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Function patterns
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// A finder plus its separator, as one 9x9 stamp centred on (cx, cy).
|
||||
//
|
||||
// Writing it by Chebyshev distance rather than as a literal bitmap is not
|
||||
// cleverness for its own sake: the concentric rings *are* the definition -- dark
|
||||
// 3x3 core, light ring, dark ring, light separator -- and the edge stamps run
|
||||
// off the symbol, which set_fn() clips.
|
||||
static void
|
||||
draw_finder(struct qr *q, int cx, int cy)
|
||||
{
|
||||
for (int dy = -4; dy <= 4; dy++)
|
||||
for (int dx = -4; dx <= 4; dx++) {
|
||||
int d = cheb(dx, dy);
|
||||
set_fn(q, cx + dx, cy + dy, d != 2 && d != 4);
|
||||
}
|
||||
}
|
||||
|
||||
// The 5x5 alignment pattern: dark 3x3 outline with a light ring inside.
|
||||
static void
|
||||
draw_alignment(struct qr *q, int cx, int cy)
|
||||
{
|
||||
for (int dy = -2; dy <= 2; dy++)
|
||||
for (int dx = -2; dx <= 2; dx++)
|
||||
set_fn(q, cx + dx, cy + dy, cheb(dx, dy) != 1);
|
||||
}
|
||||
|
||||
// The 15-bit format information, twice, plus the always-dark module.
|
||||
//
|
||||
// Called once with the real mask at the end, and once early with mask 0 purely
|
||||
// to *reserve* these modules -- the zigzag below skips anything marked as a
|
||||
// function pattern, so the reservation has to exist before data is placed.
|
||||
static void
|
||||
draw_format(struct qr *q, int mask)
|
||||
{
|
||||
// 0b01 is error correction level L. Five data bits, then a BCH(15,5) check
|
||||
// computed with generator 0x537, then XOR 0x5412 so an all-zero format never
|
||||
// produces an all-zero pattern.
|
||||
int data = (1 << 3) | mask;
|
||||
int rem = data;
|
||||
for (int i = 0; i < 10; i++)
|
||||
rem = (rem << 1) ^ ((rem >> 9) * 0x537);
|
||||
int bits = ((data << 10) | rem) ^ 0x5412;
|
||||
|
||||
int n = q->size;
|
||||
|
||||
// First copy: down the left of the top-right finder, then left along the top
|
||||
// of the bottom-left one. The 6/7 and 7/8 jogs skip the timing pattern.
|
||||
for (int i = 0; i <= 5; i++)
|
||||
set_fn(q, 8, i, (bits >> i) & 1);
|
||||
set_fn(q, 8, 7, (bits >> 6) & 1);
|
||||
set_fn(q, 8, 8, (bits >> 7) & 1);
|
||||
set_fn(q, 7, 8, (bits >> 8) & 1);
|
||||
for (int i = 9; i < 15; i++)
|
||||
set_fn(q, 14 - i, 8, (bits >> i) & 1);
|
||||
|
||||
// Second copy, so the format survives damage to either corner.
|
||||
for (int i = 0; i < 8; i++)
|
||||
set_fn(q, n - 1 - i, 8, (bits >> i) & 1);
|
||||
for (int i = 8; i < 15; i++)
|
||||
set_fn(q, 8, n - 15 + i, (bits >> i) & 1);
|
||||
|
||||
set_fn(q, 8, n - 8, 1); // the dark module, always set, always here
|
||||
}
|
||||
|
||||
static void
|
||||
draw_function_patterns(struct qr *q)
|
||||
{
|
||||
int n = q->size;
|
||||
|
||||
// Timing patterns first: the finders overwrite their ends, which is correct.
|
||||
for (int i = 0; i < n; i++) {
|
||||
set_fn(q, 6, i, i % 2 == 0);
|
||||
set_fn(q, i, 6, i % 2 == 0);
|
||||
}
|
||||
|
||||
draw_finder(q, 3, 3);
|
||||
draw_finder(q, n - 4, 3);
|
||||
draw_finder(q, 3, n - 4);
|
||||
|
||||
// For versions 2-6 the alignment centres are {6, n-7}, and three of those
|
||||
// four combinations collide with a finder. Only the bottom-right survives,
|
||||
// which is why there is no position table here. Version 7 would need one.
|
||||
if (q->size > 21)
|
||||
draw_alignment(q, n - 7, n - 7);
|
||||
|
||||
draw_format(q, 0); // reserve; overwritten once a mask is chosen
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Masking
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static int
|
||||
mask_bit(int mask, int x, int y)
|
||||
{
|
||||
switch (mask) {
|
||||
case 0: return (x + y) % 2 == 0;
|
||||
case 1: return y % 2 == 0;
|
||||
case 2: return x % 3 == 0;
|
||||
case 3: return (x + y) % 3 == 0;
|
||||
case 4: return (x / 3 + y / 2) % 2 == 0;
|
||||
case 5: return x * y % 2 + x * y % 3 == 0;
|
||||
case 6: return (x * y % 2 + x * y % 3) % 2 == 0;
|
||||
case 7: return ((x + y) % 2 + x * y % 3) % 2 == 0;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void
|
||||
apply_mask(struct qr *q, int mask)
|
||||
{
|
||||
for (int y = 0; y < q->size; y++)
|
||||
for (int x = 0; x < q->size; x++)
|
||||
if (!q->fn[y][x])
|
||||
q->mod[y][x] ^= (unsigned char)mask_bit(mask, x, y);
|
||||
}
|
||||
|
||||
// The standard's four penalty rules. Lower is better.
|
||||
//
|
||||
// A decoder will read any legal mask, so this is not correctness -- it is scan
|
||||
// reliability, which is the whole point when the reader is a handheld camera
|
||||
// pointed at a screen. Rule 3 in particular exists to stop the data accidentally
|
||||
// containing something that looks like a finder pattern.
|
||||
static int
|
||||
penalty(const struct qr *q)
|
||||
{
|
||||
int n = q->size, score = 0, dark = 0;
|
||||
|
||||
for (int y = 0; y < n; y++) {
|
||||
for (int x = 0; x < n; x++)
|
||||
dark += q->mod[y][x];
|
||||
|
||||
// Rule 1, both directions: runs of five or more identical modules.
|
||||
for (int dir = 0; dir < 2; dir++) {
|
||||
int run = 1;
|
||||
int prev = dir ? q->mod[0][y] : q->mod[y][0];
|
||||
for (int i = 1; i < n; i++) {
|
||||
int cur = dir ? q->mod[i][y] : q->mod[y][i];
|
||||
if (cur == prev) {
|
||||
run++;
|
||||
} else {
|
||||
if (run >= 5) score += 3 + (run - 5);
|
||||
run = 1;
|
||||
prev = cur;
|
||||
}
|
||||
}
|
||||
if (run >= 5) score += 3 + (run - 5);
|
||||
}
|
||||
}
|
||||
|
||||
// Rule 2: every 2x2 block of one colour.
|
||||
for (int y = 0; y + 1 < n; y++)
|
||||
for (int x = 0; x + 1 < n; x++) {
|
||||
int c = q->mod[y][x];
|
||||
if (c == q->mod[y][x + 1] && c == q->mod[y + 1][x] && c == q->mod[y + 1][x + 1])
|
||||
score += 3;
|
||||
}
|
||||
|
||||
// Rule 3: the finder-lookalike 1011101 with four light modules on either
|
||||
// side, in either direction.
|
||||
for (int dir = 0; dir < 2; dir++)
|
||||
for (int a = 0; a < n; a++) {
|
||||
unsigned bits = 0;
|
||||
for (int b = 0; b < n; b++) {
|
||||
int m = dir ? q->mod[b][a] : q->mod[a][b];
|
||||
bits = ((bits << 1) | (unsigned)m) & 0x7FF;
|
||||
if (b >= 10 && (bits == 0x05D || bits == 0x5D0))
|
||||
score += 40;
|
||||
}
|
||||
}
|
||||
|
||||
// Rule 4: deviation from an even split of dark and light, in 5% steps.
|
||||
int total = n * n;
|
||||
int k = (absi(dark * 20 - total * 10) + total - 1) / total;
|
||||
score += k * 10;
|
||||
|
||||
return score;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Encode
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static void
|
||||
put_bits(unsigned char *buf, int *pos, unsigned val, int n)
|
||||
{
|
||||
for (int i = n - 1; i >= 0; i--) {
|
||||
buf[*pos >> 3] |= (unsigned char)(((val >> i) & 1) << (7 - (*pos & 7)));
|
||||
(*pos)++;
|
||||
}
|
||||
}
|
||||
|
||||
int
|
||||
qr_encode(struct qr *q, const unsigned char *data, int len)
|
||||
{
|
||||
int v;
|
||||
|
||||
if (len < 0)
|
||||
return -1;
|
||||
for (v = 1; v <= QR_MAX_VERSION; v++)
|
||||
if (len <= qr_capacity(v))
|
||||
break;
|
||||
if (v > QR_MAX_VERSION)
|
||||
return -1;
|
||||
|
||||
int total = vinfo[v].total;
|
||||
int ecclen = vinfo[v].ecc;
|
||||
int blocks = vinfo[v].blocks;
|
||||
int datacw = total - ecclen * blocks;
|
||||
|
||||
// The table claims equal-sized blocks for every version this file supports.
|
||||
// If that is ever untrue the interleave below is silently wrong, so check it
|
||||
// rather than trust it.
|
||||
if (datacw % blocks != 0)
|
||||
return -1;
|
||||
int blocklen = datacw / blocks;
|
||||
|
||||
// --- 1. payload -> data codewords ---------------------------------------
|
||||
unsigned char cw[172]; // >= max total codewords
|
||||
memset(cw, 0, sizeof(cw));
|
||||
|
||||
int pos = 0;
|
||||
put_bits(cw, &pos, 0x4, 4); // byte mode
|
||||
put_bits(cw, &pos, (unsigned)len, 8);
|
||||
for (int i = 0; i < len; i++)
|
||||
put_bits(cw, &pos, data[i], 8);
|
||||
|
||||
// Terminator, up to four zero bits, then round up to a whole codeword.
|
||||
int spare = datacw * 8 - pos;
|
||||
put_bits(cw, &pos, 0, spare < 4 ? spare : 4);
|
||||
if (pos & 7)
|
||||
put_bits(cw, &pos, 0, 8 - (pos & 7));
|
||||
|
||||
// Fill the rest with the standard alternating pad bytes. They are specified
|
||||
// values, not arbitrary filler: a decoder uses the character count to know
|
||||
// where the data ends, but the pad pattern keeps the symbol from having large
|
||||
// uniform regions that mask selection would then have to fight.
|
||||
for (int i = pos / 8; i < datacw; i++)
|
||||
cw[i] = (i - pos / 8) % 2 == 0 ? 0xEC : 0x11;
|
||||
|
||||
// --- 2 and 3. error correction, then interleave -------------------------
|
||||
unsigned char gen[30];
|
||||
unsigned char ec[4][30];
|
||||
rs_divisor(ecclen, gen);
|
||||
for (int b = 0; b < blocks; b++)
|
||||
rs_remainder(&cw[b * blocklen], blocklen, gen, ecclen, ec[b]);
|
||||
|
||||
unsigned char out[172];
|
||||
int o = 0;
|
||||
for (int i = 0; i < blocklen; i++)
|
||||
for (int b = 0; b < blocks; b++)
|
||||
out[o++] = cw[b * blocklen + i];
|
||||
for (int i = 0; i < ecclen; i++)
|
||||
for (int b = 0; b < blocks; b++)
|
||||
out[o++] = ec[b][i];
|
||||
|
||||
// --- 4. function patterns ------------------------------------------------
|
||||
memset(q, 0, sizeof(*q));
|
||||
q->size = 17 + 4 * v;
|
||||
draw_function_patterns(q);
|
||||
|
||||
// --- 5. zigzag placement -------------------------------------------------
|
||||
//
|
||||
// Two-module-wide columns walked from the right edge leftwards, alternating
|
||||
// upward and downward. Column 6 is skipped entirely because it is the vertical
|
||||
// timing pattern -- without that step every subsequent column lands one place
|
||||
// out and the symbol is quietly unreadable.
|
||||
int bit = 0;
|
||||
int nbits = o * 8;
|
||||
for (int right = q->size - 1; right >= 1; right -= 2) {
|
||||
if (right == 6)
|
||||
right = 5;
|
||||
for (int vert = 0; vert < q->size; vert++) {
|
||||
for (int j = 0; j < 2; j++) {
|
||||
int x = right - j;
|
||||
int upward = ((right + 1) & 2) == 0;
|
||||
int y = upward ? q->size - 1 - vert : vert;
|
||||
if (!q->fn[y][x] && bit < nbits) {
|
||||
q->mod[y][x] = (unsigned char)((out[bit >> 3] >> (7 - (bit & 7))) & 1);
|
||||
bit++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --- 6. pick a mask ------------------------------------------------------
|
||||
int best = 0, bestscore = 0;
|
||||
for (int m = 0; m < 8; m++) {
|
||||
apply_mask(q, m);
|
||||
draw_format(q, m);
|
||||
int s = penalty(q);
|
||||
if (m == 0 || s < bestscore) {
|
||||
bestscore = s;
|
||||
best = m;
|
||||
}
|
||||
apply_mask(q, m); // XOR again to undo; masking is involutive
|
||||
}
|
||||
apply_mask(q, best);
|
||||
draw_format(q, best);
|
||||
|
||||
return 0;
|
||||
}
|
||||
55
kernel/core/sleeplock.c
Normal file
55
kernel/core/sleeplock.c
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
// Sleeping locks
|
||||
|
||||
#include "types.h"
|
||||
#include "defs.h"
|
||||
#include "param.h"
|
||||
#include "x86.h"
|
||||
#include "memlayout.h"
|
||||
#include "mmu.h"
|
||||
#include "proc.h"
|
||||
#include "spinlock.h"
|
||||
#include "sleeplock.h"
|
||||
|
||||
void
|
||||
initsleeplock(struct sleeplock *lk, char *name)
|
||||
{
|
||||
initlock(&lk->lk, "sleep lock");
|
||||
lk->name = name;
|
||||
lk->locked = 0;
|
||||
lk->pid = 0;
|
||||
}
|
||||
|
||||
void
|
||||
acquiresleep(struct sleeplock *lk)
|
||||
{
|
||||
acquire(&lk->lk);
|
||||
while (lk->locked) {
|
||||
sleep(lk, &lk->lk);
|
||||
}
|
||||
lk->locked = 1;
|
||||
if (myproc()) {
|
||||
lk->pid = myproc()->pid;
|
||||
} else {
|
||||
lk->pid = 0;
|
||||
}
|
||||
release(&lk->lk);
|
||||
}
|
||||
|
||||
void
|
||||
releasesleep(struct sleeplock *lk)
|
||||
{
|
||||
acquire(&lk->lk);
|
||||
lk->locked = 0;
|
||||
lk->pid = 0;
|
||||
wakeup(lk);
|
||||
release(&lk->lk);
|
||||
}
|
||||
|
||||
int
|
||||
holdingsleep(struct sleeplock *lk)
|
||||
{
|
||||
acquire(&lk->lk);
|
||||
int r = lk->locked;
|
||||
release(&lk->lk);
|
||||
return r;
|
||||
}
|
||||
115
kernel/core/spinlock.c
Normal file
115
kernel/core/spinlock.c
Normal file
|
|
@ -0,0 +1,115 @@
|
|||
#include "types.h"
|
||||
#include "defs.h"
|
||||
#include "param.h"
|
||||
#include "x86.h"
|
||||
#include "memlayout.h"
|
||||
#include "mmu.h"
|
||||
#include "proc.h"
|
||||
#include "spinlock.h"
|
||||
|
||||
void
|
||||
initlock(struct spinlock *lk, char *name)
|
||||
{
|
||||
lk->name = name;
|
||||
lk->locked = 0;
|
||||
lk->cpu = 0;
|
||||
}
|
||||
|
||||
void
|
||||
acquire(struct spinlock *lk)
|
||||
{
|
||||
pushcli();
|
||||
if(holding(lk))
|
||||
panic("acquire");
|
||||
|
||||
{
|
||||
uint64 spins = 0;
|
||||
while(xchg(&lk->locked, 1) != 0){
|
||||
if(++spins == 200000000ULL){
|
||||
cprintf("acquire: stuck on \"%s\" held by cpu=%p pcs0=%p pcs1=%p pcs2=%p, acquirer cpu=%p\n",
|
||||
lk->name, lk->cpu, lk->pcs[0], lk->pcs[1], lk->pcs[2], mycpu());
|
||||
panic("acquire: deadlock");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
__sync_synchronize();
|
||||
|
||||
lk->cpu = mycpu();
|
||||
getcallerpcs(&lk, lk->pcs);
|
||||
}
|
||||
|
||||
void
|
||||
release(struct spinlock *lk)
|
||||
{
|
||||
if(!holding(lk))
|
||||
panic("release");
|
||||
|
||||
lk->pcs[0] = 0;
|
||||
lk->cpu = 0;
|
||||
|
||||
__sync_synchronize();
|
||||
|
||||
asm volatile("movq $0, %0" : "+m" (lk->locked) : );
|
||||
|
||||
popcli();
|
||||
}
|
||||
|
||||
void
|
||||
getcallerpcs(void *v, addr_t pcs[])
|
||||
{
|
||||
addr_t *rbp;
|
||||
|
||||
asm volatile("mov %%rbp, %0" : "=r" (rbp));
|
||||
getstackpcs(rbp, pcs);
|
||||
}
|
||||
|
||||
void
|
||||
getstackpcs(addr_t *rbp, addr_t pcs[])
|
||||
{
|
||||
int i;
|
||||
|
||||
for(i = 0; i < 10; i++){
|
||||
if(rbp == 0 || rbp < (addr_t*)KERNBASE || rbp == (addr_t*)0xffffffff)
|
||||
break;
|
||||
pcs[i] = rbp[1];
|
||||
rbp = (addr_t*)rbp[0];
|
||||
}
|
||||
for(; i < 10; i++)
|
||||
pcs[i] = 0;
|
||||
}
|
||||
|
||||
int
|
||||
holding(struct spinlock *lock)
|
||||
{
|
||||
return lock->locked && lock->cpu == mycpu();
|
||||
}
|
||||
|
||||
void
|
||||
pushcli(void)
|
||||
{
|
||||
int eflags;
|
||||
|
||||
eflags = readeflags();
|
||||
cli();
|
||||
if(mycpu() == 0){
|
||||
extern uint64 kernel_fs_base;
|
||||
cprintf("pushcli: cpu==NULL cs=0x%p kernel_fs_base=0x%p actual_fs_base=0x%p\n",
|
||||
readcs(), kernel_fs_base, rdmsr(0xC0000100));
|
||||
panic("pushcli: cpu is NULL");
|
||||
}
|
||||
if(mycpu()->ncli == 0)
|
||||
mycpu()->intena = eflags & FL_IF;
|
||||
mycpu()->ncli += 1;
|
||||
}
|
||||
|
||||
void
|
||||
popcli(void)
|
||||
{
|
||||
if(readeflags()&FL_IF)
|
||||
panic("popcli - interruptible");
|
||||
if(--mycpu()->ncli < 0)
|
||||
panic("popcli");
|
||||
if(mycpu()->ncli == 0 && mycpu()->intena)
|
||||
sti();
|
||||
}
|
||||
113
kernel/core/string.c
Normal file
113
kernel/core/string.c
Normal file
|
|
@ -0,0 +1,113 @@
|
|||
#include "types.h"
|
||||
#include "x86.h"
|
||||
|
||||
void*
|
||||
memset(void *dst, int c, uint64 n)
|
||||
{
|
||||
uint64 c64;
|
||||
|
||||
if ((addr_t)dst % 8 == 0 && n % 8 == 0) {
|
||||
c &= 0xFF;
|
||||
c64 = (uint64)c;
|
||||
c64 = (c64 << 56) | (c64 << 48) | (c64 << 40) | (c64 << 32) |
|
||||
(c64 << 24) | (c64 << 16) | (c64 << 8) | c64;
|
||||
stosq(dst, c64, n / 8);
|
||||
} else {
|
||||
stosb(dst, c, n);
|
||||
}
|
||||
return dst;
|
||||
}
|
||||
|
||||
int
|
||||
memcmp(const void *v1, const void *v2, uint n)
|
||||
{
|
||||
const uchar *s1, *s2;
|
||||
|
||||
s1 = v1;
|
||||
s2 = v2;
|
||||
while(n-- > 0){
|
||||
if(*s1 != *s2)
|
||||
return *s1 - *s2;
|
||||
s1++, s2++;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void*
|
||||
memmove(void *dst, const void *src, uint n)
|
||||
{
|
||||
const char *s;
|
||||
char *d;
|
||||
|
||||
s = src;
|
||||
d = dst;
|
||||
if(s < d && s + n > d){
|
||||
s += n;
|
||||
d += n;
|
||||
while(n-- > 0)
|
||||
*--d = *--s;
|
||||
} else
|
||||
while(n-- > 0)
|
||||
*d++ = *s++;
|
||||
|
||||
return dst;
|
||||
}
|
||||
|
||||
void*
|
||||
memcpy(void *dst, const void *src, uint n)
|
||||
{
|
||||
return memmove(dst, src, n);
|
||||
}
|
||||
|
||||
int
|
||||
strncmp(const char *p, const char *q, uint n)
|
||||
{
|
||||
while(n > 0 && *p && *p == *q)
|
||||
n--, p++, q++;
|
||||
if(n == 0)
|
||||
return 0;
|
||||
return (uchar)*p - (uchar)*q;
|
||||
}
|
||||
|
||||
char*
|
||||
strncpy(char *s, const char *t, int n)
|
||||
{
|
||||
char *os = s;
|
||||
while(n-- > 0 && (*s++ = *t++) != 0)
|
||||
;
|
||||
while(n-- > 0)
|
||||
*s++ = 0;
|
||||
return os;
|
||||
}
|
||||
|
||||
char*
|
||||
safestrcpy(char *s, const char *t, int n)
|
||||
{
|
||||
char *os = s;
|
||||
if(n <= 0)
|
||||
return os;
|
||||
while(--n > 0 && (*s++ = *t++) != 0)
|
||||
;
|
||||
*s = 0;
|
||||
return os;
|
||||
}
|
||||
|
||||
int
|
||||
strlen(const char *s)
|
||||
{
|
||||
int n;
|
||||
|
||||
for(n = 0; s[n]; n++)
|
||||
;
|
||||
return n;
|
||||
}
|
||||
|
||||
int
|
||||
strnlen(const char *s, int max)
|
||||
{
|
||||
int n;
|
||||
for(n = 0; n < max && s[n]; n++)
|
||||
;
|
||||
return n;
|
||||
}
|
||||
116
kernel/core/time.c
Normal file
116
kernel/core/time.c
Normal file
|
|
@ -0,0 +1,116 @@
|
|||
// Wall-clock time.
|
||||
//
|
||||
// The kernel has always known how long it had been running -- `ticks`, bumped
|
||||
// 100 times a second by the timer interrupt -- and has never known what time it
|
||||
// was. sys_gettimeofday() returned `ticks / 100` as though it were seconds
|
||||
// since the epoch, so every Schunix machine believed it was the 1st of January
|
||||
// 1970 and that the date advanced at the rate of its own uptime. `date` printed
|
||||
// a number that barely moved, which is what it looks like when a clock is
|
||||
// counting from the wrong place rather than not counting at all.
|
||||
//
|
||||
// cmostime() in kernel/arch/x86_64/lapic.c has been able to read the hardware
|
||||
// clock the whole time. Nothing called it.
|
||||
//
|
||||
// The arrangement here is the usual one: read the RTC once at boot, convert it
|
||||
// to a Unix timestamp, and add uptime to it thereafter. The RTC is slow to read
|
||||
// (it is polled over two I/O ports and has to be sampled twice to catch it
|
||||
// mid-update) so reading it on every gettimeofday would be absurd; and `ticks`
|
||||
// is a perfectly good interval clock once it has something to count from.
|
||||
|
||||
#include "types.h"
|
||||
#include "defs.h"
|
||||
#include "date.h"
|
||||
#include "param.h"
|
||||
#include "spinlock.h"
|
||||
|
||||
extern uint ticks;
|
||||
|
||||
// Seconds between the Unix epoch and the moment this machine booted. Zero until
|
||||
// time_init() runs, which makes the behaviour before that point exactly what it
|
||||
// used to be -- uptime presented as absolute time -- rather than something new
|
||||
// and differently wrong.
|
||||
uint64 boot_epoch;
|
||||
|
||||
// Days from 1970-01-01 to the given civil date, proleptic Gregorian.
|
||||
//
|
||||
// Howard Hinnant's days_from_civil. It is worth using rather than a loop over
|
||||
// years because it is branchless, exact for any year, and has no table to get
|
||||
// wrong; the alternative -- accumulating 365 or 366 days per year since 1970 --
|
||||
// gets leap centuries wrong unless you remember that 1900 was not a leap year
|
||||
// and 2000 was, which is precisely the sort of thing that is discovered on a
|
||||
// February morning years later.
|
||||
//
|
||||
// The trick is shifting the year so that March is month 1, which puts the leap
|
||||
// day at the *end* of the year and makes the leap rule depend only on the year
|
||||
// number rather than on where in the year you are.
|
||||
static int64
|
||||
days_from_civil(int64 y, unsigned m, unsigned d)
|
||||
{
|
||||
y -= m <= 2;
|
||||
const int64 era = (y >= 0 ? y : y - 399) / 400;
|
||||
const unsigned yoe = (unsigned)(y - era * 400); // [0, 399]
|
||||
const unsigned doy = (153 * (m + (m > 2 ? -3 : 9)) + 2) / 5 + d - 1;
|
||||
const unsigned doe = yoe * 365 + yoe / 4 - yoe / 100 + doy; // [0, 146096]
|
||||
return era * 146097 + (int64)doe - 719468;
|
||||
}
|
||||
|
||||
// Read the hardware clock and work out when we booted.
|
||||
//
|
||||
// The RTC is read in UTC. That is not universally true on a PC -- a machine
|
||||
// that has had Windows installed on it usually keeps the RTC in local time --
|
||||
// but there is nowhere to store a timezone yet and no way to ask, and UTC is
|
||||
// the honest default for a system with no configuration. When there is a
|
||||
// settings store, this is where the offset goes.
|
||||
void
|
||||
time_init(void)
|
||||
{
|
||||
struct rtcdate r;
|
||||
|
||||
cmostime(&r);
|
||||
|
||||
// cmostime() returns a two-digit year on hardware that only stores one.
|
||||
// 2000-2099 is the right guess now and stays right for a lifetime; a machine
|
||||
// reporting 70 means the battery is flat, and the epoch is as good an answer
|
||||
// as any for that.
|
||||
int64 year = r.year;
|
||||
if (year < 100)
|
||||
year += 2000;
|
||||
|
||||
// A wildly implausible reading means the CMOS is not telling us anything.
|
||||
// Leaving boot_epoch at zero keeps the old behaviour, which is wrong but
|
||||
// predictable, rather than putting the machine somewhere in the Bronze Age.
|
||||
if (r.month < 1 || r.month > 12 || r.day < 1 || r.day > 31 ||
|
||||
r.hour > 23 || r.minute > 59 || r.second > 60 || year < 1970) {
|
||||
cprintf("time: CMOS clock is not plausible, staying at the epoch\n");
|
||||
return;
|
||||
}
|
||||
|
||||
int64 days = days_from_civil(year, r.month, r.day);
|
||||
uint64 now = (uint64)(days * 86400) + r.hour * 3600 + r.minute * 60 + r.second;
|
||||
|
||||
// Subtract the uptime so far, so boot_epoch is the moment of boot rather than
|
||||
// the moment this function ran. It is only a fraction of a second, but it
|
||||
// keeps boot_epoch meaning exactly what its name says.
|
||||
uint64 up = ticks / 100;
|
||||
boot_epoch = now > up ? now - up : now;
|
||||
|
||||
cprintf("time: %d-%d-%d %d:%d:%d UTC from the hardware clock\n",
|
||||
(int)year, r.month, r.day, r.hour, r.minute, r.second);
|
||||
}
|
||||
|
||||
// Seconds and microseconds since the epoch. One place, so gettimeofday,
|
||||
// clock_gettime and anything added later cannot disagree about what time it is.
|
||||
void
|
||||
wallclock(uint64 *sec, uint64 *usec)
|
||||
{
|
||||
uint xticks;
|
||||
|
||||
acquire(&tickslock);
|
||||
xticks = ticks;
|
||||
release(&tickslock);
|
||||
|
||||
if (sec)
|
||||
*sec = boot_epoch + xticks / 100;
|
||||
if (usec)
|
||||
*usec = (uint64)(xticks % 100) * 10000;
|
||||
}
|
||||
79
kernel/core/uaccess.c
Normal file
79
kernel/core/uaccess.c
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
// User-memory access helpers — see include/uaccess.h for the rules.
|
||||
//
|
||||
// Everything here validates against the *current* process's page table
|
||||
// (myproc()->pgdir), which is also the live CR3 while a syscall runs, so a
|
||||
// range that passes access_ok() can be dereferenced directly by kernel
|
||||
// code for the remainder of the syscall (single-threaded processes can't
|
||||
// unmap it out from under us mid-call).
|
||||
|
||||
#include "types.h"
|
||||
#include "defs.h"
|
||||
#include "param.h"
|
||||
#include "memlayout.h"
|
||||
#include "mmu.h"
|
||||
#include "proc.h"
|
||||
#include "errno.h"
|
||||
#include "uaccess.h"
|
||||
|
||||
int
|
||||
access_ok(addr_t uaddr, uint64 size)
|
||||
{
|
||||
addr_t a, end;
|
||||
|
||||
if(size == 0)
|
||||
return 1;
|
||||
end = uaddr + size;
|
||||
if(end < uaddr || end > KERNBASE)
|
||||
return 0;
|
||||
for(a = PGROUNDDOWN(uaddr); a < end; a += PGSIZE)
|
||||
if(uva2ka(myproc()->pgdir, (char*)a) == 0)
|
||||
return 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
void*
|
||||
user_ptr(addr_t uaddr, uint64 size)
|
||||
{
|
||||
if(uaddr == 0 || !access_ok(uaddr, size))
|
||||
return 0;
|
||||
return (void*)uaddr;
|
||||
}
|
||||
|
||||
int
|
||||
copy_from_user(void *dst, addr_t uaddr, uint64 n)
|
||||
{
|
||||
if(uaddr + n < uaddr || uaddr + n > KERNBASE)
|
||||
return -EFAULT;
|
||||
if(copyin(myproc()->pgdir, uaddr, dst, n) < 0)
|
||||
return -EFAULT;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
copy_to_user(addr_t uaddr, const void *src, uint64 n)
|
||||
{
|
||||
if(uaddr + n < uaddr || uaddr + n > KERNBASE)
|
||||
return -EFAULT;
|
||||
if(copyout(myproc()->pgdir, uaddr, (void*)src, n) < 0)
|
||||
return -EFAULT;
|
||||
return 0;
|
||||
}
|
||||
|
||||
long
|
||||
strlen_user(addr_t uaddr)
|
||||
{
|
||||
addr_t a = uaddr;
|
||||
|
||||
while(a - uaddr < USER_STR_MAX){
|
||||
// Scan through the per-page kernel alias so an unmapped page is a
|
||||
// clean failure instead of a fault.
|
||||
char *ka = uva2ka(myproc()->pgdir, (char*)a);
|
||||
if(ka == 0)
|
||||
return -EFAULT;
|
||||
addr_t pgend = PGROUNDDOWN(a) + PGSIZE;
|
||||
for(; a < pgend; a++, ka++)
|
||||
if(*ka == 0)
|
||||
return a - uaddr;
|
||||
}
|
||||
return -EFAULT;
|
||||
}
|
||||
160
kernel/dev/bio.c
Normal file
160
kernel/dev/bio.c
Normal file
|
|
@ -0,0 +1,160 @@
|
|||
// Buffer cache.
|
||||
//
|
||||
// The buffer cache is a linked list of buf structures holding
|
||||
// cached copies of disk block contents. Caching disk blocks
|
||||
// in memory reduces the number of disk reads and also provides
|
||||
// a synchronization point for disk blocks used by multiple processes.
|
||||
//
|
||||
// Interface:
|
||||
// * To get a buffer for a particular disk block, call bread.
|
||||
// * After changing buffer data, call bwrite to write it to disk.
|
||||
// * When done with the buffer, call brelse.
|
||||
// * Do not use the buffer after calling brelse.
|
||||
// * Only one process at a time can use a buffer,
|
||||
// so do not keep them longer than necessary.
|
||||
//
|
||||
// The implementation uses two state flags internally:
|
||||
// * B_VALID: the buffer data has been read from the disk.
|
||||
// * B_DIRTY: the buffer data has been modified
|
||||
// and needs to be written to disk.
|
||||
|
||||
#include "types.h"
|
||||
#include "defs.h"
|
||||
#include "param.h"
|
||||
#include "spinlock.h"
|
||||
#include "sleeplock.h"
|
||||
#include "fs.h"
|
||||
#include "buf.h"
|
||||
|
||||
struct {
|
||||
struct spinlock lock;
|
||||
struct buf buf[NBUF];
|
||||
|
||||
// Linked list of all buffers, through prev/next.
|
||||
// head.next is most recently used.
|
||||
struct buf head;
|
||||
} bcache;
|
||||
|
||||
void
|
||||
binit(void)
|
||||
{
|
||||
struct buf *b;
|
||||
|
||||
initlock(&bcache.lock, "bcache");
|
||||
//PAGEBREAK!
|
||||
|
||||
// Create linked list of buffers
|
||||
bcache.head.prev = &bcache.head;
|
||||
bcache.head.next = &bcache.head;
|
||||
for(b = bcache.buf; b < bcache.buf+NBUF; b++){
|
||||
b->next = bcache.head.next;
|
||||
b->prev = &bcache.head;
|
||||
initsleeplock(&b->lock, "buffer");
|
||||
bcache.head.next->prev = b;
|
||||
bcache.head.next = b;
|
||||
}
|
||||
}
|
||||
|
||||
// Route a request to the driver that owns b->dev. Everything above this line
|
||||
// is device-agnostic; everything below picks a backend. RAMDISK_DEV is served
|
||||
// out of memory (kernel/dev/ramdisk.c), USBDISK_DEV by bulk transfers to a
|
||||
// USB stick, everything else by the IDE driver.
|
||||
static void
|
||||
bdevrw(struct buf *b)
|
||||
{
|
||||
if(b->dev == RAMDISK_DEV){
|
||||
ramdiskrw(b);
|
||||
return;
|
||||
}
|
||||
if(b->dev == USBDISK_DEV){
|
||||
usb_storage_rw(b);
|
||||
return;
|
||||
}
|
||||
iderw(b);
|
||||
}
|
||||
|
||||
// Look through buffer cache for block on device dev.
|
||||
// If not found, allocate a buffer.
|
||||
// In either case, return locked buffer.
|
||||
static struct buf*
|
||||
bget(uint dev, uint blockno)
|
||||
{
|
||||
struct buf *b;
|
||||
|
||||
acquire(&bcache.lock);
|
||||
|
||||
// Is the block already cached?
|
||||
for(b = bcache.head.next; b != &bcache.head; b = b->next){
|
||||
if(b->dev == dev && b->blockno == blockno){
|
||||
b->refcnt++;
|
||||
release(&bcache.lock);
|
||||
acquiresleep(&b->lock);
|
||||
return b;
|
||||
}
|
||||
}
|
||||
|
||||
// Not cached; recycle some unused buffer and clean buffer
|
||||
// "clean" because B_DIRTY and not locked means log.c
|
||||
// hasn't yet committed the changes to the buffer.
|
||||
for(b = bcache.head.prev; b != &bcache.head; b = b->prev){
|
||||
if(b->refcnt == 0 && (b->flags & B_DIRTY) == 0) {
|
||||
b->dev = dev;
|
||||
b->blockno = blockno;
|
||||
b->flags = 0;
|
||||
b->refcnt = 1;
|
||||
release(&bcache.lock);
|
||||
acquiresleep(&b->lock);
|
||||
return b;
|
||||
}
|
||||
}
|
||||
panic("bget: no buffers");
|
||||
}
|
||||
|
||||
// Return a locked buf with the contents of the indicated block.
|
||||
struct buf*
|
||||
bread(uint dev, uint blockno)
|
||||
{
|
||||
struct buf *b;
|
||||
|
||||
b = bget(dev, blockno);
|
||||
if(!(b->flags & B_VALID)) {
|
||||
bdevrw(b);
|
||||
}
|
||||
return b;
|
||||
}
|
||||
|
||||
// Write b's contents to disk. Must be locked.
|
||||
void
|
||||
bwrite(struct buf *b)
|
||||
{
|
||||
if(!holdingsleep(&b->lock))
|
||||
panic("bwrite");
|
||||
b->flags |= B_DIRTY;
|
||||
bdevrw(b);
|
||||
}
|
||||
|
||||
// Release a locked buffer.
|
||||
// Move to the head of the MRU list.
|
||||
void
|
||||
brelse(struct buf *b)
|
||||
{
|
||||
if(!holdingsleep(&b->lock))
|
||||
panic("brelse");
|
||||
|
||||
releasesleep(&b->lock);
|
||||
|
||||
acquire(&bcache.lock);
|
||||
b->refcnt--;
|
||||
if (b->refcnt == 0) {
|
||||
// no one is waiting for it.
|
||||
b->next->prev = b->prev;
|
||||
b->prev->next = b->next;
|
||||
b->next = bcache.head.next;
|
||||
b->prev = &bcache.head;
|
||||
bcache.head.next->prev = b;
|
||||
bcache.head.next = b;
|
||||
}
|
||||
|
||||
release(&bcache.lock);
|
||||
}
|
||||
//PAGEBREAK!
|
||||
107
kernel/dev/cdev.c
Normal file
107
kernel/dev/cdev.c
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
#include "types.h"
|
||||
#include "defs.h"
|
||||
#include "param.h"
|
||||
#include "spinlock.h"
|
||||
#include "sleeplock.h"
|
||||
#include "fs.h"
|
||||
#include "file.h"
|
||||
#include "x86.h"
|
||||
#include "errno.h"
|
||||
|
||||
extern int consoleread(struct inode*, uint, char*, int);
|
||||
extern int consolewrite(struct inode*, uint, char*, int);
|
||||
extern int mouseread(struct inode*, uint, char*, int);
|
||||
|
||||
static uint64 rseed = 0x2A99554433221100ULL;
|
||||
|
||||
int
|
||||
nullwrite(struct inode *ip, uint off, char *dst, int n)
|
||||
{
|
||||
return n;
|
||||
}
|
||||
|
||||
int
|
||||
nullread(struct inode *ip, uint off, char *dst, int n)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
zeroread(struct inode *ip, uint off, char *dst, int n)
|
||||
{
|
||||
memset(dst, 0, n);
|
||||
return n;
|
||||
}
|
||||
|
||||
int
|
||||
randomread(struct inode *ip, uint off, char *dst, int n)
|
||||
{
|
||||
for(int i = 0; i < n; i++){
|
||||
rseed = rseed * 6364136223846793005ULL + 1442695040888963407ULL;
|
||||
dst[i] = (rseed >> 32) & 0xFF;
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
// /dev/full — reads as an endless run of zero bytes, every write fails with
|
||||
// ENOSPC. Programs use it to exercise their out-of-disk-space paths.
|
||||
int
|
||||
fullwrite(struct inode *ip, uint off, char *dst, int n)
|
||||
{
|
||||
return -ENOSPC;
|
||||
}
|
||||
|
||||
int
|
||||
mousewrite(struct inode *ip, uint off, char *dst, int n)
|
||||
{
|
||||
return n;
|
||||
}
|
||||
|
||||
int
|
||||
memread(struct inode *ip, uint off, char *dst, int n)
|
||||
{
|
||||
switch(ip->minor) {
|
||||
case 1: return consoleread(ip, off, dst, n);
|
||||
case 2: return nullread(ip, off, dst, n);
|
||||
case 3: return consoleread(ip, off, dst, n);
|
||||
case 5: return zeroread(ip, off, dst, n);
|
||||
case 7: return zeroread(ip, off, dst, n); // /dev/full reads as zeros
|
||||
case 8:
|
||||
case 9: return randomread(ip, off, dst, n);
|
||||
default: return -1;
|
||||
}
|
||||
}
|
||||
|
||||
int
|
||||
memwrite(struct inode *ip, uint off, char *dst, int n)
|
||||
{
|
||||
switch(ip->minor) {
|
||||
case 1: return consolewrite(ip, off, dst, n);
|
||||
case 7: return fullwrite(ip, off, dst, n); // /dev/full: always ENOSPC
|
||||
case 2:
|
||||
case 5:
|
||||
case 8:
|
||||
case 9: return nullwrite(ip, off, dst, n);
|
||||
default: return -1;
|
||||
}
|
||||
}
|
||||
|
||||
struct devsw devsw[] = {
|
||||
[0] = { 0, 0 },
|
||||
[1] = { memread, memwrite },
|
||||
// /dev/fb0 (major 2) — the framebuffer, kernel/dev/fb.c. This used to be
|
||||
// { nullread, nullwrite }, so writing pixels to it succeeded and did
|
||||
// nothing.
|
||||
// The close hook gives the text console its screen back when whatever was
|
||||
// drawing on the framebuffer exits — see fbclose().
|
||||
[2] = { fbread, fbwrite, 0, fbclose },
|
||||
[3] = { mouseread, mousewrite },
|
||||
[4] = { 0, 0 },
|
||||
// /dev/tty (major 5) — alias for the console
|
||||
[5] = { memread, memwrite },
|
||||
// /proc status files (major 6) — see kernel/dev/procfs.c
|
||||
[6] = { procread, procwrite },
|
||||
// /dev/input/eventN (major 7) — raw evdev events, kernel/dev/input.c. The
|
||||
// minor is the device index, so event0 is the keyboard and event1 the mouse.
|
||||
[7] = { inputread, inputwrite, inputready, inputclose, inputopen },
|
||||
};
|
||||
308
kernel/dev/device.c
Normal file
308
kernel/dev/device.c
Normal file
|
|
@ -0,0 +1,308 @@
|
|||
// The device model. See include/device.h for why it exists.
|
||||
//
|
||||
// A fixed table rather than a linked list: this is populated once during boot
|
||||
// by whichever bus found each device, and read afterwards. Nothing is removed
|
||||
// yet -- hot-unplug would need it, and when that arrives the in_use flag is
|
||||
// already the hook for it.
|
||||
//
|
||||
// No lock. Every device_add() happens during single-threaded boot, before
|
||||
// userspace exists, and every reader afterwards only walks. If a bus ever
|
||||
// enumerates at runtime -- USB hotplug is the obvious one -- this needs a
|
||||
// spinlock, and the reader in procfs needs to take it.
|
||||
|
||||
#include "types.h"
|
||||
#include "defs.h"
|
||||
#include "param.h"
|
||||
#include "device.h"
|
||||
#include "display.h"
|
||||
#include "theme.h"
|
||||
#include "text.h"
|
||||
|
||||
static struct device devices[NDEVICE];
|
||||
static int ndevices;
|
||||
|
||||
static const char *bus_names[DEVBUS_NBUS] = {
|
||||
[DEVBUS_PLATFORM] = "platform",
|
||||
[DEVBUS_PCI] = "pci",
|
||||
[DEVBUS_USB] = "usb",
|
||||
};
|
||||
|
||||
static const char *class_names[DEVCLASS_NCLASS] = {
|
||||
[DEVCLASS_OTHER] = "other",
|
||||
[DEVCLASS_DISPLAY] = "display",
|
||||
[DEVCLASS_INPUT] = "input",
|
||||
[DEVCLASS_STORAGE] = "storage",
|
||||
[DEVCLASS_NET] = "net",
|
||||
[DEVCLASS_BUS] = "bus",
|
||||
};
|
||||
|
||||
const char *
|
||||
dev_bus_name(enum dev_bus b)
|
||||
{
|
||||
if (b < 0 || b >= DEVBUS_NBUS || !bus_names[b])
|
||||
return "?";
|
||||
return bus_names[b];
|
||||
}
|
||||
|
||||
const char *
|
||||
dev_class_name(enum dev_class c)
|
||||
{
|
||||
if (c < 0 || c >= DEVCLASS_NCLASS || !class_names[c])
|
||||
return "?";
|
||||
return class_names[c];
|
||||
}
|
||||
|
||||
// How many devices already share this base name, so "vga" becomes vga0, vga1.
|
||||
static int
|
||||
count_base(const char *base)
|
||||
{
|
||||
int n = 0;
|
||||
for (int i = 0; i < ndevices; i++) {
|
||||
const char *a = devices[i].name, *b = base;
|
||||
while (*b && *a == *b) { a++; b++; }
|
||||
// Matched the whole base and the rest is the index we appended.
|
||||
if (*b == '\0' && *a >= '0' && *a <= '9')
|
||||
n++;
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
struct device *
|
||||
device_add(const char *base, enum dev_bus bus, enum dev_class class,
|
||||
const char *driver)
|
||||
{
|
||||
if (ndevices >= NDEVICE)
|
||||
return 0;
|
||||
|
||||
struct device *d = &devices[ndevices++];
|
||||
d->bus = bus;
|
||||
d->class = class;
|
||||
d->driver = driver;
|
||||
d->in_use = 1;
|
||||
|
||||
int idx = count_base(base);
|
||||
int n = 0;
|
||||
while (base[n] && n < DEV_NAMELEN - 3) {
|
||||
d->name[n] = base[n];
|
||||
n++;
|
||||
}
|
||||
// Single digit is enough for NDEVICE of 32 per base name, and a two-digit
|
||||
// path that is never taken is a two-digit path that is never tested.
|
||||
if (idx > 9)
|
||||
idx = 9;
|
||||
d->name[n++] = (char)('0' + idx);
|
||||
d->name[n] = '\0';
|
||||
|
||||
return d;
|
||||
}
|
||||
|
||||
void
|
||||
device_set_desc(struct device *d, const char *s)
|
||||
{
|
||||
if (!d)
|
||||
return;
|
||||
int n = 0;
|
||||
while (s[n] && n < (int)sizeof(d->desc) - 1) {
|
||||
d->desc[n] = s[n];
|
||||
n++;
|
||||
}
|
||||
d->desc[n] = '\0';
|
||||
}
|
||||
|
||||
struct device *
|
||||
device_at(int i)
|
||||
{
|
||||
if (i < 0 || i >= ndevices)
|
||||
return 0;
|
||||
return &devices[i];
|
||||
}
|
||||
|
||||
int
|
||||
device_count(void)
|
||||
{
|
||||
return ndevices;
|
||||
}
|
||||
|
||||
struct device *
|
||||
device_find_class(enum dev_class class)
|
||||
{
|
||||
for (int i = 0; i < ndevices; i++)
|
||||
// Unclaimed devices are deliberately skipped. A display nobody drives is
|
||||
// not a display you can draw on, and returning it would push the "is there
|
||||
// a driver" check out to every caller.
|
||||
if (devices[i].class == class && devices[i].driver)
|
||||
return &devices[i];
|
||||
return 0;
|
||||
}
|
||||
|
||||
// --- the display class -----------------------------------------------------
|
||||
//
|
||||
// Thin wrappers over device_find_class() plus a null check on each op. They
|
||||
// exist so that consumers are not each written around a vtable that may be
|
||||
// absent -- a compositor asking to present on a machine with no display driver
|
||||
// should be a no-op, not a null dereference in the middle of a frame.
|
||||
|
||||
struct device *
|
||||
display_get(void)
|
||||
{
|
||||
return device_find_class(DEVCLASS_DISPLAY);
|
||||
}
|
||||
|
||||
int
|
||||
display_mode(struct display_mode *out)
|
||||
{
|
||||
struct device *d = display_get();
|
||||
if (!d || !d->ops || !((struct display_ops *)d->ops)->mode)
|
||||
return -1;
|
||||
((struct display_ops *)d->ops)->mode(d, out);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void *
|
||||
display_acquire(uint32 *stride_out)
|
||||
{
|
||||
struct device *d = display_get();
|
||||
if (!d || !d->ops || !((struct display_ops *)d->ops)->acquire)
|
||||
return 0;
|
||||
return ((struct display_ops *)d->ops)->acquire(d, stride_out);
|
||||
}
|
||||
|
||||
void
|
||||
display_present(int x, int y, int w, int h)
|
||||
{
|
||||
struct device *d = display_get();
|
||||
if (!d || !d->ops || !((struct display_ops *)d->ops)->present)
|
||||
return;
|
||||
((struct display_ops *)d->ops)->present(d, x, y, w, h);
|
||||
}
|
||||
|
||||
void
|
||||
display_wait_vblank(void)
|
||||
{
|
||||
struct device *d = display_get();
|
||||
if (!d || !d->ops || !((struct display_ops *)d->ops)->wait_vblank)
|
||||
return;
|
||||
((struct display_ops *)d->ops)->wait_vblank(d);
|
||||
}
|
||||
|
||||
// Drive the whole display path once and report what happened: find the class,
|
||||
// read the mode, acquire a back buffer, draw into RAM, present it.
|
||||
//
|
||||
// A self-test rather than a unit test because none of this can be checked
|
||||
// without a display: the assertion is "did the right pixels reach the screen",
|
||||
// and the only instrument for that is a screendump. Triggered with
|
||||
// `echo d > /proc/sysrq` and photographed by tools/dispshot.ps1.
|
||||
//
|
||||
// The pattern is chosen to be wrong in visible ways. Four solid quadrants in
|
||||
// theme colours catch a stride error (the bands would slant) and a bpp error
|
||||
// (the colours would be wrong); the white frame one pixel inside the edge
|
||||
// catches an off-by-one in the present rectangle; and the fact that it is
|
||||
// drawn entirely into RAM before a single byte reaches the framebuffer is what
|
||||
// double buffering means.
|
||||
void
|
||||
display_selftest(void)
|
||||
{
|
||||
struct display_mode m;
|
||||
uint32 stride;
|
||||
|
||||
if (display_mode(&m) != 0) {
|
||||
cprintf("display: no display bound\n");
|
||||
return;
|
||||
}
|
||||
cprintf("display: %dx%d %dbpp stride %d\n", m.width, m.height, m.bpp, m.stride);
|
||||
|
||||
unsigned char *b = display_acquire(&stride);
|
||||
if (!b) {
|
||||
cprintf("display: no back buffer available\n");
|
||||
return;
|
||||
}
|
||||
|
||||
static const uint32 quad[4] = {
|
||||
SCH_ACCENT, SCH_ACCENT_DIM, SCH_DIM, SCH_BG
|
||||
};
|
||||
for (int y = 0; y < m.height; y++) {
|
||||
uint32 *row = (uint32 *)(b + (uint64)y * stride);
|
||||
int top = y < m.height / 2;
|
||||
for (int x = 0; x < m.width; x++) {
|
||||
int left = x < m.width / 2;
|
||||
row[x] = quad[(top ? 0 : 2) + (left ? 0 : 1)];
|
||||
}
|
||||
}
|
||||
for (int x = 1; x < m.width - 1; x++) {
|
||||
*(uint32 *)(b + 1 * (uint64)stride + x * 4) = SCH_FG;
|
||||
*(uint32 *)(b + (uint64)(m.height - 2) * stride + x * 4) = SCH_FG;
|
||||
}
|
||||
for (int y = 1; y < m.height - 1; y++) {
|
||||
*(uint32 *)(b + (uint64)y * stride + 1 * 4) = SCH_FG;
|
||||
*(uint32 *)(b + (uint64)y * stride + (m.width - 2) * 4) = SCH_FG;
|
||||
}
|
||||
|
||||
// Between frames, so the blit does not land halfway down a scan-out.
|
||||
display_wait_vblank();
|
||||
display_present(0, 0, m.width, m.height);
|
||||
cprintf("display: presented a full frame\n");
|
||||
}
|
||||
|
||||
// Draw a text specimen through the display class. `echo t > /proc/sysrq`.
|
||||
//
|
||||
// It exercises the three things a character-cell console cannot do, so a
|
||||
// screenshot says whether the layer works rather than whether it compiled:
|
||||
// glyphs on a common baseline at arbitrary x (not multiples of eight), text
|
||||
// blended over an existing background rather than drawn into cleared cells, and
|
||||
// codepoints above 255.
|
||||
void
|
||||
text_selftest(void)
|
||||
{
|
||||
struct display_mode m;
|
||||
uint32 stride;
|
||||
|
||||
if (display_mode(&m) != 0) {
|
||||
cprintf("text: no display\n");
|
||||
return;
|
||||
}
|
||||
unsigned char *b = display_acquire(&stride);
|
||||
if (!b) {
|
||||
cprintf("text: no back buffer\n");
|
||||
return;
|
||||
}
|
||||
|
||||
struct surface s = { b, stride, m.width, m.height };
|
||||
struct font *f = font_builtin();
|
||||
|
||||
// A background with structure in it, so blending is visible as blending
|
||||
// rather than as drawing onto a flat colour.
|
||||
for (int y = 0; y < m.height; y++) {
|
||||
uint32 *row = (uint32 *)(b + (uint64)y * stride);
|
||||
for (int x = 0; x < m.width; x++)
|
||||
row[x] = ((x / 32 + y / 32) & 1) ? SCH_BG : SCH_ACCENT_DIM;
|
||||
}
|
||||
|
||||
int base = f->ops->baseline(f);
|
||||
int line = f->ops->height(f) + 6;
|
||||
int y = 80;
|
||||
|
||||
text_draw(&s, f, 60, y + base, "Schunix text rendering", SCH_FG);
|
||||
y += line * 2;
|
||||
|
||||
// Deliberately not on a multiple of eight. On the old console this string
|
||||
// could only start at x = 56 or x = 64.
|
||||
text_draw(&s, f, 61, y + base, "positioned at x=61, off the cell grid", SCH_DIM);
|
||||
y += line;
|
||||
text_draw(&s, f, 63, y + base, "and here at x=63", SCH_DIM);
|
||||
y += line * 2;
|
||||
|
||||
// Above U+00FF, which a 256-entry table cannot index at all.
|
||||
text_draw(&s, f, 60, y + base,
|
||||
"utf-8: \xe2\x94\x8c\xe2\x94\x80\xe2\x94\x90 "
|
||||
"\xe2\x96\x88\xe2\x96\x93\xe2\x96\x92\xe2\x96\x91 "
|
||||
"\xc2\xb0 \xc2\xb1 \xe2\x86\x92 \xe2\x80\xa2", SCH_ACCENT);
|
||||
y += line * 2;
|
||||
|
||||
// Measure, then centre on it -- the thing you cannot do without metrics.
|
||||
const char *msg = "measured and centred";
|
||||
int w = text_measure(f, msg);
|
||||
text_draw(&s, f, (m.width - w) / 2, y + base, msg, SCH_FG);
|
||||
|
||||
display_present(0, 0, m.width, m.height);
|
||||
cprintf("text: drew a specimen, \"%s\" measures %d px\n", msg, w);
|
||||
}
|
||||
202
kernel/dev/fb.c
Normal file
202
kernel/dev/fb.c
Normal file
|
|
@ -0,0 +1,202 @@
|
|||
// /dev/fb0 -- the linear framebuffer as a file.
|
||||
//
|
||||
// Until now this node existed but was wired to major 2, whose devsw entry was
|
||||
// { nullread, nullwrite }: reads returned end-of-file and writes were thrown
|
||||
// away, silently. The only thing that actually worked was mmap, and only
|
||||
// because sys_mmap special-cased the major number.
|
||||
//
|
||||
// Reads and writes here address the framebuffer by byte offset, so lseek
|
||||
// positions the "cursor" and a whole frame can be written with one write().
|
||||
// That is slower than mmap and always will be -- it is a copy -- but it is
|
||||
// what makes the device inspectable from a shell, which is worth a great deal
|
||||
// while there is no GUI yet to inspect it with.
|
||||
|
||||
#include "types.h"
|
||||
#include "defs.h"
|
||||
#include "param.h"
|
||||
#include "mmu.h"
|
||||
#include "proc.h"
|
||||
#include "spinlock.h"
|
||||
#include "sleeplock.h"
|
||||
#include "fs.h"
|
||||
#include "file.h"
|
||||
#include "fb.h"
|
||||
#include "uaccess.h"
|
||||
#include "errno.h"
|
||||
|
||||
// Describe one colour channel. A channel that does not exist gets length 0,
|
||||
// which is how callers are supposed to detect "no alpha" rather than by
|
||||
// assuming from bits_per_pixel.
|
||||
static void
|
||||
chan(struct fb_bitfield *f, unsigned int offset, unsigned int length)
|
||||
{
|
||||
f->offset = offset;
|
||||
f->length = length;
|
||||
f->msb_right = 0;
|
||||
}
|
||||
|
||||
// Pixel layout for the depths vga_draw_pixel knows how to write. Keeping the
|
||||
// two in step matters: this is the kernel telling userspace how to build a
|
||||
// pixel, and if it disagrees with how the kernel builds one, every colour
|
||||
// drawn by a program is wrong in a way that looks like a hardware fault.
|
||||
static void
|
||||
pixel_format(struct fb_var_screeninfo *v, unsigned int bpp)
|
||||
{
|
||||
switch (bpp) {
|
||||
case 32:
|
||||
chan(&v->red, 16, 8); chan(&v->green, 8, 8); chan(&v->blue, 0, 8);
|
||||
chan(&v->transp, 24, 0); // the byte is there; nothing honours it
|
||||
break;
|
||||
case 24:
|
||||
chan(&v->red, 16, 8); chan(&v->green, 8, 8); chan(&v->blue, 0, 8);
|
||||
chan(&v->transp, 0, 0);
|
||||
break;
|
||||
case 16:
|
||||
// RGB565, matching the packing vga_draw_pixel does for this depth.
|
||||
chan(&v->red, 11, 5); chan(&v->green, 5, 6); chan(&v->blue, 0, 5);
|
||||
chan(&v->transp, 0, 0);
|
||||
break;
|
||||
default:
|
||||
chan(&v->red, 0, 0); chan(&v->green, 0, 0); chan(&v->blue, 0, 0);
|
||||
chan(&v->transp, 0, 0);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
int
|
||||
fbread(struct inode *ip, uint off, char *dst, int n)
|
||||
{
|
||||
volatile uchar *fb = vga_fb_ptr();
|
||||
uint64 size = vga_fb_size();
|
||||
|
||||
if (!fb)
|
||||
return -ENODEV;
|
||||
if (n < 0)
|
||||
return -EINVAL;
|
||||
if ((uint64)off >= size)
|
||||
return 0; // past the end of the screen: EOF
|
||||
if ((uint64)off + (uint64)n > size)
|
||||
n = (int)(size - (uint64)off);
|
||||
|
||||
memmove(dst, (void*)(fb + off), n);
|
||||
return n;
|
||||
}
|
||||
|
||||
// Set once something has drawn through this device, so that fbclose knows
|
||||
// whether the screen still belongs to the text console. Reading the
|
||||
// framebuffer, or asking it its geometry, does not count -- `fbinfo` with no
|
||||
// arguments must not wipe its own output off the screen.
|
||||
static int fb_dirty;
|
||||
|
||||
// Called from sys_mmap when /dev/fb0 is mapped. A mapping is a licence to
|
||||
// draw, and the drawing happens with no further syscalls, so this is the last
|
||||
// point at which the kernel hears about it at all.
|
||||
void
|
||||
fb_mapped(void)
|
||||
{
|
||||
fb_dirty = 1;
|
||||
}
|
||||
|
||||
int
|
||||
fbwrite(struct inode *ip, uint off, char *src, int n)
|
||||
{
|
||||
volatile uchar *fb = vga_fb_ptr();
|
||||
uint64 size = vga_fb_size();
|
||||
|
||||
if (!fb)
|
||||
return -ENODEV;
|
||||
if (n < 0)
|
||||
return -EINVAL;
|
||||
if ((uint64)off >= size)
|
||||
return -ENOSPC; // a screen cannot be extended
|
||||
if ((uint64)off + (uint64)n > size)
|
||||
n = (int)(size - (uint64)off);
|
||||
|
||||
memmove((void*)(fb + off), src, n);
|
||||
fb_dirty = 1;
|
||||
return n;
|
||||
}
|
||||
|
||||
// The last descriptor on /dev/fb0 is going away.
|
||||
//
|
||||
// Whatever was drawing has finished, and the screen still holds its final
|
||||
// frame -- so the shell prompt underneath is invisible and the machine looks
|
||||
// hung. It is not: the process exited cleanly and the console is writing
|
||||
// happily into a framebuffer nobody can read any more. There is no character
|
||||
// backing store to repaint from (the console draws glyphs straight into the
|
||||
// framebuffer and scrolls with a memmove), so the honest recovery is to clear
|
||||
// and go home rather than to pretend the old text can be restored.
|
||||
void
|
||||
fbclose(struct inode *ip)
|
||||
{
|
||||
if (!fb_dirty)
|
||||
return;
|
||||
fb_dirty = 0;
|
||||
|
||||
// Said out loud, because a screen clearing on its own is otherwise
|
||||
// indistinguishable from a bug -- and this is the only place in the system
|
||||
// that wipes the display without being asked to.
|
||||
cprintf("fb0: released by pid %d, console restored\n", myproc() ? myproc()->pid : -1);
|
||||
vga_screen_reset();
|
||||
}
|
||||
|
||||
// FBIO* handling, called from sys_ioctl once it has established that the fd
|
||||
// refers to this device. `arg` is the raw user address; each request
|
||||
// validates it for the size it is actually going to touch, rather than for
|
||||
// the largest struct here -- fb_fix_screeninfo is half the size of
|
||||
// fb_var_screeninfo, and a buffer sitting near the end of a mapping is a
|
||||
// legitimate place to put the smaller one.
|
||||
int
|
||||
fb_ioctl(int request, addr_t arg)
|
||||
{
|
||||
int w, h;
|
||||
|
||||
if (!vga_fb_ptr())
|
||||
return -ENODEV;
|
||||
|
||||
vga_dims(&w, &h);
|
||||
|
||||
switch (request) {
|
||||
case FBIOGET_VSCREENINFO: {
|
||||
struct fb_var_screeninfo *v =
|
||||
(struct fb_var_screeninfo *)user_ptr(arg, sizeof(*v));
|
||||
if (!v)
|
||||
return -EFAULT;
|
||||
memset(v, 0, sizeof(*v));
|
||||
v->xres = v->xres_virtual = (unsigned int)w;
|
||||
v->yres = v->yres_virtual = (unsigned int)h;
|
||||
v->bits_per_pixel = vga_fb_bpp();
|
||||
v->activate = FB_ACTIVATE_NOW;
|
||||
pixel_format(v, v->bits_per_pixel);
|
||||
// height/width (physical millimetres) and every timing field stay zero:
|
||||
// VBE does not report them and a guess would be worse than an admission.
|
||||
return 0;
|
||||
}
|
||||
|
||||
case FBIOGET_FSCREENINFO: {
|
||||
struct fb_fix_screeninfo *f =
|
||||
(struct fb_fix_screeninfo *)user_ptr(arg, sizeof(*f));
|
||||
if (!f)
|
||||
return -EFAULT;
|
||||
memset(f, 0, sizeof(*f));
|
||||
safestrcpy(f->id, "schunixfb", sizeof(f->id));
|
||||
f->smem_start = (unsigned long)vga_fb_phys();
|
||||
f->smem_len = (unsigned int)vga_fb_size();
|
||||
f->type = FB_TYPE_PACKED_PIXELS;
|
||||
f->visual = FB_VISUAL_TRUECOLOR;
|
||||
f->line_length = vga_fb_stride();
|
||||
return 0;
|
||||
}
|
||||
|
||||
// The mode came from the bootloader's VBE call and we have no way to
|
||||
// change it. Saying so is more useful than accepting the request and
|
||||
// leaving the caller to discover, by drawing, that nothing happened.
|
||||
case FBIOPUT_VSCREENINFO:
|
||||
return -EINVAL;
|
||||
|
||||
case FBIOBLANK:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return -ENOTTY;
|
||||
}
|
||||
208
kernel/dev/ide.c
Normal file
208
kernel/dev/ide.c
Normal file
|
|
@ -0,0 +1,208 @@
|
|||
// Simple PIO-based (non-DMA) IDE driver code.
|
||||
|
||||
#include "types.h"
|
||||
#include "defs.h"
|
||||
#include "irq.h"
|
||||
#include "param.h"
|
||||
#include "memlayout.h"
|
||||
#include "mmu.h"
|
||||
#include "proc.h"
|
||||
#include "x86.h"
|
||||
#include "traps.h"
|
||||
#include "spinlock.h"
|
||||
#include "sleeplock.h"
|
||||
#include "fs.h"
|
||||
#include "buf.h"
|
||||
|
||||
#define SECTOR_SIZE 512
|
||||
#define IDE_BSY 0x80
|
||||
#define IDE_DRDY 0x40
|
||||
#define IDE_DF 0x20
|
||||
#define IDE_ERR 0x01
|
||||
|
||||
#define IDE_CMD_READ 0x20
|
||||
#define IDE_CMD_WRITE 0x30
|
||||
#define IDE_CMD_RDMUL 0xc4
|
||||
#define IDE_CMD_WRMUL 0xc5
|
||||
|
||||
// idequeue points to the buf now being read/written to the disk.
|
||||
// idequeue->qnext points to the next buf to be processed.
|
||||
// You must hold idelock while manipulating queue.
|
||||
|
||||
static struct spinlock idelock;
|
||||
static struct buf *idequeue;
|
||||
|
||||
static int havedisk0;
|
||||
static int havedisk1;
|
||||
static void idestart(struct buf*);
|
||||
|
||||
// Wait for the drive to become ready, or give up.
|
||||
//
|
||||
// This used to be an unbounded `while`. On a machine with nothing attached to
|
||||
// the primary channel the status register reads 0xFF forever, so the very
|
||||
// first call from ideinit() hung the kernel -- no panic, no message, just a
|
||||
// dead machine right after the VGA banner. That is not an exotic
|
||||
// configuration: it is every CD boot, and most hardware newer than IDE.
|
||||
//
|
||||
// The bound is deliberately enormous. A real drive answers in microseconds;
|
||||
// this only expires when there is nothing there at all.
|
||||
static int
|
||||
idewait(int checkerr)
|
||||
{
|
||||
int r = 0;
|
||||
|
||||
for(int i = 0; i < 20000000; i++){
|
||||
r = inb(0x1f7);
|
||||
if((r & (IDE_BSY|IDE_DRDY)) == IDE_DRDY){
|
||||
if(checkerr && (r & (IDE_DF|IDE_ERR)) != 0)
|
||||
return -1;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
static void
|
||||
ide_irq(void *arg)
|
||||
{
|
||||
(void)arg;
|
||||
ideintr();
|
||||
}
|
||||
|
||||
void
|
||||
ideinit(void)
|
||||
{
|
||||
initlock(&idelock, "ide");
|
||||
// ISA signalling: edge-triggered, active high. Everything at a fixed
|
||||
// legacy port is wired this way; PCI devices are not.
|
||||
irq_register(IRQ_IDE, ide_irq, 0, "ide", 0);
|
||||
|
||||
// A timeout here means there is no drive on this channel. Say so and leave
|
||||
// havedisk0/1 clear: with a ramdisk root there is nothing else the system
|
||||
// needs from IDE, and booting on is strictly better than stopping.
|
||||
if(idewait(0) < 0){
|
||||
cprintf("ide: no drive on the primary channel\n");
|
||||
return;
|
||||
}
|
||||
havedisk0 = 1;
|
||||
|
||||
// Check if disk 1 is present
|
||||
outb(0x1f6, 0xe0 | (1<<4));
|
||||
for(int i=0; i<1000; i++){
|
||||
if(inb(0x1f7) != 0){
|
||||
havedisk1 = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Switch back to disk 0.
|
||||
outb(0x1f6, 0xe0 | (0<<4));
|
||||
}
|
||||
|
||||
// Is there actually a drive there? Callers that probe -- rather than callers
|
||||
// that know what they are looking for -- have to ask, because reading an
|
||||
// absent drive is a bug and iderw() treats it as one.
|
||||
int
|
||||
ide_disk_present(int dev)
|
||||
{
|
||||
return dev == 0 ? havedisk0 : havedisk1;
|
||||
}
|
||||
|
||||
// Start the request for b. Caller must hold idelock.
|
||||
static void
|
||||
idestart(struct buf *b)
|
||||
{
|
||||
if(b == 0)
|
||||
panic("idestart");
|
||||
if(b->blockno >= FSSIZE)
|
||||
panic("incorrect blockno");
|
||||
int sector_per_block = BSIZE/SECTOR_SIZE;
|
||||
int sector = b->blockno * sector_per_block;
|
||||
int read_cmd = (sector_per_block == 1) ? IDE_CMD_READ : IDE_CMD_RDMUL;
|
||||
int write_cmd = (sector_per_block == 1) ? IDE_CMD_WRITE : IDE_CMD_WRMUL;
|
||||
|
||||
if (sector_per_block > 7) panic("idestart");
|
||||
|
||||
idewait(0);
|
||||
outb(0x3f6, 0); // generate interrupt
|
||||
outb(0x1f2, sector_per_block); // number of sectors
|
||||
outb(0x1f3, sector & 0xff);
|
||||
outb(0x1f4, (sector >> 8) & 0xff);
|
||||
outb(0x1f5, (sector >> 16) & 0xff);
|
||||
outb(0x1f6, 0xe0 | ((b->dev&1)<<4) | ((sector>>24)&0x0f));
|
||||
if(b->flags & B_DIRTY){
|
||||
outb(0x1f7, write_cmd);
|
||||
outsl(0x1f0, b->data, BSIZE/4);
|
||||
} else {
|
||||
outb(0x1f7, read_cmd);
|
||||
}
|
||||
}
|
||||
|
||||
// Interrupt handler.
|
||||
void
|
||||
ideintr(void)
|
||||
{
|
||||
struct buf *b;
|
||||
|
||||
// First queued buffer is the active request.
|
||||
acquire(&idelock);
|
||||
if((b = idequeue) == 0){
|
||||
release(&idelock);
|
||||
// cprintf("spurious IDE interrupt\n");
|
||||
return;
|
||||
}
|
||||
idequeue = b->qnext;
|
||||
|
||||
// Read data if needed.
|
||||
if(!(b->flags & B_DIRTY) && idewait(1) >= 0)
|
||||
insl(0x1f0, b->data, BSIZE/4);
|
||||
|
||||
// Wake process waiting for this buf.
|
||||
b->flags |= B_VALID;
|
||||
b->flags &= ~B_DIRTY;
|
||||
wakeup(b);
|
||||
|
||||
// Start disk on next buf in queue.
|
||||
if(idequeue != 0)
|
||||
idestart(idequeue);
|
||||
|
||||
release(&idelock);
|
||||
}
|
||||
|
||||
//PAGEBREAK!
|
||||
// Sync buf with disk.
|
||||
// If B_DIRTY is set, write buf to disk, clear B_DIRTY, set B_VALID.
|
||||
// Else if B_VALID is not set, read buf from disk, set B_VALID.
|
||||
void
|
||||
iderw(struct buf *b)
|
||||
{
|
||||
struct buf **pp;
|
||||
|
||||
if(!holdingsleep(&b->lock))
|
||||
panic("iderw: buf not locked");
|
||||
if((b->flags & (B_VALID|B_DIRTY)) == B_VALID)
|
||||
panic("iderw: nothing to do");
|
||||
if(b->dev != 0 && !havedisk1)
|
||||
panic("iderw: ide disk 1 not present");
|
||||
if(b->dev == 0 && !havedisk0)
|
||||
panic("iderw: ide disk 0 not present");
|
||||
|
||||
acquire(&idelock); //DOC:acquire-lock
|
||||
|
||||
// Append b to idequeue.
|
||||
b->qnext = 0;
|
||||
for(pp=&idequeue; *pp; pp=&(*pp)->qnext) //DOC:insert-queue
|
||||
;
|
||||
*pp = b;
|
||||
|
||||
// Start disk if necessary.
|
||||
if(idequeue == b)
|
||||
idestart(b);
|
||||
|
||||
// Wait for request to finish.
|
||||
while((b->flags & (B_VALID|B_DIRTY)) != B_VALID){
|
||||
sleep(b, &idelock);
|
||||
}
|
||||
|
||||
release(&idelock);
|
||||
}
|
||||
394
kernel/dev/input.c
Normal file
394
kernel/dev/input.c
Normal file
|
|
@ -0,0 +1,394 @@
|
|||
// Raw input event devices -- /dev/input/event0 and /dev/input/event1.
|
||||
//
|
||||
// Everything that produces input feeds this: the PS/2 keyboard and mouse, and
|
||||
// the USB HID driver. The console keeps its own path and is unaffected; these
|
||||
// nodes are a *second* consumer of the same interrupts, so a program can read
|
||||
// keys while the shell still gets characters.
|
||||
//
|
||||
// Two devices rather than one because they are two devices. A compositor
|
||||
// grabs the pointer and leaves the keyboard alone, or the other way round, and
|
||||
// merging them into a single stream makes that impossible to express.
|
||||
|
||||
#include "types.h"
|
||||
#include "defs.h"
|
||||
#include "param.h"
|
||||
#include "mmu.h"
|
||||
#include "proc.h"
|
||||
#include "spinlock.h"
|
||||
#include "sleeplock.h"
|
||||
#include "fs.h"
|
||||
#include "file.h"
|
||||
#include "input.h"
|
||||
#include "errno.h"
|
||||
|
||||
#define NEVENT 256 // per device; ~10 seconds of frantic typing
|
||||
|
||||
struct input_dev {
|
||||
struct spinlock lock;
|
||||
struct input_event ev[NEVENT];
|
||||
uint r, w; // free-running; the difference is the count
|
||||
|
||||
// Exclusive grab (EVIOCGRAB). While set, keys still arrive here but stop
|
||||
// being handed to the console's line discipline.
|
||||
//
|
||||
// Without this a full-screen program does not really own the keyboard: it
|
||||
// reads /dev/input/event0 *and* the shell behind it reads the same keystrokes
|
||||
// off the tty. The observed failure was a full-screen program quietly typing
|
||||
// its entire session into the shell underneath, so the first Enter after
|
||||
// exiting ran whatever had accumulated. The grab is also exactly what a
|
||||
// compositor needs, which is why it lives here rather than in any one client.
|
||||
int grabbed;
|
||||
|
||||
// How many descriptors are open on this device.
|
||||
//
|
||||
// Events are only queued while this is non-zero, and the backlog is dropped
|
||||
// on the first open. Both halves matter: without them the ring is a log of
|
||||
// every key pressed since boot, and the next program to open the device is
|
||||
// handed the lot as though the user had just typed it.
|
||||
int opens;
|
||||
};
|
||||
|
||||
static struct input_dev idev[NINPUTDEV];
|
||||
static int input_up;
|
||||
|
||||
extern uint ticks;
|
||||
|
||||
void
|
||||
inputinit(void)
|
||||
{
|
||||
int i;
|
||||
|
||||
for(i = 0; i < NINPUTDEV; i++)
|
||||
initlock(&idev[i].lock, "input");
|
||||
|
||||
// Producers run from interrupt handlers and can fire before this point in
|
||||
// boot. Until the locks exist, an event has nowhere safe to go.
|
||||
__sync_synchronize();
|
||||
input_up = 1;
|
||||
}
|
||||
|
||||
// Append one event. Called from interrupt context, so it never sleeps and
|
||||
// never blocks on a full ring.
|
||||
void
|
||||
input_report(int dev, int type, int code, int value)
|
||||
{
|
||||
struct input_dev *d;
|
||||
struct input_event *e;
|
||||
|
||||
if(!input_up || dev < 0 || dev >= NINPUTDEV)
|
||||
return;
|
||||
d = &idev[dev];
|
||||
|
||||
// Nobody is listening, so there is nowhere for this to go. Linux does the
|
||||
// same: an evdev device with no clients does not accumulate.
|
||||
if(d->opens == 0)
|
||||
return;
|
||||
|
||||
acquire(&d->lock);
|
||||
|
||||
// A full ring drops the *oldest* event, not the newest. Dropping the newest
|
||||
// is the obvious choice and it is wrong: the event most likely to be lost is
|
||||
// then a key release, and a lost release leaves the key held down forever
|
||||
// as far as every reader is concerned. Losing the oldest costs history, not
|
||||
// state.
|
||||
if(d->w - d->r >= NEVENT)
|
||||
d->r++;
|
||||
|
||||
e = &d->ev[d->w % NEVENT];
|
||||
e->time.tv_sec = ticks / 100;
|
||||
e->time.tv_usec = (ticks % 100) * 10000;
|
||||
e->type = (uint16)type;
|
||||
e->code = (uint16)code;
|
||||
e->value = (int32)value;
|
||||
d->w++;
|
||||
|
||||
release(&d->lock);
|
||||
}
|
||||
|
||||
// End a report. A reader that acts on each event as it arrives will see a
|
||||
// diagonal mouse move as two separate moves; the SYN is how it knows the two
|
||||
// axes belong to the same physical motion.
|
||||
void
|
||||
input_sync(int dev)
|
||||
{
|
||||
if(!input_up || dev < 0 || dev >= NINPUTDEV)
|
||||
return;
|
||||
input_report(dev, EV_SYN, SYN_REPORT, 0);
|
||||
wakeup(&idev[dev]);
|
||||
}
|
||||
|
||||
int
|
||||
input_ready(int dev)
|
||||
{
|
||||
int n;
|
||||
|
||||
if(!input_up || dev < 0 || dev >= NINPUTDEV)
|
||||
return 0;
|
||||
acquire(&idev[dev].lock);
|
||||
n = idev[dev].w != idev[dev].r;
|
||||
release(&idev[dev].lock);
|
||||
return n;
|
||||
}
|
||||
|
||||
// A descriptor has been opened on this device.
|
||||
//
|
||||
// The backlog is discarded, which is the whole point. Events queued before a
|
||||
// program opened the device were meant for whoever had it *then* -- almost
|
||||
// always the shell, which already consumed them as a command line. Replaying
|
||||
// them into the new owner means a program is handed the keystrokes that
|
||||
// launched it, as input, the instant it starts. That was a real crash and not a
|
||||
// cosmetic one: the replayed characters were interpreted as commands, and one of
|
||||
// them asked for a mode the program had not initialised yet.
|
||||
//
|
||||
// Linux gets this for free by giving every open its own client queue. One
|
||||
// shared ring plus "start from now" is the same answer for a single reader,
|
||||
// which is the case that exists.
|
||||
void
|
||||
inputopen(struct inode *ip)
|
||||
{
|
||||
int dev = ip->minor;
|
||||
|
||||
if(dev < 0 || dev >= NINPUTDEV)
|
||||
return;
|
||||
acquire(&idev[dev].lock);
|
||||
idev[dev].opens++;
|
||||
idev[dev].r = idev[dev].w; // start from now, not from boot
|
||||
release(&idev[dev].lock);
|
||||
}
|
||||
|
||||
// The devsw ready hook: would a read block?
|
||||
int
|
||||
inputready(struct inode *ip)
|
||||
{
|
||||
return input_ready(ip->minor);
|
||||
}
|
||||
|
||||
// Is this device grabbed? Asked by the keyboard drivers before they hand a
|
||||
// character to the console.
|
||||
int
|
||||
input_is_grabbed(int dev)
|
||||
{
|
||||
if(!input_up || dev < 0 || dev >= NINPUTDEV)
|
||||
return 0;
|
||||
return idev[dev].grabbed;
|
||||
}
|
||||
|
||||
// EVIOCGRAB. Linux's semantics: one holder at a time, and it is not an error
|
||||
// to grab what you already hold.
|
||||
int
|
||||
input_grab(int dev, int on)
|
||||
{
|
||||
if(!input_up || dev < 0 || dev >= NINPUTDEV)
|
||||
return -ENODEV;
|
||||
on = on ? 1 : 0;
|
||||
if(idev[dev].grabbed != on){
|
||||
idev[dev].grabbed = on;
|
||||
// Said out loud. A grabbed keyboard is a console that has gone deaf on
|
||||
// purpose, and that is indistinguishable from one that has gone deaf by
|
||||
// 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);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// The last descriptor on this device is going away.
|
||||
//
|
||||
// Releasing the grab here is not tidiness, it is the difference between a
|
||||
// crashed game and a machine you have to reboot: a program that grabs the
|
||||
// keyboard and dies without ungrabbing would leave the console permanently
|
||||
// deaf. The close hook makes the release unconditional.
|
||||
void
|
||||
inputclose(struct inode *ip)
|
||||
{
|
||||
int dev = ip->minor;
|
||||
|
||||
if(dev < 0 || dev >= NINPUTDEV)
|
||||
return;
|
||||
if(idev[dev].opens > 0)
|
||||
idev[dev].opens--;
|
||||
if(idev[dev].grabbed){
|
||||
idev[dev].grabbed = 0;
|
||||
cprintf("input: event%d grab released\n", dev);
|
||||
}
|
||||
}
|
||||
|
||||
// ioctl on /dev/input/eventN. Only EVIOCGRAB so far; the EVIOCG* query
|
||||
// requests (name, id, capability bitmaps) can arrive when something needs
|
||||
// them.
|
||||
int
|
||||
input_ioctl(struct inode *ip, int request, addr_t arg)
|
||||
{
|
||||
int on;
|
||||
|
||||
if(request == EVIOCGRAB){
|
||||
// Linux passes the flag *by value* in the argument, not by pointer.
|
||||
on = (int)arg;
|
||||
return input_grab(ip->minor, on);
|
||||
}
|
||||
return -ENOTTY;
|
||||
}
|
||||
|
||||
// A read returns whole events only. Handing back a partial struct would
|
||||
// desynchronise the reader permanently -- every later event would be split
|
||||
// across two reads and reassembled wrong.
|
||||
int
|
||||
inputread(struct inode *ip, uint off, char *dst, int n)
|
||||
{
|
||||
struct input_dev *d;
|
||||
int got = 0;
|
||||
int dev = ip->minor;
|
||||
|
||||
if(dev < 0 || dev >= NINPUTDEV)
|
||||
return -ENODEV;
|
||||
if(n < (int)sizeof(struct input_event))
|
||||
return -EINVAL;
|
||||
d = &idev[dev];
|
||||
|
||||
acquire(&d->lock);
|
||||
while(d->r == d->w){
|
||||
if(myproc()->killed){
|
||||
release(&d->lock);
|
||||
return -EINTR;
|
||||
}
|
||||
sleep(d, &d->lock);
|
||||
}
|
||||
|
||||
while(d->r != d->w && got + (int)sizeof(struct input_event) <= n){
|
||||
memmove(dst + got, &d->ev[d->r % NEVENT], sizeof(struct input_event));
|
||||
d->r++;
|
||||
got += sizeof(struct input_event);
|
||||
}
|
||||
release(&d->lock);
|
||||
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.
|
||||
int
|
||||
inputwrite(struct inode *ip, uint off, char *src, int n)
|
||||
{
|
||||
return n;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Producers
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// AT scancode set 1, E0-prefixed. The unprefixed bytes need no table at all:
|
||||
// Linux's keycodes were numbered from this set and still match it below 0x54.
|
||||
// These are the ones that do not.
|
||||
static const struct { uint8 sc; uint8 key; } e0map[] = {
|
||||
{0x1C, 96}, // KP_ENTER
|
||||
{0x1D, KEY_RIGHTCTRL},
|
||||
{0x35, 98}, // KP_SLASH
|
||||
{0x38, KEY_RIGHTALT},
|
||||
{0x47, KEY_HOME},
|
||||
{0x48, KEY_UP},
|
||||
{0x49, KEY_PAGEUP},
|
||||
{0x4B, KEY_LEFT},
|
||||
{0x4D, KEY_RIGHT},
|
||||
{0x4F, KEY_END},
|
||||
{0x50, KEY_DOWN},
|
||||
{0x51, KEY_PAGEDOWN},
|
||||
{0x52, KEY_INSERT},
|
||||
{0x53, KEY_DELETE},
|
||||
{0x5B, KEY_LEFTMETA},
|
||||
{0x5C, KEY_RIGHTMETA},
|
||||
};
|
||||
|
||||
// One byte from the PS/2 port, before it has been turned into a character.
|
||||
void
|
||||
input_ps2_scancode(int raw, int e0)
|
||||
{
|
||||
int code = raw & 0x7F;
|
||||
int down = (raw & 0x80) ? 0 : 1;
|
||||
int key = 0;
|
||||
unsigned i;
|
||||
|
||||
if(e0){
|
||||
for(i = 0; i < sizeof(e0map)/sizeof(e0map[0]); i++)
|
||||
if(e0map[i].sc == code){
|
||||
key = e0map[i].key;
|
||||
break;
|
||||
}
|
||||
if(key == 0)
|
||||
return; // an E0 key nothing has a name for
|
||||
} else {
|
||||
if(code == 0 || code > 0x58)
|
||||
return;
|
||||
key = code;
|
||||
}
|
||||
|
||||
input_report(INPUT_KBD, EV_KEY, key, down);
|
||||
input_sync(INPUT_KBD);
|
||||
}
|
||||
|
||||
// USB HID usage -> Linux keycode. The mapping is not derivable: HID numbers
|
||||
// keys by position on a US layout starting at 'a', and the AT set numbers them
|
||||
// by the order IBM wired the matrix. The table is the same one Linux carries
|
||||
// for the same reason.
|
||||
static const uint8 hid_keycode[] = {
|
||||
0, 0, 0, 0, 30, 48, 46, 32, 18, 33, 34, 35, 23, 36, 37, 38,
|
||||
50, 49, 24, 25, 16, 19, 31, 20, 22, 47, 17, 45, 21, 44, 2, 3,
|
||||
4, 5, 6, 7, 8, 9, 10, 11, 28, 1, 14, 15, 57, 12, 13, 26,
|
||||
27, 43, 43, 39, 40, 41, 51, 52, 53, 58, 59, 60, 61, 62, 63, 64,
|
||||
65, 66, 67, 68, 87, 88, 99, 70,119,110,102,104,111,107,109,106,
|
||||
105,108,103, 69, 98, 55, 74, 78, 96, 79, 80, 81, 75, 76, 77, 71,
|
||||
72, 73, 82, 83, 86,127,116,117,
|
||||
};
|
||||
|
||||
// The eight modifier bits in a boot report, in bit order.
|
||||
static const uint8 hid_modkey[8] = {
|
||||
KEY_LEFTCTRL, KEY_LEFTSHIFT, KEY_LEFTALT, KEY_LEFTMETA,
|
||||
KEY_RIGHTCTRL, KEY_RIGHTSHIFT, KEY_RIGHTALT, KEY_RIGHTMETA,
|
||||
};
|
||||
|
||||
int
|
||||
input_hid_keycode(int usage)
|
||||
{
|
||||
if(usage >= 0xE0 && usage <= 0xE7)
|
||||
return hid_modkey[usage - 0xE0];
|
||||
if(usage < 0 || usage >= (int)sizeof(hid_keycode))
|
||||
return 0;
|
||||
return hid_keycode[usage];
|
||||
}
|
||||
|
||||
void
|
||||
input_key(int usage, int down)
|
||||
{
|
||||
int key = input_hid_keycode(usage);
|
||||
|
||||
if(key == 0)
|
||||
return;
|
||||
input_report(INPUT_KBD, EV_KEY, key, down);
|
||||
input_sync(INPUT_KBD);
|
||||
}
|
||||
|
||||
// One mouse report: relative motion plus the current button state. `buttons`
|
||||
// is a bitmask in the order both PS/2 and HID boot protocol use -- left,
|
||||
// right, middle -- which is why neither caller has to reorder it.
|
||||
void
|
||||
input_mouse(int dx, int dy, int buttons)
|
||||
{
|
||||
static int last;
|
||||
static const int btncode[3] = { BTN_LEFT, BTN_RIGHT, BTN_MIDDLE };
|
||||
int i;
|
||||
|
||||
if(dx)
|
||||
input_report(INPUT_MOUSE, EV_REL, REL_X, dx);
|
||||
if(dy)
|
||||
input_report(INPUT_MOUSE, EV_REL, REL_Y, dy);
|
||||
|
||||
// Only transitions. A button held down for a second is one press event, not
|
||||
// a hundred -- a reader that has to filter repeats itself cannot tell a held
|
||||
// button from a very fast double-click.
|
||||
for(i = 0; i < 3; i++){
|
||||
int now = (buttons >> i) & 1;
|
||||
if(now != ((last >> i) & 1))
|
||||
input_report(INPUT_MOUSE, EV_KEY, btncode[i], now);
|
||||
}
|
||||
last = buttons;
|
||||
|
||||
input_sync(INPUT_MOUSE);
|
||||
}
|
||||
214
kernel/dev/irq.c
Normal file
214
kernel/dev/irq.c
Normal file
|
|
@ -0,0 +1,214 @@
|
|||
// Hardware interrupt registration. See include/irq.h for why this exists.
|
||||
|
||||
#include "types.h"
|
||||
#include "defs.h"
|
||||
#include "param.h"
|
||||
#include "traps.h"
|
||||
#include "spinlock.h"
|
||||
#include "irq.h"
|
||||
|
||||
struct irq_action {
|
||||
irq_handler_t fn;
|
||||
void *arg;
|
||||
const char *name;
|
||||
uint64 count;
|
||||
};
|
||||
|
||||
static struct {
|
||||
struct irq_action act[NCHAIN];
|
||||
int n; // handlers installed; only ever grows during boot
|
||||
int enabled;
|
||||
int level_low;
|
||||
uint64 unhandled;
|
||||
} line[NIRQ];
|
||||
|
||||
// Guards registration against itself. The *dispatch* path deliberately does
|
||||
// not take it: an interrupt handler that blocked on a lock held by whoever
|
||||
// was registering would deadlock the CPU it fired on. Instead registration
|
||||
// publishes an entry by writing all of its fields, issuing a barrier, and
|
||||
// only then incrementing n -- so a concurrent dispatcher either does not see
|
||||
// the entry at all or sees it complete.
|
||||
//
|
||||
// Removal is the harder direction, because compacting the array *does* move
|
||||
// entries a dispatcher may be walking. irq_unregister therefore masks the
|
||||
// line first and only re-routes it once the chain has been rewritten: with
|
||||
// the line masked no new interrupt can arrive, and any handler already
|
||||
// running holds no reference past its own return.
|
||||
static struct spinlock irqlock;
|
||||
|
||||
void
|
||||
irq_init(void)
|
||||
{
|
||||
initlock(&irqlock, "irq");
|
||||
}
|
||||
|
||||
// IRQ_ERROR and IRQ_SPURIOUS are not IOAPIC pins -- they are local APIC
|
||||
// vectors that xv6 numbered as though they were, so T_IRQ0+19 and T_IRQ0+31
|
||||
// are already spoken for. A PCI device routed to pin 19 would deliver on the
|
||||
// same vector as the LAPIC error interrupt. Refuse it here rather than let it
|
||||
// be debugged later.
|
||||
static int
|
||||
reserved(int irq)
|
||||
{
|
||||
return irq == IRQ_ERROR || irq == IRQ_SPURIOUS || irq == IRQ_TIMER;
|
||||
}
|
||||
|
||||
int
|
||||
irq_register(int irq, irq_handler_t fn, void *arg, const char *name,
|
||||
int level_low)
|
||||
{
|
||||
struct irq_action *a;
|
||||
|
||||
if(irq < 0 || irq >= NIRQ || fn == 0)
|
||||
return -1;
|
||||
if(reserved(irq)){
|
||||
cprintf("irq_register: %s wants irq %d, which is reserved\n", name, irq);
|
||||
return -1;
|
||||
}
|
||||
|
||||
acquire(&irqlock);
|
||||
|
||||
if(line[irq].n >= NCHAIN){
|
||||
release(&irqlock);
|
||||
cprintf("irq_register: irq %d chain full, %s not attached\n", irq, name);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Two devices sharing a line must agree on how it is signalled; they
|
||||
// physically cannot be wired both ways.
|
||||
if(line[irq].n > 0 && line[irq].level_low != level_low){
|
||||
release(&irqlock);
|
||||
cprintf("irq_register: irq %d shared with conflicting trigger mode "
|
||||
"(%s)\n", irq, name);
|
||||
return -1;
|
||||
}
|
||||
|
||||
a = &line[irq].act[line[irq].n];
|
||||
a->fn = fn;
|
||||
a->arg = arg;
|
||||
a->name = name;
|
||||
a->count = 0;
|
||||
__sync_synchronize(); // publish the entry before it is reachable
|
||||
line[irq].n++;
|
||||
|
||||
if(!line[irq].enabled){
|
||||
line[irq].level_low = level_low;
|
||||
ioapicroute(irq, 0, level_low);
|
||||
line[irq].enabled = 1;
|
||||
}
|
||||
|
||||
release(&irqlock);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void
|
||||
irq_unregister(int irq, irq_handler_t fn, void *arg)
|
||||
{
|
||||
int i, j;
|
||||
|
||||
if(irq < 0 || irq >= NIRQ)
|
||||
return;
|
||||
|
||||
acquire(&irqlock);
|
||||
for(i = 0; i < line[irq].n; i++){
|
||||
if(line[irq].act[i].fn == fn && line[irq].act[i].arg == arg){
|
||||
// Mask before touching the chain, always -- not only when this is the
|
||||
// last handler. Compacting the array moves entries that a dispatcher on
|
||||
// another CPU may be walking, so the line has to be quiet for the
|
||||
// duration, and then brought back only if anything is left on it.
|
||||
ioapicmask(irq);
|
||||
line[irq].enabled = 0;
|
||||
|
||||
for(j = i; j + 1 < line[irq].n; j++)
|
||||
line[irq].act[j] = line[irq].act[j+1];
|
||||
line[irq].n--;
|
||||
__sync_synchronize();
|
||||
|
||||
if(line[irq].n > 0){
|
||||
ioapicroute(irq, 0, line[irq].level_low);
|
||||
line[irq].enabled = 1;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
release(&irqlock);
|
||||
}
|
||||
|
||||
void
|
||||
irq_dispatch(int irq)
|
||||
{
|
||||
int i, n;
|
||||
|
||||
if(irq < 0 || irq >= NIRQ)
|
||||
return;
|
||||
|
||||
n = line[irq].n;
|
||||
if(n == 0){
|
||||
line[irq].unhandled++;
|
||||
return;
|
||||
}
|
||||
|
||||
// Every handler on a shared line is offered the interrupt; each one is
|
||||
// responsible for recognising that its device is idle and returning.
|
||||
for(i = 0; i < n; i++){
|
||||
line[irq].act[i].count++;
|
||||
line[irq].act[i].fn(line[irq].act[i].arg);
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// /proc/interrupts
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static int
|
||||
emit(char *buf, int cap, int off, const char *s)
|
||||
{
|
||||
while(*s && off < cap)
|
||||
buf[off++] = *s++;
|
||||
return off;
|
||||
}
|
||||
|
||||
static int
|
||||
emit_dec(char *buf, int cap, int off, uint64 v, int width)
|
||||
{
|
||||
char tmp[24];
|
||||
int n = 0, i;
|
||||
|
||||
if(v == 0)
|
||||
tmp[n++] = '0';
|
||||
while(v > 0){
|
||||
tmp[n++] = '0' + (v % 10);
|
||||
v /= 10;
|
||||
}
|
||||
for(i = n; i < width && off < cap; i++)
|
||||
buf[off++] = ' ';
|
||||
while(n > 0 && off < cap)
|
||||
buf[off++] = tmp[--n];
|
||||
return off;
|
||||
}
|
||||
|
||||
int
|
||||
irq_info(char *buf, int cap)
|
||||
{
|
||||
int irq, i, off = 0;
|
||||
|
||||
for(irq = 0; irq < NIRQ; irq++){
|
||||
if(line[irq].n == 0 && line[irq].unhandled == 0)
|
||||
continue;
|
||||
off = emit_dec(buf, cap, off, irq, 4);
|
||||
off = emit(buf, cap, off, ": ");
|
||||
for(i = 0; i < line[irq].n; i++){
|
||||
off = emit_dec(buf, cap, off, line[irq].act[i].count, 10);
|
||||
off = emit(buf, cap, off, " ");
|
||||
off = emit(buf, cap, off,
|
||||
line[irq].act[i].name ? line[irq].act[i].name : "?");
|
||||
off = emit(buf, cap, off, i + 1 < line[irq].n ? ", " : "");
|
||||
}
|
||||
if(line[irq].n == 0)
|
||||
off = emit(buf, cap, off, "(unhandled)");
|
||||
if(line[irq].unhandled)
|
||||
off = emit(buf, cap, off, " [spurious]");
|
||||
off = emit(buf, cap, off, line[irq].level_low ? " level\n" : " edge\n");
|
||||
}
|
||||
return off;
|
||||
}
|
||||
75
kernel/dev/kbd.c
Normal file
75
kernel/dev/kbd.c
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
#include "types.h"
|
||||
#include "x86.h"
|
||||
#include "defs.h"
|
||||
#include "kbd.h"
|
||||
#include "input.h"
|
||||
|
||||
int
|
||||
kbdgetc(void)
|
||||
{
|
||||
static uint shift;
|
||||
static uchar *charcode[4] = {
|
||||
normalmap, shiftmap, ctlmap, ctlmap
|
||||
};
|
||||
uint st, data, c;
|
||||
|
||||
st = inb(KBSTATP);
|
||||
if((st & KBS_DIB) == 0)
|
||||
return -1;
|
||||
|
||||
if(st & 0x20)
|
||||
return -1;
|
||||
|
||||
data = inb(KBDATAP);
|
||||
|
||||
if(data == 0xE0){
|
||||
shift |= E0ESC;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Feed /dev/input/event0 before the byte is turned into a character. This
|
||||
// has to happen here rather than in a second handler because the byte can
|
||||
// only be read from the port once -- whoever reads it owes it to everyone
|
||||
// else. The console path below is untouched: both consumers see every key.
|
||||
input_ps2_scancode(data, (shift & E0ESC) != 0);
|
||||
|
||||
// While something holds an exclusive grab on the keyboard, the console gets
|
||||
// nothing. The shift/caps state below is still tracked, so releasing the
|
||||
// grab does not leave the line discipline believing a modifier is held.
|
||||
if(input_is_grabbed(INPUT_KBD)){
|
||||
if(data & 0x80){
|
||||
data = (shift & E0ESC ? data : data & 0x7F);
|
||||
shift &= ~(shiftcode[data] | E0ESC);
|
||||
} else {
|
||||
shift |= shiftcode[data];
|
||||
shift ^= togglecode[data];
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
if(data & 0x80){
|
||||
data = (shift & E0ESC ? data : data & 0x7F);
|
||||
shift &= ~(shiftcode[data] | E0ESC);
|
||||
return 0;
|
||||
} else if(shift & E0ESC){
|
||||
data |= 0x80;
|
||||
shift &= ~E0ESC;
|
||||
}
|
||||
|
||||
shift |= shiftcode[data];
|
||||
shift ^= togglecode[data];
|
||||
c = charcode[shift & (CTL | SHIFT)][data];
|
||||
if(shift & CAPSLOCK){
|
||||
if('a' <= c && c <= 'z')
|
||||
c += 'A' - 'a';
|
||||
else if('A' <= c && c <= 'Z')
|
||||
c += 'a' - 'A';
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
void
|
||||
kbdintr(void)
|
||||
{
|
||||
consoleintr(kbdgetc);
|
||||
}
|
||||
214
kernel/dev/mouse.c
Normal file
214
kernel/dev/mouse.c
Normal file
|
|
@ -0,0 +1,214 @@
|
|||
#include "types.h"
|
||||
#include "defs.h"
|
||||
#include "irq.h"
|
||||
#include "x86.h"
|
||||
#include "traps.h"
|
||||
#include "spinlock.h"
|
||||
#include "sleeplock.h"
|
||||
#include "fs.h"
|
||||
#include "file.h"
|
||||
|
||||
#define KBD_S_STAT 0x64
|
||||
#define KBD_S_DATA 0x60
|
||||
#define KBD_C_WRITE 0xD4
|
||||
#define MOUSE_ACK 0xFA
|
||||
|
||||
struct {
|
||||
struct spinlock lock;
|
||||
int x;
|
||||
int y;
|
||||
uint8 buttons;
|
||||
} mouse_state;
|
||||
|
||||
static void
|
||||
mouse_wait(int type)
|
||||
{
|
||||
int timeout = 100000;
|
||||
if(type == 0){
|
||||
while(timeout-- && !(inb(KBD_S_STAT) & 0x01));
|
||||
} else {
|
||||
while(timeout-- && (inb(KBD_S_STAT) & 0x02));
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
mouse_write(uchar data)
|
||||
{
|
||||
mouse_wait(1);
|
||||
outb(KBD_S_STAT, KBD_C_WRITE);
|
||||
mouse_wait(1);
|
||||
outb(KBD_S_DATA, data);
|
||||
}
|
||||
|
||||
static uchar
|
||||
mouse_read(void)
|
||||
{
|
||||
mouse_wait(0);
|
||||
return inb(KBD_S_DATA);
|
||||
}
|
||||
|
||||
static void
|
||||
mouse_irq(void *arg)
|
||||
{
|
||||
(void)arg;
|
||||
mouseintr();
|
||||
}
|
||||
|
||||
// Move the pointer. Caller holds mouse_state.lock.
|
||||
//
|
||||
// Positive dy is *down*, matching both the screen and USB HID. The PS/2
|
||||
// packet reports the opposite, which is why its caller negates first --
|
||||
// there is one pointer and two protocols disagreeing about which way is up,
|
||||
// and the conversion belongs at the edge rather than in here.
|
||||
static void
|
||||
mouse_move_locked(int dx, int dy)
|
||||
{
|
||||
mouse_state.x += dx;
|
||||
mouse_state.y += dy;
|
||||
|
||||
if(mouse_state.x < 0) mouse_state.x = 0;
|
||||
if(mouse_state.y < 0) mouse_state.y = 0;
|
||||
if(mouse_state.x >= 800) mouse_state.x = 799;
|
||||
if(mouse_state.y >= 600) mouse_state.y = 599;
|
||||
}
|
||||
|
||||
// The same pointer, moved by something that is not the PS/2 port. USB HID
|
||||
// uses this; anything else that produces motion events should too.
|
||||
void
|
||||
mouse_inject(int dx, int dy, int buttons)
|
||||
{
|
||||
acquire(&mouse_state.lock);
|
||||
mouse_state.buttons = buttons;
|
||||
mouse_move_locked(dx, dy);
|
||||
release(&mouse_state.lock);
|
||||
|
||||
input_mouse(dx, dy, buttons);
|
||||
}
|
||||
|
||||
void
|
||||
mouseintr(void)
|
||||
{
|
||||
static uchar buf[3];
|
||||
static int phase = 0;
|
||||
uchar status;
|
||||
|
||||
status = inb(KBD_S_STAT);
|
||||
|
||||
if(!(status & 0x01) || !(status & 0x20)){
|
||||
return;
|
||||
}
|
||||
|
||||
uchar data = inb(KBD_S_DATA);
|
||||
|
||||
acquire(&mouse_state.lock);
|
||||
|
||||
if(phase == 0 && !(data & 0x08)){
|
||||
release(&mouse_state.lock);
|
||||
return;
|
||||
}
|
||||
|
||||
buf[phase++] = data;
|
||||
|
||||
if(phase == 3){
|
||||
phase = 0;
|
||||
|
||||
mouse_state.buttons = buf[0] & 0x07;
|
||||
int dx = (buf[0] & 0x10) ? (int)buf[1] - 256 : buf[1];
|
||||
int dy = (buf[0] & 0x20) ? (int)buf[2] - 256 : buf[2];
|
||||
|
||||
// The overflow bits mean the delta saturated and is meaningless; moving
|
||||
// by it would throw the pointer across the screen.
|
||||
if(!(buf[0] & 0x40) && !(buf[0] & 0x80))
|
||||
mouse_move_locked(dx, -dy);
|
||||
else
|
||||
dx = dy = 0;
|
||||
|
||||
release(&mouse_state.lock);
|
||||
|
||||
// Outside the lock: input_report takes its own, and holding two at once
|
||||
// is how a lock ordering rule gets invented by accident.
|
||||
input_mouse(dx, -dy, buf[0] & 0x07);
|
||||
return;
|
||||
}
|
||||
|
||||
release(&mouse_state.lock);
|
||||
}
|
||||
|
||||
int get_mouse_x(void) {
|
||||
int x;
|
||||
acquire(&mouse_state.lock);
|
||||
x = mouse_state.x;
|
||||
release(&mouse_state.lock);
|
||||
return x;
|
||||
}
|
||||
|
||||
int get_mouse_y(void) {
|
||||
int y;
|
||||
acquire(&mouse_state.lock);
|
||||
y = mouse_state.y;
|
||||
release(&mouse_state.lock);
|
||||
return y;
|
||||
}
|
||||
|
||||
uint8 get_mouse_buttons(void) {
|
||||
uint8 b;
|
||||
acquire(&mouse_state.lock);
|
||||
b = mouse_state.buttons;
|
||||
release(&mouse_state.lock);
|
||||
return b;
|
||||
}
|
||||
|
||||
int
|
||||
mouseread(struct inode *ip, uint off, char *dst, int n)
|
||||
{
|
||||
struct mouse_data m;
|
||||
int size = sizeof(struct mouse_data);
|
||||
|
||||
if(n < size) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
m.x = get_mouse_x();
|
||||
m.y = get_mouse_y();
|
||||
m.buttons = get_mouse_buttons();
|
||||
|
||||
memmove(dst, &m, size);
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
void
|
||||
mouseinit(void)
|
||||
{
|
||||
initlock(&mouse_state.lock, "mouse");
|
||||
mouse_state.x = 400;
|
||||
mouse_state.y = 300;
|
||||
|
||||
while(inb(KBD_S_STAT) & 0x01)
|
||||
inb(KBD_S_DATA);
|
||||
|
||||
mouse_wait(1);
|
||||
outb(KBD_S_STAT, 0xA8);
|
||||
|
||||
mouse_wait(1);
|
||||
outb(KBD_S_STAT, 0x20);
|
||||
mouse_wait(0);
|
||||
uchar cb = inb(KBD_S_DATA);
|
||||
|
||||
cb |= 0x02;
|
||||
cb |= 0x01;
|
||||
cb &= ~0x20;
|
||||
|
||||
mouse_wait(1);
|
||||
outb(KBD_S_STAT, 0x60);
|
||||
mouse_wait(1);
|
||||
outb(KBD_S_DATA, cb);
|
||||
|
||||
mouse_write(0xF6);
|
||||
mouse_read();
|
||||
|
||||
mouse_write(0xF4);
|
||||
mouse_read();
|
||||
|
||||
irq_register(IRQ_MOUSE, mouse_irq, 0, "ps2-mouse", 0);
|
||||
}
|
||||
492
kernel/dev/pci.c
Normal file
492
kernel/dev/pci.c
Normal file
|
|
@ -0,0 +1,492 @@
|
|||
// PCI bus enumeration.
|
||||
//
|
||||
// Everything here is done through the two-port configuration mechanism at
|
||||
// 0xCF8/0xCFC: write a (bus, slot, function, register) address to one port,
|
||||
// read or write the value at the other. It is the oldest and least clever way
|
||||
// to reach config space, and it is the one that works everywhere without
|
||||
// first having to find an ACPI table to tell you where the memory-mapped
|
||||
// version lives.
|
||||
//
|
||||
// The scan is recursive rather than a flat sweep of all 256 buses. A flat
|
||||
// sweep does find everything on a PC, but it also probes 8192 slots that are
|
||||
// not there, and it cannot tell you which bridge a device sits behind -- and
|
||||
// that matters as soon as interrupt routing does.
|
||||
|
||||
#include "types.h"
|
||||
#include "defs.h"
|
||||
#include "param.h"
|
||||
#include "x86.h"
|
||||
#include "spinlock.h"
|
||||
#include "sleeplock.h"
|
||||
#include "fs.h"
|
||||
#include "file.h"
|
||||
#include "pci.h"
|
||||
#include "device.h"
|
||||
|
||||
static struct pci_dev devs[NPCI];
|
||||
static int ndevs;
|
||||
|
||||
static void scan_bus(uint8 bus);
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Configuration space access
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static uint32
|
||||
cfg_addr(uint8 bus, uint8 slot, uint8 func, uint8 off)
|
||||
{
|
||||
return 0x80000000u
|
||||
| ((uint32)bus << 16)
|
||||
| ((uint32)slot << 11)
|
||||
| ((uint32)func << 8)
|
||||
| (off & 0xFC); // the low two bits are not part of the address
|
||||
}
|
||||
|
||||
uint32
|
||||
pci_read32(uint8 bus, uint8 slot, uint8 func, uint8 off)
|
||||
{
|
||||
outl(PCI_CONFIG_ADDR, cfg_addr(bus, slot, func, off));
|
||||
return inl(PCI_CONFIG_DATA);
|
||||
}
|
||||
|
||||
void
|
||||
pci_write32(uint8 bus, uint8 slot, uint8 func, uint8 off, uint32 val)
|
||||
{
|
||||
outl(PCI_CONFIG_ADDR, cfg_addr(bus, slot, func, off));
|
||||
outl(PCI_CONFIG_DATA, val);
|
||||
}
|
||||
|
||||
// Config space is only addressable in dwords, so narrower reads are a shift
|
||||
// and a mask of the containing dword rather than a narrower bus cycle.
|
||||
uint16
|
||||
pci_read16(uint8 bus, uint8 slot, uint8 func, uint8 off)
|
||||
{
|
||||
return (uint16)(pci_read32(bus, slot, func, off) >> ((off & 2) * 8));
|
||||
}
|
||||
|
||||
uint8
|
||||
pci_read8(uint8 bus, uint8 slot, uint8 func, uint8 off)
|
||||
{
|
||||
return (uint8)(pci_read32(bus, slot, func, off) >> ((off & 3) * 8));
|
||||
}
|
||||
|
||||
void
|
||||
pci_enable(struct pci_dev *d, uint16 bits)
|
||||
{
|
||||
uint32 cmd = pci_read32(d->bus, d->slot, d->func, PCI_COMMAND);
|
||||
pci_write32(d->bus, d->slot, d->func, PCI_COMMAND, cmd | bits);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Capabilities
|
||||
//
|
||||
// Everything a device can do beyond the 64 bytes of standard config header --
|
||||
// MSI, MSI-X, power management, and the whole of virtio's configuration -- is
|
||||
// described by a singly linked list of capability structures living in the
|
||||
// device's config space. A driver that needs any of it has to walk that list.
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// Returns the config-space offset of the first capability with this ID, or 0.
|
||||
uint8
|
||||
pci_find_cap(struct pci_dev *d, uint8 id)
|
||||
{
|
||||
uint8 off;
|
||||
int guard;
|
||||
|
||||
if(!(pci_read16(d->bus, d->slot, d->func, PCI_STATUS) & PCI_STATUS_CAPLIST))
|
||||
return 0;
|
||||
|
||||
off = pci_read8(d->bus, d->slot, d->func, PCI_CAP_PTR) & 0xFC;
|
||||
|
||||
// The list is built by the device, so it can be circular if the device is
|
||||
// broken or if we misread it. 48 is more capabilities than the 192 usable
|
||||
// bytes can hold; past that we are certainly looping.
|
||||
for(guard = 0; off >= 0x40 && guard < 48; guard++){
|
||||
if(pci_read8(d->bus, d->slot, d->func, off) == id)
|
||||
return off;
|
||||
off = pci_read8(d->bus, d->slot, d->func, off + 1) & 0xFC;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Point a device's MSI at a CPU and a vector.
|
||||
//
|
||||
// MSI is not a wire: it is a memory write the device performs to a magic
|
||||
// address that the local APIC decodes as an interrupt. That makes it strictly
|
||||
// better than INTx for anything modern -- no sharing, no level/edge question,
|
||||
// and no chance of the interrupt arriving before the DMA it announces, since
|
||||
// both are posted writes travelling the same path in order.
|
||||
//
|
||||
// Returns 0 on success, -1 if the device has no MSI capability.
|
||||
int
|
||||
pci_msi_enable(struct pci_dev *d, int vector, int cpu)
|
||||
{
|
||||
uint8 cap = pci_find_cap(d, PCI_CAP_MSI);
|
||||
uint16 ctl;
|
||||
|
||||
if(cap == 0)
|
||||
return -1;
|
||||
|
||||
ctl = pci_read16(d->bus, d->slot, d->func, cap + 2);
|
||||
|
||||
// 0xFEE00000 | (apic id << 12) is the LAPIC's message address; the data
|
||||
// word is the vector, with delivery mode 000 (fixed) and edge trigger.
|
||||
pci_write32(d->bus, d->slot, d->func, cap + 4,
|
||||
0xFEE00000u | ((uint32)cpu << 12));
|
||||
|
||||
if(ctl & (1 << 7)){ // 64-bit address capable: data moved to +12
|
||||
pci_write32(d->bus, d->slot, d->func, cap + 8, 0);
|
||||
pci_write32(d->bus, d->slot, d->func, cap + 12, (uint32)vector);
|
||||
} else {
|
||||
pci_write32(d->bus, d->slot, d->func, cap + 8, (uint32)vector);
|
||||
}
|
||||
|
||||
// Enable, and request exactly one vector (multiple-message-enable = 0).
|
||||
// Message control shares its dword with the capability ID and next pointer,
|
||||
// and config space is only writable a dword at a time, so read-modify-write
|
||||
// the whole thing rather than clobbering the list this capability is on.
|
||||
{
|
||||
uint32 dw = pci_read32(d->bus, d->slot, d->func, cap);
|
||||
ctl = (ctl & ~0x0070) | 1;
|
||||
dw = (dw & 0x0000FFFF) | ((uint32)ctl << 16);
|
||||
pci_write32(d->bus, d->slot, d->func, cap, dw);
|
||||
}
|
||||
|
||||
// INTx must be masked or the device will signal both.
|
||||
pci_write32(d->bus, d->slot, d->func, PCI_COMMAND,
|
||||
pci_read32(d->bus, d->slot, d->func, PCI_COMMAND) | (1 << 10));
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Base address registers
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// A BAR does not report its own size. You find it by writing all ones and
|
||||
// reading back: the device leaves the bits it does not decode as zero, so the
|
||||
// size is one more than the inverse of what comes back. The original value
|
||||
// has to be put back afterwards -- this is a live device whose decoder is
|
||||
// briefly pointed at the top of the address space while we look.
|
||||
static void
|
||||
read_bars(struct pci_dev *d)
|
||||
{
|
||||
for (int i = 0; i < 6; i++) {
|
||||
uint8 off = PCI_BAR0 + i * 4;
|
||||
uint32 lo = pci_read32(d->bus, d->slot, d->func, off);
|
||||
|
||||
if (lo == 0)
|
||||
continue;
|
||||
|
||||
pci_write32(d->bus, d->slot, d->func, off, 0xFFFFFFFF);
|
||||
uint32 mask = pci_read32(d->bus, d->slot, d->func, off);
|
||||
pci_write32(d->bus, d->slot, d->func, off, lo);
|
||||
|
||||
if (mask == 0)
|
||||
continue;
|
||||
|
||||
if (lo & 1) { // I/O space
|
||||
d->bar[i].is_io = 1;
|
||||
d->bar[i].base = lo & ~0x3u;
|
||||
d->bar[i].size = (~(mask & ~0x3u) + 1) & 0xFFFF;
|
||||
continue;
|
||||
}
|
||||
|
||||
d->bar[i].prefetch = (lo >> 3) & 1;
|
||||
|
||||
if (((lo >> 1) & 3) == 2) { // 64-bit BAR: eats the next slot
|
||||
uint32 hi = pci_read32(d->bus, d->slot, d->func, off + 4);
|
||||
pci_write32(d->bus, d->slot, d->func, off + 4, 0xFFFFFFFF);
|
||||
uint32 hmask = pci_read32(d->bus, d->slot, d->func, off + 4);
|
||||
pci_write32(d->bus, d->slot, d->func, off + 4, hi);
|
||||
|
||||
d->bar[i].is_64 = 1;
|
||||
d->bar[i].base = ((uint64)hi << 32) | (lo & ~0xFu);
|
||||
d->bar[i].size = ~(((uint64)hmask << 32) | (mask & ~0xFu)) + 1;
|
||||
i++; // the upper half is not its own BAR
|
||||
continue;
|
||||
}
|
||||
|
||||
d->bar[i].base = lo & ~0xFu;
|
||||
d->bar[i].size = (~(mask & ~0xFu) + 1) & 0xFFFFFFFF;
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Enumeration
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static void
|
||||
add_func(uint8 bus, uint8 slot, uint8 func)
|
||||
{
|
||||
uint16 vendor = pci_read16(bus, slot, func, PCI_VENDOR_ID);
|
||||
|
||||
if (vendor == 0xFFFF) // nothing plugged in
|
||||
return;
|
||||
|
||||
if (ndevs >= NPCI) {
|
||||
cprintf("pci: more than %d devices, ignoring the rest\n", NPCI);
|
||||
return;
|
||||
}
|
||||
|
||||
struct pci_dev *d = &devs[ndevs++];
|
||||
d->bus = bus;
|
||||
d->slot = slot;
|
||||
d->func = func;
|
||||
|
||||
d->vendor = vendor;
|
||||
d->device = pci_read16(bus, slot, func, PCI_DEVICE_ID);
|
||||
d->revision = pci_read8(bus, slot, func, PCI_REVISION);
|
||||
d->prog_if = pci_read8(bus, slot, func, PCI_PROG_IF);
|
||||
d->subclass = pci_read8(bus, slot, func, PCI_SUBCLASS);
|
||||
d->class = pci_read8(bus, slot, func, PCI_CLASS);
|
||||
d->header_type = pci_read8(bus, slot, func, PCI_HEADER_TYPE);
|
||||
d->irq = pci_read8(bus, slot, func, PCI_INTERRUPT_LINE);
|
||||
|
||||
// Only header type 0 has six BARs. A bridge (type 1) has two and a very
|
||||
// different layout, and reading it as though it had six would hand out
|
||||
// window registers as if they were device apertures.
|
||||
if ((d->header_type & 0x7F) == 0)
|
||||
read_bars(d);
|
||||
|
||||
// Walk down through a PCI-to-PCI bridge to whatever is behind it.
|
||||
if ((d->header_type & 0x7F) == 1) {
|
||||
uint8 secondary = pci_read8(bus, slot, func, PCI_SECONDARY_BUS);
|
||||
if (secondary != 0 && secondary != bus)
|
||||
scan_bus(secondary);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
scan_bus(uint8 bus)
|
||||
{
|
||||
for (uint8 slot = 0; slot < 32; slot++) {
|
||||
if (pci_read16(bus, slot, 0, PCI_VENDOR_ID) == 0xFFFF)
|
||||
continue;
|
||||
|
||||
add_func(bus, slot, 0);
|
||||
|
||||
// Bit 7 of the header type says the device has more than one function.
|
||||
// Probing all eight unconditionally is harmless on real hardware but
|
||||
// produces phantom entries on some emulated devices, which answer every
|
||||
// function with a copy of function 0.
|
||||
if (pci_read8(bus, slot, 0, PCI_HEADER_TYPE) & 0x80)
|
||||
for (uint8 func = 1; func < 8; func++)
|
||||
add_func(bus, slot, func);
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Lookup
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
int
|
||||
pci_count(void)
|
||||
{
|
||||
return ndevs;
|
||||
}
|
||||
|
||||
struct pci_dev *
|
||||
pci_get(int i)
|
||||
{
|
||||
return (i >= 0 && i < ndevs) ? &devs[i] : 0;
|
||||
}
|
||||
|
||||
struct pci_dev *
|
||||
pci_find_class(uint8 class, uint8 subclass)
|
||||
{
|
||||
for (int i = 0; i < ndevs; i++)
|
||||
if (devs[i].class == class && devs[i].subclass == subclass)
|
||||
return &devs[i];
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct pci_dev *
|
||||
pci_find_id(uint16 vendor, uint16 device)
|
||||
{
|
||||
for (int i = 0; i < ndevs; i++)
|
||||
if (devs[i].vendor == vendor && devs[i].device == device)
|
||||
return &devs[i];
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Driver binding
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static int
|
||||
matches(const struct pci_match *m, struct pci_dev *d)
|
||||
{
|
||||
if (m->vendor != PCI_ANY)
|
||||
return m->vendor == d->vendor &&
|
||||
(m->device == PCI_ANY || m->device == d->device);
|
||||
|
||||
// A class match, for drivers that handle a kind of device rather than a
|
||||
// particular model.
|
||||
return m->class == d->class &&
|
||||
(m->subclass == 0xFF || m->subclass == d->subclass);
|
||||
}
|
||||
|
||||
// Offer every unclaimed device to a driver. Called by pci_init() for each
|
||||
// entry in the registry; see kernel/drivers/registry.c.
|
||||
// Defined further down with the /proc/pci formatting it was written for.
|
||||
static const char *class_name(uint8 class, uint8 subclass);
|
||||
|
||||
// Translate a PCI class code into the bus-agnostic one the device model uses.
|
||||
//
|
||||
// The two vocabularies are deliberately separate. PCI's class byte describes
|
||||
// how the card presents itself on that bus; DEVCLASS describes what the rest of
|
||||
// the kernel can *do* with it, and a USB disk has to land in the same bucket as
|
||||
// a SATA one despite sharing no numbering with it at all.
|
||||
enum dev_class
|
||||
pci_class_of(const struct pci_dev *d)
|
||||
{
|
||||
switch (d->class) {
|
||||
case PCI_CLASS_DISPLAY: return DEVCLASS_DISPLAY;
|
||||
case PCI_CLASS_STORAGE: return DEVCLASS_STORAGE;
|
||||
case PCI_CLASS_NETWORK: return DEVCLASS_NET;
|
||||
case PCI_CLASS_SERIAL_BUS: return DEVCLASS_BUS;
|
||||
default: return DEVCLASS_OTHER;
|
||||
}
|
||||
}
|
||||
|
||||
// Human-readable description, the same text /proc/pci prints for the device.
|
||||
void
|
||||
pci_describe(const struct pci_dev *d, char *out, int cap)
|
||||
{
|
||||
const char *s = class_name(d->class, d->subclass);
|
||||
int n = 0;
|
||||
while (s[n] && n < cap - 1) {
|
||||
out[n] = s[n];
|
||||
n++;
|
||||
}
|
||||
out[n] = '\0';
|
||||
}
|
||||
|
||||
void
|
||||
pci_bind(const struct pci_driver *drv)
|
||||
{
|
||||
for (int i = 0; i < ndevs; i++) {
|
||||
struct pci_dev *d = &devs[i];
|
||||
if (d->driver)
|
||||
continue;
|
||||
for (const struct pci_match *m = drv->match; m->vendor || m->class; m++) {
|
||||
if (!matches(m, d))
|
||||
continue;
|
||||
if (drv->probe(d) == 0) {
|
||||
d->driver = drv->name;
|
||||
// Publish it in the bus-agnostic device list. Done here rather than in
|
||||
// each driver's probe() so that a driver cannot forget to, and so the
|
||||
// list is populated in bind order -- which is the order a device
|
||||
// manager should show.
|
||||
struct device *dev = device_add(drv->devname ? drv->devname : drv->name,
|
||||
DEVBUS_PCI, pci_class_of(d), drv->name);
|
||||
if (dev) {
|
||||
dev->ops = (void *)drv->ops;
|
||||
dev->vendor = d->vendor;
|
||||
dev->product = d->device;
|
||||
dev->b = d->bus; dev->s = d->slot; dev->f = d->func;
|
||||
pci_describe(d, dev->desc, sizeof(dev->desc));
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// Fixed-width lowercase hex, the way lspci prints addresses.
|
||||
static int
|
||||
put_hex(char *out, uint64 v, int digits)
|
||||
{
|
||||
static const char d[] = "0123456789abcdef";
|
||||
for (int i = digits - 1; i >= 0; i--)
|
||||
*out++ = d[(v >> (i * 4)) & 0xF];
|
||||
return digits;
|
||||
}
|
||||
|
||||
static int
|
||||
put_str(char *out, const char *s)
|
||||
{
|
||||
int n = 0;
|
||||
while (s[n]) { out[n] = s[n]; n++; }
|
||||
return n;
|
||||
}
|
||||
|
||||
static const char *
|
||||
class_name(uint8 class, uint8 subclass)
|
||||
{
|
||||
switch (class) {
|
||||
case PCI_CLASS_STORAGE:
|
||||
return subclass == 0x01 ? "IDE controller"
|
||||
: subclass == 0x06 ? "SATA controller"
|
||||
: subclass == 0x08 ? "NVMe controller"
|
||||
: "storage controller";
|
||||
case PCI_CLASS_NETWORK: return "network controller";
|
||||
case PCI_CLASS_DISPLAY: return "display controller";
|
||||
case PCI_CLASS_MULTIMEDIA: return "multimedia device";
|
||||
case PCI_CLASS_BRIDGE:
|
||||
return subclass == 0x00 ? "host bridge"
|
||||
: subclass == 0x01 ? "ISA bridge"
|
||||
: subclass == 0x04 ? "PCI bridge"
|
||||
: "bridge";
|
||||
case PCI_CLASS_SERIAL_BUS:
|
||||
return subclass == 0x03 ? "USB controller" : "serial bus controller";
|
||||
}
|
||||
return "device";
|
||||
}
|
||||
|
||||
// Fills a buffer for /proc/pci. Returns bytes written.
|
||||
//
|
||||
// There is no snprintf in the kernel, so this appends by hand in the same
|
||||
// style as kernel/dev/procfs.c. The output is deliberately lspci-shaped --
|
||||
// "bus:slot.func vendor:device description" -- because that is the format
|
||||
// anyone reading it will already know how to scan.
|
||||
int
|
||||
pci_info(char *buf, int cap)
|
||||
{
|
||||
int len = 0;
|
||||
|
||||
for (int i = 0; i < ndevs; i++) {
|
||||
struct pci_dev *d = &devs[i];
|
||||
|
||||
if (len > cap - 96) // room for one more line
|
||||
break;
|
||||
|
||||
len += put_hex(buf + len, d->bus, 2);
|
||||
buf[len++] = ':';
|
||||
len += put_hex(buf + len, d->slot, 2);
|
||||
buf[len++] = '.';
|
||||
len += put_hex(buf + len, d->func, 1);
|
||||
buf[len++] = ' ';
|
||||
len += put_hex(buf + len, d->vendor, 4);
|
||||
buf[len++] = ':';
|
||||
len += put_hex(buf + len, d->device, 4);
|
||||
buf[len++] = ' ';
|
||||
len += put_str(buf + len, class_name(d->class, d->subclass));
|
||||
len += put_str(buf + len, " [");
|
||||
len += put_str(buf + len, d->driver ? d->driver : "-");
|
||||
buf[len++] = ']';
|
||||
buf[len++] = '\n';
|
||||
}
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
void
|
||||
pci_init(void)
|
||||
{
|
||||
ndevs = 0;
|
||||
|
||||
// If there is no host bridge at 0:0.0 there is no PCI, and every config
|
||||
// read will come back 0xFFFFFFFF. Saying so once beats 8192 silent probes.
|
||||
if (pci_read16(0, 0, 0, PCI_VENDOR_ID) == 0xFFFF) {
|
||||
cprintf("pci: no host bridge; bus not present\n");
|
||||
return;
|
||||
}
|
||||
|
||||
scan_bus(0);
|
||||
cprintf("pci: %d device%s\n", ndevs, ndevs == 1 ? "" : "s");
|
||||
|
||||
pci_bind_all();
|
||||
}
|
||||
431
kernel/dev/procfs.c
Normal file
431
kernel/dev/procfs.c
Normal file
|
|
@ -0,0 +1,431 @@
|
|||
// /proc — synthesised kernel status files.
|
||||
//
|
||||
// Each file is a character device node under major PROCFS_MAJOR, distinguished
|
||||
// by its minor number, created by init at boot. Reading one renders the whole
|
||||
// text into a scratch buffer and returns the slice starting at the caller's
|
||||
// file offset, so a normal read-until-zero loop terminates.
|
||||
//
|
||||
// SCOPE: these are the *global* files. Per-process directories (/proc/<pid>/…)
|
||||
// are not here, because they need something this kernel does not have yet:
|
||||
// synthetic directories, i.e. a directory whose entries are generated at
|
||||
// lookup and getdents time rather than read from disk. That is a real VFS
|
||||
// feature touching iget/ilock/dirlookup/getdents, and it is the natural
|
||||
// companion to the SchFS work rather than a bolt-on to the current fs.c.
|
||||
// The namespace (which files exist, including one directory per process) is
|
||||
// kernel/fs/procfs.c; this file only generates the *content* of the global
|
||||
// files. `ps` and `top` work through the former.
|
||||
|
||||
#include "types.h"
|
||||
#include "defs.h"
|
||||
#include "irq.h"
|
||||
#include "x86.h"
|
||||
#include "device.h"
|
||||
#include "display.h"
|
||||
#include "param.h"
|
||||
#include "memlayout.h"
|
||||
#include "mmu.h"
|
||||
#include "spinlock.h"
|
||||
#include "sleeplock.h"
|
||||
#include "fs.h"
|
||||
#include "file.h"
|
||||
#include "proc.h"
|
||||
#include "uaccess.h"
|
||||
#include "errno.h"
|
||||
|
||||
#define PROC_BUFSZ 2048
|
||||
|
||||
// --- tiny formatting helpers (no snprintf in the kernel) -------------------
|
||||
|
||||
struct sbuf {
|
||||
char *p;
|
||||
int len;
|
||||
int cap;
|
||||
};
|
||||
|
||||
static void
|
||||
sb_putc(struct sbuf *sb, char c)
|
||||
{
|
||||
if (sb->len < sb->cap - 1)
|
||||
sb->p[sb->len++] = c;
|
||||
}
|
||||
|
||||
static void
|
||||
sb_puts(struct sbuf *sb, const char *s)
|
||||
{
|
||||
while (*s)
|
||||
sb_putc(sb, *s++);
|
||||
}
|
||||
|
||||
// A string in a fixed-width column, space-padded. cprintf has no width
|
||||
// specifier and neither does this buffer, so tables build their own columns.
|
||||
// Over-long values are allowed to run past their column rather than be
|
||||
// truncated: a misaligned row is easier to read than a mangled name.
|
||||
static void
|
||||
sb_pad(struct sbuf *sb, const char *s, int width)
|
||||
{
|
||||
int n = 0;
|
||||
while (s[n]) {
|
||||
sb_putc(sb, s[n]);
|
||||
n++;
|
||||
}
|
||||
while (n++ < width)
|
||||
sb_putc(sb, ' ');
|
||||
}
|
||||
|
||||
// Four lowercase hex digits, zero-padded. PCI and USB identifiers are always
|
||||
// written this way, and a lookup table keyed on "8086:100e" does not match
|
||||
// "8086:100E" or "8086:10e".
|
||||
static void
|
||||
sb_puthex4(struct sbuf *sb, uint32 v)
|
||||
{
|
||||
static const char hex[] = "0123456789abcdef";
|
||||
for (int i = 3; i >= 0; i--)
|
||||
sb_putc(sb, hex[(v >> (i * 4)) & 0xF]);
|
||||
}
|
||||
|
||||
static void
|
||||
sb_putu(struct sbuf *sb, uint64 v)
|
||||
{
|
||||
char tmp[24];
|
||||
int i = 0;
|
||||
if (v == 0) { sb_putc(sb, '0'); return; }
|
||||
while (v && i < (int)sizeof(tmp)) { tmp[i++] = '0' + (v % 10); v /= 10; }
|
||||
while (i--) sb_putc(sb, tmp[i]);
|
||||
}
|
||||
|
||||
// "<label><value> <unit>\n" — the shape almost every /proc line takes.
|
||||
static void
|
||||
sb_kv(struct sbuf *sb, const char *label, uint64 v, const char *unit)
|
||||
{
|
||||
sb_puts(sb, label);
|
||||
sb_putu(sb, v);
|
||||
if (unit && *unit) { sb_putc(sb, ' '); sb_puts(sb, unit); }
|
||||
sb_putc(sb, '\n');
|
||||
}
|
||||
|
||||
// --- the files -------------------------------------------------------------
|
||||
|
||||
// PHYSTOP is the top of usable physical memory; kalloc hands out everything
|
||||
// above the kernel image. Reporting the total honestly and leaving "free" as a
|
||||
// share of it is better than the invented constants sysinfo() used to return.
|
||||
#define TOTAL_KB (PHYSTOP / 1024)
|
||||
|
||||
static void
|
||||
gen_meminfo(struct sbuf *sb)
|
||||
{
|
||||
uint64 total = TOTAL_KB;
|
||||
uint64 freek = kalloc_free_bytes() / 1024;
|
||||
sb_kv(sb, "MemTotal: ", total, "kB");
|
||||
sb_kv(sb, "MemFree: ", freek, "kB");
|
||||
sb_kv(sb, "MemAvailable: ", freek, "kB");
|
||||
sb_kv(sb, "Buffers: ", 0, "kB");
|
||||
sb_kv(sb, "Cached: ", 0, "kB");
|
||||
sb_kv(sb, "SwapTotal: ", 0, "kB");
|
||||
sb_kv(sb, "SwapFree: ", 0, "kB");
|
||||
}
|
||||
|
||||
static void
|
||||
gen_uptime(struct sbuf *sb)
|
||||
{
|
||||
uint t = uptime(); // timer ticks, 100 Hz
|
||||
sb_putu(sb, t / 100);
|
||||
sb_putc(sb, '.');
|
||||
sb_putu(sb, (t % 100) / 10);
|
||||
sb_putu(sb, (t % 100) % 10);
|
||||
sb_puts(sb, " 0.00\n"); // idle time: not tracked
|
||||
}
|
||||
|
||||
static void
|
||||
gen_cpuinfo(struct sbuf *sb)
|
||||
{
|
||||
// Online CPUs, not present ones -- the same rule Linux follows, and the
|
||||
// reason matters: something reading this to size a thread pool would
|
||||
// otherwise be told a VM with four processors has four to schedule on, when
|
||||
// three of them are enumerated and idle. "cpu cores" stays the present count,
|
||||
// which is what that field means.
|
||||
// Real values, from CPUID. These used to read "Schunix" and "Schunix virtual
|
||||
// CPU" -- a thing to print when you have not asked the processor, and
|
||||
// actively unhelpful, because identifying the machine is the one question
|
||||
// /proc/cpuinfo exists to answer.
|
||||
//
|
||||
// Leaf 0 returns the vendor string in EBX:EDX:ECX, in that order, which is
|
||||
// not the order anyone expects. Leaves 0x80000002..4 hold the 48-character
|
||||
// brand string. Leaf 0x80000000 reports the highest extended leaf, and asking
|
||||
// for one the CPU does not implement returns the highest supported leaf's
|
||||
// value rather than an error -- so checking first is not optional.
|
||||
char vendor[13], brand[49];
|
||||
uint32 a, b, c, d;
|
||||
|
||||
cpuid_leaf(0, &a, &b, &c, &d);
|
||||
*(uint32 *)&vendor[0] = b;
|
||||
*(uint32 *)&vendor[4] = d;
|
||||
*(uint32 *)&vendor[8] = c;
|
||||
vendor[12] = '\0';
|
||||
|
||||
brand[0] = '\0';
|
||||
cpuid_leaf(0x80000000, &a, 0, 0, 0);
|
||||
if (a >= 0x80000004) {
|
||||
for (int leaf = 0; leaf < 3; leaf++) {
|
||||
cpuid_leaf(0x80000002 + leaf, &a, &b, &c, &d);
|
||||
*(uint32 *)&brand[leaf * 16 + 0] = a;
|
||||
*(uint32 *)&brand[leaf * 16 + 4] = b;
|
||||
*(uint32 *)&brand[leaf * 16 + 8] = c;
|
||||
*(uint32 *)&brand[leaf * 16 + 12] = d;
|
||||
}
|
||||
brand[48] = '\0';
|
||||
}
|
||||
|
||||
// Intel pads the brand string with leading spaces, which read as a broken
|
||||
// field rather than a name.
|
||||
const char *model = brand;
|
||||
while (*model == ' ')
|
||||
model++;
|
||||
if (*model == '\0')
|
||||
model = "unknown";
|
||||
|
||||
for (int i = 0; i < ncpu_online; i++) {
|
||||
sb_kv(sb, "processor\t: ", i, 0);
|
||||
sb_puts(sb, "vendor_id\t: ");
|
||||
sb_puts(sb, vendor);
|
||||
sb_putc(sb, '\n');
|
||||
sb_puts(sb, "model name\t: ");
|
||||
sb_puts(sb, model);
|
||||
sb_putc(sb, '\n');
|
||||
sb_kv(sb, "cpu cores\t: ", ncpu, 0);
|
||||
sb_putc(sb, '\n');
|
||||
}
|
||||
if (ncpu_online < ncpu) {
|
||||
sb_kv(sb, "cpus present\t: ", ncpu, 0);
|
||||
sb_puts(sb, "note\t\t: additional processors are enumerated but not "
|
||||
"started; see docs/roadmap.md\n");
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
gen_version(struct sbuf *sb)
|
||||
{
|
||||
sb_puts(sb, "Schunix version 0.1.0 (schunix) #1 SMP x86_64\n");
|
||||
}
|
||||
|
||||
static void
|
||||
gen_stat(struct sbuf *sb)
|
||||
{
|
||||
uint t = uptime();
|
||||
sb_puts(sb, "cpu ");
|
||||
sb_putu(sb, t); sb_puts(sb, " 0 0 0 0 0 0 0 0 0\n");
|
||||
// Online, for the same reason as cpuinfo: top and friends count the cpuN
|
||||
// lines here to decide how many cores to draw.
|
||||
for (int i = 0; i < ncpu_online; i++) {
|
||||
sb_puts(sb, "cpu");
|
||||
sb_putu(sb, i);
|
||||
sb_putc(sb, ' ');
|
||||
sb_putu(sb, t); sb_puts(sb, " 0 0 0 0 0 0 0 0 0\n");
|
||||
}
|
||||
sb_kv(sb, "processes ", (uint64)proc_num(), 0);
|
||||
sb_kv(sb, "procs_running ", 1, 0);
|
||||
sb_kv(sb, "btime ", 0, 0);
|
||||
}
|
||||
|
||||
// Everything attached, whatever bus found it. The one place that answers "what
|
||||
// is in this machine" across PCI, USB and platform devices at once -- which is
|
||||
// the question a device manager is, so this is where one would read from.
|
||||
//
|
||||
// Columns rather than key/value: this is a table of like things, and a reader
|
||||
// scanning for an unclaimed device wants the driver column to line up.
|
||||
static void
|
||||
gen_devices(struct sbuf *sb)
|
||||
{
|
||||
sb_puts(sb, "name bus class driver id device\n");
|
||||
for (int i = 0; i < device_count(); i++) {
|
||||
struct device *d = device_at(i);
|
||||
if (!d)
|
||||
break;
|
||||
// 14 for the name and driver columns: "virtio-gpu0" is eleven characters
|
||||
// and overran a ten-wide column, which sb_pad allows on purpose but which
|
||||
// made the whole table unreadable.
|
||||
sb_pad(sb, d->name, 14);
|
||||
sb_pad(sb, dev_bus_name(d->bus), 10);
|
||||
sb_pad(sb, dev_class_name(d->class), 10);
|
||||
// An unclaimed device is the interesting row, so it gets a visible marker
|
||||
// rather than an empty column.
|
||||
sb_pad(sb, d->driver ? d->driver : "(none)", 14);
|
||||
|
||||
// vendor:product, the way lspci and lsusb print it. The kernel has no
|
||||
// business resolving these to names -- that is what /usr/share/usb.ids is
|
||||
// for, and it belongs to a userspace tool. What the kernel owes is the
|
||||
// number, in the form a lookup expects.
|
||||
if (d->vendor || d->product) {
|
||||
sb_puthex4(sb, d->vendor);
|
||||
sb_putc(sb, ':');
|
||||
sb_puthex4(sb, d->product);
|
||||
sb_putc(sb, ' ');
|
||||
} else {
|
||||
sb_puts(sb, " ");
|
||||
}
|
||||
sb_puts(sb, d->desc[0] ? d->desc : "-");
|
||||
sb_putc(sb, '\n');
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
gen_loadavg(struct sbuf *sb)
|
||||
{
|
||||
// No load tracking; report zeros but a truthful process count.
|
||||
sb_puts(sb, "0.00 0.00 0.00 1/");
|
||||
sb_putu(sb, (uint64)proc_num());
|
||||
sb_puts(sb, " 1\n");
|
||||
}
|
||||
|
||||
// df and mountpoint parse this. One root filesystem, no mount table yet.
|
||||
static void
|
||||
gen_mounts(struct sbuf *sb)
|
||||
{
|
||||
// Root must be last: df keeps the last mount point that prefixes the path
|
||||
// it was asked about, so listing / first made `df /` report /proc instead.
|
||||
sb_puts(sb, "devtmpfs /dev devtmpfs rw 0 0\n");
|
||||
sb_puts(sb, "proc /proc proc rw 0 0\n");
|
||||
sb_puts(sb, "/dev/root / schfs rw 0 0\n");
|
||||
}
|
||||
|
||||
static void
|
||||
gen_filesystems(struct sbuf *sb)
|
||||
{
|
||||
sb_puts(sb, "\tschfs\n");
|
||||
sb_puts(sb, "nodev\tproc\n");
|
||||
sb_puts(sb, "nodev\tdevtmpfs\n");
|
||||
}
|
||||
|
||||
// --- dispatch --------------------------------------------------------------
|
||||
|
||||
#define PROC_MEMINFO 1
|
||||
#define PROC_UPTIME 2
|
||||
#define PROC_CPUINFO 3
|
||||
#define PROC_VERSION 4
|
||||
#define PROC_STAT 5
|
||||
#define PROC_LOADAVG 6
|
||||
#define PROC_MOUNTS 7
|
||||
#define PROC_FILESYSTEMS 8
|
||||
#define PROC_SCHFS 9
|
||||
#define PROC_SYSRQ 10
|
||||
#define PROC_PCI 11
|
||||
#define PROC_INTERRUPTS 12
|
||||
#define PROC_DEVICES 13
|
||||
|
||||
// Render global file `id` (a PROC_* constant) into `buf`. Returns its length.
|
||||
//
|
||||
// Split out of procread() so the /proc *filesystem* (kernel/fs/procfs.c) can
|
||||
// use the same generators: that file owns the namespace, this one owns the
|
||||
// content, and they have no other business with each other.
|
||||
int
|
||||
proc_gen_global(int id, char *buf, int cap)
|
||||
{
|
||||
struct sbuf sb = { buf, 0, cap };
|
||||
|
||||
switch (id) {
|
||||
case PROC_MEMINFO: gen_meminfo(&sb); break;
|
||||
case PROC_UPTIME: gen_uptime(&sb); break;
|
||||
case PROC_CPUINFO: gen_cpuinfo(&sb); break;
|
||||
case PROC_VERSION: gen_version(&sb); break;
|
||||
case PROC_STAT: gen_stat(&sb); break;
|
||||
case PROC_LOADAVG: gen_loadavg(&sb); break;
|
||||
case PROC_MOUNTS: gen_mounts(&sb); break;
|
||||
case PROC_FILESYSTEMS: gen_filesystems(&sb); break;
|
||||
// SchFS renders straight into the buffer rather than through sbuf, because
|
||||
// it lives in kernel/fs/schfs and must not depend on this file's helpers.
|
||||
case PROC_SCHFS: sb.len = schfs_kinfo(buf, PROC_BUFSZ); break;
|
||||
// Same arrangement as SchFS above: pci.c renders its own lines so that it
|
||||
// does not depend on this file's helpers.
|
||||
case PROC_PCI: sb.len = pci_info(buf, PROC_BUFSZ); break;
|
||||
// Likewise irq.c: it owns the table, so it renders it.
|
||||
case PROC_INTERRUPTS: sb.len = irq_info(buf, PROC_BUFSZ); break;
|
||||
case PROC_DEVICES: gen_devices(&sb); break;
|
||||
default: return 0;
|
||||
}
|
||||
return sb.len;
|
||||
}
|
||||
|
||||
int
|
||||
procread(struct inode *ip, uint off, char *dst, int n)
|
||||
{
|
||||
char buf[PROC_BUFSZ];
|
||||
int len = proc_gen_global(ip->minor, buf, PROC_BUFSZ);
|
||||
|
||||
if (len <= 0 || (int)off >= len)
|
||||
return 0; // EOF
|
||||
int avail = len - (int)off;
|
||||
if (n > avail)
|
||||
n = avail;
|
||||
memmove(dst, buf + off, n);
|
||||
return n;
|
||||
}
|
||||
|
||||
// /proc is read-only, with one deliberate exception.
|
||||
//
|
||||
// /proc/sysrq is the same idea as Linux's sysrq-trigger: a way to make the
|
||||
// kernel do something drastic on purpose. Right now the only command is 'c',
|
||||
// crash — which exists because the crash screen is otherwise the one part of
|
||||
// the system that can only be tested by having a bug. A screen you cannot look
|
||||
// at until something goes wrong is a screen nobody has ever actually read.
|
||||
// One sysrq command byte. Shared by the device path (procwrite, below) and
|
||||
// the filesystem path (kernel/fs/procfs.c), so the two cannot drift.
|
||||
static void
|
||||
proc_sysrq_cmd(char cmd)
|
||||
{
|
||||
switch (cmd) {
|
||||
case 'c':
|
||||
panic("sysrq: crash requested from /proc/sysrq");
|
||||
case 'u':
|
||||
xhci_dump();
|
||||
break;
|
||||
case 's':
|
||||
usb_storage_dump();
|
||||
break;
|
||||
case 'd':
|
||||
display_selftest();
|
||||
break;
|
||||
case 'n':
|
||||
e1000_selftest();
|
||||
break;
|
||||
case 't':
|
||||
text_selftest();
|
||||
break;
|
||||
default:
|
||||
cprintf("sysrq: unknown command '%c' "
|
||||
"(known: c = crash, u = dump USB, s = read the USB disk, "
|
||||
"d = display self-test)\n",
|
||||
cmd);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// `user_src` says whether `src` is a user address that still needs copying in;
|
||||
// the filesystem hands one over, the device path has already copied.
|
||||
int
|
||||
proc_write_sysrq(int user_src, uint64 src, uint n)
|
||||
{
|
||||
char c;
|
||||
|
||||
if (n < 1)
|
||||
return -1;
|
||||
if (user_src) {
|
||||
if (copy_from_user(&c, src, 1) < 0)
|
||||
return -EFAULT;
|
||||
} else {
|
||||
c = *(char *)src;
|
||||
}
|
||||
proc_sysrq_cmd(c);
|
||||
return (int)n;
|
||||
}
|
||||
|
||||
// The character-device path: /proc/sysrq reached through the old mknod'd node
|
||||
// rather than through the /proc filesystem. `src` is already a kernel buffer
|
||||
// here, which is why it passes user_src = 0.
|
||||
int
|
||||
procwrite(struct inode *ip, uint off, char *src, int n)
|
||||
{
|
||||
(void)off;
|
||||
|
||||
if (ip->minor != PROC_SYSRQ)
|
||||
return -1;
|
||||
return proc_write_sysrq(0, (uint64)src, (uint)n);
|
||||
}
|
||||
92
kernel/dev/ramdisk.c
Normal file
92
kernel/dev/ramdisk.c
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
// Memory-backed block device.
|
||||
//
|
||||
// Serves an entire SCHFS image out of RAM, so the system can boot and run a
|
||||
// shell with the physical disk untouched. That is what makes destructive work
|
||||
// on the real disk survivable: reformatting, running mkfs, or bringing up a new
|
||||
// on-disk format cannot take away the root you are running from. It is also the
|
||||
// first piece of the installer -- an install environment is exactly "a root in
|
||||
// RAM plus a disk you are allowed to destroy".
|
||||
//
|
||||
// The image is linked into the kernel as a binary blob (see the ramdisk_obj
|
||||
// rule in the Makefile) and is served in place: the blob lives in writable
|
||||
// kernel memory, so writes to the ramdisk simply modify it. They are lost on
|
||||
// reboot, which is the expected behaviour for this kind of root.
|
||||
//
|
||||
// Enabled by building with RAMROOT=1. Without it this file still compiles, the
|
||||
// device reports zero size, and the kernel boots from the IDE disk as before.
|
||||
|
||||
#include "types.h"
|
||||
#include "defs.h"
|
||||
#include "param.h"
|
||||
#include "memlayout.h"
|
||||
#include "mmu.h"
|
||||
#include "spinlock.h"
|
||||
#include "sleeplock.h"
|
||||
#include "fs.h"
|
||||
#include "buf.h"
|
||||
|
||||
#ifdef RAMDISK_ROOT
|
||||
// Supplied by `ld -r -b binary` over build/fs-ram.img, renamed by objcopy.
|
||||
extern uchar _binary_ramdisk_start[];
|
||||
extern uchar _binary_ramdisk_size[];
|
||||
#endif
|
||||
|
||||
static uchar *rd_base;
|
||||
static uint64 rd_size; // bytes
|
||||
static uint rd_blocks; // rd_size / BSIZE
|
||||
|
||||
void
|
||||
ramdiskinit(void)
|
||||
{
|
||||
#ifdef RAMDISK_ROOT
|
||||
rd_base = _binary_ramdisk_start;
|
||||
rd_size = (uint64)_binary_ramdisk_size;
|
||||
rd_blocks = (uint)(rd_size / BSIZE);
|
||||
|
||||
if(rd_blocks == 0)
|
||||
panic("ramdiskinit: embedded image is empty");
|
||||
|
||||
// A truncated image is worth catching here rather than as a confusing
|
||||
// "invalid file system" panic later: the superblock lives in block 1.
|
||||
if(rd_blocks < 2)
|
||||
panic("ramdiskinit: embedded image smaller than a superblock");
|
||||
|
||||
cprintf("ramdisk: %u blocks (%u KB) at %p\n",
|
||||
rd_blocks, (uint)(rd_size / 1024), rd_base);
|
||||
#else
|
||||
rd_base = 0;
|
||||
rd_size = 0;
|
||||
rd_blocks = 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
int
|
||||
ramdisk_present(void)
|
||||
{
|
||||
return rd_blocks != 0;
|
||||
}
|
||||
|
||||
// Sync buf with the in-memory image. Mirrors iderw()'s contract: on a read,
|
||||
// fill b->data and set B_VALID; on a write, copy out and clear B_DIRTY.
|
||||
void
|
||||
ramdiskrw(struct buf *b)
|
||||
{
|
||||
if(!holdingsleep(&b->lock))
|
||||
panic("ramdiskrw: buf not locked");
|
||||
if((b->flags & (B_VALID|B_DIRTY)) == B_VALID)
|
||||
panic("ramdiskrw: nothing to do");
|
||||
if(rd_base == 0)
|
||||
panic("ramdiskrw: no ramdisk");
|
||||
if(b->blockno >= rd_blocks)
|
||||
panic("ramdiskrw: block out of range");
|
||||
|
||||
uchar *p = rd_base + (uint64)b->blockno * BSIZE;
|
||||
|
||||
if(b->flags & B_DIRTY){
|
||||
memmove(p, b->data, BSIZE);
|
||||
b->flags &= ~B_DIRTY;
|
||||
} else {
|
||||
memmove(b->data, p, BSIZE);
|
||||
}
|
||||
b->flags |= B_VALID;
|
||||
}
|
||||
242
kernel/dev/text.c
Normal file
242
kernel/dev/text.c
Normal file
|
|
@ -0,0 +1,242 @@
|
|||
// Text rendering. See include/text.h for what this replaces and why.
|
||||
|
||||
#include "types.h"
|
||||
#include "defs.h"
|
||||
#include "param.h"
|
||||
#include "text.h"
|
||||
|
||||
// Declared, not included. include/font.h *defines* font_data rather than
|
||||
// declaring it, so including it here as well as in vga.c gives two definitions
|
||||
// of the same array and the link fails. The header is generated data; leaving
|
||||
// it alone and referring to the symbol is less fragile than editing it.
|
||||
extern unsigned char font_data[256][16];
|
||||
|
||||
// --- UTF-8 -----------------------------------------------------------------
|
||||
|
||||
uint32
|
||||
utf8_next(const char **p)
|
||||
{
|
||||
const unsigned char *s = (const unsigned char *)*p;
|
||||
uint32 c = s[0];
|
||||
|
||||
if (c < 0x80) {
|
||||
*p += 1;
|
||||
return c;
|
||||
}
|
||||
|
||||
int extra;
|
||||
uint32 cp;
|
||||
if ((c & 0xE0) == 0xC0) { extra = 1; cp = c & 0x1F; }
|
||||
else if ((c & 0xF0) == 0xE0) { extra = 2; cp = c & 0x0F; }
|
||||
else if ((c & 0xF8) == 0xF0) { extra = 3; cp = c & 0x07; }
|
||||
else {
|
||||
// A continuation byte or 0xF8..0xFF where a lead byte should be. Consume
|
||||
// exactly one so a malformed string cannot stall the loop -- a decoder that
|
||||
// does not advance on bad input is a hang, not a rendering artefact.
|
||||
*p += 1;
|
||||
return 0xFFFD;
|
||||
}
|
||||
|
||||
for (int i = 1; i <= extra; i++) {
|
||||
if ((s[i] & 0xC0) != 0x80) { // truncated sequence
|
||||
*p += 1;
|
||||
return 0xFFFD;
|
||||
}
|
||||
cp = (cp << 6) | (s[i] & 0x3F);
|
||||
}
|
||||
*p += extra + 1;
|
||||
|
||||
// Surrogates are not characters, and an overlong encoding is the classic way
|
||||
// to smuggle a byte past a filter that checks the decoded form.
|
||||
if (cp >= 0xD800 && cp <= 0xDFFF)
|
||||
return 0xFFFD;
|
||||
if ((extra == 1 && cp < 0x80) || (extra == 2 && cp < 0x800) ||
|
||||
(extra == 3 && cp < 0x10000))
|
||||
return 0xFFFD;
|
||||
return cp;
|
||||
}
|
||||
|
||||
// --- the built-in bitmap font as a provider --------------------------------
|
||||
//
|
||||
// font_data is 256 CP437 glyphs of 8x16, one bit per pixel. Expanded here into
|
||||
// 8-bit coverage so that every caller works in one representation and an
|
||||
// outline rasteriser can be added without touching any of them.
|
||||
//
|
||||
// The expansion buffer is static and reused per glyph. That is fine because
|
||||
// glyphs are consumed immediately by the blitter and nothing holds one; it
|
||||
// would not be fine the moment a caller kept a struct glyph across calls, which
|
||||
// is why the header says nothing about the lifetime being longer than that.
|
||||
|
||||
#define BUILTIN_W 8
|
||||
#define BUILTIN_H 16
|
||||
|
||||
static unsigned char builtin_cov[BUILTIN_W * BUILTIN_H];
|
||||
|
||||
// Unicode to CP437, for the range where they differ and it matters.
|
||||
//
|
||||
// The built-in font is CP437, not Latin-1 -- a fact that has bitten this
|
||||
// codebase before. Rather than pretend otherwise, map the handful of
|
||||
// codepoints worth having and fall back for the rest. This table exists to be
|
||||
// *deleted* when a real font arrives; it is a bridge, not a design.
|
||||
static uint32
|
||||
to_cp437(uint32 cp)
|
||||
{
|
||||
if (cp < 0x80)
|
||||
return cp;
|
||||
switch (cp) {
|
||||
case 0x00A0: return 0x20; // no-break space
|
||||
case 0x00B0: return 0xF8; // degree
|
||||
case 0x00B1: return 0xF1; // plus-minus
|
||||
case 0x00B7: return 0xFA; // middle dot
|
||||
case 0x2022: return 0x07; // bullet
|
||||
case 0x2190: return 0x1B; // arrows
|
||||
case 0x2191: return 0x18;
|
||||
case 0x2192: return 0x1A;
|
||||
case 0x2193: return 0x19;
|
||||
case 0x2500: return 0xC4; // box drawing, the part worth keeping
|
||||
case 0x2502: return 0xB3;
|
||||
case 0x250C: return 0xDA;
|
||||
case 0x2510: return 0xBF;
|
||||
case 0x2514: return 0xC0;
|
||||
case 0x2518: return 0xD9;
|
||||
case 0x251C: return 0xC3;
|
||||
case 0x2524: return 0xB4;
|
||||
case 0x252C: return 0xC2;
|
||||
case 0x2534: return 0xC1;
|
||||
case 0x253C: return 0xC5;
|
||||
case 0x2588: return 0xDB; // full block
|
||||
case 0x2591: return 0xB0; // shades
|
||||
case 0x2592: return 0xB1;
|
||||
case 0x2593: return 0xB2;
|
||||
default: return 0; // "no glyph"
|
||||
}
|
||||
}
|
||||
|
||||
static int
|
||||
builtin_glyph(struct font *f, uint32 cp, struct glyph *out)
|
||||
{
|
||||
uint32 idx = to_cp437(cp);
|
||||
if (idx == 0 && cp != 0)
|
||||
return -1; // caller substitutes
|
||||
|
||||
for (int y = 0; y < BUILTIN_H; y++) {
|
||||
unsigned char bits = font_data[idx & 0xFF][y];
|
||||
for (int x = 0; x < BUILTIN_W; x++)
|
||||
builtin_cov[y * BUILTIN_W + x] = (bits & (0x80 >> x)) ? 0xFF : 0x00;
|
||||
}
|
||||
|
||||
out->cov = builtin_cov;
|
||||
out->w = BUILTIN_W;
|
||||
out->h = BUILTIN_H;
|
||||
out->bearing_x = 0;
|
||||
// The whole cell sits above the baseline, less two rows of descender space.
|
||||
// The value matters only for consistency with whatever font comes next.
|
||||
out->bearing_y = BUILTIN_H - 3;
|
||||
out->advance = BUILTIN_W;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int builtin_height(struct font *f) { return BUILTIN_H; }
|
||||
static int builtin_baseline(struct font *f) { return BUILTIN_H - 3; }
|
||||
|
||||
static const struct font_ops builtin_ops = {
|
||||
.glyph = builtin_glyph,
|
||||
.height = builtin_height,
|
||||
.baseline = builtin_baseline,
|
||||
};
|
||||
|
||||
static struct font builtin = { &builtin_ops, 0 };
|
||||
|
||||
struct font *
|
||||
font_builtin(void)
|
||||
{
|
||||
return &builtin;
|
||||
}
|
||||
|
||||
// --- blitting --------------------------------------------------------------
|
||||
|
||||
// Blend `color` over the pixel at (x, y) by coverage a.
|
||||
//
|
||||
// Reads the destination, which a bitmap font never needs -- but an
|
||||
// anti-aliased edge is defined as a blend with what is already there, and a
|
||||
// renderer that cannot do it is a renderer that cannot draw a real typeface.
|
||||
static void
|
||||
blend_px(struct surface *s, int x, int y, uint32 color, unsigned a)
|
||||
{
|
||||
if (x < 0 || y < 0 || x >= s->width || y >= s->height)
|
||||
return;
|
||||
|
||||
uint32 *px = (uint32 *)((unsigned char *)s->pixels + (uint64)y * s->stride) + x;
|
||||
if (a >= 255) {
|
||||
*px = color;
|
||||
return;
|
||||
}
|
||||
uint32 dst = *px;
|
||||
uint32 r = (((color >> 16) & 0xFF) * a + ((dst >> 16) & 0xFF) * (255 - a)) / 255;
|
||||
uint32 g = (((color >> 8) & 0xFF) * a + ((dst >> 8) & 0xFF) * (255 - a)) / 255;
|
||||
uint32 b = (((color ) & 0xFF) * a + ((dst ) & 0xFF) * (255 - a)) / 255;
|
||||
*px = (r << 16) | (g << 8) | b;
|
||||
}
|
||||
|
||||
int
|
||||
text_draw_glyph(struct surface *s, struct font *f, int x, int y,
|
||||
uint32 cp, uint32 color)
|
||||
{
|
||||
struct glyph g;
|
||||
|
||||
if (f->ops->glyph(f, cp, &g) != 0) {
|
||||
// Substitute rather than skip. A missing glyph that draws nothing makes
|
||||
// text silently shorter and the fault impossible to see; a visible box
|
||||
// says exactly what happened and where.
|
||||
if (f->ops->glyph(f, 0x25A1, &g) != 0 && // white square
|
||||
f->ops->glyph(f, '?', &g) != 0)
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ox = x + g.bearing_x;
|
||||
int oy = y - g.bearing_y; // bearing is measured up from the baseline
|
||||
|
||||
for (int gy = 0; gy < g.h; gy++)
|
||||
for (int gx = 0; gx < g.w; gx++) {
|
||||
unsigned a = g.cov[gy * g.w + gx];
|
||||
if (a)
|
||||
blend_px(s, ox + gx, oy + gy, color, a);
|
||||
}
|
||||
|
||||
return g.advance;
|
||||
}
|
||||
|
||||
int
|
||||
text_draw(struct surface *s, struct font *f, int x, int y,
|
||||
const char *utf8, uint32 color)
|
||||
{
|
||||
int pen = x;
|
||||
const char *p = utf8;
|
||||
|
||||
while (*p) {
|
||||
uint32 cp = utf8_next(&p);
|
||||
if (cp == '\n')
|
||||
break; // layout is the caller's business, not ours
|
||||
pen += text_draw_glyph(s, f, pen, y, cp, color);
|
||||
}
|
||||
return pen - x;
|
||||
}
|
||||
|
||||
int
|
||||
text_measure(struct font *f, const char *utf8)
|
||||
{
|
||||
int w = 0;
|
||||
const char *p = utf8;
|
||||
struct glyph g;
|
||||
|
||||
while (*p) {
|
||||
uint32 cp = utf8_next(&p);
|
||||
if (cp == '\n')
|
||||
break;
|
||||
if (f->ops->glyph(f, cp, &g) == 0)
|
||||
w += g.advance;
|
||||
else if (f->ops->glyph(f, '?', &g) == 0)
|
||||
w += g.advance;
|
||||
}
|
||||
return w;
|
||||
}
|
||||
606
kernel/dev/ttf.c
Normal file
606
kernel/dev/ttf.c
Normal file
|
|
@ -0,0 +1,606 @@
|
|||
// TrueType outlines. See include/ttf.h for scope.
|
||||
//
|
||||
// Written from Apple's TrueType Reference Manual and the OpenType
|
||||
// specification. Nothing here is derived from FreeType or stb_truetype.
|
||||
|
||||
#ifdef TTF_HOST
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
typedef unsigned char uint8;
|
||||
typedef unsigned short uint16;
|
||||
typedef unsigned int uint32;
|
||||
typedef signed short int16;
|
||||
#else
|
||||
#include "types.h"
|
||||
#include "defs.h"
|
||||
#include "param.h"
|
||||
#endif
|
||||
|
||||
#include "text.h"
|
||||
#include "ttf.h"
|
||||
|
||||
// Every multi-byte field in an sfnt file is big-endian, whatever the machine
|
||||
// is. Read them byte by byte rather than casting: the file is not guaranteed
|
||||
// to be aligned, and on x86 a cast would silently byte-swap everything.
|
||||
static uint16 rd16(const uint8 *p) { return (uint16)((p[0] << 8) | p[1]); }
|
||||
static int16 rs16(const uint8 *p) { return (int16)rd16(p); }
|
||||
static uint32 rd32(const uint8 *p)
|
||||
{
|
||||
return ((uint32)p[0] << 24) | ((uint32)p[1] << 16) |
|
||||
((uint32)p[2] << 8) | p[3];
|
||||
}
|
||||
|
||||
static uint32
|
||||
find_table(const uint8 *d, uint32 len, const char *tag, uint32 *out_len)
|
||||
{
|
||||
if (len < 12)
|
||||
return 0;
|
||||
int n = rd16(d + 4);
|
||||
for (int i = 0; i < n; i++) {
|
||||
const uint8 *e = d + 12 + 16 * i;
|
||||
if ((uint32)(12 + 16 * i + 16) > len)
|
||||
return 0;
|
||||
if (e[0] == (uint8)tag[0] && e[1] == (uint8)tag[1] &&
|
||||
e[2] == (uint8)tag[2] && e[3] == (uint8)tag[3]) {
|
||||
uint32 off = rd32(e + 8), sz = rd32(e + 12);
|
||||
if (off >= len)
|
||||
return 0;
|
||||
if (out_len)
|
||||
*out_len = sz;
|
||||
return off;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
ttf_open(struct ttf *f, const void *data, uint32 len, int px)
|
||||
{
|
||||
const uint8 *d = data;
|
||||
|
||||
memset(f, 0, sizeof(*f));
|
||||
f->data = d;
|
||||
f->len = len;
|
||||
f->px = px;
|
||||
|
||||
// 0x00010000 is TrueType; 'true' is Apple's older tag. 'OTTO' is CFF
|
||||
// outlines, which this cannot read -- rejecting it explicitly beats failing
|
||||
// later with an empty glyph and no explanation.
|
||||
if (len < 12)
|
||||
return -1;
|
||||
uint32 ver = rd32(d);
|
||||
if (ver == 0x4F54544FU) { // 'OTTO'
|
||||
return -1;
|
||||
}
|
||||
if (ver != 0x00010000U && ver != 0x74727565U)
|
||||
return -1;
|
||||
|
||||
uint32 head = find_table(d, len, "head", 0);
|
||||
uint32 maxp = find_table(d, len, "maxp", 0);
|
||||
uint32 hhea = find_table(d, len, "hhea", 0);
|
||||
f->hmtx = find_table(d, len, "hmtx", 0);
|
||||
f->glyf = find_table(d, len, "glyf", &f->glyf_len);
|
||||
f->loca = find_table(d, len, "loca", &f->loca_len);
|
||||
if (!head || !maxp || !hhea || !f->hmtx || !f->glyf || !f->loca)
|
||||
return -1;
|
||||
|
||||
f->units_per_em = rd16(d + head + 18);
|
||||
f->long_loca = rs16(d + head + 50);
|
||||
f->num_glyphs = rd16(d + maxp + 4);
|
||||
f->ascent = rs16(d + hhea + 4);
|
||||
f->descent = rs16(d + hhea + 6);
|
||||
f->num_hmetrics = rd16(d + hhea + 34);
|
||||
if (f->units_per_em == 0)
|
||||
return -1;
|
||||
|
||||
// cmap: prefer a Windows BMP subtable (platform 3, encoding 1), fall back to
|
||||
// symbol (3,0). Format 4 only -- it covers the basic multilingual plane,
|
||||
// which is every codepoint this system will draw for a long time.
|
||||
uint32 cmap = find_table(d, len, "cmap", 0);
|
||||
if (cmap) {
|
||||
int n = rd16(d + cmap + 2);
|
||||
for (int i = 0; i < n; i++) {
|
||||
const uint8 *rec = d + cmap + 4 + 8 * i;
|
||||
int plat = rd16(rec), enc = rd16(rec + 2);
|
||||
uint32 sub = cmap + rd32(rec + 4);
|
||||
if (sub + 4 > len)
|
||||
continue;
|
||||
if (rd16(d + sub) != 4)
|
||||
continue;
|
||||
if (plat == 3 && (enc == 1 || enc == 0)) {
|
||||
f->cmap4 = sub;
|
||||
if (enc == 1)
|
||||
break; // exact match; keep looking otherwise
|
||||
}
|
||||
}
|
||||
}
|
||||
return f->cmap4 ? 0 : -1;
|
||||
}
|
||||
|
||||
// Codepoint to glyph index, cmap format 4.
|
||||
//
|
||||
// The format is four parallel arrays of segments plus an idDelta/idRangeOffset
|
||||
// pair whose second form is the awkward one: idRangeOffset is a byte offset
|
||||
// *from its own address* into glyphIdArray, which is why the arithmetic below
|
||||
// looks like it does rather than being an index.
|
||||
static int
|
||||
glyph_index(struct ttf *f, uint32 cp)
|
||||
{
|
||||
if (cp > 0xFFFF || !f->cmap4)
|
||||
return 0;
|
||||
|
||||
const uint8 *t = f->data + f->cmap4;
|
||||
int segx2 = rd16(t + 6);
|
||||
const uint8 *ends = t + 14;
|
||||
const uint8 *starts = ends + segx2 + 2;
|
||||
const uint8 *deltas = starts + segx2;
|
||||
const uint8 *ranges = deltas + segx2;
|
||||
|
||||
for (int i = 0; i < segx2; i += 2) {
|
||||
if (rd16(ends + i) < cp)
|
||||
continue;
|
||||
if (rd16(starts + i) > cp)
|
||||
return 0; // in a gap between segments
|
||||
int ro = rd16(ranges + i);
|
||||
if (ro == 0)
|
||||
return (int)((cp + rd16(deltas + i)) & 0xFFFF);
|
||||
const uint8 *p = ranges + i + ro + (cp - rd16(starts + i)) * 2;
|
||||
if (p + 1 >= f->data + f->len)
|
||||
return 0;
|
||||
int g = rd16(p);
|
||||
return g ? (int)((g + rd16(deltas + i)) & 0xFFFF) : 0;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
advance_for(struct ttf *f, int gid)
|
||||
{
|
||||
// hmtx holds numberOfHMetrics full entries, then only left side bearings --
|
||||
// monospaced tails are stored once. Clamping is the whole trick.
|
||||
int i = gid < f->num_hmetrics ? gid : f->num_hmetrics - 1;
|
||||
if (i < 0)
|
||||
return 0;
|
||||
return rd16(f->data + f->hmtx + 4 * i);
|
||||
}
|
||||
|
||||
// --- outline extraction ----------------------------------------------------
|
||||
|
||||
#define MAXPTS 512
|
||||
#define MAXCONT 32
|
||||
|
||||
struct outline {
|
||||
int x[MAXPTS], y[MAXPTS]; // font units
|
||||
uint8 on[MAXPTS]; // on-curve flag
|
||||
int ends[MAXCONT];
|
||||
int npts, ncont;
|
||||
};
|
||||
|
||||
static int
|
||||
load_outline(struct ttf *f, int gid, struct outline *o)
|
||||
{
|
||||
uint32 g0, g1;
|
||||
|
||||
if (gid < 0 || gid >= f->num_glyphs)
|
||||
return -1;
|
||||
if (f->long_loca) {
|
||||
g0 = rd32(f->data + f->loca + 4 * gid);
|
||||
g1 = rd32(f->data + f->loca + 4 * gid + 4);
|
||||
} else {
|
||||
g0 = (uint32)rd16(f->data + f->loca + 2 * gid) * 2;
|
||||
g1 = (uint32)rd16(f->data + f->loca + 2 * gid + 2) * 2;
|
||||
}
|
||||
o->npts = o->ncont = 0;
|
||||
if (g0 >= g1)
|
||||
return 0; // empty glyph, e.g. space -- not an error
|
||||
|
||||
const uint8 *g = f->data + f->glyf + g0;
|
||||
int nc = rs16(g);
|
||||
|
||||
// A negative contour count means a composite: the glyph is other glyphs
|
||||
// placed at offsets. This is not an exotic case to skip -- `i` is built from
|
||||
// a dotless i plus the dot, so skipping composites loses a letter from
|
||||
// "Schunix", which is exactly how the gap was found.
|
||||
//
|
||||
// Only the translated case is handled: components with a scale or a 2x2
|
||||
// matrix are rare outside of synthesised obliques, and applying a transform
|
||||
// wrongly is worse than not applying one.
|
||||
if (nc < 0) {
|
||||
const uint8 *p = g + 10;
|
||||
int flags;
|
||||
do {
|
||||
flags = rd16(p);
|
||||
int cgid = rd16(p + 2);
|
||||
p += 4;
|
||||
|
||||
int dx, dy;
|
||||
if (flags & 0x0001) { // ARG_1_AND_2_ARE_WORDS
|
||||
dx = rs16(p); dy = rs16(p + 2); p += 4;
|
||||
} else {
|
||||
dx = (signed char)p[0]; dy = (signed char)p[1]; p += 2;
|
||||
}
|
||||
if (!(flags & 0x0002)) // ARGS_ARE_XY_VALUES
|
||||
dx = dy = 0; // point matching: not supported
|
||||
|
||||
if (flags & 0x0008) p += 2; // WE_HAVE_A_SCALE
|
||||
else if (flags & 0x0040) p += 4; // X_AND_Y_SCALE
|
||||
else if (flags & 0x0080) p += 8; // TWO_BY_TWO
|
||||
|
||||
// Recurse, then merge the component's contours into ours with the offset
|
||||
// applied. Depth is bounded by the recursion into load_outline, which
|
||||
// rejects anything that would overflow the point arrays.
|
||||
struct outline sub;
|
||||
if (load_outline(f, cgid, &sub) != 0)
|
||||
continue;
|
||||
if (o->npts + sub.npts > MAXPTS || o->ncont + sub.ncont > MAXCONT)
|
||||
break;
|
||||
for (int i = 0; i < sub.npts; i++) {
|
||||
o->x[o->npts + i] = sub.x[i] + dx;
|
||||
o->y[o->npts + i] = sub.y[i] + dy;
|
||||
o->on[o->npts + i] = sub.on[i];
|
||||
}
|
||||
for (int i = 0; i < sub.ncont; i++)
|
||||
o->ends[o->ncont + i] = sub.ends[i] + o->npts;
|
||||
o->npts += sub.npts;
|
||||
o->ncont += sub.ncont;
|
||||
} while (flags & 0x0020); // MORE_COMPONENTS
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (nc > MAXCONT)
|
||||
return -1;
|
||||
o->ncont = nc;
|
||||
|
||||
const uint8 *p = g + 10;
|
||||
for (int i = 0; i < nc; i++) {
|
||||
o->ends[i] = rd16(p);
|
||||
p += 2;
|
||||
}
|
||||
int npts = nc ? o->ends[nc - 1] + 1 : 0;
|
||||
if (npts > MAXPTS)
|
||||
return -1;
|
||||
o->npts = npts;
|
||||
|
||||
p += 2 + rd16(p); // skip the hinting instructions
|
||||
|
||||
// Flags are run-length encoded: bit 3 means "the next byte is a repeat
|
||||
// count". Missing that turns every glyph with a repeated flag into garbage.
|
||||
uint8 flags[MAXPTS];
|
||||
for (int i = 0; i < npts; ) {
|
||||
uint8 fl = *p++;
|
||||
flags[i++] = fl;
|
||||
if (fl & 8) {
|
||||
int rep = *p++;
|
||||
while (rep-- > 0 && i < npts)
|
||||
flags[i++] = fl;
|
||||
}
|
||||
}
|
||||
|
||||
// Coordinates are deltas, and each axis is stored in one of three ways
|
||||
// depending on two flag bits: 8-bit with a separate sign bit, or 16-bit
|
||||
// signed, or "same as the previous point" which occupies no bytes at all.
|
||||
int v = 0;
|
||||
for (int i = 0; i < npts; i++) {
|
||||
if (flags[i] & 2) {
|
||||
int dv = *p++;
|
||||
v += (flags[i] & 16) ? dv : -dv;
|
||||
} else if (!(flags[i] & 16)) {
|
||||
v += rs16(p);
|
||||
p += 2;
|
||||
}
|
||||
o->x[i] = v;
|
||||
o->on[i] = (flags[i] & 1) != 0;
|
||||
}
|
||||
v = 0;
|
||||
for (int i = 0; i < npts; i++) {
|
||||
if (flags[i] & 4) {
|
||||
int dv = *p++;
|
||||
v += (flags[i] & 32) ? dv : -dv;
|
||||
} else if (!(flags[i] & 32)) {
|
||||
v += rs16(p);
|
||||
p += 2;
|
||||
}
|
||||
o->y[i] = v;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// --- rasterising -----------------------------------------------------------
|
||||
//
|
||||
// Scanline fill with the non-zero winding rule, four sub-scanlines per output
|
||||
// row. Coverage is accumulated in fixed point per pixel, so a horizontal span
|
||||
// ending part-way through a pixel contributes a fraction rather than being
|
||||
// rounded -- which is where anti-aliasing actually comes from. Vertical
|
||||
// smoothing comes from the four samples.
|
||||
|
||||
#define SUBS 4
|
||||
|
||||
struct edge { int x0, y0, x1, y1; }; // in 1/64 pixel
|
||||
|
||||
#define MAXEDGE 1024
|
||||
|
||||
struct raster {
|
||||
struct edge e[MAXEDGE];
|
||||
int n;
|
||||
};
|
||||
|
||||
static void
|
||||
add_edge(struct raster *r, int x0, int y0, int x1, int y1)
|
||||
{
|
||||
if (y0 == y1 || r->n >= MAXEDGE)
|
||||
return; // horizontal edges contribute no crossings
|
||||
r->e[r->n].x0 = x0; r->e[r->n].y0 = y0;
|
||||
r->e[r->n].x1 = x1; r->e[r->n].y1 = y1;
|
||||
r->n++;
|
||||
}
|
||||
|
||||
// Flatten a quadratic bézier into line segments.
|
||||
//
|
||||
// Fixed subdivision rather than adaptive. At text sizes the control polygon is
|
||||
// a few pixels across, so eight segments is already below the point where the
|
||||
// difference is visible, and an adaptive version would spend more time deciding
|
||||
// than drawing.
|
||||
static void
|
||||
quad_to(struct raster *r, int x0, int y0, int cx, int cy, int x1, int y1)
|
||||
{
|
||||
const int N = 8;
|
||||
int px = x0, py = y0;
|
||||
for (int i = 1; i <= N; i++) {
|
||||
int t = (i * 256) / N, u = 256 - t;
|
||||
// (u*u*p0 + 2*u*t*c + t*t*p1) / 65536
|
||||
int nx = (u * u * x0 + 2 * u * t * cx + t * t * x1) >> 16;
|
||||
int ny = (u * u * y0 + 2 * u * t * cy + t * t * y1) >> 16;
|
||||
add_edge(r, px, py, nx, ny);
|
||||
px = nx; py = ny;
|
||||
}
|
||||
}
|
||||
|
||||
int
|
||||
ttf_render(struct ttf *f, uint32 cp, struct glyph *out, unsigned char *cov,
|
||||
int covcap, int *ox, int *oy)
|
||||
{
|
||||
struct outline o;
|
||||
struct raster r;
|
||||
int gid = glyph_index(f, cp);
|
||||
|
||||
if (gid == 0 && cp != 0)
|
||||
return -1;
|
||||
if (load_outline(f, gid, &o) != 0)
|
||||
return -1;
|
||||
|
||||
// Font units to 1/64 pixel.
|
||||
int upem = f->units_per_em;
|
||||
#define SX(v) (((v) * f->px * 64) / upem)
|
||||
|
||||
int minx = 0, miny = 0, maxx = 0, maxy = 0;
|
||||
if (o.npts) {
|
||||
minx = maxx = SX(o.x[0]);
|
||||
miny = maxy = SX(o.y[0]);
|
||||
for (int i = 1; i < o.npts; i++) {
|
||||
int X = SX(o.x[i]), Y = SX(o.y[i]);
|
||||
if (X < minx) minx = X;
|
||||
if (X > maxx) maxx = X;
|
||||
if (Y < miny) miny = Y;
|
||||
if (Y > maxy) maxy = Y;
|
||||
}
|
||||
}
|
||||
|
||||
int x0 = minx >> 6, y0 = miny >> 6;
|
||||
int w = ((maxx + 63) >> 6) - x0 + 1;
|
||||
int h = ((maxy + 63) >> 6) - y0 + 1;
|
||||
if (w < 1) w = 1;
|
||||
if (h < 1) h = 1;
|
||||
if (w * h > covcap)
|
||||
return -1;
|
||||
|
||||
memset(cov, 0, (uint32)(w * h));
|
||||
|
||||
// Build the edge list, contour by contour. Off-curve points imply an
|
||||
// on-curve point midway between consecutive off-curve neighbours -- TrueType
|
||||
// omits those, and a renderer that does not synthesise them draws corners
|
||||
// where the font has curves.
|
||||
r.n = 0;
|
||||
int start = 0;
|
||||
for (int c = 0; c < o.ncont; c++) {
|
||||
int end = o.ends[c];
|
||||
int n = end - start + 1;
|
||||
if (n < 2) { start = end + 1; continue; }
|
||||
|
||||
int px = 0, py = 0, have = 0, fx = 0, fy = 0;
|
||||
int cx = 0, cy = 0, havec = 0;
|
||||
|
||||
for (int k = 0; k <= n; k++) {
|
||||
int i = start + (k % n);
|
||||
int X = SX(o.x[i]) - (x0 << 6), Y = SX(o.y[i]) - (y0 << 6);
|
||||
|
||||
if (o.on[i]) {
|
||||
if (!have) { fx = px = X; fy = py = Y; have = 1; }
|
||||
else if (havec) { quad_to(&r, px, py, cx, cy, X, Y); px = X; py = Y; havec = 0; }
|
||||
else { add_edge(&r, px, py, X, Y); px = X; py = Y; }
|
||||
} else {
|
||||
if (havec) {
|
||||
int mx = (cx + X) / 2, my = (cy + Y) / 2; // implied on-curve point
|
||||
if (!have) { fx = px = mx; fy = py = my; have = 1; }
|
||||
else { quad_to(&r, px, py, cx, cy, mx, my); px = mx; py = my; }
|
||||
}
|
||||
cx = X; cy = Y; havec = 1;
|
||||
}
|
||||
}
|
||||
if (have) {
|
||||
if (havec) quad_to(&r, px, py, cx, cy, fx, fy);
|
||||
else add_edge(&r, px, py, fx, fy);
|
||||
}
|
||||
start = end + 1;
|
||||
}
|
||||
|
||||
// Fill. Y grows upward in font space and downward in the bitmap, so the
|
||||
// sample line is mirrored as it is computed.
|
||||
for (int row = 0; row < h; row++) {
|
||||
for (int s = 0; s < SUBS; s++) {
|
||||
int sy = ((h - 1 - row) << 6) + (s * 64) / SUBS + 32 / SUBS;
|
||||
|
||||
int xs[64], nx = 0, dir[64];
|
||||
for (int i = 0; i < r.n && nx < 64; i++) {
|
||||
struct edge *e = &r.e[i];
|
||||
int ya = e->y0, yb = e->y1, d = 1;
|
||||
if (ya > yb) { int t = ya; ya = yb; yb = t; d = -1; }
|
||||
if (sy < ya || sy >= yb)
|
||||
continue;
|
||||
int t = ((sy - e->y0) * 1024) / (e->y1 - e->y0);
|
||||
xs[nx] = e->x0 + ((e->x1 - e->x0) * t) / 1024;
|
||||
dir[nx] = d;
|
||||
nx++;
|
||||
}
|
||||
// Insertion sort: nx is small and this keeps the direction paired.
|
||||
for (int i = 1; i < nx; i++) {
|
||||
int vx = xs[i], vd = dir[i], j = i - 1;
|
||||
while (j >= 0 && xs[j] > vx) { xs[j+1] = xs[j]; dir[j+1] = dir[j]; j--; }
|
||||
xs[j+1] = vx; dir[j+1] = vd;
|
||||
}
|
||||
|
||||
int wind = 0;
|
||||
for (int i = 0; i + 1 < nx; i++) {
|
||||
wind += dir[i];
|
||||
if (wind == 0)
|
||||
continue; // non-zero winding
|
||||
int xa = xs[i], xb = xs[i + 1];
|
||||
for (int col = xa >> 6; col <= (xb >> 6) && col < w; col++) {
|
||||
if (col < 0)
|
||||
continue;
|
||||
// Fraction of this pixel the span covers, in 1/64ths.
|
||||
int l = xa > (col << 6) ? xa : (col << 6);
|
||||
int rr = xb < ((col + 1) << 6) ? xb : ((col + 1) << 6);
|
||||
if (rr <= l)
|
||||
continue;
|
||||
int add = ((rr - l) * 255) / (64 * SUBS);
|
||||
int v = cov[row * w + col] + add;
|
||||
cov[row * w + col] = (unsigned char)(v > 255 ? 255 : v);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
out->cov = cov;
|
||||
out->w = w;
|
||||
out->h = h;
|
||||
out->bearing_x = x0;
|
||||
out->bearing_y = (maxy + 63) >> 6;
|
||||
out->advance = (advance_for(f, gid) * f->px + upem / 2) / upem;
|
||||
if (ox) *ox = x0;
|
||||
if (oy) *oy = y0;
|
||||
#undef SX
|
||||
return 0;
|
||||
}
|
||||
|
||||
// --- font provider ---------------------------------------------------------
|
||||
|
||||
// --- glyph cache -----------------------------------------------------------
|
||||
//
|
||||
// Rasterising is not cheap -- it walks the outline, flattens every curve and
|
||||
// fills four sub-scanlines per row -- and a terminal draws the same hundred
|
||||
// glyphs over and over. Without a cache every character costs that again, and
|
||||
// worse: the single scratch buffer meant a glyph was only valid until the next
|
||||
// call, so a caller could not hold two at once.
|
||||
//
|
||||
// Direct-mapped over printable ASCII, which is the case that matters and needs
|
||||
// no eviction policy, no hashing and no allocation. Anything outside that range
|
||||
// still renders, just uncached, through the scratch buffer.
|
||||
//
|
||||
// The pool is filled linearly and never freed. A cache that can only fill up is
|
||||
// the right shape here because the contents are pure functions of (codepoint,
|
||||
// size) -- nothing ever becomes stale -- and the only event that invalidates it
|
||||
// is a size change, which drops the whole thing.
|
||||
|
||||
#define CACHE_LO 32 // space
|
||||
#define CACHE_HI 127 // exclusive
|
||||
#define CACHE_N (CACHE_HI - CACHE_LO)
|
||||
#define CACHE_POOL (48 * 1024)
|
||||
|
||||
struct gcache {
|
||||
struct glyph g;
|
||||
uint32 off; // into pool, valid only when `have`
|
||||
int have;
|
||||
};
|
||||
|
||||
static struct gcache cache[CACHE_N];
|
||||
static unsigned char cache_pool[CACHE_POOL];
|
||||
static uint32 cache_used;
|
||||
static int cache_px; // size the cache was built at
|
||||
|
||||
// Scratch for anything the cache cannot hold. Same contract as before -- valid
|
||||
// until the next call -- and now only used off the common path.
|
||||
static unsigned char ttf_cov[128 * 128];
|
||||
|
||||
static int
|
||||
ttf_glyph_op(struct font *fo, uint32 cp, struct glyph *out)
|
||||
{
|
||||
struct ttf *f = fo->priv;
|
||||
|
||||
// A size change makes every cached bitmap wrong. Dropping the lot is correct
|
||||
// and costs nothing: it happens when a font is resized, not per glyph.
|
||||
if (cache_px != f->px) {
|
||||
for (int i = 0; i < CACHE_N; i++)
|
||||
cache[i].have = 0;
|
||||
cache_used = 0;
|
||||
cache_px = f->px;
|
||||
}
|
||||
|
||||
if (cp < CACHE_LO || cp >= CACHE_HI)
|
||||
return ttf_render(f, cp, out, ttf_cov, (int)sizeof(ttf_cov), 0, 0);
|
||||
|
||||
struct gcache *c = &cache[cp - CACHE_LO];
|
||||
if (c->have) {
|
||||
*out = c->g;
|
||||
out->cov = cache_pool + c->off; // rebind: the pool may have moved in
|
||||
return 0; // a future revision, the offset has not
|
||||
}
|
||||
|
||||
struct glyph g;
|
||||
if (ttf_render(f, cp, &g, ttf_cov, (int)sizeof(ttf_cov), 0, 0) != 0)
|
||||
return -1;
|
||||
|
||||
uint32 need = (uint32)(g.w * g.h);
|
||||
if (cache_used + need <= CACHE_POOL) {
|
||||
memmove(cache_pool + cache_used, g.cov, need);
|
||||
c->off = cache_used;
|
||||
c->g = g;
|
||||
c->g.cov = cache_pool + cache_used; // not the scratch buffer
|
||||
c->have = 1;
|
||||
cache_used += need;
|
||||
*out = c->g;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Pool exhausted. Still correct, just uncached -- and the glyph is in the
|
||||
// scratch buffer, which is exactly the old contract.
|
||||
*out = g;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ttf_height_op(struct font *fo)
|
||||
{
|
||||
struct ttf *f = fo->priv;
|
||||
return ((f->ascent - f->descent) * f->px + f->units_per_em / 2) / f->units_per_em;
|
||||
}
|
||||
|
||||
static int ttf_baseline_op(struct font *fo)
|
||||
{
|
||||
struct ttf *f = fo->priv;
|
||||
return (f->ascent * f->px + f->units_per_em / 2) / f->units_per_em;
|
||||
}
|
||||
|
||||
static const struct font_ops ttf_ops = {
|
||||
.glyph = ttf_glyph_op,
|
||||
.height = ttf_height_op,
|
||||
.baseline = ttf_baseline_op,
|
||||
};
|
||||
|
||||
static struct font ttf_font_obj;
|
||||
|
||||
struct font *
|
||||
ttf_font(struct ttf *f)
|
||||
{
|
||||
ttf_font_obj.ops = &ttf_ops;
|
||||
ttf_font_obj.priv = f;
|
||||
return &ttf_font_obj;
|
||||
}
|
||||
95
kernel/dev/uart.c
Normal file
95
kernel/dev/uart.c
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
// Intel 8250 serial port (UART).
|
||||
|
||||
#include "types.h"
|
||||
#include "defs.h"
|
||||
#include "irq.h"
|
||||
#include "param.h"
|
||||
#include "traps.h"
|
||||
#include "spinlock.h"
|
||||
#include "sleeplock.h"
|
||||
#include "fs.h"
|
||||
#include "file.h"
|
||||
#include "mmu.h"
|
||||
#include "proc.h"
|
||||
#include "x86.h"
|
||||
|
||||
#define COM1 0x3f8
|
||||
|
||||
static int uart; // is there a uart?
|
||||
|
||||
void
|
||||
uartearlyinit(void)
|
||||
{
|
||||
char *p;
|
||||
|
||||
// Turn off the FIFO
|
||||
outb(COM1+2, 0);
|
||||
|
||||
// 9600 baud, 8 data bits, 1 stop bit, parity off.
|
||||
outb(COM1+3, 0x80); // Unlock divisor
|
||||
outb(COM1+0, 115200/9600);
|
||||
outb(COM1+1, 0);
|
||||
outb(COM1+3, 0x03); // Lock divisor, 8 data bits.
|
||||
outb(COM1+4, 0);
|
||||
outb(COM1+1, 0x01); // Enable receive interrupts.
|
||||
|
||||
// If status is 0xFF, no serial port.
|
||||
if(inb(COM1+5) == 0xFF)
|
||||
return;
|
||||
uart = 1;
|
||||
|
||||
|
||||
|
||||
// Announce that we're here.
|
||||
for(p="Schunix UART...\n"; *p; p++)
|
||||
uartputc(*p);
|
||||
}
|
||||
|
||||
// irq.c hands every handler an opaque argument, because a driver with two
|
||||
// controllers needs to know which one interrupted. There is only one COM1.
|
||||
static void
|
||||
uart_irq(void *arg)
|
||||
{
|
||||
(void)arg;
|
||||
uartintr();
|
||||
}
|
||||
|
||||
void
|
||||
uartinit(void)
|
||||
{
|
||||
if(!uart)
|
||||
return;
|
||||
|
||||
// Acknowledge pre-existing interrupt conditions;
|
||||
// enable interrupts.
|
||||
inb(COM1+2);
|
||||
inb(COM1+0);
|
||||
irq_register(IRQ_COM1, uart_irq, 0, "serial", 0);
|
||||
}
|
||||
void
|
||||
uartputc(int c)
|
||||
{
|
||||
int i;
|
||||
|
||||
if(!uart)
|
||||
return;
|
||||
for(i = 0; i < 128 && !(inb(COM1+5) & 0x20); i++)
|
||||
microdelay(10);
|
||||
outb(COM1+0, c);
|
||||
}
|
||||
|
||||
static int
|
||||
uartgetc(void)
|
||||
{
|
||||
if(!uart)
|
||||
return -1;
|
||||
if(!(inb(COM1+5) & 0x01))
|
||||
return -1;
|
||||
return inb(COM1+0);
|
||||
}
|
||||
|
||||
void
|
||||
uartintr(void)
|
||||
{
|
||||
consoleintr(uartgetc);
|
||||
}
|
||||
555
kernel/dev/vga.c
Normal file
555
kernel/dev/vga.c
Normal file
|
|
@ -0,0 +1,555 @@
|
|||
#include "types.h"
|
||||
#include "x86.h"
|
||||
#include "memlayout.h"
|
||||
#include "defs.h"
|
||||
#include "theme.h"
|
||||
#include "font.h"
|
||||
#include "logo.h"
|
||||
#include "device.h"
|
||||
#include "display.h"
|
||||
#include "dma.h"
|
||||
|
||||
#define BACKSPACE 0x100
|
||||
#define CHAR_HEIGHT 16
|
||||
#define CHAR_WIDTH 8
|
||||
#define COLOR_BG SCH_BG
|
||||
|
||||
volatile uchar *vga_mem = 0;
|
||||
// Physical base of the linear framebuffer, as reported by VBE. Every other
|
||||
// part of the kernel that needs to map the framebuffer -- fork's VMA copy,
|
||||
// mmap of /dev/fb0 -- asks for this rather than assuming a value. Three
|
||||
// places used to hardcode 0xfd000000, which is only where QEMU's stdvga
|
||||
// happens to put it.
|
||||
static uint64 vga_phys = 0;
|
||||
static uint32 vga_stride = 0;
|
||||
static uint32 vga_bpp = 0;
|
||||
static uint32 vga_width = 800;
|
||||
static uint32 vga_height = 600;
|
||||
static int cursor_x = 0;
|
||||
static int cursor_y = 0;
|
||||
// Current fill color for glyph backgrounds, backspace erase, and scroll —
|
||||
// normally COLOR_BG, but panic() switches this to paint the whole console
|
||||
// a solid color (e.g. PSOGM's purple) instead of leaving cream-colored boxes
|
||||
// around each character.
|
||||
static uint32 vga_bg = COLOR_BG;
|
||||
|
||||
void
|
||||
vga_set_bg(uint32 color)
|
||||
{
|
||||
vga_bg = color;
|
||||
}
|
||||
|
||||
// Written by boot/stage2.S's VBE call and read by boot/stage2main.c under
|
||||
// the same name (VBE_INFO_ADDR) — not 0x9000, since stage2's own code now
|
||||
// runs up to nearly 0x9000 once the boot-menu/progress-bar logic is
|
||||
// included, which would otherwise clobber a mode info block placed there.
|
||||
#define VBE_INFO_ADDR 0x30000
|
||||
|
||||
void
|
||||
vga_init(uint64 phys_addr) {
|
||||
if (phys_addr == 0) {
|
||||
phys_addr = *(uint32*)(P2V(VBE_INFO_ADDR) + 40);
|
||||
}
|
||||
vga_mem = (volatile uchar*)P2V(phys_addr);
|
||||
vga_phys = phys_addr;
|
||||
|
||||
vga_stride = *(ushort*)(P2V(VBE_INFO_ADDR) + 16);
|
||||
vga_width = *(ushort*)(P2V(VBE_INFO_ADDR) + 18);
|
||||
vga_height = *(ushort*)(P2V(VBE_INFO_ADDR) + 20);
|
||||
vga_bpp = *(uchar*)(P2V(VBE_INFO_ADDR) + 25);
|
||||
|
||||
if (vga_width == 0) vga_width = 800;
|
||||
if (vga_height == 0) vga_height = 600;
|
||||
if (vga_stride == 0) vga_stride = vga_width * (vga_bpp / 8);
|
||||
|
||||
cprintf("VGA: %dx%d, %d bpp, stride %d, LFB: 0x%x\n", vga_width, vga_height, vga_bpp, vga_stride, (uint32)phys_addr);
|
||||
|
||||
// Deliberately does NOT clear the screen.
|
||||
//
|
||||
// The bootloader has already drawn the splash, and wiping it here is what
|
||||
// produced the old two-act boot: a purple splash for an instant, then an
|
||||
// abrupt cream screen filling with kernel log lines. The splash now stays up
|
||||
// for the whole of boot, and console_takeover() clears it exactly once --
|
||||
// when userspace starts and the display changes owner.
|
||||
}
|
||||
|
||||
void
|
||||
vga_draw_pixel(int x, int y, uint32 color)
|
||||
{
|
||||
if (!vga_mem || x < 0 || x >= vga_width || y < 0 || y >= vga_height)
|
||||
return;
|
||||
|
||||
uint32 offset = (y * vga_stride) + (x * (vga_bpp / 8));
|
||||
|
||||
if (vga_bpp == 32) {
|
||||
*(volatile uint32*)(vga_mem + offset) = color;
|
||||
} else if (vga_bpp == 24) {
|
||||
vga_mem[offset] = color & 0xFF;
|
||||
vga_mem[offset + 1] = (color >> 8) & 0xFF;
|
||||
vga_mem[offset + 2] = (color >> 16) & 0xFF;
|
||||
} else if (vga_bpp == 16) {
|
||||
uint16 c16 = (((color >> 19) & 0x1F) << 11) | (((color >> 10) & 0x3F) << 5) | ((color >> 3) & 0x1F);
|
||||
*(volatile uint16*)(vga_mem + offset) = c16;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
vga_clear(uint32 color)
|
||||
{
|
||||
for (int y = 0; y < vga_height; y++) {
|
||||
for (int x = 0; x < vga_width; x++) {
|
||||
vga_draw_pixel(x, y, color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
vga_draw_char(int x, int y, uchar c, uint32 color)
|
||||
{
|
||||
// No `c > 127` rejection any more: font_data holds 256 Latin-1 glyphs, and
|
||||
// a uchar cannot index past them.
|
||||
if (!vga_mem) return;
|
||||
|
||||
for (int row = 0; row < CHAR_HEIGHT; row++) {
|
||||
uchar row_data = font_data[c][row];
|
||||
for (int col = 0; col < CHAR_WIDTH; col++) {
|
||||
if (row_data & (0x80 >> col)) {
|
||||
vga_draw_pixel(x + col, y + row, color);
|
||||
} else {
|
||||
vga_draw_pixel(x + col, y + row, vga_bg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
vga_puts(int x, int y, char *s, uint32 color)
|
||||
{
|
||||
while (*s) {
|
||||
vga_putc((uchar)*s, color);
|
||||
s++;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
vga_putc(int c, uint32 color)
|
||||
{
|
||||
if (!vga_mem) return;
|
||||
|
||||
if (c == BACKSPACE || c == 0x08 || c == '\b' || c == 127) {
|
||||
if (cursor_x >= CHAR_WIDTH) {
|
||||
cursor_x -= CHAR_WIDTH;
|
||||
for (int i = 0; i < CHAR_HEIGHT; i++) {
|
||||
for (int j = 0; j < CHAR_WIDTH; j++) {
|
||||
vga_draw_pixel(cursor_x + j, cursor_y + i, vga_bg);
|
||||
}
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (c == '\n') {
|
||||
cursor_x = 0;
|
||||
cursor_y += CHAR_HEIGHT;
|
||||
} else if (c == '\r') {
|
||||
cursor_x = 0;
|
||||
} else if (c == '\t') {
|
||||
// Advance to the next multiple of eight columns, erasing as it goes so a
|
||||
// tab overwrites rather than skipping over stale characters.
|
||||
//
|
||||
// There was no tab case at all, so '\t' fell through to vga_draw_char and
|
||||
// was drawn as glyph 9 -- which in CP437 is a hollow circle. Pressing Tab
|
||||
// printed a circle. Same for every other control character: the font has
|
||||
// a picture for each of them, and none of those pictures is what the
|
||||
// character means.
|
||||
int col = cursor_x / CHAR_WIDTH;
|
||||
int stop = (col + 8) & ~7;
|
||||
while (col < stop && cursor_x < (int)vga_width) {
|
||||
for (int i = 0; i < CHAR_HEIGHT; i++)
|
||||
for (int j = 0; j < CHAR_WIDTH; j++)
|
||||
vga_draw_pixel(cursor_x + j, cursor_y + i, vga_bg);
|
||||
cursor_x += CHAR_WIDTH;
|
||||
col++;
|
||||
}
|
||||
} else if (c < 32) {
|
||||
return; // any other control character: not a glyph
|
||||
} else {
|
||||
vga_draw_char(cursor_x, cursor_y, (uchar)c, color);
|
||||
cursor_x += CHAR_WIDTH;
|
||||
}
|
||||
|
||||
if (cursor_x >= vga_width) {
|
||||
cursor_x = 0;
|
||||
cursor_y += CHAR_HEIGHT;
|
||||
}
|
||||
|
||||
if (cursor_y > (vga_height - CHAR_HEIGHT)) {
|
||||
uint32 line_size = vga_stride * CHAR_HEIGHT;
|
||||
uint32 total_size = vga_stride * vga_height;
|
||||
uint32 copy_size = total_size - line_size;
|
||||
|
||||
memmove((void*)vga_mem, (void*)(vga_mem + line_size), copy_size);
|
||||
|
||||
for (int y = vga_height - CHAR_HEIGHT; y < vga_height; y++) {
|
||||
for (int x = 0; x < vga_width; x++) {
|
||||
vga_draw_pixel(x, y, vga_bg);
|
||||
}
|
||||
}
|
||||
cursor_y = vga_height - CHAR_HEIGHT;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
vga_screen_reset(void)
|
||||
{
|
||||
vga_bg = COLOR_BG;
|
||||
vga_clear(COLOR_BG);
|
||||
cursor_x = 0;
|
||||
cursor_y = 0;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Drawing primitives for the crash screen */
|
||||
/* */
|
||||
/* The crash screen lays itself out rather than printing, which needs */
|
||||
/* two things the console never did: filled shapes, and text at an */
|
||||
/* absolute position that leaves the cursor alone. */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
int
|
||||
vga_ready(void)
|
||||
{
|
||||
return vga_mem != 0;
|
||||
}
|
||||
|
||||
void
|
||||
vga_dims(int *w, int *h)
|
||||
{
|
||||
if (w) *w = (int)vga_width;
|
||||
if (h) *h = (int)vga_height;
|
||||
}
|
||||
|
||||
int vga_char_w(void) { return CHAR_WIDTH; }
|
||||
int vga_char_h(void) { return CHAR_HEIGHT; }
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Framebuffer geometry */
|
||||
/* */
|
||||
/* vga.c is the only place that reads the VBE mode info, so it is the */
|
||||
/* only place that knows where the framebuffer actually is. Everything */
|
||||
/* else asks. See kernel/dev/fb.c for the device these back. */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
uint64 vga_fb_phys(void) { return vga_phys; }
|
||||
|
||||
// Bytes of framebuffer, measured in scanlines rather than as
|
||||
// width * height * bpp/8 -- the two differ whenever the card pads its rows,
|
||||
// and it is the stride that says how far apart two rows really are.
|
||||
uint64 vga_fb_size(void)
|
||||
{
|
||||
if (!vga_mem)
|
||||
return 0;
|
||||
return (uint64)vga_stride * (uint64)vga_height;
|
||||
}
|
||||
|
||||
uint32 vga_fb_stride(void) { return vga_stride; }
|
||||
uint32 vga_fb_bpp(void) { return vga_bpp; }
|
||||
|
||||
volatile uchar *vga_fb_ptr(void) { return vga_mem; }
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Cursor addressing and erasing */
|
||||
/* */
|
||||
/* What an ANSI escape parser needs and vga_putc alone cannot give it: */
|
||||
/* the ability to put the cursor somewhere specific and to erase a */
|
||||
/* region without writing spaces over it. See kernel/core/console.c. */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
int vga_cols(void) { return vga_width ? (int)(vga_width / CHAR_WIDTH) : 0; }
|
||||
int vga_rows(void) { return vga_height ? (int)(vga_height / CHAR_HEIGHT) : 0; }
|
||||
|
||||
void
|
||||
vga_cursor_get(int *col, int *row)
|
||||
{
|
||||
if (col) *col = cursor_x / CHAR_WIDTH;
|
||||
if (row) *row = cursor_y / CHAR_HEIGHT;
|
||||
}
|
||||
|
||||
// Clamped rather than validated: an escape sequence is untrusted input that
|
||||
// arrives from any program that can write to the terminal, and "CUP to row
|
||||
// 9999" should put the cursor on the last row, not off the end of the
|
||||
// framebuffer.
|
||||
void
|
||||
vga_cursor_set(int col, int row)
|
||||
{
|
||||
int maxc = vga_cols(), maxr = vga_rows();
|
||||
|
||||
if (maxc <= 0 || maxr <= 0)
|
||||
return;
|
||||
if (col < 0) col = 0;
|
||||
if (row < 0) row = 0;
|
||||
if (col >= maxc) col = maxc - 1;
|
||||
if (row >= maxr) row = maxr - 1;
|
||||
|
||||
cursor_x = col * CHAR_WIDTH;
|
||||
cursor_y = row * CHAR_HEIGHT;
|
||||
}
|
||||
|
||||
// Fill a run of character cells with the background colour.
|
||||
static void
|
||||
erase_cells(int col, int row, int count)
|
||||
{
|
||||
int maxc = vga_cols();
|
||||
|
||||
while (count-- > 0) {
|
||||
int x = col * CHAR_WIDTH, y = row * CHAR_HEIGHT;
|
||||
for (int i = 0; i < CHAR_HEIGHT; i++)
|
||||
for (int j = 0; j < CHAR_WIDTH; j++)
|
||||
vga_draw_pixel(x + j, y + i, vga_bg);
|
||||
if (++col >= maxc) { col = 0; row++; }
|
||||
}
|
||||
}
|
||||
|
||||
// ED -- erase in display. 0: cursor to end, 1: start to cursor, 2: all.
|
||||
void
|
||||
vga_erase_display(int mode)
|
||||
{
|
||||
int col, row, maxc = vga_cols(), maxr = vga_rows();
|
||||
|
||||
vga_cursor_get(&col, &row);
|
||||
switch (mode) {
|
||||
case 0: erase_cells(col, row, (maxr - row) * maxc - col); break;
|
||||
case 1: erase_cells(0, 0, row * maxc + col + 1); break;
|
||||
default: vga_clear(vga_bg); break;
|
||||
}
|
||||
}
|
||||
|
||||
// EL -- erase in line. Same three modes, bounded to the current row.
|
||||
void
|
||||
vga_erase_line(int mode)
|
||||
{
|
||||
int col, row, maxc = vga_cols();
|
||||
|
||||
vga_cursor_get(&col, &row);
|
||||
switch (mode) {
|
||||
case 0: erase_cells(col, row, maxc - col); break;
|
||||
case 1: erase_cells(0, row, col + 1); break;
|
||||
default: erase_cells(0, row, maxc); break;
|
||||
}
|
||||
}
|
||||
|
||||
// Row pointer computed once per row rather than per pixel. The crash screen
|
||||
// paints a solid card behind the QR symbol, and at 8 pixels per module that is
|
||||
// tens of thousands of writes going through vga_draw_pixel's bounds check and
|
||||
// multiply.
|
||||
void
|
||||
vga_fill_rect(int x0, int y0, int w, int h, uint32 color)
|
||||
{
|
||||
if (!vga_mem)
|
||||
return;
|
||||
if (x0 < 0) { w += x0; x0 = 0; }
|
||||
if (y0 < 0) { h += y0; y0 = 0; }
|
||||
if (x0 + w > (int)vga_width) w = (int)vga_width - x0;
|
||||
if (y0 + h > (int)vga_height) h = (int)vga_height - y0;
|
||||
if (w <= 0 || h <= 0)
|
||||
return;
|
||||
|
||||
// Only the 32bpp case gets the fast path; anything else is rare enough that
|
||||
// correctness through vga_draw_pixel is the better trade.
|
||||
if (vga_bpp != 32) {
|
||||
for (int y = y0; y < y0 + h; y++)
|
||||
for (int x = x0; x < x0 + w; x++)
|
||||
vga_draw_pixel(x, y, color);
|
||||
return;
|
||||
}
|
||||
for (int y = y0; y < y0 + h; y++) {
|
||||
volatile uint32 *row = (volatile uint32 *)(vga_mem + y * vga_stride) + x0;
|
||||
for (int x = 0; x < w; x++)
|
||||
row[x] = color;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
vga_fill_circle(int cx, int cy, int r, uint32 color)
|
||||
{
|
||||
for (int dy = -r; dy <= r; dy++)
|
||||
for (int dx = -r; dx <= r; dx++)
|
||||
if (dx * dx + dy * dy <= r * r)
|
||||
vga_draw_pixel(cx + dx, cy + dy, color);
|
||||
}
|
||||
|
||||
// Text at an absolute position, leaving the console cursor alone. The crash
|
||||
// screen lays itself out, so vga_putc's cursor tracking and scrolling are
|
||||
// exactly what it does not want.
|
||||
void
|
||||
vga_text_at(int x, int y, const char *s, uint32 color)
|
||||
{
|
||||
for (int i = 0; s[i]; i++)
|
||||
vga_draw_char(x + i * CHAR_WIDTH, y, (uchar)s[i], color);
|
||||
}
|
||||
|
||||
// --- Logo ------------------------------------------------------------------
|
||||
//
|
||||
// The boot splash's logo is drawn by the *loader*, not here -- see splash() in
|
||||
// boot/stage2main.c. It has to be, because the kernel does not reach vga_init()
|
||||
// until roughly the last 300ms of a 2.8s boot: drawing it here put the logo on
|
||||
// screen for one frame before console_takeover() wiped it, which is worse than
|
||||
// not having one.
|
||||
//
|
||||
// What lives here is the same drawing code against the same mask, for the
|
||||
// things that want the mark *after* boot -- the crash screen, and eventually
|
||||
// whatever schinit shows. Keeping one copy of the compositing logic is why this
|
||||
// is not simply deleted.
|
||||
|
||||
// Blend `fg` over `bg` by coverage a (0..255). Per-channel, integer only: this
|
||||
// runs before any floating point is safe to touch.
|
||||
static uint32
|
||||
blend(uint32 bg, uint32 fg, uint a)
|
||||
{
|
||||
uint32 r = (((fg >> 16) & 0xFF) * a + ((bg >> 16) & 0xFF) * (255 - a)) / 255;
|
||||
uint32 g = (((fg >> 8) & 0xFF) * a + ((bg >> 8) & 0xFF) * (255 - a)) / 255;
|
||||
uint32 b = (((fg ) & 0xFF) * a + ((bg ) & 0xFF) * (255 - a)) / 255;
|
||||
return (r << 16) | (g << 8) | b;
|
||||
}
|
||||
|
||||
void
|
||||
vga_draw_logo(int x, int y, uint32 color, uint32 bg)
|
||||
{
|
||||
if (!vga_mem)
|
||||
return;
|
||||
for (int dy = 0; dy < LOGO_H; dy++) {
|
||||
for (int dx = 0; dx < LOGO_W; dx++) {
|
||||
uint a = LOGO_INK(dy * LOGO_W + dx);
|
||||
// Skip the background rather than painting it. The mask is a square but
|
||||
// the mark is a circle, so most of it is empty -- and drawing that empty
|
||||
// area in bg would stamp a visible box over whatever is underneath.
|
||||
if (a == 0)
|
||||
continue;
|
||||
vga_draw_pixel(x + dx, y + dy, a == 255 ? color : blend(bg, color, a));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Centred, for callers that own the whole screen.
|
||||
void
|
||||
vga_logo_centered(int cy, uint32 color, uint32 bg)
|
||||
{
|
||||
if (!vga_mem || vga_width < LOGO_W)
|
||||
return;
|
||||
vga_draw_logo(((int)vga_width - LOGO_W) / 2, cy - LOGO_H / 2, color, bg);
|
||||
}
|
||||
|
||||
// --- display class implementation ------------------------------------------
|
||||
//
|
||||
// See include/display.h. This is the VBE linear framebuffer presenting itself
|
||||
// as a generic display, so a compositor can be written against the class and
|
||||
// keep working when virtio-gpu arrives.
|
||||
|
||||
static uchar *back; // shadow buffer in RAM, or 0 if never acquired
|
||||
static uint64 back_bytes;
|
||||
|
||||
static void
|
||||
vga_disp_mode(struct device *d, struct display_mode *m)
|
||||
{
|
||||
m->width = (int)vga_width;
|
||||
m->height = (int)vga_height;
|
||||
m->bpp = (int)vga_bpp;
|
||||
m->stride = vga_stride;
|
||||
}
|
||||
|
||||
static void *
|
||||
vga_disp_acquire(struct device *d, uint32 *stride_out)
|
||||
{
|
||||
if (stride_out)
|
||||
*stride_out = vga_stride;
|
||||
if (back)
|
||||
return back;
|
||||
if (!vga_mem || vga_height == 0 || vga_stride == 0)
|
||||
return 0;
|
||||
|
||||
back_bytes = (uint64)vga_stride * vga_height;
|
||||
|
||||
// From the DMA arena, which is the wrong pool and is used here because it is
|
||||
// the only *physically contiguous* allocator in the kernel -- kalloc hands
|
||||
// out single pages whose neighbours are adjacent only by chance, and a
|
||||
// framebuffer wants one flat span.
|
||||
//
|
||||
// At 800x600x32 this is 1.9 MB of a 4 MB arena meant for device rings, which
|
||||
// is tolerable only because nothing else large lives there yet. The real fix
|
||||
// is a contiguous page allocator, or reserving this at boot from the memory
|
||||
// map -- which is work that belongs with the E820 change in docs/roadmap.md
|
||||
// §3b, since that is what stops the kernel pretending 224 MB exists.
|
||||
//
|
||||
// Allocated on first use rather than at boot on purpose: a machine that never
|
||||
// starts a compositor should not pay two megabytes for one.
|
||||
back = dma_alloc(back_bytes, 4096);
|
||||
if (!back) {
|
||||
cprintf("display: no room for a %d KB back buffer; drawing will tear\n",
|
||||
(int)(back_bytes / 1024));
|
||||
return 0;
|
||||
}
|
||||
memset(back, 0, back_bytes);
|
||||
cprintf("display: %d KB back buffer, double buffered\n",
|
||||
(int)(back_bytes / 1024));
|
||||
return back;
|
||||
}
|
||||
|
||||
// Blit a rectangle from the shadow buffer to the framebuffer.
|
||||
//
|
||||
// Row at a time rather than one memmove for the whole rectangle: only a
|
||||
// full-width rectangle is contiguous in either buffer, and a compositor
|
||||
// updating one window is the case worth being right about.
|
||||
static void
|
||||
vga_disp_present(struct device *d, int x, int y, int w, int h)
|
||||
{
|
||||
if (!back || !vga_mem)
|
||||
return;
|
||||
|
||||
if (x < 0) { w += x; x = 0; }
|
||||
if (y < 0) { h += y; y = 0; }
|
||||
if (x + w > (int)vga_width) w = (int)vga_width - x;
|
||||
if (y + h > (int)vga_height) h = (int)vga_height - y;
|
||||
if (w <= 0 || h <= 0)
|
||||
return;
|
||||
|
||||
int bytes = w * (int)(vga_bpp / 8);
|
||||
uint32 off = (uint32)x * (vga_bpp / 8);
|
||||
for (int row = y; row < y + h; row++)
|
||||
memmove((void *)(vga_mem + row * vga_stride + off),
|
||||
back + row * vga_stride + off, bytes);
|
||||
}
|
||||
|
||||
// Wait for vertical retrace, by polling the VGA input status register.
|
||||
//
|
||||
// Bit 3 of 0x3DA is set during vertical retrace. This is the original 1987 VGA
|
||||
// interface and it still works on everything that emulates a VGA, QEMU's
|
||||
// stdvga included -- VBE's own protected-mode interface for this is optional
|
||||
// and frequently absent, so the old port is the portable answer.
|
||||
//
|
||||
// Two loops, not one: waiting only for the flag to *be* set would return
|
||||
// immediately if a retrace is already in progress, having just missed the
|
||||
// start of it. Waiting for the current one to end and the next to begin is
|
||||
// what actually lands the blit between frames.
|
||||
//
|
||||
// The counters are a deadlock guard. On hardware that never reports retrace
|
||||
// this returns after a bounded spin instead of hanging the caller forever,
|
||||
// which matters because this can be called with the console locked.
|
||||
static void
|
||||
vga_disp_wait_vblank(struct device *d)
|
||||
{
|
||||
int guard;
|
||||
|
||||
for (guard = 0; guard < 1000000; guard++)
|
||||
if (!(inb(0x3DA) & 0x08))
|
||||
break;
|
||||
for (guard = 0; guard < 1000000; guard++)
|
||||
if (inb(0x3DA) & 0x08)
|
||||
break;
|
||||
}
|
||||
|
||||
const struct display_ops vga_display_ops = {
|
||||
.mode = vga_disp_mode,
|
||||
.acquire = vga_disp_acquire,
|
||||
.present = vga_disp_present,
|
||||
.wait_vblank = vga_disp_wait_vblank,
|
||||
};
|
||||
1365
kernel/drivers/bus/xhci/xhci.c
Normal file
1365
kernel/drivers/bus/xhci/xhci.c
Normal file
File diff suppressed because it is too large
Load diff
95
kernel/drivers/gpu/vga/vga_pci.c
Normal file
95
kernel/drivers/gpu/vga/vga_pci.c
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
// Display controller -- the first driver to find its hardware by asking.
|
||||
//
|
||||
// It does not draw anything: kernel/dev/vga.c already owns the framebuffer,
|
||||
// and it learned where that is from the bootloader's VBE call. What this adds
|
||||
// is a second, independent answer to the same question, from the bus rather
|
||||
// than from the BIOS -- and a complaint when the two disagree.
|
||||
//
|
||||
// That is worth more than it sounds. Three places in the kernel used to
|
||||
// hardcode 0xfd000000 as the framebuffer address, and the only reason nobody
|
||||
// noticed was that QEMU's stdvga happens to put it there. A disagreement
|
||||
// between VBE and the card's own BAR is exactly the symptom that would have
|
||||
// shown up first on hardware where the guess was wrong, and until now there
|
||||
// was nothing in the kernel capable of noticing it.
|
||||
|
||||
#include "types.h"
|
||||
#include "defs.h"
|
||||
#include "param.h"
|
||||
#include "x86.h"
|
||||
#include "spinlock.h"
|
||||
#include "sleeplock.h"
|
||||
#include "fs.h"
|
||||
#include "file.h"
|
||||
#include "pci.h"
|
||||
#include "display.h"
|
||||
|
||||
// The framebuffer lives in the first memory BAR big enough to hold a screen.
|
||||
// Display controllers commonly expose two apertures -- the framebuffer and a
|
||||
// much smaller register block -- and which index they land on varies, so the
|
||||
// choice is made by size rather than by position.
|
||||
static struct pci_bar *
|
||||
framebuffer_bar(struct pci_dev *d)
|
||||
{
|
||||
uint64 need = vga_fb_size();
|
||||
|
||||
for (int i = 0; i < 6; i++) {
|
||||
struct pci_bar *b = &d->bar[i];
|
||||
if (b->base == 0 || b->is_io)
|
||||
continue;
|
||||
if (b->size >= need)
|
||||
return b;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
vga_pci_probe(struct pci_dev *d)
|
||||
{
|
||||
struct pci_bar *bar = framebuffer_bar(d);
|
||||
uint64 vbe = vga_fb_phys();
|
||||
|
||||
// Memory decoding has to be on for the aperture to respond at all. The BIOS
|
||||
// will have done this already to put up a boot splash, but a device we only
|
||||
// reached through enumeration cannot be assumed to have been touched.
|
||||
pci_enable(d, PCI_CMD_MEMORY);
|
||||
|
||||
if (bar == 0) {
|
||||
cprintf("vga: display controller at %x:%x.%x has no framebuffer aperture\n",
|
||||
d->bus, d->slot, d->func);
|
||||
return 0; // still ours; nothing more to do
|
||||
}
|
||||
|
||||
if (vbe != 0 && bar->base != vbe) {
|
||||
// Trust VBE: it describes the mode that is actually programmed and being
|
||||
// displayed, which is the thing vga.c is writing into. The BAR is the
|
||||
// card's whole aperture and the mode may sit at an offset within it. Say
|
||||
// so rather than silently preferring one.
|
||||
cprintf("vga: framebuffer disagreement -- VBE says %x, BAR says %x\n",
|
||||
(uint32)vbe, (uint32)bar->base);
|
||||
}
|
||||
|
||||
cprintf("vga: display controller %x:%x, aperture %x (%d MB)\n",
|
||||
d->vendor, d->device, (uint32)bar->base,
|
||||
(uint32)(bar->size / (1024 * 1024)));
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Matched by class, not by model: every VGA-compatible controller answers
|
||||
// class 0x03, and this driver cares about none of the differences between
|
||||
// them.
|
||||
static const struct pci_match vga_pci_match[] = {
|
||||
{ PCI_ANY, PCI_ANY, PCI_CLASS_DISPLAY, 0xFF },
|
||||
{ 0, 0, 0, 0 },
|
||||
};
|
||||
|
||||
// The display vtable lives in kernel/dev/vga.c, which owns the framebuffer;
|
||||
// this file only finds the card on the bus. Publishing it here is what makes
|
||||
// device_find_class(DEVCLASS_DISPLAY) return something a compositor can use.
|
||||
extern const struct display_ops vga_display_ops;
|
||||
|
||||
const struct pci_driver vga_pci_driver = {
|
||||
.name = "vga",
|
||||
.match = vga_pci_match,
|
||||
.ops = &vga_display_ops,
|
||||
.probe = vga_pci_probe,
|
||||
};
|
||||
435
kernel/drivers/gpu/virtio_gpu/virtio_gpu.c
Normal file
435
kernel/drivers/gpu/virtio_gpu/virtio_gpu.c
Normal file
|
|
@ -0,0 +1,435 @@
|
|||
// virtio-gpu, 2D. Written from the OASIS virtio 1.1 specification.
|
||||
//
|
||||
// This is the second implementation of the display class (include/display.h),
|
||||
// and the first one that is a real GPU driver rather than a flat framebuffer.
|
||||
// The difference that matters for a compositor: presenting is an explicit
|
||||
// operation. With VBE you write pixels and hope the scanout is not looking;
|
||||
// here you transfer a rectangle to the host and then flush it, and the host
|
||||
// does not show a half-drawn frame.
|
||||
//
|
||||
// Bring-up is the standard virtio 1.0 sequence and the order is not negotiable:
|
||||
// reset, ACKNOWLEDGE, DRIVER, read features, write features, FEATURES_OK, check
|
||||
// FEATURES_OK stuck, set up queues, DRIVER_OK. A device that finds the sequence
|
||||
// wrong is entitled to simply refuse, and it will not say why.
|
||||
|
||||
#include "types.h"
|
||||
#include "defs.h"
|
||||
#include "param.h"
|
||||
#include "x86.h"
|
||||
#include "spinlock.h"
|
||||
#include "sleeplock.h"
|
||||
#include "fs.h"
|
||||
#include "file.h"
|
||||
#include "mmu.h"
|
||||
#include "memlayout.h"
|
||||
#include "pci.h"
|
||||
#include "device.h"
|
||||
#include "display.h"
|
||||
#include "dma.h"
|
||||
#include "virtio.h"
|
||||
|
||||
// One control queue is enough for 2D: there is also a cursor queue, which is
|
||||
// only worth having once there is a hardware cursor to move.
|
||||
#define CTRL_QUEUE 0
|
||||
|
||||
struct vgpu {
|
||||
volatile uint8 *common; // common configuration structure
|
||||
volatile uint8 *notify; // notification area
|
||||
uint32 notify_mult; // queue_notify_off multiplier
|
||||
|
||||
// The queue. Allocated as one contiguous block from the DMA arena, because
|
||||
// the device reads it by physical address and kalloc's pages are adjacent
|
||||
// only by chance.
|
||||
struct vring_desc *desc;
|
||||
struct vring_avail *avail;
|
||||
struct vring_used *used;
|
||||
uint16 qsize;
|
||||
uint16 notify_off;
|
||||
uint16 last_used;
|
||||
uint16 next_desc;
|
||||
|
||||
// A command buffer and a response buffer, both in DMA memory, reused for
|
||||
// every request. Only one command is ever in flight -- everything here is
|
||||
// called from process context during setup or from present(), and a GPU
|
||||
// command queue with one entry is not the bottleneck in a 2D compositor.
|
||||
void *cmd;
|
||||
void *resp;
|
||||
|
||||
int width, height;
|
||||
void *fb; // the framebuffer we scan out from
|
||||
uint32 fb_bytes;
|
||||
int ready;
|
||||
};
|
||||
|
||||
static struct vgpu gpu;
|
||||
|
||||
// --- MMIO accessors --------------------------------------------------------
|
||||
//
|
||||
// Explicit widths. The common configuration structure has 8-, 16- and 32-bit
|
||||
// fields side by side, and a device is entitled to ignore an access made at the
|
||||
// wrong width -- silently, with no error anywhere.
|
||||
|
||||
static inline uint8 r8 (volatile uint8 *p, int o) { return *(volatile uint8 *)(p + o); }
|
||||
static inline uint16 r16(volatile uint8 *p, int o) { return *(volatile uint16 *)(p + o); }
|
||||
static inline uint32 r32(volatile uint8 *p, int o) { return *(volatile uint32 *)(p + o); }
|
||||
static inline void w8 (volatile uint8 *p, int o, uint8 v) { *(volatile uint8 *)(p + o) = v; }
|
||||
static inline void w16(volatile uint8 *p, int o, uint16 v) { *(volatile uint16 *)(p + o) = v; }
|
||||
static inline void w32(volatile uint8 *p, int o, uint32 v) { *(volatile uint32 *)(p + o) = v; }
|
||||
|
||||
static void
|
||||
w64(volatile uint8 *p, int o, uint64 v)
|
||||
{
|
||||
// Two 32-bit halves, low first. The specification defines these fields as a
|
||||
// pair of 32-bit registers precisely so that a 32-bit driver can write them,
|
||||
// and some implementations latch on the high write.
|
||||
w32(p, o, (uint32)v);
|
||||
w32(p, o + 4, (uint32)(v >> 32));
|
||||
}
|
||||
|
||||
// --- capability walk -------------------------------------------------------
|
||||
|
||||
// Find a virtio configuration structure and map it.
|
||||
//
|
||||
// Each lives at an offset inside one of the device's BARs, and which BAR is not
|
||||
// fixed -- the device says so in a vendor-specific PCI capability. Assuming BAR
|
||||
// 0 works on QEMU today and is exactly the kind of assumption that stops
|
||||
// working on the next device.
|
||||
static volatile uint8 *
|
||||
find_cfg(struct pci_dev *d, int want, uint32 *out_mult)
|
||||
{
|
||||
uint8 off = pci_read8(d->bus, d->slot, d->func, 0x34) & 0xFC;
|
||||
|
||||
while (off) {
|
||||
uint8 id = pci_read8(d->bus, d->slot, d->func, off);
|
||||
uint8 next = pci_read8(d->bus, d->slot, d->func, off + 1);
|
||||
|
||||
if (id == PCI_CAP_ID_VNDR) {
|
||||
uint8 type = pci_read8(d->bus, d->slot, d->func, off + 3);
|
||||
uint8 bar = pci_read8(d->bus, d->slot, d->func, off + 4);
|
||||
uint32 o = pci_read32(d->bus, d->slot, d->func, off + 8);
|
||||
|
||||
if (type == want && bar < 6 && d->bar[bar].base) {
|
||||
if (out_mult) // only the notify capability has this trailing field
|
||||
*out_mult = pci_read32(d->bus, d->slot, d->func, off + 16);
|
||||
return (volatile uint8 *)P2V(d->bar[bar].base + o);
|
||||
}
|
||||
}
|
||||
off = next;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// --- virtqueue -------------------------------------------------------------
|
||||
|
||||
static int
|
||||
queue_setup(struct vgpu *g)
|
||||
{
|
||||
w16(g->common, VCOMMON_Q_SELECT, CTRL_QUEUE);
|
||||
|
||||
uint16 qsize = r16(g->common, VCOMMON_Q_SIZE);
|
||||
if (qsize == 0)
|
||||
return -1;
|
||||
if (qsize > 64)
|
||||
qsize = 64; // more than we will ever have outstanding
|
||||
g->qsize = qsize;
|
||||
g->notify_off = r16(g->common, VCOMMON_Q_NOTIFY_OFF);
|
||||
|
||||
// The three rings, each aligned as the specification requires: 16 for the
|
||||
// descriptor table, 2 for available, 4 for used. Allocated as one block and
|
||||
// carved up, so there is one physical address to reason about.
|
||||
uint32 desc_sz = qsize * sizeof(struct vring_desc);
|
||||
uint32 avail_sz = 6 + 2 * qsize;
|
||||
uint32 used_sz = 6 + 8 * qsize;
|
||||
|
||||
uint8 *mem = dma_alloc(4096 * 3, 4096);
|
||||
if (!mem)
|
||||
return -1;
|
||||
memset(mem, 0, 4096 * 3);
|
||||
|
||||
g->desc = (struct vring_desc *) mem;
|
||||
g->avail = (struct vring_avail *)(mem + ((desc_sz + 1) & ~1U));
|
||||
g->used = (struct vring_used *)(mem + 4096); // its own page, 4-aligned
|
||||
(void)avail_sz; (void)used_sz;
|
||||
|
||||
w64(g->common, VCOMMON_Q_DESC, dma_phys(g->desc));
|
||||
w64(g->common, VCOMMON_Q_DRIVER, dma_phys(g->avail));
|
||||
w64(g->common, VCOMMON_Q_DEVICE, dma_phys(g->used));
|
||||
w16(g->common, VCOMMON_Q_ENABLE, 1);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Submit one command and wait for the reply.
|
||||
//
|
||||
// Two descriptors: the request, which the device reads, and the response, which
|
||||
// it writes. Polled rather than interrupt-driven -- every caller is either boot
|
||||
// setup or present(), both of which want the answer before continuing, and an
|
||||
// interrupt would only add a sleep and a wakeup to a wait of a few microseconds.
|
||||
static int
|
||||
cmd(struct vgpu *g, uint32 cmdlen, uint32 resplen)
|
||||
{
|
||||
uint16 head = g->next_desc;
|
||||
uint16 second = (uint16)((head + 1) % g->qsize);
|
||||
|
||||
g->desc[head].addr = dma_phys(g->cmd);
|
||||
g->desc[head].len = cmdlen;
|
||||
g->desc[head].flags = VRING_DESC_F_NEXT;
|
||||
g->desc[head].next = second;
|
||||
|
||||
g->desc[second].addr = dma_phys(g->resp);
|
||||
g->desc[second].len = resplen;
|
||||
g->desc[second].flags = VRING_DESC_F_WRITE;
|
||||
g->desc[second].next = 0;
|
||||
|
||||
g->next_desc = (uint16)((second + 1) % g->qsize);
|
||||
|
||||
g->avail->ring[g->avail->idx % g->qsize] = head;
|
||||
__sync_synchronize(); // ring entry visible before idx
|
||||
g->avail->idx++;
|
||||
__sync_synchronize();
|
||||
|
||||
// Notify. The address is the notify region plus this queue's offset scaled by
|
||||
// the multiplier the device advertised; the value written is the queue index.
|
||||
*(volatile uint16 *)(g->notify + g->notify_off * g->notify_mult) = CTRL_QUEUE;
|
||||
|
||||
// Bounded, because a device that never answers must not hang the boot. At
|
||||
// QEMU's speed the reply is there almost immediately; on a wedged device this
|
||||
// returns an error that a caller can report.
|
||||
for (int i = 0; i < 10000000; i++) {
|
||||
if (g->used->idx != g->last_used) {
|
||||
g->last_used = g->used->idx;
|
||||
__sync_synchronize();
|
||||
struct virtio_gpu_ctrl_hdr *h = g->resp;
|
||||
return (h->type == VIRTIO_GPU_RESP_OK_NODATA ||
|
||||
h->type == VIRTIO_GPU_RESP_OK_DISPLAY_INFO) ? 0 : -1;
|
||||
}
|
||||
}
|
||||
cprintf("virtio-gpu: device did not answer\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
static void
|
||||
hdr_init(void *p, uint32 type)
|
||||
{
|
||||
struct virtio_gpu_ctrl_hdr *h = p;
|
||||
memset(h, 0, sizeof(*h));
|
||||
h->type = type;
|
||||
}
|
||||
|
||||
// --- the 2D sequence -------------------------------------------------------
|
||||
|
||||
static int
|
||||
setup_scanout(struct vgpu *g)
|
||||
{
|
||||
// What resolution is the display actually at? Asking is the point of
|
||||
// GET_DISPLAY_INFO; guessing 1024x768 is how a driver ends up working on
|
||||
// exactly one host configuration.
|
||||
hdr_init(g->cmd, VIRTIO_GPU_CMD_GET_DISPLAY_INFO);
|
||||
if (cmd(g, sizeof(struct virtio_gpu_ctrl_hdr),
|
||||
sizeof(struct virtio_gpu_resp_display_info)) != 0)
|
||||
return -1;
|
||||
|
||||
struct virtio_gpu_resp_display_info *info = g->resp;
|
||||
if (!info->pmodes[0].enabled || info->pmodes[0].r.width == 0) {
|
||||
cprintf("virtio-gpu: no enabled scanout\n");
|
||||
return -1;
|
||||
}
|
||||
g->width = (int)info->pmodes[0].r.width;
|
||||
g->height = (int)info->pmodes[0].r.height;
|
||||
|
||||
// A host resource to scan out from.
|
||||
{
|
||||
struct virtio_gpu_resource_create_2d *c = g->cmd;
|
||||
hdr_init(c, VIRTIO_GPU_CMD_RESOURCE_CREATE_2D);
|
||||
c->resource_id = 1;
|
||||
c->format = VIRTIO_GPU_FORMAT_B8G8R8X8_UNORM;
|
||||
c->width = g->width;
|
||||
c->height = g->height;
|
||||
if (cmd(g, sizeof(*c), sizeof(struct virtio_gpu_ctrl_hdr)) != 0)
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Guest memory to back it. This is the buffer the rest of the kernel draws
|
||||
// into; the device never reads it directly -- TRANSFER_TO_HOST_2D copies from
|
||||
// here into the host resource, which is what makes presenting explicit.
|
||||
g->fb_bytes = (uint32)g->width * g->height * 4;
|
||||
g->fb = dma_alloc(g->fb_bytes, 4096);
|
||||
if (!g->fb) {
|
||||
cprintf("virtio-gpu: no room for a %d KB framebuffer\n", g->fb_bytes / 1024);
|
||||
return -1;
|
||||
}
|
||||
memset(g->fb, 0, g->fb_bytes);
|
||||
|
||||
{
|
||||
struct virtio_gpu_resource_attach_backing *a = g->cmd;
|
||||
hdr_init(a, VIRTIO_GPU_CMD_RESOURCE_ATTACH_BACKING);
|
||||
a->resource_id = 1;
|
||||
a->nr_entries = 1;
|
||||
struct virtio_gpu_mem_entry *e = (struct virtio_gpu_mem_entry *)(a + 1);
|
||||
e->addr = dma_phys(g->fb);
|
||||
e->length = g->fb_bytes;
|
||||
e->padding = 0;
|
||||
if (cmd(g, sizeof(*a) + sizeof(*e), sizeof(struct virtio_gpu_ctrl_hdr)) != 0)
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Point scanout 0 at it.
|
||||
{
|
||||
struct virtio_gpu_set_scanout *s = g->cmd;
|
||||
hdr_init(s, VIRTIO_GPU_CMD_SET_SCANOUT);
|
||||
s->r.x = s->r.y = 0;
|
||||
s->r.width = g->width;
|
||||
s->r.height = g->height;
|
||||
s->scanout_id = 0;
|
||||
s->resource_id = 1;
|
||||
if (cmd(g, sizeof(*s), sizeof(struct virtio_gpu_ctrl_hdr)) != 0)
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// --- display class ---------------------------------------------------------
|
||||
|
||||
static void
|
||||
vgpu_mode(struct device *d, struct display_mode *m)
|
||||
{
|
||||
m->width = gpu.width;
|
||||
m->height = gpu.height;
|
||||
m->bpp = 32;
|
||||
m->stride = (uint32)gpu.width * 4;
|
||||
}
|
||||
|
||||
static void *
|
||||
vgpu_acquire(struct device *d, uint32 *stride_out)
|
||||
{
|
||||
if (stride_out)
|
||||
*stride_out = (uint32)gpu.width * 4;
|
||||
// No separate back buffer: the guest-side framebuffer already *is* one. The
|
||||
// host cannot see it until TRANSFER_TO_HOST_2D, so drawing into it is
|
||||
// inherently off-screen -- which is the whole reason this device is better to
|
||||
// composite against than a raw VBE aperture.
|
||||
return gpu.ready ? gpu.fb : 0;
|
||||
}
|
||||
|
||||
static void
|
||||
vgpu_present(struct device *d, int x, int y, int w, int h)
|
||||
{
|
||||
if (!gpu.ready)
|
||||
return;
|
||||
if (x < 0) { w += x; x = 0; }
|
||||
if (y < 0) { h += y; y = 0; }
|
||||
if (x + w > gpu.width) w = gpu.width - x;
|
||||
if (y + h > gpu.height) h = gpu.height - y;
|
||||
if (w <= 0 || h <= 0)
|
||||
return;
|
||||
|
||||
{
|
||||
struct virtio_gpu_transfer_to_host_2d *t = gpu.cmd;
|
||||
hdr_init(t, VIRTIO_GPU_CMD_TRANSFER_TO_HOST_2D);
|
||||
t->r.x = x; t->r.y = y; t->r.width = w; t->r.height = h;
|
||||
t->offset = (uint64)y * gpu.width * 4 + (uint64)x * 4;
|
||||
t->resource_id = 1;
|
||||
if (cmd(&gpu, sizeof(*t), sizeof(struct virtio_gpu_ctrl_hdr)) != 0)
|
||||
return;
|
||||
}
|
||||
{
|
||||
struct virtio_gpu_resource_flush *f = gpu.cmd;
|
||||
hdr_init(f, VIRTIO_GPU_CMD_RESOURCE_FLUSH);
|
||||
f->r.x = x; f->r.y = y; f->r.width = w; f->r.height = h;
|
||||
f->resource_id = 1;
|
||||
cmd(&gpu, sizeof(*f), sizeof(struct virtio_gpu_ctrl_hdr));
|
||||
}
|
||||
}
|
||||
|
||||
// No wait_vblank. The device presents whole rectangles atomically, so there is
|
||||
// nothing to synchronise against -- and a null op is more honest than one that
|
||||
// spins and achieves nothing. display_wait_vblank() handles the absence.
|
||||
const struct display_ops virtio_gpu_display_ops = {
|
||||
.mode = vgpu_mode,
|
||||
.acquire = vgpu_acquire,
|
||||
.present = vgpu_present,
|
||||
.wait_vblank = 0,
|
||||
};
|
||||
|
||||
// --- probe -----------------------------------------------------------------
|
||||
|
||||
static int
|
||||
vgpu_probe(struct pci_dev *d)
|
||||
{
|
||||
struct vgpu *g = &gpu;
|
||||
|
||||
if (g->ready)
|
||||
return -1; // one display is enough for now
|
||||
|
||||
pci_enable(d, 0x0006); // memory space + bus master
|
||||
|
||||
g->common = find_cfg(d, VIRTIO_PCI_CAP_COMMON_CFG, 0);
|
||||
g->notify = find_cfg(d, VIRTIO_PCI_CAP_NOTIFY_CFG, &g->notify_mult);
|
||||
if (!g->common || !g->notify) {
|
||||
cprintf("virtio-gpu: no modern virtio capabilities; not a 1.0 device\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Reset, then the status handshake in the specified order.
|
||||
w8(g->common, VCOMMON_STATUS, 0);
|
||||
while (r8(g->common, VCOMMON_STATUS) != 0)
|
||||
;
|
||||
w8(g->common, VCOMMON_STATUS, VSTATUS_ACK);
|
||||
w8(g->common, VCOMMON_STATUS, VSTATUS_ACK | VSTATUS_DRIVER);
|
||||
|
||||
// Accept VIRTIO_F_VERSION_1 and nothing else. Feature bit 32 lives in the
|
||||
// second 32-bit window, which is what the select registers are for.
|
||||
w32(g->common, VCOMMON_DFEAT_SEL, 1);
|
||||
uint32 hi = r32(g->common, VCOMMON_DFEAT);
|
||||
if (!(hi & (1u << (VIRTIO_F_VERSION_1 - 32)))) {
|
||||
cprintf("virtio-gpu: device is not virtio 1.0\n");
|
||||
w8(g->common, VCOMMON_STATUS, VSTATUS_FAILED);
|
||||
return -1;
|
||||
}
|
||||
w32(g->common, VCOMMON_GFEAT_SEL, 0);
|
||||
w32(g->common, VCOMMON_GFEAT, 0);
|
||||
w32(g->common, VCOMMON_GFEAT_SEL, 1);
|
||||
w32(g->common, VCOMMON_GFEAT, 1u << (VIRTIO_F_VERSION_1 - 32));
|
||||
|
||||
w8(g->common, VCOMMON_STATUS, VSTATUS_ACK | VSTATUS_DRIVER | VSTATUS_FEATURES_OK);
|
||||
if (!(r8(g->common, VCOMMON_STATUS) & VSTATUS_FEATURES_OK)) {
|
||||
// The device is allowed to clear this bit to reject the feature set, and
|
||||
// this readback is the only way it ever tells you.
|
||||
cprintf("virtio-gpu: device refused our features\n");
|
||||
w8(g->common, VCOMMON_STATUS, VSTATUS_FAILED);
|
||||
return -1;
|
||||
}
|
||||
|
||||
g->cmd = dma_alloc(4096, 16);
|
||||
g->resp = dma_alloc(4096, 16);
|
||||
if (!g->cmd || !g->resp || queue_setup(g) != 0) {
|
||||
cprintf("virtio-gpu: queue setup failed\n");
|
||||
w8(g->common, VCOMMON_STATUS, VSTATUS_FAILED);
|
||||
return -1;
|
||||
}
|
||||
|
||||
w8(g->common, VCOMMON_STATUS,
|
||||
VSTATUS_ACK | VSTATUS_DRIVER | VSTATUS_FEATURES_OK | VSTATUS_DRIVER_OK);
|
||||
|
||||
if (setup_scanout(g) != 0) {
|
||||
w8(g->common, VCOMMON_STATUS, VSTATUS_FAILED);
|
||||
return -1;
|
||||
}
|
||||
|
||||
g->ready = 1;
|
||||
cprintf("virtio-gpu: %dx%d, %d KB framebuffer, queue %d\n",
|
||||
g->width, g->height, g->fb_bytes / 1024, g->qsize);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct pci_match virtio_gpu_match[] = {
|
||||
{ VIRTIO_VENDOR, VIRTIO_DEV_GPU, 0, 0 },
|
||||
{ 0, 0, 0, 0 },
|
||||
};
|
||||
|
||||
const struct pci_driver virtio_gpu_driver = {
|
||||
.name = "virtio-gpu",
|
||||
.match = virtio_gpu_match,
|
||||
.ops = &virtio_gpu_display_ops,
|
||||
.probe = vgpu_probe,
|
||||
};
|
||||
456
kernel/drivers/net/e1000/e1000.c
Normal file
456
kernel/drivers/net/e1000/e1000.c
Normal file
|
|
@ -0,0 +1,456 @@
|
|||
// Intel 8254x gigabit Ethernet, written from Intel's public datasheet (317453).
|
||||
//
|
||||
// The first network device. There is no IP stack yet, so what this provides is
|
||||
// the layer under one: a MAC address, a transmit path, and a receive ring that
|
||||
// fills with frames. That is deliberately the right order -- an IP stack
|
||||
// written before a driver has nothing to be tested against, and every bug in it
|
||||
// looks like a driver bug.
|
||||
//
|
||||
// Bring-up follows the manual's initialisation sequence (section 14.3-14.5):
|
||||
// reset, read the MAC out of EEPROM, clear the multicast table, set up the
|
||||
// receive and transmit rings, then enable each. The order matters -- enabling a
|
||||
// receiver whose ring base is still zero makes the device DMA into physical
|
||||
// address 0, which on this kernel is the boot page table.
|
||||
|
||||
#include "types.h"
|
||||
#include "defs.h"
|
||||
#include "param.h"
|
||||
#include "x86.h"
|
||||
#include "spinlock.h"
|
||||
#include "sleeplock.h"
|
||||
#include "fs.h"
|
||||
#include "file.h"
|
||||
#include "mmu.h"
|
||||
#include "memlayout.h"
|
||||
#include "pci.h"
|
||||
#include "device.h"
|
||||
#include "dma.h"
|
||||
#include "irq.h"
|
||||
#include "e1000.h"
|
||||
|
||||
struct e1000 {
|
||||
volatile uint8 *regs;
|
||||
|
||||
struct e1000_tx_desc *tx;
|
||||
struct e1000_rx_desc *rx;
|
||||
uint8 *txbuf[E1000_NTX];
|
||||
uint8 *rxbuf[E1000_NRX];
|
||||
uint16 tx_next;
|
||||
uint16 rx_next;
|
||||
|
||||
uint8 mac[6];
|
||||
int ready;
|
||||
uint64 rx_packets, tx_packets, rx_dropped;
|
||||
|
||||
struct spinlock lock;
|
||||
};
|
||||
|
||||
static struct e1000 nic;
|
||||
|
||||
static inline uint32 rd(struct e1000 *e, int off) { return *(volatile uint32 *)(e->regs + off); }
|
||||
static inline void wr(struct e1000 *e, int off, uint32 v) { *(volatile uint32 *)(e->regs + off) = v; }
|
||||
|
||||
// The EEPROM is read one 16-bit word at a time through a single register: write
|
||||
// the address with the start bit, poll for done, take the data out of the top
|
||||
// half.
|
||||
//
|
||||
// Bounded, because a device whose EEPROM never reports done would otherwise
|
||||
// hang the boot -- and QEMU's e1000 does answer, but a real card with a blank
|
||||
// EEPROM is a thing that exists.
|
||||
static int
|
||||
eeprom_read(struct e1000 *e, int addr, uint16 *out)
|
||||
{
|
||||
wr(e, E1000_EERD, (addr << E1000_EERD_ADDR_SHIFT) | E1000_EERD_START);
|
||||
for (int i = 0; i < 100000; i++) {
|
||||
uint32 v = rd(e, E1000_EERD);
|
||||
if (v & E1000_EERD_DONE) {
|
||||
*out = (uint16)(v >> E1000_EERD_DATA_SHIFT);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
// The MAC address, from EEPROM words 0..2, little-endian within each word.
|
||||
//
|
||||
// Falls back to the receive-address registers, which the firmware may already
|
||||
// have programmed. QEMU fills both; some cards leave the EEPROM path
|
||||
// unimplemented, and a driver that only knows one of the two ends up with a MAC
|
||||
// of all zeroes and no idea why nothing answers it.
|
||||
static void
|
||||
read_mac(struct e1000 *e)
|
||||
{
|
||||
uint16 w;
|
||||
int ok = 1;
|
||||
|
||||
for (int i = 0; i < 3; i++) {
|
||||
if (eeprom_read(e, i, &w) != 0) { ok = 0; break; }
|
||||
e->mac[i * 2] = (uint8)(w & 0xFF);
|
||||
e->mac[i * 2 + 1] = (uint8)(w >> 8);
|
||||
}
|
||||
if (ok)
|
||||
return;
|
||||
|
||||
uint32 lo = rd(e, E1000_RAL), hi = rd(e, E1000_RAH);
|
||||
e->mac[0] = (uint8)lo; e->mac[1] = (uint8)(lo >> 8);
|
||||
e->mac[2] = (uint8)(lo >> 16); e->mac[3] = (uint8)(lo >> 24);
|
||||
e->mac[4] = (uint8)hi; e->mac[5] = (uint8)(hi >> 8);
|
||||
}
|
||||
|
||||
static int
|
||||
rings_setup(struct e1000 *e)
|
||||
{
|
||||
// One block for both rings and every buffer, from the DMA arena: the device
|
||||
// reads all of it by physical address, and kalloc's pages are contiguous only
|
||||
// by accident.
|
||||
uint32 txring = E1000_NTX * sizeof(struct e1000_tx_desc);
|
||||
uint32 rxring = E1000_NRX * sizeof(struct e1000_rx_desc);
|
||||
|
||||
e->tx = dma_alloc(txring, 16);
|
||||
e->rx = dma_alloc(rxring, 16);
|
||||
if (!e->tx || !e->rx)
|
||||
return -1;
|
||||
memset(e->tx, 0, txring);
|
||||
memset(e->rx, 0, rxring);
|
||||
|
||||
for (int i = 0; i < E1000_NTX; i++) {
|
||||
e->txbuf[i] = dma_alloc(E1000_BUFSZ, 16);
|
||||
if (!e->txbuf[i])
|
||||
return -1;
|
||||
e->tx[i].addr = dma_phys(e->txbuf[i]);
|
||||
// DD set on every transmit descriptor at start-up, so the first pass round
|
||||
// the ring sees them as free. The hardware only sets it after it has used
|
||||
// one, and a driver that waits for a bit the device has never had reason to
|
||||
// write waits forever.
|
||||
e->tx[i].status = E1000_TXD_STAT_DD;
|
||||
}
|
||||
|
||||
for (int i = 0; i < E1000_NRX; i++) {
|
||||
e->rxbuf[i] = dma_alloc(E1000_BUFSZ, 16);
|
||||
if (!e->rxbuf[i])
|
||||
return -1;
|
||||
e->rx[i].addr = dma_phys(e->rxbuf[i]);
|
||||
e->rx[i].status = 0;
|
||||
}
|
||||
|
||||
wr(e, E1000_TDBAL, (uint32)dma_phys(e->tx));
|
||||
wr(e, E1000_TDBAH, (uint32)(dma_phys(e->tx) >> 32));
|
||||
wr(e, E1000_TDLEN, txring);
|
||||
wr(e, E1000_TDH, 0);
|
||||
wr(e, E1000_TDT, 0);
|
||||
|
||||
wr(e, E1000_RDBAL, (uint32)dma_phys(e->rx));
|
||||
wr(e, E1000_RDBAH, (uint32)(dma_phys(e->rx) >> 32));
|
||||
wr(e, E1000_RDLEN, rxring);
|
||||
wr(e, E1000_RDH, 0);
|
||||
// Tail points at the last descriptor the hardware may fill. Setting it to
|
||||
// NRX-1 hands it every descriptor but one; the ring is full when head meets
|
||||
// tail, so one must always be held back or "full" and "empty" look identical.
|
||||
wr(e, E1000_RDT, E1000_NRX - 1);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Queue one frame. Returns 0, or -1 if the ring is full.
|
||||
//
|
||||
// Copies into the descriptor's own buffer rather than mapping the caller's.
|
||||
// A zero-copy path wants the caller to guarantee the memory stays put and is
|
||||
// physically contiguous, which nothing above this can promise yet.
|
||||
int
|
||||
e1000_transmit(const void *frame, int len)
|
||||
{
|
||||
struct e1000 *e = &nic;
|
||||
|
||||
if (!e->ready || len <= 0 || len > E1000_BUFSZ)
|
||||
return -1;
|
||||
|
||||
acquire(&e->lock);
|
||||
struct e1000_tx_desc *d = &e->tx[e->tx_next];
|
||||
if (!(d->status & E1000_TXD_STAT_DD)) {
|
||||
release(&e->lock);
|
||||
return -1; // the device has not finished with it
|
||||
}
|
||||
|
||||
memmove(e->txbuf[e->tx_next], frame, len);
|
||||
d->length = (uint16)len;
|
||||
d->cmd = E1000_TXD_CMD_EOP | E1000_TXD_CMD_IFCS | E1000_TXD_CMD_RS;
|
||||
d->status = 0;
|
||||
|
||||
e->tx_next = (uint16)((e->tx_next + 1) % E1000_NTX);
|
||||
__sync_synchronize(); // descriptor visible before the tail moves
|
||||
wr(e, E1000_TDT, e->tx_next);
|
||||
e->tx_packets++;
|
||||
release(&e->lock);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Take one received frame, if there is one. Returns its length, or 0.
|
||||
//
|
||||
// Polled. There is nothing above this to wake yet, and a receive path that
|
||||
// wakes a nonexistent protocol stack is harder to reason about than one that is
|
||||
// asked. The interrupt handler below exists to keep the cause register clear
|
||||
// and to count; when an IP stack arrives, this is where the hand-off changes.
|
||||
int
|
||||
e1000_receive(void *out, int cap)
|
||||
{
|
||||
struct e1000 *e = &nic;
|
||||
|
||||
if (!e->ready)
|
||||
return 0;
|
||||
|
||||
acquire(&e->lock);
|
||||
struct e1000_rx_desc *d = &e->rx[e->rx_next];
|
||||
if (!(d->status & E1000_RXD_STAT_DD)) {
|
||||
release(&e->lock);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int len = d->length;
|
||||
if (len > cap)
|
||||
len = cap;
|
||||
memmove(out, e->rxbuf[e->rx_next], len);
|
||||
|
||||
d->status = 0;
|
||||
__sync_synchronize();
|
||||
// Hand the descriptor back by moving the tail to it. The tail always trails
|
||||
// the descriptor just released, which is why it is the *previous* index.
|
||||
wr(e, E1000_RDT, e->rx_next);
|
||||
e->rx_next = (uint16)((e->rx_next + 1) % E1000_NRX);
|
||||
e->rx_packets++;
|
||||
release(&e->lock);
|
||||
return len;
|
||||
}
|
||||
|
||||
static void
|
||||
e1000_intr(void *arg)
|
||||
{
|
||||
struct e1000 *e = arg;
|
||||
// Reading ICR clears it. Not reading it leaves the cause asserted, and with a
|
||||
// level-triggered PCI interrupt that means the handler is re-entered forever.
|
||||
uint32 cause = rd(e, E1000_ICR);
|
||||
(void)cause;
|
||||
}
|
||||
|
||||
int
|
||||
e1000_info(char *buf, int cap)
|
||||
{
|
||||
struct e1000 *e = &nic;
|
||||
int n = 0;
|
||||
|
||||
if (!e->ready)
|
||||
return 0;
|
||||
|
||||
static const char hex[] = "0123456789abcdef";
|
||||
n += 0;
|
||||
for (int i = 0; i < 6 && n + 3 < cap; i++) {
|
||||
buf[n++] = hex[e->mac[i] >> 4];
|
||||
buf[n++] = hex[e->mac[i] & 0xF];
|
||||
if (i < 5)
|
||||
buf[n++] = ':';
|
||||
}
|
||||
if (n < cap) buf[n++] = '\n';
|
||||
return n;
|
||||
}
|
||||
|
||||
static int
|
||||
e1000_probe(struct pci_dev *d)
|
||||
{
|
||||
struct e1000 *e = &nic;
|
||||
|
||||
if (e->ready)
|
||||
return -1;
|
||||
if (!d->bar[0].base)
|
||||
return -1;
|
||||
|
||||
initlock(&e->lock, "e1000");
|
||||
pci_enable(d, 0x0006); // memory space + bus master
|
||||
e->regs = (volatile uint8 *)P2V(d->bar[0].base);
|
||||
|
||||
// Reset, then wait for it to clear. The manual asks for a delay after reset
|
||||
// before any other register is touched; polling CTRL is that delay.
|
||||
wr(e, E1000_CTRL, rd(e, E1000_CTRL) | E1000_CTRL_RST);
|
||||
for (int i = 0; i < 1000000; i++)
|
||||
if (!(rd(e, E1000_CTRL) & E1000_CTRL_RST))
|
||||
break;
|
||||
|
||||
wr(e, E1000_IMC, 0xFFFFFFFF); // mask everything while we set up
|
||||
rd(e, E1000_ICR); // and clear whatever was pending
|
||||
|
||||
wr(e, E1000_CTRL, rd(e, E1000_CTRL) | E1000_CTRL_SLU | E1000_CTRL_ASDE);
|
||||
|
||||
read_mac(e);
|
||||
|
||||
// The multicast table is undefined after reset. Left as it comes up, the
|
||||
// device accepts multicast traffic for addresses nobody asked for.
|
||||
for (int i = 0; i < 128; i++)
|
||||
wr(e, E1000_MTA + i * 4, 0);
|
||||
|
||||
// Program our own MAC into the receive-address filter, with the valid bit.
|
||||
{
|
||||
uint32 lo = (uint32)e->mac[0] | ((uint32)e->mac[1] << 8) |
|
||||
((uint32)e->mac[2] << 16) | ((uint32)e->mac[3] << 24);
|
||||
uint32 hi = (uint32)e->mac[4] | ((uint32)e->mac[5] << 8) | (1u << 31);
|
||||
wr(e, E1000_RAL, lo);
|
||||
wr(e, E1000_RAH, hi);
|
||||
}
|
||||
|
||||
if (rings_setup(e) != 0) {
|
||||
cprintf("e1000: no DMA memory for the rings\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
wr(e, E1000_TIPG, 10 | (8 << 10) | (6 << 20)); // the manual's IEEE defaults
|
||||
wr(e, E1000_TCTL, E1000_TCTL_EN | E1000_TCTL_PSP |
|
||||
(0x10 << E1000_TCTL_CT_SHIFT) |
|
||||
(0x40 << E1000_TCTL_COLD_SHIFT));
|
||||
|
||||
wr(e, E1000_RCTL, E1000_RCTL_EN | E1000_RCTL_BAM | E1000_RCTL_SECRC |
|
||||
E1000_RCTL_SZ_2048 | E1000_RCTL_LBM_NONE);
|
||||
|
||||
if (d->irq != 0xFF)
|
||||
// level_low = 1: this is a PCI interrupt, which is level-triggered and
|
||||
// active low. Registering it as edge-triggered makes it fire once and then
|
||||
// wedge, because the device holds the line asserted and no further edge
|
||||
// ever arrives. See the comment on irq_register in include/irq.h.
|
||||
irq_register(d->irq, e1000_intr, e, "e1000", 1);
|
||||
wr(e, E1000_IMS, E1000_ICR_RXT0 | E1000_ICR_RXDMT0 | E1000_ICR_LSC);
|
||||
|
||||
e->ready = 1;
|
||||
|
||||
cprintf("e1000: %x:%x:%x:%x:%x:%x, link %s, irq %d\n",
|
||||
e->mac[0], e->mac[1], e->mac[2], e->mac[3], e->mac[4], e->mac[5],
|
||||
(rd(e, E1000_STATUS) & E1000_STATUS_LU) ? "up" : "down", d->irq);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Every 8254x that shares the register map above. See include/e1000.h for
|
||||
// where the IDs come from and why the PCIe parts are excluded.
|
||||
#define M(id) { E1000_VENDOR, (id), 0, 0 }
|
||||
static const struct pci_match e1000_match[] = {
|
||||
M(E1000_DEV_82540EM), M(E1000_DEV_82540EM_LOM),
|
||||
M(E1000_DEV_82540EP_LOM), M(E1000_DEV_82540EP),
|
||||
M(E1000_DEV_82540EP_LP),
|
||||
M(E1000_DEV_82541EI), M(E1000_DEV_82541EI_MOBILE),
|
||||
M(E1000_DEV_82541ER_LOM), M(E1000_DEV_82541ER),
|
||||
M(E1000_DEV_82541GI), M(E1000_DEV_82541GI_MOBILE),
|
||||
M(E1000_DEV_82541GI_LF),
|
||||
M(E1000_DEV_82544EI_FIBER), M(E1000_DEV_82544GC_COPPER),
|
||||
M(E1000_DEV_82544GC_LOM),
|
||||
M(E1000_DEV_82545EM_COPPER), M(E1000_DEV_82545EM_FIBER),
|
||||
M(E1000_DEV_82545GM_COPPER), M(E1000_DEV_82545GM_FIBER),
|
||||
M(E1000_DEV_82545GM_SERDES),
|
||||
M(E1000_DEV_82546EB_COPPER), M(E1000_DEV_82546EB_FIBER),
|
||||
M(E1000_DEV_82546EB_QUAD),
|
||||
M(E1000_DEV_82546GB_COPPER), M(E1000_DEV_82546GB_FIBER),
|
||||
M(E1000_DEV_82546GB_SERDES),
|
||||
M(E1000_DEV_82547EI),
|
||||
{ 0, 0, 0, 0 },
|
||||
};
|
||||
#undef M
|
||||
|
||||
const struct pci_driver e1000_driver = {
|
||||
.name = "e1000",
|
||||
.devname = "eth",
|
||||
.match = e1000_match,
|
||||
.ops = 0, // no class vtable yet; there is no net class
|
||||
.probe = e1000_probe,
|
||||
};
|
||||
|
||||
// --- self-test: does this thing actually move packets? ---------------------
|
||||
//
|
||||
// The transmit and receive paths were written together and neither had ever
|
||||
// carried a frame. That is exactly the state in which a driver looks finished
|
||||
// and is not: the rings are set up, the registers read back plausibly, and the
|
||||
// first thing to discover otherwise would have been a protocol stack failing
|
||||
// for reasons indistinguishable from its own bugs.
|
||||
//
|
||||
// So: build an ARP request by hand, send it, and wait for the reply. It proves
|
||||
// the whole path at once -- descriptor format, DD handshake, tail bookkeeping,
|
||||
// DMA addresses, the receive ring handback, and the MAC filter -- because an
|
||||
// ARP reply is addressed to *us* and will not arrive if the filter is wrong.
|
||||
//
|
||||
// Under QEMU's user-mode networking the guest is 10.0.2.15 and the gateway is
|
||||
// 10.0.2.2. SLIRP is silent until spoken to, which is why this has to transmit
|
||||
// before it can expect to receive.
|
||||
//
|
||||
// `echo n > /proc/sysrq`.
|
||||
|
||||
static void
|
||||
put_be16(uint8 *p, uint16 v)
|
||||
{
|
||||
p[0] = (uint8)(v >> 8);
|
||||
p[1] = (uint8)v;
|
||||
}
|
||||
|
||||
const unsigned char *
|
||||
e1000_mac(void)
|
||||
{
|
||||
return nic.mac;
|
||||
}
|
||||
|
||||
void
|
||||
e1000_selftest(void)
|
||||
{
|
||||
struct e1000 *e = &nic;
|
||||
uint8 frame[42];
|
||||
uint8 reply[E1000_BUFSZ];
|
||||
|
||||
if (!e->ready) {
|
||||
cprintf("e1000: no device\n");
|
||||
return;
|
||||
}
|
||||
|
||||
static const uint8 me[4] = { 10, 0, 2, 15 }; // QEMU SLIRP guest address
|
||||
static const uint8 them[4] = { 10, 0, 2, 2 }; // and its gateway
|
||||
|
||||
memset(frame, 0, sizeof(frame));
|
||||
for (int i = 0; i < 6; i++)
|
||||
frame[i] = 0xFF; // broadcast
|
||||
for (int i = 0; i < 6; i++)
|
||||
frame[6 + i] = e->mac[i];
|
||||
put_be16(&frame[12], 0x0806); // ARP
|
||||
|
||||
put_be16(&frame[14], 1); // Ethernet
|
||||
put_be16(&frame[16], 0x0800); // IPv4
|
||||
frame[18] = 6; // hardware address length
|
||||
frame[19] = 4; // protocol address length
|
||||
put_be16(&frame[20], 1); // request
|
||||
for (int i = 0; i < 6; i++)
|
||||
frame[22 + i] = e->mac[i]; // sender hardware address
|
||||
for (int i = 0; i < 4; i++)
|
||||
frame[28 + i] = me[i]; // sender protocol address
|
||||
// target hardware address stays zero -- that is what is being asked
|
||||
for (int i = 0; i < 4; i++)
|
||||
frame[38 + i] = them[i];
|
||||
|
||||
cprintf("e1000: asking who has %d.%d.%d.%d\n", them[0], them[1], them[2], them[3]);
|
||||
if (e1000_transmit(frame, sizeof(frame)) != 0) {
|
||||
cprintf("e1000: transmit ring full\n");
|
||||
return;
|
||||
}
|
||||
|
||||
// Bounded poll. There is no interrupt path to sleep on yet and this runs from
|
||||
// a sysrq write, so spinning briefly is the honest way to wait.
|
||||
for (int spin = 0; spin < 20000000; spin++) {
|
||||
int n = e1000_receive(reply, sizeof(reply));
|
||||
if (n <= 0)
|
||||
continue;
|
||||
|
||||
cprintf("e1000: %d bytes from %x:%x:%x:%x:%x:%x type %x\n",
|
||||
n, reply[6], reply[7], reply[8], reply[9], reply[10], reply[11],
|
||||
((uint32)reply[12] << 8) | reply[13]);
|
||||
|
||||
if (n >= 42 && reply[12] == 0x08 && reply[13] == 0x06 &&
|
||||
reply[20] == 0 && reply[21] == 2) {
|
||||
cprintf("e1000: ARP reply -- %d.%d.%d.%d is at %x:%x:%x:%x:%x:%x\n",
|
||||
reply[28], reply[29], reply[30], reply[31],
|
||||
reply[22], reply[23], reply[24], reply[25],
|
||||
reply[26], reply[27]);
|
||||
cprintf("e1000: self-test passed, %d tx %d rx\n",
|
||||
(int)e->tx_packets, (int)e->rx_packets);
|
||||
return;
|
||||
}
|
||||
}
|
||||
cprintf("e1000: no reply -- %d tx, %d rx\n",
|
||||
(int)e->tx_packets, (int)e->rx_packets);
|
||||
}
|
||||
114
kernel/drivers/registry.c
Normal file
114
kernel/drivers/registry.c
Normal file
|
|
@ -0,0 +1,114 @@
|
|||
// The driver registry.
|
||||
//
|
||||
// One table, one line per driver. This is the same shape as
|
||||
// kernel/sys/table.inc and kernel/dev/cdev.c's devsw: a single place that
|
||||
// lists what exists, so a driver cannot be half-registered.
|
||||
//
|
||||
// It is a plain array rather than the linker-section trick a bigger kernel
|
||||
// would use (a __attribute__((section(".drivers"))) entry per driver, walked
|
||||
// between __start_ and __stop_ symbols). That approach lets a driver register
|
||||
// itself by existing, which is genuinely nicer -- but it also means the list
|
||||
// of what is in your kernel is not written down anywhere you can read, and
|
||||
// a driver silently dropped by the linker looks exactly like one that was
|
||||
// never written. At this size, being able to read the list wins.
|
||||
//
|
||||
// To add a driver: write kernel/drivers/<name>.c exporting a struct
|
||||
// pci_driver, declare it here, and add it to the array and to the Makefile's
|
||||
// KERN_DRIVERS.
|
||||
|
||||
#include "types.h"
|
||||
#include "defs.h"
|
||||
#include "param.h"
|
||||
#include "spinlock.h"
|
||||
#include "sleeplock.h"
|
||||
#include "fs.h"
|
||||
#include "file.h"
|
||||
#include "pci.h"
|
||||
|
||||
#include "usb.h"
|
||||
#include "device.h"
|
||||
|
||||
extern const struct pci_driver vga_pci_driver;
|
||||
extern const struct pci_driver xhci_driver;
|
||||
extern const struct pci_driver virtio_gpu_driver;
|
||||
extern const struct pci_driver e1000_driver;
|
||||
|
||||
// Order matters here in one respect: virtio-gpu is offered the bus before the
|
||||
// VBE framebuffer driver, so that on a machine with both, the device with real
|
||||
// mode setting and an explicit flush wins the display class. vga_pci still
|
||||
// claims the stdvga card underneath, which is what the boot splash and the
|
||||
// text console are drawn on.
|
||||
static const struct pci_driver *drivers[] = {
|
||||
&virtio_gpu_driver,
|
||||
&vga_pci_driver,
|
||||
&e1000_driver,
|
||||
&xhci_driver,
|
||||
};
|
||||
|
||||
void
|
||||
pci_bind_all(void)
|
||||
{
|
||||
for (unsigned i = 0; i < sizeof(drivers) / sizeof(drivers[0]); i++)
|
||||
pci_bind(drivers[i]);
|
||||
}
|
||||
|
||||
// USB class drivers, same arrangement and for the same reason. These are not
|
||||
// found on the PCI bus -- they are offered a device by the USB core once its
|
||||
// interface descriptor has been read -- but a driver you cannot find in a
|
||||
// list is a driver nobody knows is there, whichever bus it sits on.
|
||||
//
|
||||
// To add one: write kernel/drivers/usb_<name>.c exporting a struct
|
||||
// usb_driver, declare it here, add it to the array and to KERN_DRIVERS.
|
||||
|
||||
extern const struct usb_driver usb_hid_driver;
|
||||
extern const struct usb_driver usb_storage_driver;
|
||||
extern const struct usb_driver usb_hub_driver;
|
||||
|
||||
static const struct usb_driver *usb_drivers[] = {
|
||||
&usb_hid_driver,
|
||||
&usb_storage_driver,
|
||||
&usb_hub_driver,
|
||||
};
|
||||
|
||||
int usb_offer(const struct usb_driver *, struct usb_dev *);
|
||||
|
||||
void
|
||||
usb_bind_all(struct usb_dev *dev)
|
||||
{
|
||||
for (unsigned i = 0; i < sizeof(usb_drivers) / sizeof(usb_drivers[0]); i++)
|
||||
if (usb_offer(usb_drivers[i], dev)) {
|
||||
// Same as the PCI path: publish it in the bus-agnostic list so that
|
||||
// "what is attached" has one answer covering every bus. Class comes from
|
||||
// the driver that claimed it rather than the USB class code, because a
|
||||
// driver knows what it made of the device and the descriptor only says
|
||||
// what the device claims to be.
|
||||
const char *n = usb_drivers[i]->name;
|
||||
struct device *d = device_add(n, DEVBUS_USB, usb_drivers[i]->devclass, n);
|
||||
if (d)
|
||||
device_set_desc(d, usb_drivers[i]->name);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Devices that are not enumerated by any bus -- they are simply known to be
|
||||
// there on a PC, and the driver pokes fixed I/O ports to find out whether it
|
||||
// is right. Listing them matters because a device manager that shows the
|
||||
// graphics card but not the keyboard is not showing the machine.
|
||||
//
|
||||
// Registered here rather than inside each driver's init so that the whole set
|
||||
// is visible in one place, which is the same reasoning as the driver arrays
|
||||
// above.
|
||||
void
|
||||
platform_register_devices(void)
|
||||
{
|
||||
struct device *d;
|
||||
|
||||
if ((d = device_add("kbd", DEVBUS_PLATFORM, DEVCLASS_INPUT, "ps2kbd")))
|
||||
device_set_desc(d, "PS/2 keyboard");
|
||||
if ((d = device_add("mouse", DEVBUS_PLATFORM, DEVCLASS_INPUT, "ps2mouse")))
|
||||
device_set_desc(d, "PS/2 mouse");
|
||||
if ((d = device_add("uart", DEVBUS_PLATFORM, DEVCLASS_OTHER, "uart8250")))
|
||||
device_set_desc(d, "16550 serial port");
|
||||
if ((d = device_add("ide", DEVBUS_PLATFORM, DEVCLASS_STORAGE, "ide")))
|
||||
device_set_desc(d, "ATA disk controller");
|
||||
}
|
||||
209
kernel/drivers/usb/core/usb.c
Normal file
209
kernel/drivers/usb/core/usb.c
Normal file
|
|
@ -0,0 +1,209 @@
|
|||
// The USB core: everything between "the controller can talk to a device" and
|
||||
// "a driver knows what the device is".
|
||||
//
|
||||
// The split matters. xhci.c deals in TRBs, slot IDs and doorbells; nothing
|
||||
// here knows any of those words. A class driver deals in reports and
|
||||
// commands; nothing here knows those either. What lives in the middle is the
|
||||
// part of USB that is the same for every device ever made: read the
|
||||
// configuration, choose one, look at the interface, find someone who wants it.
|
||||
|
||||
#include "types.h"
|
||||
#include "defs.h"
|
||||
#include "param.h"
|
||||
#include "memlayout.h"
|
||||
#include "mmu.h"
|
||||
#include "spinlock.h"
|
||||
#include "dma.h"
|
||||
#include "usb.h"
|
||||
|
||||
// A staging buffer for control transfers.
|
||||
//
|
||||
// It exists because a descriptor has to be read into memory the controller
|
||||
// can reach and that does not straddle a 64 KB boundary -- a rule that holds
|
||||
// for xHCI data stages and that a static buffer inside some other structure
|
||||
// satisfies only by luck. One page, allocated once, copied out of afterwards.
|
||||
static void *scratch;
|
||||
|
||||
static void *
|
||||
get_scratch(void)
|
||||
{
|
||||
if(scratch == 0)
|
||||
scratch = dma_alloc(4096, 4096);
|
||||
return scratch;
|
||||
}
|
||||
|
||||
static int
|
||||
get_descriptor(struct usb_dev *dev, int type, int index, void *buf, int len)
|
||||
{
|
||||
struct usb_setup s;
|
||||
|
||||
// Clear first. The scratch buffer is shared by every device on the bus, and
|
||||
// a control transfer reports success without saying how many bytes it
|
||||
// actually moved -- so a device that answers short leaves the previous
|
||||
// device's descriptor visible past the end of its own, and the parser
|
||||
// happily walks into it. Zeroed bytes stop the walk instead, because a
|
||||
// descriptor whose length is 0 is rejected.
|
||||
memset(buf, 0, len);
|
||||
|
||||
s.bmRequestType = USB_DIR_IN | USB_TYPE_STANDARD | USB_RECIP_DEVICE;
|
||||
s.bRequest = USB_REQ_GET_DESCRIPTOR;
|
||||
s.wValue = (type << 8) | index;
|
||||
s.wIndex = 0;
|
||||
s.wLength = len;
|
||||
return dev->ops->control(dev, &s, buf, len);
|
||||
}
|
||||
|
||||
static int
|
||||
set_configuration(struct usb_dev *dev, int value)
|
||||
{
|
||||
struct usb_setup s;
|
||||
|
||||
s.bmRequestType = USB_DIR_OUT | USB_TYPE_STANDARD | USB_RECIP_DEVICE;
|
||||
s.bRequest = USB_REQ_SET_CONFIG;
|
||||
s.wValue = value;
|
||||
s.wIndex = 0;
|
||||
s.wLength = 0;
|
||||
return dev->ops->control(dev, &s, 0, 0);
|
||||
}
|
||||
|
||||
// Walk the configuration descriptor.
|
||||
//
|
||||
// What comes back from GET_DESCRIPTOR(CONFIG) is not one descriptor: it is
|
||||
// the configuration descriptor followed by every interface and endpoint
|
||||
// descriptor belonging to it, packed end to end, each one starting with its
|
||||
// own length. That length is the only thing that can be relied on to find the
|
||||
// next -- descriptor types this kernel has never heard of appear in the
|
||||
// middle (HID's own, class-specific ones) and skipping them by size is the
|
||||
// only way through. A parser that assumed a fixed order would work on one
|
||||
// device and fail on the next.
|
||||
static void
|
||||
parse_config(struct usb_dev *dev)
|
||||
{
|
||||
int off = 0;
|
||||
int seen_iface = 0;
|
||||
|
||||
dev->has_intr_in = dev->has_bulk_in = dev->has_bulk_out = 0;
|
||||
|
||||
while(off + 2 <= dev->conflen){
|
||||
uint8 len = dev->conf[off];
|
||||
uint8 type = dev->conf[off + 1];
|
||||
|
||||
if(len < 2 || off + len > dev->conflen)
|
||||
break; // malformed: stop rather than guess
|
||||
|
||||
if(type == USB_DT_INTERFACE && len >= (int)sizeof(dev->iface)){
|
||||
if(seen_iface)
|
||||
break; // first interface only, for now
|
||||
memmove(&dev->iface, &dev->conf[off], sizeof(dev->iface));
|
||||
seen_iface = 1;
|
||||
} else if(type == USB_DT_ENDPOINT && seen_iface &&
|
||||
len >= (int)sizeof(struct usb_endpoint_desc)){
|
||||
struct usb_endpoint_desc ep;
|
||||
int in;
|
||||
|
||||
memmove(&ep, &dev->conf[off], sizeof(ep));
|
||||
in = USB_EP_IS_IN(ep.bEndpointAddress);
|
||||
|
||||
switch(USB_EP_XFERTYPE(ep.bmAttributes)){
|
||||
case USB_EP_INTERRUPT:
|
||||
if(in && !dev->has_intr_in){ dev->intr_in = ep; dev->has_intr_in = 1; }
|
||||
break;
|
||||
case USB_EP_BULK:
|
||||
if(in && !dev->has_bulk_in){ dev->bulk_in = ep; dev->has_bulk_in = 1; }
|
||||
if(!in && !dev->has_bulk_out){ dev->bulk_out = ep; dev->has_bulk_out = 1; }
|
||||
break;
|
||||
}
|
||||
}
|
||||
off += len;
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Class drivers
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
extern const struct usb_driver usb_hid_driver;
|
||||
void usb_bind_all(struct usb_dev *dev);
|
||||
|
||||
static int
|
||||
matches(const struct usb_driver *drv, struct usb_dev *dev)
|
||||
{
|
||||
if(drv->class != 0xFF && drv->class != dev->iface.bInterfaceClass)
|
||||
return 0;
|
||||
if(drv->subclass != 0xFF && drv->subclass != dev->iface.bInterfaceSubClass)
|
||||
return 0;
|
||||
if(drv->protocol != 0xFF && drv->protocol != dev->iface.bInterfaceProtocol)
|
||||
return 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Offer a device to one driver. Returns 1 if it was claimed.
|
||||
int
|
||||
usb_offer(const struct usb_driver *drv, struct usb_dev *dev)
|
||||
{
|
||||
if(dev->driver || !matches(drv, dev))
|
||||
return 0;
|
||||
if(drv->probe(dev) != 0)
|
||||
return 0;
|
||||
dev->driver = drv->name;
|
||||
return 1;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
void
|
||||
usb_attach(struct usb_dev *dev)
|
||||
{
|
||||
struct usb_config_desc *cd;
|
||||
void *buf = get_scratch();
|
||||
int total;
|
||||
|
||||
if(buf == 0)
|
||||
return;
|
||||
|
||||
// Nine bytes first, because wTotalLength is in them and there is no other
|
||||
// way to know how much to ask for.
|
||||
if(get_descriptor(dev, USB_DT_CONFIG, 0, buf, 9) < 0){
|
||||
cprintf("usb: slot %d would not give up its configuration\n", dev->slot);
|
||||
return;
|
||||
}
|
||||
cd = buf;
|
||||
total = cd->wTotalLength;
|
||||
|
||||
// A configuration that does not contain its own descriptor is not one we
|
||||
// can do anything with, and reading it as a length would walk off the end.
|
||||
if(cd->bDescriptorType != USB_DT_CONFIG || total < (int)sizeof(*cd)){
|
||||
cprintf("usb: slot %d returned a malformed configuration descriptor\n",
|
||||
dev->slot);
|
||||
return;
|
||||
}
|
||||
if(total > USB_MAXCONF)
|
||||
total = USB_MAXCONF;
|
||||
|
||||
if(get_descriptor(dev, USB_DT_CONFIG, 0, buf, total) < 0)
|
||||
return;
|
||||
memmove(dev->conf, buf, total);
|
||||
dev->conflen = total;
|
||||
|
||||
parse_config(dev);
|
||||
|
||||
// A device is not obliged to do anything at all until it has been
|
||||
// configured -- before this, endpoints other than 0 do not exist.
|
||||
if(set_configuration(dev, cd->bConfigurationValue) < 0){
|
||||
cprintf("usb: slot %d refused SET_CONFIGURATION\n", dev->slot);
|
||||
return;
|
||||
}
|
||||
|
||||
cprintf("usb: interface class %d.%d protocol %d, %d endpoint%s%s%s%s\n",
|
||||
dev->iface.bInterfaceClass, dev->iface.bInterfaceSubClass,
|
||||
dev->iface.bInterfaceProtocol, dev->iface.bNumEndpoints,
|
||||
dev->iface.bNumEndpoints == 1 ? "" : "s",
|
||||
dev->has_intr_in ? ", interrupt IN" : "",
|
||||
dev->has_bulk_in ? ", bulk IN" : "",
|
||||
dev->has_bulk_out ? ", bulk OUT" : "");
|
||||
|
||||
usb_bind_all(dev);
|
||||
|
||||
if(dev->driver == 0)
|
||||
cprintf("usb: no driver for this device\n");
|
||||
}
|
||||
305
kernel/drivers/usb/hid/usb_hid.c
Normal file
305
kernel/drivers/usb/hid/usb_hid.c
Normal file
|
|
@ -0,0 +1,305 @@
|
|||
// USB HID, boot protocol: keyboards and mice.
|
||||
//
|
||||
// Boot protocol is the reason a USB keyboard works in a BIOS setup screen
|
||||
// before any operating system exists. A HID device normally describes its
|
||||
// reports with a report descriptor -- a small stack language that has to be
|
||||
// parsed to know which bit means which key -- but a device claiming the boot
|
||||
// subclass also promises to support one fixed layout on request. Eight bytes
|
||||
// for a keyboard, three for a mouse, defined in the HID specification and
|
||||
// identical on every device.
|
||||
//
|
||||
// That is worth taking. A full report-descriptor parser is a real piece of
|
||||
// work and buys nothing for a keyboard; it is what a game controller or a
|
||||
// touchscreen will need, and it can arrive with them.
|
||||
|
||||
#include "types.h"
|
||||
#include "defs.h"
|
||||
#include "param.h"
|
||||
#include "spinlock.h"
|
||||
#include "dma.h"
|
||||
#include "usb.h"
|
||||
#include "input.h"
|
||||
|
||||
#define NHID 4
|
||||
|
||||
struct hid {
|
||||
int in_use;
|
||||
int proto; // USB_HID_PROTO_KEYBOARD or _MOUSE
|
||||
uchar prev[8]; // the previous report, for edge detection
|
||||
};
|
||||
|
||||
static struct hid hids[NHID];
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Keyboard
|
||||
//
|
||||
// The report is: modifiers, a reserved byte, then up to six usage IDs of keys
|
||||
// currently held. It is a *state*, not an event -- a key that stays down
|
||||
// appears in every report until it is released, so a character is emitted
|
||||
// only for usages that were not in the previous report. Sending one per
|
||||
// report instead would repeat every held key a few hundred times a second.
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#define HID_MOD_CTRL 0x11 // left | right
|
||||
#define HID_MOD_SHIFT 0x22
|
||||
#define HID_MOD_ALT 0x44
|
||||
|
||||
// Usage IDs 0x00-0x38 of the Keyboard/Keypad page. 0 means "nothing to type".
|
||||
static const uchar hid_unshifted[0x39] = {
|
||||
0, 0, 0, 0,
|
||||
'a','b','c','d','e','f','g','h','i','j','k','l','m',
|
||||
'n','o','p','q','r','s','t','u','v','w','x','y','z',
|
||||
'1','2','3','4','5','6','7','8','9','0',
|
||||
'\n', 0x1B, '\b', '\t', ' ',
|
||||
'-','=','[',']','\\','#',';','\'','`',',','.','/',
|
||||
};
|
||||
|
||||
static const uchar hid_shifted[0x39] = {
|
||||
0, 0, 0, 0,
|
||||
'A','B','C','D','E','F','G','H','I','J','K','L','M',
|
||||
'N','O','P','Q','R','S','T','U','V','W','X','Y','Z',
|
||||
'!','@','#','$','%','^','&','*','(',')',
|
||||
'\n', 0x1B, '\b', '\t', ' ',
|
||||
'_','+','{','}','|','~',':','"','~','<','>','?',
|
||||
};
|
||||
|
||||
// Characters decoded from a report, waiting to be handed to the console.
|
||||
//
|
||||
// The console pulls input through a getc function rather than being pushed
|
||||
// to (consoleintr takes one), which is exactly the shape the PS/2 driver
|
||||
// needs and exactly the wrong shape here -- a USB report arrives complete,
|
||||
// with up to six keys in it. So the report is decoded into this queue and
|
||||
// the console then drains it through the same path as any other keyboard,
|
||||
// which means the entire line discipline is reused rather than reimplemented.
|
||||
#define KQSIZE 64
|
||||
static struct {
|
||||
uchar buf[KQSIZE];
|
||||
uint r, w;
|
||||
} kq;
|
||||
|
||||
static void
|
||||
kq_put(int c)
|
||||
{
|
||||
if(kq.w - kq.r < KQSIZE)
|
||||
kq.buf[kq.w++ % KQSIZE] = c;
|
||||
}
|
||||
|
||||
static int
|
||||
usb_kbd_getc(void)
|
||||
{
|
||||
if(kq.r == kq.w)
|
||||
return -1;
|
||||
return kq.buf[kq.r++ % KQSIZE];
|
||||
}
|
||||
|
||||
// Was this usage in that report?
|
||||
static int
|
||||
was_down(const uchar *prev, uchar usage)
|
||||
{
|
||||
int i;
|
||||
|
||||
for(i = 2; i < 8; i++)
|
||||
if(prev[i] == usage)
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Turn the difference between two reports into press and release events for
|
||||
// /dev/input/event0.
|
||||
//
|
||||
// The console path below cannot do this job: it emits a character per new key
|
||||
// and nothing at all for a release, because a character stream has no way to
|
||||
// say "released". A program that wants to know a key is being *held* -- which
|
||||
// is every game and every compositor -- needs exactly the events this makes.
|
||||
static void
|
||||
keyboard_events(const uchar *prev, const uchar *rep)
|
||||
{
|
||||
int i, bit, n = 0;
|
||||
|
||||
for(bit = 0; bit < 8; bit++){
|
||||
int now = (rep[0] >> bit) & 1;
|
||||
int then = (prev[0] >> bit) & 1;
|
||||
if(now != then){
|
||||
input_report(INPUT_KBD, EV_KEY, input_hid_keycode(0xE0 + bit), now);
|
||||
n++;
|
||||
}
|
||||
}
|
||||
|
||||
for(i = 2; i < 8; i++){
|
||||
uchar u = rep[i];
|
||||
if(u && !was_down(prev, u)){
|
||||
int k = input_hid_keycode(u);
|
||||
if(k){ input_report(INPUT_KBD, EV_KEY, k, 1); n++; }
|
||||
}
|
||||
}
|
||||
for(i = 2; i < 8; i++){
|
||||
uchar u = prev[i];
|
||||
if(u && !was_down(rep, u)){
|
||||
int k = input_hid_keycode(u);
|
||||
if(k){ input_report(INPUT_KBD, EV_KEY, k, 0); n++; }
|
||||
}
|
||||
}
|
||||
|
||||
if(n)
|
||||
input_sync(INPUT_KBD);
|
||||
}
|
||||
|
||||
static void
|
||||
keyboard_report(struct hid *h, const uchar *rep, int len)
|
||||
{
|
||||
uchar mods = rep[0];
|
||||
int i, c;
|
||||
|
||||
if(len < 8)
|
||||
return;
|
||||
|
||||
keyboard_events(h->prev, rep);
|
||||
|
||||
// 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.
|
||||
if(input_is_grabbed(INPUT_KBD)){
|
||||
memmove(h->prev, rep, 8);
|
||||
return;
|
||||
}
|
||||
|
||||
for(i = 2; i < 8; i++){
|
||||
uchar usage = rep[i];
|
||||
|
||||
if(usage == 0 || usage >= 0x39)
|
||||
continue;
|
||||
if(was_down(h->prev, usage))
|
||||
continue; // still held from last time, not a new press
|
||||
|
||||
c = (mods & HID_MOD_SHIFT) ? hid_shifted[usage] : hid_unshifted[usage];
|
||||
if(c == 0)
|
||||
continue;
|
||||
|
||||
// Ctrl folds a letter down into its control character, which is how ^C
|
||||
// and ^D reach the line discipline at all.
|
||||
if(mods & HID_MOD_CTRL){
|
||||
if(c >= 'a' && c <= 'z')
|
||||
c = c - 'a' + 1;
|
||||
else if(c >= 'A' && c <= 'Z')
|
||||
c = c - 'A' + 1;
|
||||
else
|
||||
continue;
|
||||
}
|
||||
kq_put(c);
|
||||
}
|
||||
|
||||
memmove(h->prev, rep, 8);
|
||||
consoleintr(usb_kbd_getc);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Mouse
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static void
|
||||
mouse_report(struct hid *h, const uchar *rep, int len)
|
||||
{
|
||||
int dx, dy;
|
||||
|
||||
(void)h;
|
||||
if(len < 3)
|
||||
return;
|
||||
|
||||
// The deltas are signed bytes. Reading them as unsigned is the classic way
|
||||
// to get a pointer that only travels right and down.
|
||||
dx = (signed char)rep[1];
|
||||
dy = (signed char)rep[2];
|
||||
|
||||
// HID's Y axis points down, and so does the screen's. The PS/2 protocol's
|
||||
// points up, which is why mouseintr subtracts where this adds.
|
||||
mouse_inject(dx, dy, rep[0] & 0x07);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static void
|
||||
hid_complete(struct usb_dev *dev, void *buf, int len)
|
||||
{
|
||||
struct hid *h = dev->drvdata;
|
||||
|
||||
if(h == 0)
|
||||
return;
|
||||
if(h->proto == USB_HID_PROTO_KEYBOARD)
|
||||
keyboard_report(h, buf, len);
|
||||
else
|
||||
mouse_report(h, buf, len);
|
||||
}
|
||||
|
||||
static int
|
||||
hid_request(struct usb_dev *dev, int request, int value)
|
||||
{
|
||||
struct usb_setup s;
|
||||
|
||||
s.bmRequestType = USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE;
|
||||
s.bRequest = request;
|
||||
s.wValue = value;
|
||||
s.wIndex = dev->iface.bInterfaceNumber;
|
||||
s.wLength = 0;
|
||||
return dev->ops->control(dev, &s, 0, 0);
|
||||
}
|
||||
|
||||
static int
|
||||
hid_probe(struct usb_dev *dev)
|
||||
{
|
||||
struct hid *h = 0;
|
||||
int i, proto = dev->iface.bInterfaceProtocol;
|
||||
|
||||
if(!dev->has_intr_in){
|
||||
cprintf("usb-hid: no interrupt IN endpoint; nothing to read\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
for(i = 0; i < NHID; i++)
|
||||
if(!hids[i].in_use){ h = &hids[i]; break; }
|
||||
if(h == 0)
|
||||
return -1;
|
||||
|
||||
// Ask for the fixed layout explicitly. A device that supports the boot
|
||||
// subclass still starts in report protocol, so without this the reports
|
||||
// arrive in whatever shape its report descriptor declares -- which we have
|
||||
// not read and cannot interpret.
|
||||
if(hid_request(dev, USB_HID_SET_PROTOCOL, USB_HID_PROTOCOL_BOOT) < 0){
|
||||
cprintf("usb-hid: SET_PROTOCOL(boot) refused\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Idle rate 0 = "report only when something changes". The default is to
|
||||
// resend the current state every few milliseconds forever, which for a
|
||||
// keyboard means a constant stream of interrupts saying nothing.
|
||||
hid_request(dev, USB_HID_SET_IDLE, 0);
|
||||
|
||||
if(dev->ops->open_ep(dev, &dev->intr_in) < 0)
|
||||
return -1;
|
||||
|
||||
h->in_use = 1;
|
||||
h->proto = proto;
|
||||
dev->drvdata = h;
|
||||
dev->complete = hid_complete;
|
||||
|
||||
// Arm the first transfer. Until a TRB is waiting on the endpoint the device
|
||||
// has nowhere to put a report, so nothing would ever arrive.
|
||||
if(dev->ops->queue_in(dev, dev->intr_in.bEndpointAddress, 0, 0) < 0)
|
||||
return -1;
|
||||
|
||||
cprintf("usb-hid: %s on slot %d, endpoint 0x%x every %d ms\n",
|
||||
proto == USB_HID_PROTO_KEYBOARD ? "keyboard" :
|
||||
proto == USB_HID_PROTO_MOUSE ? "mouse" : "device",
|
||||
dev->slot, dev->intr_in.bEndpointAddress, dev->intr_in.bInterval);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Boot subclass only. A HID device that does not claim it needs its report
|
||||
// descriptor parsed, and we would have nothing useful to do with it.
|
||||
const struct usb_driver usb_hid_driver = {
|
||||
.name = "usb-hid",
|
||||
.class = USB_CLASS_HID,
|
||||
.subclass = USB_HID_SUBCLASS_BOOT,
|
||||
.protocol = 0xFF,
|
||||
.devclass = DEVCLASS_INPUT,
|
||||
.probe = hid_probe,
|
||||
};
|
||||
185
kernel/drivers/usb/hub/usb_hub.c
Normal file
185
kernel/drivers/usb/hub/usb_hub.c
Normal file
|
|
@ -0,0 +1,185 @@
|
|||
// USB hub class driver.
|
||||
//
|
||||
// A hub is what makes USB a tree rather than a handful of sockets, and
|
||||
// nothing about driving one is special: every operation is an ordinary
|
||||
// control request sent to the hub as a device. "Reset port 3" is a
|
||||
// SET_FEATURE(PORT_RESET, wIndex=3). The hub has an interrupt endpoint too,
|
||||
// which reports which ports have changed, but that is only needed for
|
||||
// hotplug -- at boot it is enough to walk every port and look.
|
||||
//
|
||||
// What *is* special is what the host controller has to be told. xHCI does not
|
||||
// discover topology; it is informed of it. A device three hubs deep is
|
||||
// addressed by a route string naming the port taken at each tier, and a
|
||||
// slow device behind a fast hub is scheduled against that hub's transaction
|
||||
// translator rather than against the device. So this driver's real job is
|
||||
// less "operate the hub" than "tell the controller what shape the tree is".
|
||||
|
||||
#include "types.h"
|
||||
#include "defs.h"
|
||||
#include "param.h"
|
||||
#include "spinlock.h"
|
||||
#include "dma.h"
|
||||
#include "usb.h"
|
||||
|
||||
#define MAX_PORTS 15 // per hub; more than any real bus hub has
|
||||
|
||||
static int
|
||||
hub_request(struct usb_dev *dev, int type, int req, int value, int index,
|
||||
void *buf, int len)
|
||||
{
|
||||
struct usb_setup s;
|
||||
|
||||
s.bmRequestType = type;
|
||||
s.bRequest = req;
|
||||
s.wValue = value;
|
||||
s.wIndex = index;
|
||||
s.wLength = len;
|
||||
return dev->ops->control(dev, &s, buf, len);
|
||||
}
|
||||
|
||||
static int
|
||||
port_set_feature(struct usb_dev *dev, int port, int feature)
|
||||
{
|
||||
return hub_request(dev, USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_OTHER,
|
||||
USB_REQ_SET_FEATURE, feature, port, 0, 0);
|
||||
}
|
||||
|
||||
static int
|
||||
port_clear_feature(struct usb_dev *dev, int port, int feature)
|
||||
{
|
||||
return hub_request(dev, USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_OTHER,
|
||||
USB_REQ_CLEAR_FEATURE, feature, port, 0, 0);
|
||||
}
|
||||
|
||||
// Returns wPortStatus, or -1. The change bits are in the upper half and are
|
||||
// deliberately dropped here -- they are acknowledged separately.
|
||||
static int
|
||||
port_status(struct usb_dev *dev, int port, void *scratch)
|
||||
{
|
||||
uint16 *st = scratch;
|
||||
|
||||
if(hub_request(dev, USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_OTHER,
|
||||
USB_REQ_GET_STATUS, 0, port, scratch, 4) < 0)
|
||||
return -1;
|
||||
return st[0];
|
||||
}
|
||||
|
||||
// Bring up whatever is plugged into one downstream port.
|
||||
static void
|
||||
hub_port(struct usb_dev *hub, int port, void *scratch)
|
||||
{
|
||||
int st, speed, i;
|
||||
|
||||
st = port_status(hub, port, scratch);
|
||||
if(st < 0 || !(st & PORTSTAT_CONNECTION))
|
||||
return;
|
||||
|
||||
// Acknowledge the connect-change bit before resetting. Leaving it set is
|
||||
// harmless here but confuses any later attempt to use these bits for
|
||||
// hotplug, which is what they are actually for.
|
||||
port_clear_feature(hub, port, PORT_FEAT_C_CONNECTION);
|
||||
|
||||
if(port_set_feature(hub, port, PORT_FEAT_RESET) < 0){
|
||||
cprintf("usb-hub: port %d would not reset\n", port);
|
||||
return;
|
||||
}
|
||||
|
||||
// A hub does not report reset completion by clearing the request; it sets
|
||||
// C_PORT_RESET and enables the port. Poll for the enable rather than
|
||||
// guessing at a delay, because the time depends on the device.
|
||||
for(i = 0; i < 200; i++){
|
||||
microdelay(1000);
|
||||
st = port_status(hub, port, scratch);
|
||||
if(st >= 0 && !(st & PORTSTAT_RESET) && (st & PORTSTAT_ENABLE))
|
||||
break;
|
||||
}
|
||||
port_clear_feature(hub, port, PORT_FEAT_C_RESET);
|
||||
|
||||
st = port_status(hub, port, scratch);
|
||||
if(st < 0 || !(st & PORTSTAT_ENABLE)){
|
||||
cprintf("usb-hub: port %d did not enable after reset\n", port);
|
||||
return;
|
||||
}
|
||||
|
||||
// Speed comes out of two status bits rather than a field, and the absence
|
||||
// of both means full speed.
|
||||
if(st & PORTSTAT_LOWSPEED)
|
||||
speed = USB_SPEED_LOW;
|
||||
else if(st & PORTSTAT_HIGHSPEED)
|
||||
speed = USB_SPEED_HIGH;
|
||||
else
|
||||
speed = USB_SPEED_FULL;
|
||||
|
||||
// A device needs time to settle after reset before it will answer on
|
||||
// endpoint 0. The specification calls this TRSTRCY and allows 10 ms.
|
||||
microdelay(20000);
|
||||
|
||||
hub->ops->attach(hub, port, speed);
|
||||
}
|
||||
|
||||
static int
|
||||
hub_probe(struct usb_dev *dev)
|
||||
{
|
||||
struct usb_hub_desc *hd;
|
||||
void *scratch;
|
||||
int nports, mtt, i;
|
||||
|
||||
scratch = dma_alloc(64, 64);
|
||||
if(scratch == 0)
|
||||
return -1;
|
||||
|
||||
// The hub descriptor is class-specific, so it comes from a class GET
|
||||
// request rather than the standard one -- same request number, different
|
||||
// recipient and type.
|
||||
if(hub_request(dev, USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_DEVICE,
|
||||
USB_REQ_GET_DESCRIPTOR, USB_DT_HUB << 8, 0,
|
||||
scratch, sizeof(struct usb_hub_desc)) < 0){
|
||||
cprintf("usb-hub: no hub descriptor\n");
|
||||
dma_free(scratch, 64);
|
||||
return -1;
|
||||
}
|
||||
|
||||
hd = scratch;
|
||||
nports = hd->bNbrPorts;
|
||||
if(nports < 1 || nports > MAX_PORTS){
|
||||
cprintf("usb-hub: %d ports is not a number of ports\n", nports);
|
||||
dma_free(scratch, 64);
|
||||
return -1;
|
||||
}
|
||||
mtt = (hd->wHubCharacteristics & HUB_CHAR_TT_MASK) != 0;
|
||||
|
||||
cprintf("usb-hub: %d ports on slot %d\n", nports, dev->slot);
|
||||
|
||||
// Tell the controller *before* powering anything on. Addressing a device
|
||||
// behind a slot the controller does not know is a hub fails, and it fails
|
||||
// at Address Device with a completion code rather than anywhere useful.
|
||||
if(dev->ops->set_hub(dev, nports, mtt) < 0){
|
||||
dma_free(scratch, 64);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Ports come up unpowered. bPwrOn2PwrGood is how long the hub says it needs
|
||||
// after switching power on before a device behind it is worth talking to,
|
||||
// in 2 ms units -- a number the hub knows and we do not.
|
||||
for(i = 1; i <= nports; i++)
|
||||
port_set_feature(dev, i, PORT_FEAT_POWER);
|
||||
microdelay((hd->bPwrOn2PwrGood * 2 + 10) * 1000);
|
||||
|
||||
for(i = 1; i <= nports; i++)
|
||||
hub_port(dev, i, scratch);
|
||||
|
||||
dma_free(scratch, 64);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Any hub, whatever its protocol: 0 is a full-speed hub, 1 single-TT, 2
|
||||
// multi-TT, and they differ only in how transactions are translated, which
|
||||
// is the controller's problem rather than ours.
|
||||
const struct usb_driver usb_hub_driver = {
|
||||
.name = "usb-hub",
|
||||
.class = USB_CLASS_HUB,
|
||||
.subclass = 0xFF,
|
||||
.protocol = 0xFF,
|
||||
.devclass = DEVCLASS_BUS,
|
||||
.probe = hub_probe,
|
||||
};
|
||||
395
kernel/drivers/usb/storage/usb_storage.c
Normal file
395
kernel/drivers/usb/storage/usb_storage.c
Normal file
|
|
@ -0,0 +1,395 @@
|
|||
// USB mass storage: bulk-only transport, SCSI transparent command set.
|
||||
//
|
||||
// Almost every USB stick, external drive and card reader is this, and the
|
||||
// name is a fair description of the whole protocol: there are exactly two
|
||||
// bulk endpoints and everything happens by sending three things over them.
|
||||
//
|
||||
// command a 31-byte wrapper (CBW) carrying a SCSI command block, OUT
|
||||
// data the payload, IN or OUT, if the command has one
|
||||
// status a 13-byte wrapper (CSW) saying whether it worked, IN
|
||||
//
|
||||
// That is the entire transport. The interesting content is SCSI, which is
|
||||
// what the wrapper carries and what the device actually speaks -- so a USB
|
||||
// stick is a SCSI disk that happens to be reached over USB, and the commands
|
||||
// below (INQUIRY, READ CAPACITY, READ(10), WRITE(10)) are the same ones a
|
||||
// SCSI drive answered in 1990.
|
||||
|
||||
#include "types.h"
|
||||
#include "defs.h"
|
||||
#include "param.h"
|
||||
#include "memlayout.h"
|
||||
#include "mmu.h"
|
||||
#include "spinlock.h"
|
||||
#include "sleeplock.h"
|
||||
#include "fs.h"
|
||||
#include "buf.h"
|
||||
#include "dma.h"
|
||||
#include "usb.h"
|
||||
|
||||
#define CBW_SIGNATURE 0x43425355 // "USBC"
|
||||
#define CSW_SIGNATURE 0x53425355 // "USBS"
|
||||
|
||||
struct cbw {
|
||||
uint32 signature;
|
||||
uint32 tag;
|
||||
uint32 data_len;
|
||||
uint8 flags; // 0x80 = device-to-host
|
||||
uint8 lun;
|
||||
uint8 cb_len; // 1..16
|
||||
uint8 cb[16];
|
||||
} __attribute__((packed));
|
||||
|
||||
struct csw {
|
||||
uint32 signature;
|
||||
uint32 tag;
|
||||
uint32 residue;
|
||||
uint8 status; // 0 = passed, 1 = failed, 2 = phase error
|
||||
} __attribute__((packed));
|
||||
|
||||
// SCSI opcodes
|
||||
#define SCSI_TEST_UNIT_READY 0x00
|
||||
#define SCSI_REQUEST_SENSE 0x03
|
||||
#define SCSI_INQUIRY 0x12
|
||||
#define SCSI_READ_CAPACITY10 0x25
|
||||
#define SCSI_READ10 0x28
|
||||
#define SCSI_WRITE10 0x2A
|
||||
|
||||
#define SECTOR_SIZE 512
|
||||
|
||||
static struct {
|
||||
struct usb_dev *dev;
|
||||
int present;
|
||||
uint32 nsectors;
|
||||
uint32 sector_size;
|
||||
uint32 tag;
|
||||
void *xfer; // DMA bounce buffer, one block
|
||||
struct cbw *cbw; // allocated once, not per command
|
||||
struct csw *csw;
|
||||
struct sleeplock lock; // one command at a time
|
||||
} ums;
|
||||
|
||||
// SCSI is big-endian, everywhere, for everything. x86 is not.
|
||||
static void
|
||||
be32(uint8 *p, uint32 v)
|
||||
{
|
||||
p[0] = v >> 24; p[1] = v >> 16; p[2] = v >> 8; p[3] = v;
|
||||
}
|
||||
|
||||
static uint32
|
||||
rd_be32(const uint8 *p)
|
||||
{
|
||||
return ((uint32)p[0] << 24) | ((uint32)p[1] << 16) |
|
||||
((uint32)p[2] << 8) | p[3];
|
||||
}
|
||||
|
||||
// One complete command-data-status exchange.
|
||||
//
|
||||
// `buf`, when there is data, must be DMA memory -- it is handed to the
|
||||
// controller as a physical address.
|
||||
static int
|
||||
bot_command(const uint8 *cdb, int cdb_len, void *buf, int len, int in)
|
||||
{
|
||||
struct usb_dev *dev = ums.dev;
|
||||
struct cbw *cbw = ums.cbw;
|
||||
struct csw *csw = ums.csw;
|
||||
uint32 tag = ++ums.tag;
|
||||
int i, data_ok = 1;
|
||||
|
||||
memset(cbw, 0, sizeof(*cbw));
|
||||
cbw->signature = CBW_SIGNATURE;
|
||||
cbw->tag = tag;
|
||||
cbw->data_len = len;
|
||||
cbw->flags = in ? 0x80 : 0x00;
|
||||
cbw->lun = 0;
|
||||
cbw->cb_len = cdb_len;
|
||||
for(i = 0; i < cdb_len && i < 16; i++)
|
||||
cbw->cb[i] = cdb[i];
|
||||
|
||||
if(dev->ops->bulk(dev, dev->bulk_out.bEndpointAddress, cbw,
|
||||
sizeof(*cbw)) != (int)sizeof(*cbw)){
|
||||
cprintf("usb-storage: the device would not take the command\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if(len > 0){
|
||||
int addr = in ? dev->bulk_in.bEndpointAddress
|
||||
: dev->bulk_out.bEndpointAddress;
|
||||
if(dev->ops->bulk(dev, addr, buf, len) < 0)
|
||||
data_ok = 0;
|
||||
}
|
||||
|
||||
// The status wrapper is not optional and not advisory: a device that has
|
||||
// sent its data is still waiting to be asked how it went.
|
||||
//
|
||||
// It is read even when the data phase failed, which is the whole point --
|
||||
// returning early there would leave the device still holding a status it
|
||||
// has not been asked for, so the *next* command's data phase would receive
|
||||
// this command's status instead. One failed read would turn into every
|
||||
// subsequent read failing, which is a far worse fault than the one that
|
||||
// started it, and one that looks nothing like its cause.
|
||||
memset(csw, 0, sizeof(*csw));
|
||||
if(dev->ops->bulk(dev, dev->bulk_in.bEndpointAddress, csw,
|
||||
sizeof(*csw)) != (int)sizeof(*csw)){
|
||||
cprintf("usb-storage: no status returned for command 0x%x\n", cdb[0]);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if(csw->signature != CSW_SIGNATURE || csw->tag != tag){
|
||||
cprintf("usb-storage: status wrapper does not match the command\n");
|
||||
return -1;
|
||||
}
|
||||
if(csw->status != 0){
|
||||
cprintf("usb-storage: command 0x%x failed (status %d)\n",
|
||||
cdb[0], csw->status);
|
||||
return -1;
|
||||
}
|
||||
if(!data_ok)
|
||||
return -1;
|
||||
|
||||
// Residue is what was *not* transferred, and a device is allowed to report
|
||||
// more than was asked for only if it is broken -- clamp rather than return
|
||||
// a negative length that a caller would read as an error code.
|
||||
if(csw->residue > (uint32)len)
|
||||
return -1;
|
||||
return len - (int)csw->residue;
|
||||
}
|
||||
|
||||
static int
|
||||
scsi_inquiry(char *vendor, char *product)
|
||||
{
|
||||
uint8 cdb[6] = { SCSI_INQUIRY, 0, 0, 0, 36, 0 };
|
||||
uint8 *r = ums.xfer;
|
||||
int i;
|
||||
|
||||
if(bot_command(cdb, sizeof(cdb), r, 36, 1) < 0)
|
||||
return -1;
|
||||
|
||||
// Bytes 8-15 are the vendor, 16-31 the product, space padded and not
|
||||
// NUL-terminated -- the only fixed-width strings left in computing.
|
||||
for(i = 0; i < 8; i++)
|
||||
vendor[i] = r[8 + i];
|
||||
vendor[8] = 0;
|
||||
for(i = 0; i < 16; i++)
|
||||
product[i] = r[16 + i];
|
||||
product[16] = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
scsi_read_capacity(void)
|
||||
{
|
||||
uint8 cdb[10] = { SCSI_READ_CAPACITY10, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
|
||||
uint8 *r = ums.xfer;
|
||||
|
||||
if(bot_command(cdb, sizeof(cdb), r, 8, 1) < 0)
|
||||
return -1;
|
||||
|
||||
// The first word is the address of the *last* sector, not the count.
|
||||
ums.nsectors = rd_be32(r) + 1;
|
||||
ums.sector_size = rd_be32(r + 4);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
scsi_rw(uint32 lba, int count, void *buf, int write)
|
||||
{
|
||||
uint8 cdb[10];
|
||||
|
||||
memset(cdb, 0, sizeof(cdb));
|
||||
cdb[0] = write ? SCSI_WRITE10 : SCSI_READ10;
|
||||
be32(&cdb[2], lba);
|
||||
cdb[7] = (count >> 8) & 0xFF;
|
||||
cdb[8] = count & 0xFF;
|
||||
|
||||
return bot_command(cdb, sizeof(cdb), buf, count * SECTOR_SIZE, !write);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Block device
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
int
|
||||
usb_storage_present(void)
|
||||
{
|
||||
return ums.present;
|
||||
}
|
||||
|
||||
// Sync one buffer-cache block with the stick. Same contract as iderw() and
|
||||
// ramdiskrw(): on a read fill b->data and set B_VALID, on a write push it out
|
||||
// and clear B_DIRTY.
|
||||
void
|
||||
usb_storage_rw(struct buf *b)
|
||||
{
|
||||
int per_block = BSIZE / SECTOR_SIZE;
|
||||
uint32 lba = b->blockno * per_block;
|
||||
int write = (b->flags & B_DIRTY) != 0;
|
||||
int i, n = -1;
|
||||
|
||||
if(!holdingsleep(&b->lock))
|
||||
panic("usb_storage_rw: buf not locked");
|
||||
if(!ums.present)
|
||||
panic("usb_storage_rw: no USB disk");
|
||||
if(lba + per_block > ums.nsectors)
|
||||
panic("usb_storage_rw: block out of range");
|
||||
|
||||
// One command at a time. Bulk-only transport has no tagging beyond the CBW
|
||||
// tag and no queueing: a second command sent while the first is mid-phase
|
||||
// desynchronises the endpoint and every command after it fails.
|
||||
acquiresleep(&ums.lock);
|
||||
|
||||
// Removable media fails transiently in ways a fixed disk does not, so a
|
||||
// couple of retries are worth having before giving up.
|
||||
for(i = 0; i < 3; i++){
|
||||
if(write)
|
||||
memmove(ums.xfer, b->data, BSIZE);
|
||||
n = scsi_rw(lba, per_block, ums.xfer, write);
|
||||
if(n == BSIZE)
|
||||
break;
|
||||
cprintf("usb-storage: %s of block %d failed, retrying\n",
|
||||
write ? "write" : "read", b->blockno);
|
||||
}
|
||||
|
||||
releasesleep(&ums.lock);
|
||||
|
||||
// A read that failed must not return quietly. bread() hands the buffer
|
||||
// straight back to its caller, which has no way to ask whether it worked --
|
||||
// so leaving B_VALID clear would deliver whatever the buffer happened to
|
||||
// contain as though it were the block on disk. Corrupting a filesystem
|
||||
// silently is worse than stopping.
|
||||
if(n != BSIZE)
|
||||
panic("usb_storage_rw: transfer failed");
|
||||
|
||||
if(!write)
|
||||
memmove(b->data, ums.xfer, BSIZE);
|
||||
b->flags |= B_VALID;
|
||||
b->flags &= ~B_DIRTY;
|
||||
}
|
||||
|
||||
// Read block 1 through the ordinary buffer cache and report what is there.
|
||||
//
|
||||
// This is the only test that proves the whole path rather than a piece of it:
|
||||
// bread() -> bdevrw() -> usb_storage_rw() -> bulk-only transport -> SCSI
|
||||
// READ(10) -> an interrupt -> a sleeping process woken with data. Everything
|
||||
// before it -- INQUIRY answering, a plausible capacity -- can be true of a
|
||||
// device that cannot actually read a sector.
|
||||
//
|
||||
// Reached with `echo s > /proc/sysrq`.
|
||||
void
|
||||
usb_storage_dump(void)
|
||||
{
|
||||
struct buf *b;
|
||||
struct superblock *sb;
|
||||
|
||||
if(!ums.present){
|
||||
cprintf("usb-storage: no disk\n");
|
||||
return;
|
||||
}
|
||||
|
||||
cprintf("usb-storage: %d sectors of %d bytes\n", ums.nsectors,
|
||||
ums.sector_size);
|
||||
|
||||
b = bread(USBDISK_DEV, 1);
|
||||
sb = (struct superblock*)b->data;
|
||||
cprintf("usb-storage: block 1 magic %x size %d nblocks %d ninodes %d\n",
|
||||
sb->magic, sb->size, sb->nblocks, sb->ninodes);
|
||||
brelse(b);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static int
|
||||
storage_probe(struct usb_dev *dev)
|
||||
{
|
||||
char vendor[9], product[17];
|
||||
int i;
|
||||
|
||||
if(ums.present)
|
||||
return -1; // one disk is enough for now
|
||||
if(!dev->has_bulk_in || !dev->has_bulk_out){
|
||||
cprintf("usb-storage: needs a bulk endpoint in each direction\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
initsleeplock(&ums.lock, "usbms");
|
||||
ums.dev = dev;
|
||||
|
||||
// One block, used for every transfer. bread() hands us b->data, which lives
|
||||
// in the buffer cache rather than in DMA memory, so everything bounces
|
||||
// through here. The wrappers are allocated once too: they are used on every
|
||||
// single I/O, and churning the DMA extent list twice per block read would
|
||||
// fragment an arena that has no business fragmenting.
|
||||
ums.xfer = dma_alloc(BSIZE, 4096);
|
||||
ums.cbw = dma_alloc(sizeof(struct cbw), 64);
|
||||
ums.csw = dma_alloc(sizeof(struct csw), 64);
|
||||
if(ums.xfer == 0 || ums.cbw == 0 || ums.csw == 0)
|
||||
goto fail;
|
||||
|
||||
// Deliberately OUT first, which is the order that used to break.
|
||||
//
|
||||
// The slot context's Context Entries field must cover the highest DCI in
|
||||
// use, and xhci_open_ep originally wrote whichever endpoint it was adding.
|
||||
// Bulk OUT here has the higher DCI, so opening it first and then IN would
|
||||
// *lower* the field and silently invalidate the endpoint that was already
|
||||
// configured. Opening IN first hid that. Now that xhci_open_ep tracks the
|
||||
// maximum itself the order genuinely does not matter -- and doing it in the
|
||||
// formerly-broken order is what keeps that true.
|
||||
if(dev->ops->open_ep(dev, &dev->bulk_out) < 0 ||
|
||||
dev->ops->open_ep(dev, &dev->bulk_in) < 0)
|
||||
goto fail;
|
||||
|
||||
// Deliberately no GET MAX LUN. It is answered with a STALL by
|
||||
// single-LUN devices, and recovering from a STALL on endpoint 0 needs a
|
||||
// Reset Endpoint command this driver does not implement yet. LUN 0 is the
|
||||
// only one used below, so asking would gain nothing and could wedge us.
|
||||
|
||||
// A device that has just been configured is usually "not ready" for the
|
||||
// first command or two while it spins up or reads its own media.
|
||||
for(i = 0; i < 5; i++){
|
||||
uint8 cdb[6] = { SCSI_TEST_UNIT_READY, 0, 0, 0, 0, 0 };
|
||||
if(bot_command(cdb, sizeof(cdb), 0, 0, 0) >= 0)
|
||||
break;
|
||||
microdelay(100000);
|
||||
}
|
||||
|
||||
if(scsi_inquiry(vendor, product) < 0)
|
||||
goto fail;
|
||||
if(scsi_read_capacity() < 0)
|
||||
goto fail;
|
||||
|
||||
if(ums.sector_size != SECTOR_SIZE){
|
||||
cprintf("usb-storage: %d-byte sectors are not supported\n",
|
||||
ums.sector_size);
|
||||
goto fail;
|
||||
}
|
||||
if(ums.nsectors == 0){
|
||||
cprintf("usb-storage: device reports no capacity\n");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
ums.present = 1;
|
||||
cprintf("usb-storage: %s %s, %d sectors (%d MB)\n",
|
||||
vendor, product, ums.nsectors,
|
||||
(int)((uint64)ums.nsectors * SECTOR_SIZE / (1024 * 1024)));
|
||||
return 0;
|
||||
|
||||
fail:
|
||||
// Give the memory back and forget the device. Leaving ums.dev set would let
|
||||
// a later block read reach a device we decided we could not drive.
|
||||
if(ums.xfer) dma_free(ums.xfer, BSIZE);
|
||||
if(ums.cbw) dma_free(ums.cbw, sizeof(struct cbw));
|
||||
if(ums.csw) dma_free(ums.csw, sizeof(struct csw));
|
||||
ums.xfer = 0;
|
||||
ums.cbw = 0;
|
||||
ums.csw = 0;
|
||||
ums.dev = 0;
|
||||
return -1;
|
||||
}
|
||||
|
||||
const struct usb_driver usb_storage_driver = {
|
||||
.name = "usb-storage",
|
||||
.class = USB_CLASS_MASS_STORAGE,
|
||||
.subclass = USB_MSC_SUBCLASS_SCSI,
|
||||
.protocol = USB_MSC_PROTO_BOT,
|
||||
.devclass = DEVCLASS_STORAGE,
|
||||
.probe = storage_probe,
|
||||
};
|
||||
241
kernel/fs/exec.c
Normal file
241
kernel/fs/exec.c
Normal file
|
|
@ -0,0 +1,241 @@
|
|||
#include "types.h"
|
||||
#include "traps.h"
|
||||
#include "param.h"
|
||||
#include "memlayout.h"
|
||||
#include "mmu.h"
|
||||
#include "proc.h"
|
||||
#include "defs.h"
|
||||
#include "x86.h"
|
||||
#include "elf.h"
|
||||
#include "fcntl.h"
|
||||
#include "errno.h"
|
||||
|
||||
// Binaries with entry points at 0x100000+ are musl/Linux-ABI.
|
||||
// Schunix-native binaries (init) are linked at 0x4000.
|
||||
#define LINUX_ABI_ENTRY_THRESHOLD 0x100000UL
|
||||
extern int linux_syscall_map[];
|
||||
extern int linux_syscall_map_size;
|
||||
|
||||
int
|
||||
exec(char *path, char **argv)
|
||||
{
|
||||
char *s, *last;
|
||||
int i, off;
|
||||
uint64 argc, sz, sp, ustack[MAXARG+1];
|
||||
struct elfhdr elf;
|
||||
struct inode *ip;
|
||||
struct proghdr ph;
|
||||
pml4e_t *pgdir, *oldpgdir;
|
||||
uint64 stack_slots[MAXARG + 32];
|
||||
int slot;
|
||||
uint64 max_sz = 0;
|
||||
uint64 rand_va = 0;
|
||||
|
||||
begin_op();
|
||||
|
||||
if((ip = namei(path)) == 0){
|
||||
end_op();
|
||||
// Must be ENOENT, not a bare -1: execvp() relies on this exact errno to
|
||||
// know it should try the next $PATH component rather than give up.
|
||||
return -ENOENT;
|
||||
}
|
||||
|
||||
ilock(ip);
|
||||
pgdir = 0;
|
||||
|
||||
if(readi(ip, 0, (uint64)&elf, 0, sizeof(elf)) != sizeof(elf))
|
||||
goto bad;
|
||||
|
||||
if(elf.magic != ELF_MAGIC)
|
||||
goto bad;
|
||||
|
||||
if((pgdir = setupkvm()) == 0)
|
||||
goto bad;
|
||||
|
||||
for(i=0, off=elf.phoff; i<elf.phnum; i++, off+=elf.phentsize){
|
||||
if(readi(ip, 0, (uint64)&ph, off, sizeof(ph)) != sizeof(ph))
|
||||
goto bad;
|
||||
if(ph.type != ELF_PROG_LOAD)
|
||||
continue;
|
||||
if(ph.memsz < ph.filesz)
|
||||
goto bad;
|
||||
if(ph.vaddr + ph.memsz < ph.vaddr)
|
||||
goto bad;
|
||||
|
||||
uint64 start = ph.vaddr & ~0xFFF;
|
||||
uint64 end = ph.vaddr + ph.memsz;
|
||||
if(allocuvm(pgdir, start, end) == 0)
|
||||
goto bad;
|
||||
|
||||
if(loaduvm(pgdir, (char*)ph.vaddr, ip, ph.off, ph.filesz) < 0)
|
||||
goto bad;
|
||||
|
||||
if(end > max_sz)
|
||||
max_sz = end;
|
||||
}
|
||||
iunlockput(ip);
|
||||
end_op();
|
||||
ip = 0;
|
||||
|
||||
sz = PGROUNDUP(max_sz);
|
||||
if((sz = allocuvm(pgdir, sz, sz + 64*PGSIZE)) == 0)
|
||||
goto bad;
|
||||
clearpteu(pgdir, (char*)(sz - 64*PGSIZE));
|
||||
sp = sz;
|
||||
|
||||
for(argc = 0; argv[argc]; argc++) {
|
||||
if(argc >= MAXARG)
|
||||
goto bad;
|
||||
sp -= strlen(argv[argc]) + 1;
|
||||
sp &= ~7;
|
||||
if(copyout(pgdir, sp, argv[argc], strlen(argv[argc]) + 1) < 0)
|
||||
goto bad;
|
||||
ustack[argc] = sp;
|
||||
}
|
||||
|
||||
// Push 16 pseudo-random bytes for AT_RANDOM.
|
||||
{
|
||||
uint8 randbuf[16];
|
||||
uint rseed = (uint)(uintptr_t)myproc() ^ (uint)myproc()->pid;
|
||||
for(int ri = 0; ri < 16; ri++){
|
||||
rseed = rseed * 1664525u + 1013904223u;
|
||||
randbuf[ri] = (uint8)(rseed >> 16);
|
||||
}
|
||||
sp -= 16; sp &= ~0xFUL;
|
||||
rand_va = sp;
|
||||
if(copyout(pgdir, rand_va, (char*)randbuf, 16) < 0) goto bad;
|
||||
}
|
||||
|
||||
slot = 0;
|
||||
stack_slots[slot++] = argc;
|
||||
for(i = 0; i < argc; i++) {
|
||||
stack_slots[slot++] = ustack[i];
|
||||
}
|
||||
stack_slots[slot++] = 0; // end of argv
|
||||
|
||||
stack_slots[slot++] = 0; // end of envp (no environment)
|
||||
|
||||
// Auxiliary vector — musl reads these for page size, clock, etc.
|
||||
stack_slots[slot++] = 6; // AT_PAGESZ
|
||||
stack_slots[slot++] = 4096;
|
||||
stack_slots[slot++] = 17; // AT_CLKTCK
|
||||
stack_slots[slot++] = 100;
|
||||
stack_slots[slot++] = 23; // AT_SECURE = 0 (not setuid)
|
||||
stack_slots[slot++] = 0;
|
||||
stack_slots[slot++] = 11; // AT_UID
|
||||
stack_slots[slot++] = 0;
|
||||
stack_slots[slot++] = 12; // AT_EUID
|
||||
stack_slots[slot++] = 0;
|
||||
stack_slots[slot++] = 13; // AT_GID
|
||||
stack_slots[slot++] = 0;
|
||||
stack_slots[slot++] = 14; // AT_EGID
|
||||
stack_slots[slot++] = 0;
|
||||
stack_slots[slot++] = 16; // AT_HWCAP
|
||||
stack_slots[slot++] = 0;
|
||||
stack_slots[slot++] = 25; // AT_RANDOM
|
||||
stack_slots[slot++] = rand_va;
|
||||
stack_slots[slot++] = 0; // AT_NULL
|
||||
stack_slots[slot++] = 0;
|
||||
|
||||
sp -= slot * sizeof(uint64);
|
||||
sp &= ~0xF;
|
||||
|
||||
if(copyout(pgdir, sp, (char*)stack_slots, slot * sizeof(uint64)) < 0)
|
||||
goto bad;
|
||||
|
||||
for(last=s=path; *s; s++)
|
||||
if(*s == '/')
|
||||
last = s+1;
|
||||
safestrcpy(myproc()->name, last, sizeof(myproc()->name));
|
||||
safestrcpy(myproc()->exe, path, sizeof(myproc()->exe));
|
||||
|
||||
for(int i = 0; i < NOFILE; i++){
|
||||
if(myproc()->ofile[i] && (myproc()->ofile_flags[i] & FD_CLOEXEC)){
|
||||
fileclose(myproc()->ofile[i]);
|
||||
myproc()->ofile[i] = 0;
|
||||
myproc()->ofile_flags[i] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Auto-select syscall ABI: musl binaries have entry >= 0x100000;
|
||||
// Schunix-native binaries (init) are linked at 0x4000.
|
||||
if (elf.entry >= LINUX_ABI_ENTRY_THRESHOLD) {
|
||||
myproc()->syscall_map = linux_syscall_map;
|
||||
myproc()->map_size = linux_syscall_map_size;
|
||||
} else {
|
||||
myproc()->syscall_map = 0;
|
||||
myproc()->map_size = 0;
|
||||
}
|
||||
|
||||
// Detach shared segments before anything forgets which mappings they were.
|
||||
//
|
||||
// This has to happen while myproc()->pgdir and myproc()->vma[] still describe the
|
||||
// *old* image: the memset below erases the bookkeeping, and freevm() at the
|
||||
// end would then free a shared memory segment's pages as though they were
|
||||
// ordinary heap. Doing it here rather than there is the difference between
|
||||
// detaching a segment and corrupting whoever else has it mapped.
|
||||
proc_release_shared(myproc());
|
||||
|
||||
oldpgdir = myproc()->pgdir;
|
||||
myproc()->pgdir = pgdir;
|
||||
myproc()->sz = sz;
|
||||
|
||||
// New address space: reset all per-process mmap/signal/TLS state.
|
||||
// sig_handler/sig_restorer point into the *old* image — POSIX resets
|
||||
// handled signals to default on exec, and keeping them would make the
|
||||
// first delivered signal jump to a stale address in the new image.
|
||||
// mmap_next likewise belongs to the old address space; left alone it
|
||||
// could hand out mmap ranges overlapping the new image's layout.
|
||||
memset(myproc()->vma, 0, sizeof(myproc()->vma));
|
||||
memset(myproc()->sig_handler, 0, sizeof(myproc()->sig_handler));
|
||||
memset(myproc()->sig_restorer, 0, sizeof(myproc()->sig_restorer));
|
||||
myproc()->fs_base = 0;
|
||||
myproc()->signal_mask = 0;
|
||||
myproc()->pending_signals = 0;
|
||||
myproc()->in_signal = 0;
|
||||
myproc()->sigsusp_active = 0;
|
||||
myproc()->mmap_next = 0;
|
||||
|
||||
myproc()->tf->rax = 0;
|
||||
myproc()->tf->rbx = 0;
|
||||
myproc()->tf->rdx = 0;
|
||||
myproc()->tf->rbp = 0;
|
||||
myproc()->tf->r8 = 0;
|
||||
myproc()->tf->r9 = 0;
|
||||
myproc()->tf->r10 = 0;
|
||||
myproc()->tf->r11 = 0x200; // RFLAGS for sysretq: IF=1 (bit 9) so user gets timer IRQs
|
||||
myproc()->tf->r12 = 0;
|
||||
myproc()->tf->r13 = 0;
|
||||
myproc()->tf->r14 = 0;
|
||||
myproc()->tf->r15 = 0;
|
||||
|
||||
myproc()->tf->rdi = argc;
|
||||
myproc()->tf->rsi = sp + 8;
|
||||
myproc()->tf->rip = elf.entry;
|
||||
myproc()->tf->rcx = elf.entry;
|
||||
myproc()->tf->rsp = sp;
|
||||
|
||||
// syscall_entry saves user RSP at kstack+PGSIZE-16 (not in the trapframe).
|
||||
// sysretq reads RSP from that slot, so update it to the new stack pointer.
|
||||
// Without this, sysretq returns with the old parent's RSP, causing lksh to
|
||||
// start with an invalid/wrong stack pointer.
|
||||
*(uint64*)((char*)myproc()->kstack + PGSIZE - 16) = sp;
|
||||
|
||||
// Userspace is running now, so the splash has done its job and the display
|
||||
// becomes a terminal. Only the first exec does anything; every later one
|
||||
// finds the console already in charge.
|
||||
console_takeover();
|
||||
|
||||
switchuvm(myproc());
|
||||
freevm(oldpgdir);
|
||||
return 0;
|
||||
|
||||
bad:
|
||||
if(pgdir)
|
||||
freevm(pgdir);
|
||||
if(ip){
|
||||
iunlockput(ip);
|
||||
end_op();
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
294
kernel/fs/file.c
Normal file
294
kernel/fs/file.c
Normal file
|
|
@ -0,0 +1,294 @@
|
|||
#include "types.h"
|
||||
#include "defs.h"
|
||||
#include "stat.h"
|
||||
#include "param.h"
|
||||
#include "fs.h"
|
||||
#include "spinlock.h"
|
||||
#include "sleeplock.h"
|
||||
#include "file.h"
|
||||
#include "posix.h"
|
||||
#include "errno.h"
|
||||
|
||||
extern struct devsw devsw[];
|
||||
|
||||
struct {
|
||||
struct spinlock lock;
|
||||
struct file file[NFILE];
|
||||
} ftable;
|
||||
|
||||
void
|
||||
fileinit(void)
|
||||
{
|
||||
initlock(&ftable.lock, "ftable");
|
||||
}
|
||||
|
||||
struct file*
|
||||
filealloc(void)
|
||||
{
|
||||
struct file *f;
|
||||
|
||||
acquire(&ftable.lock);
|
||||
for(f = ftable.file; f < ftable.file + NFILE; f++){
|
||||
if(f->ref == 0){
|
||||
f->ref = 1;
|
||||
// Cleared here rather than left to the caller: every creator sets
|
||||
// readable/writable/off, but only open() sets nonblock, so a struct file
|
||||
// recycled from a non-blocking device would hand its flag to the next
|
||||
// pipe or socket that happened to land in this slot.
|
||||
f->nonblock = 0;
|
||||
release(&ftable.lock);
|
||||
return f;
|
||||
}
|
||||
}
|
||||
release(&ftable.lock);
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct file*
|
||||
filedup(struct file *f)
|
||||
{
|
||||
acquire(&ftable.lock);
|
||||
if(f->ref < 1)
|
||||
panic("filedup");
|
||||
f->ref++;
|
||||
release(&ftable.lock);
|
||||
return f;
|
||||
}
|
||||
|
||||
void
|
||||
fileclose(struct file *f)
|
||||
{
|
||||
struct file ff;
|
||||
|
||||
acquire(&ftable.lock);
|
||||
if(f->ref < 1)
|
||||
panic("fileclose");
|
||||
if(--f->ref > 0){
|
||||
release(&ftable.lock);
|
||||
return;
|
||||
}
|
||||
ff = *f;
|
||||
f->ref = 0;
|
||||
f->type = FD_NONE;
|
||||
release(&ftable.lock);
|
||||
|
||||
if(ff.type == FD_PIPE)
|
||||
pipeclose(ff.pipe, ff.writable);
|
||||
else if(ff.type == FD_SOCKET)
|
||||
sockclose(ff.sock);
|
||||
else if(ff.type == FD_INODE){
|
||||
// Give a device a chance to put the hardware back before the inode goes.
|
||||
// Done here rather than in exit() because a program that closes the
|
||||
// framebuffer without exiting has finished with it just as thoroughly.
|
||||
if(ff.ip && ff.ip->type == T_DEV && ff.ip->major >= 0 &&
|
||||
ff.ip->major < NDEV && devsw[ff.ip->major].close)
|
||||
devsw[ff.ip->major].close(ff.ip);
|
||||
|
||||
begin_op();
|
||||
iput(ff.ip);
|
||||
end_op();
|
||||
}
|
||||
}
|
||||
|
||||
int
|
||||
filestat(struct file *f, struct stat *st)
|
||||
{
|
||||
if(f->type == FD_INODE){
|
||||
ilock(f->ip);
|
||||
stati(f->ip, st);
|
||||
iunlock(f->ip);
|
||||
return 0;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int
|
||||
fileread(struct file *f, char *addr, int n)
|
||||
{
|
||||
int r;
|
||||
|
||||
if(f->readable == 0)
|
||||
return -1;
|
||||
|
||||
// O_NONBLOCK on a pipe or a socket. Reported here rather than inside
|
||||
// piperead/sockread so that the blocking implementations stay unchanged:
|
||||
// this is a check of the same condition their sleep loops wait on, made one
|
||||
// level up. Writes still block -- fcntl documents that.
|
||||
if(f->type == FD_PIPE){
|
||||
if(f->nonblock && !(pipe_poll(f->pipe, f->readable, f->writable) & POLLIN))
|
||||
return -EAGAIN;
|
||||
return piperead(f->pipe, addr, n);
|
||||
}
|
||||
|
||||
if(f->type == FD_SOCKET){
|
||||
if(f->nonblock && !sockreadable(f->sock))
|
||||
return -EAGAIN;
|
||||
return sockread(f->sock, addr, n);
|
||||
}
|
||||
|
||||
if(f->type == FD_INODE){
|
||||
ilock(f->ip);
|
||||
if(f->ip->type == 3){
|
||||
if(f->ip->major < 0 || f->ip->major >= NDEV || !devsw[f->ip->major].read){
|
||||
iunlock(f->ip);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// O_NONBLOCK. Without this a game that opens an input device
|
||||
// non-blocking and drains it once per frame stops dead on the first
|
||||
// frame -- it blocks in read() waiting for a key, and the result looks
|
||||
// exactly like a hung renderer rather than a blocking read.
|
||||
if(f->nonblock && devsw[f->ip->major].ready &&
|
||||
!devsw[f->ip->major].ready(f->ip)){
|
||||
iunlock(f->ip);
|
||||
return -EAGAIN;
|
||||
}
|
||||
|
||||
r = devsw[f->ip->major].read(f->ip, f->off, addr, n);
|
||||
if(r > 0)
|
||||
f->off += r;
|
||||
iunlock(f->ip);
|
||||
return r;
|
||||
}
|
||||
|
||||
if((f->ip->rwmode & 2) == 0){
|
||||
cprintf("Schunix: permission denied (read)\n");
|
||||
iunlock(f->ip);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// The "recycle bin" check that used to sit here is gone.
|
||||
//
|
||||
// It made a file that still existed, still had a name and still had blocks
|
||||
// refuse every read with a printf -- a state no POSIX program is prepared
|
||||
// for. It was reachable only through `delete`, a NO_LINUX syscall from the
|
||||
// third-party filesystem this file derives from, which the decision to keep
|
||||
// only Linux numbering had already orphaned.
|
||||
//
|
||||
// The on-disk `showmode` field stays for now: removing it changes the inode
|
||||
// layout, and this whole file is being replaced by SchFS. It is simply
|
||||
// never consulted.
|
||||
|
||||
if((r = readi(f->ip, 0, (uint64)addr, f->off, n)) > 0)
|
||||
f->off += r;
|
||||
iunlock(f->ip);
|
||||
return r;
|
||||
}
|
||||
|
||||
panic("fileread");
|
||||
}
|
||||
|
||||
int
|
||||
filewrite(struct file *f, char *addr, int n)
|
||||
{
|
||||
int r;
|
||||
|
||||
if(f->writable == 0)
|
||||
return -1;
|
||||
|
||||
if(f->type == FD_PIPE)
|
||||
return pipewrite(f->pipe, addr, n);
|
||||
|
||||
if(f->type == FD_SOCKET)
|
||||
return sockwrite(f->sock, addr, n);
|
||||
|
||||
if(f->type == FD_INODE){
|
||||
ilock(f->ip);
|
||||
|
||||
if(f->ip->type == 3){
|
||||
if(f->ip->major < 0 || f->ip->major >= NDEV || !devsw[f->ip->major].write){
|
||||
iunlock(f->ip);
|
||||
return -1;
|
||||
}
|
||||
r = devsw[f->ip->major].write(f->ip, f->off, addr, n);
|
||||
if(r > 0)
|
||||
f->off += r;
|
||||
iunlock(f->ip);
|
||||
return r;
|
||||
}
|
||||
|
||||
if((f->ip->rwmode & 1) == 0){
|
||||
cprintf("Schunix: permission denied (write)\n");
|
||||
iunlock(f->ip);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Recycle-bin check removed here too; see the note in fileread().
|
||||
iunlock(f->ip);
|
||||
|
||||
int max = ((LOGSIZE-1-1-2) / 2) * 512;
|
||||
int i = 0;
|
||||
while(i < n){
|
||||
int n1 = n - i;
|
||||
if(n1 > max)
|
||||
n1 = max;
|
||||
|
||||
begin_op();
|
||||
ilock(f->ip);
|
||||
if ((r = writei(f->ip, 1, (uint64)(addr + i), f->off, n1)) > 0)
|
||||
f->off += r;
|
||||
|
||||
iunlock(f->ip);
|
||||
end_op();
|
||||
|
||||
if(r < 0)
|
||||
break;
|
||||
if(r != n1)
|
||||
panic("short filewrite");
|
||||
i += r;
|
||||
}
|
||||
return i == n ? n : -1;
|
||||
}
|
||||
panic("filewrite");
|
||||
}
|
||||
|
||||
// poll(2) readiness for one open file. Returns a POLL* bitmask restricted
|
||||
// to the requested events (plus error/hangup conditions, which POSIX says
|
||||
// are always reported). Pipes and the console tty have real state; other
|
||||
// inodes (regular files, block-ish devices) are always ready, per POSIX.
|
||||
int
|
||||
file_poll(struct file *f, int events)
|
||||
{
|
||||
if(f->type == FD_PIPE)
|
||||
return pipe_poll(f->pipe, f->readable, f->writable) &
|
||||
(events | POLLERR | POLLHUP);
|
||||
|
||||
if(f->type == FD_SOCKET){
|
||||
int rev = 0;
|
||||
if(sockreadable(f->sock))
|
||||
rev |= POLLIN;
|
||||
// A stream socket is writable whenever it has a peer. The buffer can
|
||||
// still be full, in which case the write blocks -- same latitude poll()
|
||||
// has always had, and the same answer a pipe gives.
|
||||
if(sock_hup(f->sock))
|
||||
rev |= POLLHUP;
|
||||
else
|
||||
rev |= POLLOUT;
|
||||
return rev & (events | POLLERR | POLLHUP);
|
||||
}
|
||||
|
||||
if(f->type == FD_INODE){
|
||||
// An input device is only readable when an event is waiting. Reporting it
|
||||
// always-ready -- which is what the generic answer below does -- turns a
|
||||
// poll loop into a busy loop that blocks in read().
|
||||
if(f->ip && f->ip->type == T_DEV && f->ip->major == INPUT_MAJOR){
|
||||
int rev = 0;
|
||||
if((events & POLLIN) && f->readable && input_ready(f->ip->minor))
|
||||
rev |= POLLIN;
|
||||
if(events & POLLOUT)
|
||||
rev |= POLLOUT;
|
||||
return rev;
|
||||
}
|
||||
|
||||
if(f->ip && f->ip->type == T_DEV && f->ip->major == CONSOLE){
|
||||
int rev = 0;
|
||||
if((events & POLLIN) && f->readable && console_input_ready())
|
||||
rev |= POLLIN;
|
||||
if((events & POLLOUT) && f->writable)
|
||||
rev |= POLLOUT;
|
||||
return rev;
|
||||
}
|
||||
return events & (POLLIN | POLLOUT);
|
||||
}
|
||||
return POLLNVAL;
|
||||
}
|
||||
1839
kernel/fs/fs.c
Normal file
1839
kernel/fs/fs.c
Normal file
File diff suppressed because it is too large
Load diff
255
kernel/fs/log.c
Normal file
255
kernel/fs/log.c
Normal file
|
|
@ -0,0 +1,255 @@
|
|||
#include "types.h"
|
||||
#include "defs.h"
|
||||
#include "param.h"
|
||||
#include "spinlock.h"
|
||||
#include "sleeplock.h"
|
||||
#include "fs.h"
|
||||
#include "buf.h"
|
||||
|
||||
// Simple logging that allows concurrent FS system calls.
|
||||
//
|
||||
// A log transaction contains the updates of multiple FS system
|
||||
// calls. The logging system only commits when there are
|
||||
// no FS system calls active. Thus there is never
|
||||
// any reasoning required about whether a commit might
|
||||
// write an uncommitted system call's updates to disk.
|
||||
//
|
||||
// A system call should call begin_op()/end_op() to mark
|
||||
// its start and end. Usually begin_op() just increments
|
||||
// the count of in-progress FS system calls and returns.
|
||||
// But if it thinks the log is close to running out, it
|
||||
// sleeps until the last outstanding end_op() commits.
|
||||
//
|
||||
// The log is a physical re-do log containing disk blocks.
|
||||
// The on-disk log format:
|
||||
// header block, containing block #s for block A, B, C, ...
|
||||
// block A
|
||||
// block B
|
||||
// block C
|
||||
// ...
|
||||
// Log appends are synchronous.
|
||||
|
||||
// Contents of the header block, used for both the on-disk header block
|
||||
// and to keep track in memory of logged block# before commit.
|
||||
struct logheader {
|
||||
int n;
|
||||
int block[LOGSIZE];
|
||||
};
|
||||
|
||||
struct log {
|
||||
struct spinlock lock;
|
||||
int start;
|
||||
int size;
|
||||
int outstanding; // how many FS sys calls are executing.
|
||||
int committing; // in commit(), please wait.
|
||||
int dev;
|
||||
struct logheader lh;
|
||||
};
|
||||
struct log log;
|
||||
|
||||
static void recover_from_log(void);
|
||||
static void commit();
|
||||
|
||||
void
|
||||
initlog(int dev)
|
||||
{
|
||||
if (sizeof(struct logheader) >= BSIZE)
|
||||
panic("initlog: too big logheader");
|
||||
|
||||
struct superblock sb;
|
||||
initlock(&log.lock, "log");
|
||||
readsb(dev, &sb);
|
||||
log.start = sb.logstart;
|
||||
log.size = sb.nlog;
|
||||
log.dev = dev;
|
||||
recover_from_log();
|
||||
}
|
||||
|
||||
// Copy committed blocks from log to their home location
|
||||
static void
|
||||
install_trans(void)
|
||||
{
|
||||
int tail;
|
||||
|
||||
for (tail = 0; tail < log.lh.n; tail++) {
|
||||
struct buf *lbuf = bread(log.dev, log.start+tail+1); // read log block
|
||||
struct buf *dbuf = bread(log.dev, log.lh.block[tail]); // read dst
|
||||
memmove(dbuf->data, lbuf->data, BSIZE); // copy block to dst
|
||||
bwrite(dbuf); // write dst to disk
|
||||
brelse(lbuf);
|
||||
brelse(dbuf);
|
||||
}
|
||||
}
|
||||
|
||||
// Read the log header from disk into the in-memory log header
|
||||
static void
|
||||
read_head(void)
|
||||
{
|
||||
struct buf *buf = bread(log.dev, log.start);
|
||||
struct logheader *lh = (struct logheader *) (buf->data);
|
||||
int i;
|
||||
log.lh.n = lh->n;
|
||||
for (i = 0; i < log.lh.n; i++) {
|
||||
log.lh.block[i] = lh->block[i];
|
||||
}
|
||||
brelse(buf);
|
||||
}
|
||||
|
||||
// Write in-memory log header to disk.
|
||||
// This is the true point at which the
|
||||
// current transaction commits.
|
||||
static void
|
||||
write_head(void)
|
||||
{
|
||||
struct buf *buf = bread(log.dev, log.start);
|
||||
struct logheader *hb = (struct logheader *) (buf->data);
|
||||
int i;
|
||||
hb->n = log.lh.n;
|
||||
for (i = 0; i < log.lh.n; i++) {
|
||||
hb->block[i] = log.lh.block[i];
|
||||
}
|
||||
bwrite(buf);
|
||||
brelse(buf);
|
||||
}
|
||||
|
||||
static void
|
||||
recover_from_log(void)
|
||||
{
|
||||
read_head();
|
||||
install_trans(); // if committed, copy from log to disk
|
||||
log.lh.n = 0;
|
||||
write_head(); // clear the log
|
||||
}
|
||||
|
||||
// called at the start of each FS system call.
|
||||
void
|
||||
begin_op(void)
|
||||
{
|
||||
// Nothing to do: the root filesystem is SchFS, which does not use this log.
|
||||
//
|
||||
// This is not a shortcut. The log exists to make a multi-block v1 update
|
||||
// atomic; SchFS gets that from copy-on-write instead -- every schfs_k* call
|
||||
// that changes anything is one transaction and is durable by the time it
|
||||
// returns. So there is nothing here for begin_op to protect.
|
||||
//
|
||||
// It also cannot do anything: initlog() ran inside fsinit(), which no longer
|
||||
// runs at all, so the lock below and everything it guards are uninitialised.
|
||||
// Taking them is where the first SchFS-root boot stopped -- after the mount,
|
||||
// before init ever ran, looking for all the world like a hang.
|
||||
//
|
||||
// The rest of this file is dead with it and goes when the v1 format does.
|
||||
// begin_op/end_op stay as no-ops meanwhile because sys_fs.c calls them in
|
||||
// dozens of places, and emptying those out is a separate change.
|
||||
return;
|
||||
|
||||
#if 0
|
||||
acquire(&log.lock);
|
||||
while(1){
|
||||
if(log.committing){
|
||||
sleep(&log, &log.lock);
|
||||
} else if(log.lh.n + (log.outstanding+1)*MAXOPBLOCKS > LOGSIZE){
|
||||
// this op might exhaust log space; wait for commit.
|
||||
sleep(&log, &log.lock);
|
||||
} else {
|
||||
log.outstanding += 1;
|
||||
release(&log.lock);
|
||||
break;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
// called at the end of each FS system call.
|
||||
// commits if this was the last outstanding operation.
|
||||
void
|
||||
end_op(void)
|
||||
{
|
||||
return; // see begin_op()
|
||||
|
||||
#if 0
|
||||
int do_commit = 0;
|
||||
|
||||
acquire(&log.lock);
|
||||
log.outstanding -= 1;
|
||||
if(log.committing)
|
||||
panic("log.committing");
|
||||
if(log.outstanding == 0){
|
||||
do_commit = 1;
|
||||
log.committing = 1;
|
||||
} else {
|
||||
// begin_op() may be waiting for log space.
|
||||
wakeup(&log);
|
||||
}
|
||||
release(&log.lock);
|
||||
|
||||
if(do_commit){
|
||||
// call commit w/o holding locks, since not allowed
|
||||
// to sleep with locks.
|
||||
commit();
|
||||
acquire(&log.lock);
|
||||
log.committing = 0;
|
||||
wakeup(&log);
|
||||
release(&log.lock);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
// Copy modified blocks from cache to log.
|
||||
static void
|
||||
write_log(void)
|
||||
{
|
||||
int tail;
|
||||
|
||||
for (tail = 0; tail < log.lh.n; tail++) {
|
||||
struct buf *to = bread(log.dev, log.start+tail+1); // log block
|
||||
struct buf *from = bread(log.dev, log.lh.block[tail]); // cache block
|
||||
memmove(to->data, from->data, BSIZE);
|
||||
bwrite(to); // write the log
|
||||
brelse(from);
|
||||
brelse(to);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
commit()
|
||||
{
|
||||
if (log.lh.n > 0) {
|
||||
write_log(); // Write modified blocks from cache to log
|
||||
write_head(); // Write header to disk -- the real commit
|
||||
install_trans(); // Now install writes to home locations
|
||||
log.lh.n = 0;
|
||||
write_head(); // Erase the transaction from the log
|
||||
}
|
||||
}
|
||||
|
||||
// Caller has modified b->data and is done with the buffer.
|
||||
// Record the block number and pin in the cache with B_DIRTY.
|
||||
// commit()/write_log() will do the disk write.
|
||||
//
|
||||
// log_write() replaces bwrite(); a typical use is:
|
||||
// bp = bread(...)
|
||||
// modify bp->data[]
|
||||
// log_write(bp)
|
||||
// brelse(bp)
|
||||
void
|
||||
log_write(struct buf *b)
|
||||
{
|
||||
int i;
|
||||
|
||||
if (log.lh.n >= LOGSIZE || log.lh.n >= log.size - 1)
|
||||
panic("too big a transaction");
|
||||
if (log.outstanding < 1)
|
||||
panic("log_write outside of trans");
|
||||
|
||||
acquire(&log.lock);
|
||||
for (i = 0; i < log.lh.n; i++) {
|
||||
if (log.lh.block[i] == b->blockno) // log absorbtion
|
||||
break;
|
||||
}
|
||||
log.lh.block[i] = b->blockno;
|
||||
if (i == log.lh.n)
|
||||
log.lh.n++;
|
||||
b->flags |= B_DIRTY; // prevent eviction
|
||||
release(&log.lock);
|
||||
}
|
||||
|
||||
166
kernel/fs/pipe.c
Normal file
166
kernel/fs/pipe.c
Normal file
|
|
@ -0,0 +1,166 @@
|
|||
#include "types.h"
|
||||
#include "defs.h"
|
||||
#include "param.h"
|
||||
#include "mmu.h"
|
||||
#include "proc.h"
|
||||
#include "fs.h"
|
||||
#include "spinlock.h"
|
||||
#include "sleeplock.h"
|
||||
#include "file.h"
|
||||
#include "posix.h"
|
||||
#include "errno.h"
|
||||
|
||||
#define PIPESIZE 512
|
||||
|
||||
struct pipe {
|
||||
struct spinlock lock;
|
||||
char data[PIPESIZE];
|
||||
uint nread; // number of bytes read
|
||||
uint nwrite; // number of bytes written
|
||||
int readopen; // read fd is still open
|
||||
int writeopen; // write fd is still open
|
||||
};
|
||||
|
||||
int
|
||||
pipealloc(struct file **f0, struct file **f1)
|
||||
{
|
||||
struct pipe *p;
|
||||
|
||||
p = 0;
|
||||
*f0 = *f1 = 0;
|
||||
if((*f0 = filealloc()) == 0 || (*f1 = filealloc()) == 0)
|
||||
goto bad;
|
||||
if((p = (struct pipe*)kalloc()) == 0)
|
||||
goto bad;
|
||||
p->readopen = 1;
|
||||
p->writeopen = 1;
|
||||
p->nwrite = 0;
|
||||
p->nread = 0;
|
||||
initlock(&p->lock, "pipe");
|
||||
(*f0)->type = FD_PIPE;
|
||||
(*f0)->readable = 1;
|
||||
(*f0)->writable = 0;
|
||||
(*f0)->pipe = p;
|
||||
(*f1)->type = FD_PIPE;
|
||||
(*f1)->readable = 0;
|
||||
(*f1)->writable = 1;
|
||||
(*f1)->pipe = p;
|
||||
return 0;
|
||||
|
||||
//PAGEBREAK: 20
|
||||
bad:
|
||||
if(p)
|
||||
kfree((char*)p);
|
||||
if(*f0)
|
||||
fileclose(*f0);
|
||||
if(*f1)
|
||||
fileclose(*f1);
|
||||
return -1;
|
||||
}
|
||||
|
||||
void
|
||||
pipeclose(struct pipe *p, int writable)
|
||||
{
|
||||
acquire(&p->lock);
|
||||
if(writable){
|
||||
p->writeopen = 0;
|
||||
wakeup(&p->nread);
|
||||
} else {
|
||||
p->readopen = 0;
|
||||
wakeup(&p->nwrite);
|
||||
}
|
||||
if(p->readopen == 0 && p->writeopen == 0){
|
||||
release(&p->lock);
|
||||
kfree((char*)p);
|
||||
} else
|
||||
release(&p->lock);
|
||||
}
|
||||
|
||||
//PAGEBREAK: 40
|
||||
int
|
||||
pipewrite(struct pipe *p, char *addr, int n)
|
||||
{
|
||||
int i;
|
||||
|
||||
acquire(&p->lock);
|
||||
|
||||
// Writing to a pipe nobody is reading is SIGPIPE and EPIPE, in that order.
|
||||
//
|
||||
// This returned a bare -1 with no signal and no errno, so `yes | head -2`
|
||||
// came back with whatever errno happened to be lying around -- "Invalid
|
||||
// argument" -- instead of quietly ending the way every shell pipeline is
|
||||
// built to. Checked up front as well as in the blocking loop below: the
|
||||
// common case is that head has already exited by the time the next write
|
||||
// arrives, and the pipe is nowhere near full.
|
||||
if(p->readopen == 0){
|
||||
release(&p->lock);
|
||||
sigsend(myproc()->pid, SIGPIPE);
|
||||
return -EPIPE;
|
||||
}
|
||||
|
||||
for(i = 0; i < n; i++){
|
||||
while(p->nwrite == p->nread + PIPESIZE){ //DOC: pipewrite-full
|
||||
if(p->readopen == 0){
|
||||
release(&p->lock);
|
||||
sigsend(myproc()->pid, SIGPIPE);
|
||||
return i > 0 ? i : -EPIPE; // a short write is still a write
|
||||
}
|
||||
if(myproc()->killed){
|
||||
release(&p->lock);
|
||||
return -1;
|
||||
}
|
||||
wakeup(&p->nread);
|
||||
sleep(&p->nwrite, &p->lock); //DOC: pipewrite-sleep
|
||||
}
|
||||
p->data[p->nwrite++ % PIPESIZE] = addr[i];
|
||||
}
|
||||
wakeup(&p->nread); //DOC: pipewrite-wakeup1
|
||||
release(&p->lock);
|
||||
return n;
|
||||
}
|
||||
|
||||
int
|
||||
piperead(struct pipe *p, char *addr, int n)
|
||||
{
|
||||
int i;
|
||||
|
||||
acquire(&p->lock);
|
||||
while(p->nread == p->nwrite && p->writeopen){ //DOC: pipe-empty
|
||||
if(myproc()->killed){
|
||||
release(&p->lock);
|
||||
return -1;
|
||||
}
|
||||
sleep(&p->nread, &p->lock); //DOC: piperead-sleep
|
||||
}
|
||||
for(i = 0; i < n; i++){ //DOC: piperead-copy
|
||||
if(p->nread == p->nwrite)
|
||||
break;
|
||||
addr[i] = p->data[p->nread++ % PIPESIZE];
|
||||
}
|
||||
wakeup(&p->nwrite); //DOC: piperead-wakeup
|
||||
release(&p->lock);
|
||||
return i;
|
||||
}
|
||||
|
||||
// Readiness for poll(2)/select(2). Reports state, doesn't block.
|
||||
int
|
||||
pipe_poll(struct pipe *p, int readable, int writable)
|
||||
{
|
||||
int rev = 0;
|
||||
|
||||
acquire(&p->lock);
|
||||
if(readable){
|
||||
if(p->nread != p->nwrite)
|
||||
rev |= POLLIN;
|
||||
if(p->writeopen == 0)
|
||||
rev |= POLLHUP;
|
||||
}
|
||||
if(writable){
|
||||
if(p->readopen == 0)
|
||||
rev |= POLLERR;
|
||||
else if(p->nwrite < p->nread + PIPESIZE)
|
||||
rev |= POLLOUT;
|
||||
}
|
||||
release(&p->lock);
|
||||
return rev;
|
||||
}
|
||||
524
kernel/fs/procfs.c
Normal file
524
kernel/fs/procfs.c
Normal file
|
|
@ -0,0 +1,524 @@
|
|||
// /proc as a real filesystem.
|
||||
//
|
||||
// It used to be a handful of character devices: init ran `mknod("/proc/uptime",
|
||||
// 6, 2)` and friends, so every file was a real inode on the root filesystem
|
||||
// with a major/minor pair behind it. That works for a fixed set of files and
|
||||
// cannot work at all for the thing /proc is mostly *for* -- one directory per
|
||||
// process. You cannot mknod a directory for a process that does not exist yet
|
||||
// and will not exist in a minute, which is why `ps` and `top` have never
|
||||
// worked: they call dirtree_flagread("/proc") and get a list with no pids in
|
||||
// it.
|
||||
//
|
||||
// So this is a synthetic filesystem behind the same `struct fs_ops` vtable
|
||||
// SchFS uses. Nothing is stored: every directory listing is generated from the
|
||||
// process table at the moment it is asked for, and every file's contents are
|
||||
// generated when it is read.
|
||||
//
|
||||
// The global files (meminfo, uptime, pci, ...) keep their existing generators
|
||||
// in kernel/dev/procfs.c; this layer only decides what exists and hands the
|
||||
// reads over. That split is deliberate -- the generators are about content and
|
||||
// this file is about namespace, and they change for different reasons.
|
||||
|
||||
#include "types.h"
|
||||
#include "defs.h"
|
||||
#include "param.h"
|
||||
#include "memlayout.h"
|
||||
#include "mmu.h"
|
||||
#include "proc.h"
|
||||
#include "spinlock.h"
|
||||
#include "sleeplock.h"
|
||||
#include "fs.h"
|
||||
#include "file.h"
|
||||
#include "stat.h"
|
||||
#include "uaccess.h"
|
||||
#include "errno.h"
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Inode numbering
|
||||
//
|
||||
// An inode number has to survive being handed out, stored in a struct inode,
|
||||
// and given back later with no other context -- so it encodes the whole
|
||||
// identity of the object.
|
||||
//
|
||||
// 1 the root, /proc
|
||||
// PROCFS_GLOBAL | id a global file, id being the PROC_* minor that
|
||||
// kernel/dev/procfs.c already uses
|
||||
// pid << 8 the directory /proc/<pid>
|
||||
// (pid << 8) | fileid a file inside it
|
||||
//
|
||||
// pid is bounded by NPROC and file ids by PF_MAX, so the ranges cannot
|
||||
// collide. Shifting by 8 rather than packing tightly keeps the numbers
|
||||
// readable in a debugger, which matters more than density here.
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// Size of the scratch buffer every generator renders into. Must stay equal to
|
||||
// PROC_BUFSZ in kernel/dev/procfs.c: the SchFS and PCI generators there write
|
||||
// up to PROC_BUFSZ regardless of the cap they are handed, so a smaller value
|
||||
// here would be a buffer overrun rather than a truncation.
|
||||
#define PROCFS_GENBUF 2048
|
||||
|
||||
#define PROCFS_ROOT 1ULL
|
||||
#define PROCFS_GLOBAL 0x10000ULL
|
||||
|
||||
#define PF_DIR 0 // the directory itself
|
||||
#define PF_STAT 1
|
||||
#define PF_STATUS 2
|
||||
#define PF_CMDLINE 3
|
||||
#define PF_STATM 4
|
||||
#define PF_EXE 5
|
||||
#define PF_MAX 6
|
||||
|
||||
#define INO_PID(ino) ((int)((ino) >> 8))
|
||||
#define INO_FILE(ino) ((int)((ino) & 0xFF))
|
||||
#define INO_IS_GLOBAL(ino) (((ino) & PROCFS_GLOBAL) != 0)
|
||||
|
||||
// The global files, by the name they appear under and the PROC_* id that
|
||||
// kernel/dev/procfs.c generates them from.
|
||||
struct global_file { const char *name; int id; };
|
||||
|
||||
static const struct global_file globals[] = {
|
||||
{ "meminfo", 1 },
|
||||
{ "uptime", 2 },
|
||||
{ "cpuinfo", 3 },
|
||||
{ "version", 4 },
|
||||
{ "stat", 5 },
|
||||
{ "loadavg", 6 },
|
||||
{ "mounts", 7 },
|
||||
{ "filesystems", 8 },
|
||||
{ "schfs", 9 },
|
||||
{ "sysrq", 10 },
|
||||
{ "pci", 11 },
|
||||
{ "interrupts", 12 },
|
||||
{ "devices", 13 },
|
||||
};
|
||||
#define NGLOBALS ((int)(sizeof(globals) / sizeof(globals[0])))
|
||||
|
||||
// Per-process files. `statm` and `status` are here because ps reads them; a
|
||||
// missing file is not an error to it, but a present one is cheaper than the
|
||||
// fallback paths it takes without them.
|
||||
static const struct global_file pidfiles[] = {
|
||||
{ "stat", PF_STAT },
|
||||
{ "status", PF_STATUS },
|
||||
{ "cmdline", PF_CMDLINE },
|
||||
{ "statm", PF_STATM },
|
||||
{ "exe", PF_EXE },
|
||||
};
|
||||
#define NPIDFILES ((int)(sizeof(pidfiles) / sizeof(pidfiles[0])))
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Content
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// A tiny append-only formatter. procfs files are small and generated whole,
|
||||
// so there is no reason for anything cleverer.
|
||||
struct sb { char *p; int n, cap; };
|
||||
|
||||
static void sbputc(struct sb *s, char c)
|
||||
{ if (s->n < s->cap - 1) s->p[s->n++] = c; }
|
||||
|
||||
static void sbputs(struct sb *s, const char *t)
|
||||
{ while (*t) sbputc(s, *t++); }
|
||||
|
||||
static void sbputi(struct sb *s, int64 v)
|
||||
{
|
||||
char tmp[24];
|
||||
int i = 0, neg = 0;
|
||||
uint64 u;
|
||||
if (v < 0) { neg = 1; u = (uint64)(-v); } else u = (uint64)v;
|
||||
do { tmp[i++] = '0' + (int)(u % 10); u /= 10; } while (u);
|
||||
if (neg) sbputc(s, '-');
|
||||
while (i--) sbputc(s, tmp[i]);
|
||||
}
|
||||
|
||||
// Linux's /proc/<pid>/stat, as far as anything here reads it.
|
||||
//
|
||||
// The field order is not negotiable: ps counts fields by position, so a
|
||||
// missing one silently shifts everything after it. Fields Schunix does not
|
||||
// track are zero, which is what Linux itself reports for things a given
|
||||
// process has never done.
|
||||
static char
|
||||
state_char(int st)
|
||||
{
|
||||
switch (st) {
|
||||
case RUNNING: return 'R';
|
||||
case RUNNABLE: return 'R';
|
||||
case SLEEPING: return 'S';
|
||||
case ZOMBIE: return 'Z';
|
||||
case EMBRYO: return 'R';
|
||||
default: return '?';
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
gen_pid_stat(struct sb *s, const struct procinfo *pi)
|
||||
{
|
||||
int i;
|
||||
|
||||
sbputi(s, pi->pid);
|
||||
sbputs(s, " (");
|
||||
sbputs(s, pi->name);
|
||||
sbputs(s, ") ");
|
||||
sbputc(s, state_char(pi->state));
|
||||
sbputc(s, ' ');
|
||||
sbputi(s, pi->ppid); // 4 ppid
|
||||
sbputc(s, ' ');
|
||||
sbputi(s, pi->pgid); // 5 pgrp
|
||||
sbputc(s, ' ');
|
||||
sbputi(s, pi->sid); // 6 session
|
||||
sbputs(s, " 0 -1 0"); // 7 tty_nr, 8 tpgid, 9 flags
|
||||
|
||||
// 10..17: minflt cminflt majflt cmajflt utime stime cutime cstime.
|
||||
// No per-process accounting yet, so these are honestly zero rather than
|
||||
// invented -- a fabricated CPU time is worse than none, because top would
|
||||
// draw a plausible graph of a number nobody measured.
|
||||
for (i = 0; i < 8; i++)
|
||||
sbputs(s, " 0");
|
||||
|
||||
sbputs(s, " 20 0 1 0 0 "); // priority nice num_threads itrealvalue starttime
|
||||
sbputi(s, (int64)pi->sz); // 23 vsize, in bytes
|
||||
sbputc(s, ' ');
|
||||
sbputi(s, (int64)(pi->sz / PGSIZE)); // 24 rss, in pages
|
||||
sbputs(s, " 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0\n");
|
||||
}
|
||||
|
||||
static void
|
||||
gen_pid_status(struct sb *s, const struct procinfo *pi)
|
||||
{
|
||||
sbputs(s, "Name:\t"); sbputs(s, pi->name); sbputc(s, '\n');
|
||||
sbputs(s, "State:\t"); sbputc(s, state_char(pi->state));
|
||||
sbputs(s, "\nTgid:\t"); sbputi(s, pi->pid);
|
||||
sbputs(s, "\nPid:\t"); sbputi(s, pi->pid);
|
||||
sbputs(s, "\nPPid:\t"); sbputi(s, pi->ppid);
|
||||
sbputs(s, "\nUid:\t"); sbputi(s, pi->uid);
|
||||
sbputc(s, '\t'); sbputi(s, pi->uid);
|
||||
sbputc(s, '\t'); sbputi(s, pi->uid);
|
||||
sbputc(s, '\t'); sbputi(s, pi->uid);
|
||||
sbputs(s, "\nVmSize:\t"); sbputi(s, (int64)(pi->sz / 1024));
|
||||
sbputs(s, " kB\nThreads:\t1\n");
|
||||
}
|
||||
|
||||
static void
|
||||
gen_pid_statm(struct sb *s, const struct procinfo *pi)
|
||||
{
|
||||
int64 pages = (int64)(pi->sz / PGSIZE);
|
||||
sbputi(s, pages); sbputc(s, ' ');
|
||||
sbputi(s, pages); sbputs(s, " 0 0 0 0 0\n");
|
||||
}
|
||||
|
||||
// argv is not kept after exec, so the best honest answer is the process name.
|
||||
// Linux separates arguments with NUL and ps turns those into spaces; one
|
||||
// NUL-terminated string is a valid degenerate case of that.
|
||||
static void
|
||||
gen_pid_cmdline(struct sb *s, const struct procinfo *pi)
|
||||
{
|
||||
sbputs(s, pi->name);
|
||||
sbputc(s, '\0');
|
||||
}
|
||||
|
||||
// Generate the whole of one file into `buf`. Returns its length.
|
||||
static int
|
||||
gen_file(uint64 ino, char *buf, int cap)
|
||||
{
|
||||
struct sb s = { buf, 0, cap };
|
||||
struct procinfo pi;
|
||||
|
||||
if (INO_IS_GLOBAL(ino))
|
||||
return proc_gen_global((int)(ino & 0xFF), buf, cap);
|
||||
|
||||
if (proc_snapshot_pid(INO_PID(ino), &pi) < 0)
|
||||
return 0; // it exited between lookup and read
|
||||
|
||||
switch (INO_FILE(ino)) {
|
||||
case PF_STAT: gen_pid_stat(&s, &pi); break;
|
||||
case PF_STATUS: gen_pid_status(&s, &pi); break;
|
||||
case PF_CMDLINE: gen_pid_cmdline(&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;
|
||||
default: return 0;
|
||||
}
|
||||
return s.n;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// fs_ops
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static void
|
||||
procfs_iload(struct inode *ip)
|
||||
{
|
||||
uint64 ino = ip->inum;
|
||||
|
||||
ip->valid = 1;
|
||||
ip->nlink = 1;
|
||||
ip->major = ip->minor = 0;
|
||||
// Read-only, with one exception: /proc/sysrq is the deliberate write target
|
||||
// (`echo c > /proc/sysrq` to crash on purpose). Marking everything 2 made
|
||||
// filewrite() refuse it with "permission denied", which the USB suite
|
||||
// caught immediately -- three of its checks drive the disk through sysrq.
|
||||
ip->rwmode = (INO_IS_GLOBAL(ino) && (ino & 0xFF) == 10) ? 3 : 2;
|
||||
ip->showmode = 1;
|
||||
ip->atime = ip->mtime = ip->ctime = 0;
|
||||
|
||||
if (ino == PROCFS_ROOT || (!INO_IS_GLOBAL(ino) && INO_FILE(ino) == PF_DIR)) {
|
||||
ip->type = T_DIR;
|
||||
ip->size = 0;
|
||||
ip->nlink = 2;
|
||||
return;
|
||||
}
|
||||
|
||||
ip->type = T_FILE;
|
||||
// Size is reported as zero.
|
||||
//
|
||||
// A synthetic file has no length until it is generated, and generating it
|
||||
// here -- on every stat(), for every process -- to fill in a number almost
|
||||
// nobody reads would be absurd. Linux does exactly the same thing, which is
|
||||
// why `ls -l /proc` shows a column of zeroes, and why every tool that reads
|
||||
// /proc reads until EOF instead of trusting st_size.
|
||||
ip->size = 0;
|
||||
}
|
||||
|
||||
static void procfs_istore(struct inode *ip) { (void)ip; }
|
||||
static void procfs_ifree(struct inode *ip) { (void)ip; }
|
||||
static void procfs_truncate(struct inode *ip, uint64 n) { (void)ip; (void)n; }
|
||||
|
||||
static int
|
||||
procfs_read(struct inode *ip, int user_dst, uint64 dst, uint64 off, uint n)
|
||||
{
|
||||
char *buf;
|
||||
int len, r;
|
||||
|
||||
if (ip->type == T_DIR)
|
||||
return -EISDIR;
|
||||
|
||||
// The 2048-byte scratch used to be a local. KSTACKSIZE is 4096, so that was
|
||||
// half the kernel stack in one array, on a path already several frames deep
|
||||
// -- a generator with any locals of its own, or one more frame below it,
|
||||
// runs off the end. /proc/schfs is what finally did it: the canary at the
|
||||
// bottom of the stack came back zeroed. Nothing about SchFS caused this; it
|
||||
// only pushed an existing overrun over the edge.
|
||||
//
|
||||
// A whole page for a 2 KB buffer is not wasteful here -- kalloc's unit is a
|
||||
// page either way, and this is not a hot path.
|
||||
if ((buf = kalloc()) == 0)
|
||||
return -ENOMEM;
|
||||
|
||||
len = gen_file(ip->inum, buf, PROCFS_GENBUF);
|
||||
if (off >= (uint64)len) {
|
||||
kfree(buf);
|
||||
return 0;
|
||||
}
|
||||
if (off + n > (uint64)len)
|
||||
n = (uint)((uint64)len - off);
|
||||
|
||||
r = (int)n;
|
||||
if (user_dst) {
|
||||
if (copy_to_user(dst, buf + off, n) < 0)
|
||||
r = -EFAULT;
|
||||
} else {
|
||||
memmove((char *)dst, buf + off, n);
|
||||
}
|
||||
|
||||
kfree(buf);
|
||||
return r;
|
||||
}
|
||||
|
||||
static int
|
||||
procfs_write(struct inode *ip, int user_src, uint64 src, uint64 off, uint n)
|
||||
{
|
||||
// Only /proc/sysrq accepts writes, and its handler lives with the other
|
||||
// generators. Everything else is read-only, and says so.
|
||||
if (INO_IS_GLOBAL(ip->inum) && (ip->inum & 0xFF) == 10)
|
||||
return proc_write_sysrq(user_src, src, n);
|
||||
(void)off;
|
||||
return -EROFS;
|
||||
}
|
||||
|
||||
// Decimal string -> pid, or -1 if it is not all digits. This is also what
|
||||
// makes a name like "meminfo" fall through to the global table.
|
||||
static int
|
||||
name_to_pid(const char *name)
|
||||
{
|
||||
int v = 0;
|
||||
|
||||
if (!name || !*name)
|
||||
return -1;
|
||||
while (*name) {
|
||||
if (*name < '0' || *name > '9')
|
||||
return -1;
|
||||
v = v * 10 + (*name++ - '0');
|
||||
if (v > 1 << 20)
|
||||
return -1;
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
static struct inode *
|
||||
procfs_dirlookup(struct inode *dp, char *name, uint *poff)
|
||||
{
|
||||
struct procinfo pi;
|
||||
int i, pid;
|
||||
|
||||
if (poff)
|
||||
*poff = 0;
|
||||
|
||||
if (namecmp(name, ".") == 0)
|
||||
return iget(dp->dev, dp->inum);
|
||||
if (namecmp(name, "..") == 0)
|
||||
return iget(dp->dev, PROCFS_ROOT); // /proc/<pid>/.. is /proc
|
||||
|
||||
// Inside /proc/<pid>/
|
||||
if (dp->inum != PROCFS_ROOT) {
|
||||
for (i = 0; i < NPIDFILES; i++)
|
||||
if (namecmp(name, (char *)pidfiles[i].name) == 0)
|
||||
return iget(dp->dev, (dp->inum & ~0xFFULL) | pidfiles[i].id);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// In /proc itself: a number is a process, a word is a global file.
|
||||
pid = name_to_pid(name);
|
||||
if (pid > 0) {
|
||||
if (proc_snapshot_pid(pid, &pi) < 0)
|
||||
return 0;
|
||||
return iget(dp->dev, ((uint64)pid << 8) | PF_DIR);
|
||||
}
|
||||
|
||||
for (i = 0; i < NGLOBALS; i++)
|
||||
if (namecmp(name, (char *)globals[i].name) == 0)
|
||||
return iget(dp->dev, PROCFS_GLOBAL | globals[i].id);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Nothing in /proc can be created, removed or linked. Saying so with an error
|
||||
// is better than a silent no-op: a program that thinks it wrote a file and
|
||||
// finds it missing later has a much harder problem to debug.
|
||||
static int procfs_dirlink(struct inode *dp, char *name, uint64 inum)
|
||||
{ (void)dp; (void)name; (void)inum; return -EROFS; }
|
||||
static int procfs_dirunlink(struct inode *dp, char *name, uint off)
|
||||
{ (void)dp; (void)name; (void)off; return -EROFS; }
|
||||
static int procfs_dirempty(struct inode *dp) { (void)dp; return 0; }
|
||||
static struct inode *procfs_ialloc(uint dev, short type)
|
||||
{ (void)dev; (void)type; return 0; }
|
||||
|
||||
// One getdents64 record.
|
||||
struct linux_dirent64 {
|
||||
uint64 d_ino;
|
||||
int64 d_off;
|
||||
ushort d_reclen;
|
||||
uchar d_type;
|
||||
char d_name[];
|
||||
};
|
||||
|
||||
#define DT_DIR_ 4
|
||||
#define DT_REG_ 8
|
||||
|
||||
static int
|
||||
emit(char *out, int cap, int used, uint64 ino, int type, const char *name)
|
||||
{
|
||||
struct linux_dirent64 *de;
|
||||
int nlen = 0, reclen;
|
||||
|
||||
while (name[nlen]) nlen++;
|
||||
reclen = (int)sizeof(*de) + nlen + 1;
|
||||
reclen = (reclen + 7) & ~7; // 8-byte aligned, as getdents64 requires
|
||||
if (used + reclen > cap)
|
||||
return -1;
|
||||
|
||||
de = (struct linux_dirent64 *)(out + used);
|
||||
de->d_ino = ino;
|
||||
de->d_off = used + reclen;
|
||||
de->d_reclen = (ushort)reclen;
|
||||
de->d_type = (uchar)type;
|
||||
memmove(de->d_name, name, nlen);
|
||||
de->d_name[nlen] = 0;
|
||||
return reclen;
|
||||
}
|
||||
|
||||
static void
|
||||
utoa(uint v, char *buf)
|
||||
{
|
||||
char t[12];
|
||||
int i = 0, j = 0;
|
||||
do { t[i++] = '0' + (v % 10); v /= 10; } while (v);
|
||||
while (i) buf[j++] = t[--i];
|
||||
buf[j] = 0;
|
||||
}
|
||||
|
||||
// The listing is generated fresh each time, and *off is an index into it.
|
||||
//
|
||||
// A process that exits between two getdents calls therefore shifts everything
|
||||
// after it up by one, and one entry can be missed. That is a real race and it
|
||||
// is the same one Linux has -- /proc has no stable ordering to offer, and
|
||||
// anything that wants a consistent snapshot has to re-read.
|
||||
static int
|
||||
procfs_getdents(struct inode *dp, char *out, int cap, uint64 *off)
|
||||
{
|
||||
struct procinfo pi;
|
||||
char name[16];
|
||||
int used = 0, idx = 0, i, r;
|
||||
uint64 want = *off;
|
||||
|
||||
#define EMIT(ino, type, nm) do { \
|
||||
if (idx++ >= (int)want) { \
|
||||
r = emit(out, cap, used, (ino), (type), (nm)); \
|
||||
if (r < 0) goto full; \
|
||||
used += r; (*off)++; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
if (dp->inum == PROCFS_ROOT) {
|
||||
EMIT(PROCFS_ROOT, DT_DIR_, ".");
|
||||
EMIT(PROCFS_ROOT, DT_DIR_, "..");
|
||||
for (i = 0; i < NGLOBALS; i++)
|
||||
EMIT(PROCFS_GLOBAL | globals[i].id, DT_REG_, globals[i].name);
|
||||
for (i = 0; proc_snapshot(i, &pi) == 0; i++) {
|
||||
utoa((uint)pi.pid, name);
|
||||
EMIT(((uint64)pi.pid << 8) | PF_DIR, DT_DIR_, name);
|
||||
}
|
||||
} else {
|
||||
EMIT(dp->inum, DT_DIR_, ".");
|
||||
EMIT(PROCFS_ROOT, DT_DIR_, "..");
|
||||
for (i = 0; i < NPIDFILES; i++)
|
||||
EMIT((dp->inum & ~0xFFULL) | pidfiles[i].id, DT_REG_, pidfiles[i].name);
|
||||
}
|
||||
|
||||
full:
|
||||
#undef EMIT
|
||||
return used;
|
||||
}
|
||||
|
||||
const struct fs_ops procfs_ops = {
|
||||
.name = "procfs",
|
||||
.iload = procfs_iload,
|
||||
.istore = procfs_istore,
|
||||
.ifree = procfs_ifree,
|
||||
.read = procfs_read,
|
||||
.write = procfs_write,
|
||||
.truncate = procfs_truncate,
|
||||
.dirlookup = procfs_dirlookup,
|
||||
.dirlink = procfs_dirlink,
|
||||
.dirunlink = procfs_dirunlink,
|
||||
.dirempty = procfs_dirempty,
|
||||
.ialloc = procfs_ialloc,
|
||||
.getdents = procfs_getdents,
|
||||
};
|
||||
|
||||
// Graft this filesystem over /proc.
|
||||
//
|
||||
// Called by init once it has made the directory. The mknod'd files that used
|
||||
// to live there are simply covered: path resolution stops at the mount point,
|
||||
// so they are unreachable and harmless. Removing them from init is a separate,
|
||||
// later cleanup -- doing both at once would make a boot failure ambiguous
|
||||
// between "the mount is wrong" and "the nodes are missing".
|
||||
void
|
||||
procfs_mount(void)
|
||||
{
|
||||
struct inode *dp = namei("/proc");
|
||||
|
||||
if (dp == 0) {
|
||||
cprintf("procfs: /proc does not exist; not mounted\n");
|
||||
return;
|
||||
}
|
||||
if (mount_add(dp->dev, dp->inum, PROCFS_DEV, PROCFS_ROOT) < 0)
|
||||
cprintf("procfs: /proc already has something mounted on it\n");
|
||||
iput(dp);
|
||||
}
|
||||
1181
kernel/fs/schfs/btree.c
Normal file
1181
kernel/fs/schfs/btree.c
Normal file
File diff suppressed because it is too large
Load diff
215
kernel/fs/schfs/btree.h
Normal file
215
kernel/fs/schfs/btree.h
Normal file
|
|
@ -0,0 +1,215 @@
|
|||
// SchFS copy-on-write B-tree.
|
||||
//
|
||||
// This is the load-bearing piece of the whole filesystem: inodes, directory
|
||||
// entries, extent maps and indexes are all just records in this one tree, so a
|
||||
// subtle bug here is a bug in everything. It is therefore written as portable C
|
||||
// against an abstract block device, and exercised on the host by
|
||||
// tools/schfs_test.c before it is ever run inside the kernel.
|
||||
//
|
||||
// Copy-on-write means no block that is part of the *committed* tree is ever
|
||||
// modified. An update copies the path from the leaf back up to the root, and
|
||||
// the caller then publishes the new root. A crash therefore lands on either the
|
||||
// old tree or the new one, never a mixture — which is why there is no journal
|
||||
// and no fsck.
|
||||
//
|
||||
// "Committed" is the operative word: a block first written during the
|
||||
// transaction group now being built is not reachable from any published root,
|
||||
// so it may be rewritten in place. See schfs_btree.generation.
|
||||
|
||||
#ifndef SCHFS_BTREE_H
|
||||
#define SCHFS_BTREE_H
|
||||
|
||||
#include "schfs.h"
|
||||
|
||||
// Block device the tree lives on. The kernel supplies one backed by the buffer
|
||||
// cache; the test harness supplies one backed by a plain file or by memory.
|
||||
struct schfs_bdev {
|
||||
void *ctx;
|
||||
int (*bread)(void *ctx, uint64_t blk, void *buf);
|
||||
int (*bwrite)(void *ctx, uint64_t blk, const void *buf);
|
||||
// Allocate a block for copy-on-write. Must never return a block that is
|
||||
// reachable from the last committed root.
|
||||
uint64_t (*balloc)(void *ctx);
|
||||
// Release a block that the new tree no longer references. Freeing may only
|
||||
// take effect at commit, since the old tree must stay readable until then.
|
||||
void (*bfree)(void *ctx, uint64_t blk);
|
||||
// Optional: allocate n physically contiguous blocks, returning the first,
|
||||
// or 0 if that cannot be satisfied. This is what lets a file become one
|
||||
// extent rather than one per block, and it has to be a separate entry point
|
||||
// because an allocator with a free list will happily return scattered
|
||||
// blocks from balloc. NULL means unsupported; callers then fall back to
|
||||
// allocating singly and accept the extra extent records.
|
||||
uint64_t (*balloc_run)(void *ctx, uint64_t n);
|
||||
};
|
||||
|
||||
struct schfs_btree {
|
||||
struct schfs_bdev *dev;
|
||||
uint64_t root; // block number of the current root
|
||||
uint32_t root_crc;
|
||||
uint64_t root_gen; // generation stamped in the root block itself
|
||||
// The transaction group being built. Every block written by a mutation is
|
||||
// stamped with this, and a block already carrying it is rewritten in place
|
||||
// rather than copied — it cannot be part of a committed tree. The volume
|
||||
// layer advances it on every commit; nothing else may touch it.
|
||||
uint64_t generation;
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Scratch buffers
|
||||
//
|
||||
// Nothing in this file may put a block on the stack. A SchFS block is 4096
|
||||
// bytes and so is the entire kernel stack (KSTACKSIZE), so even one such local
|
||||
// overflows it — the kstack canary caught exactly that on the first boot of the
|
||||
// read path. Mutations need three blocks live at once (the node being edited,
|
||||
// the sibling a split produces, and a repacking buffer), so callers hand in
|
||||
// three and the tree borrows them.
|
||||
//
|
||||
// The three must not alias, and their contents do not survive across calls.
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
struct schfs_ctx {
|
||||
void *a; // node currently being edited
|
||||
void *b; // sibling produced by a split
|
||||
void *c; // repacking scratch for leaves
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Cursor
|
||||
//
|
||||
// The read path is driven through a cursor for the same reason: a cursor holds
|
||||
// exactly one block buffer, supplied by the caller, and remembers the path as
|
||||
// block numbers, re-reading a parent on the way back up. Walking a tree of any
|
||||
// depth therefore costs one page rather than one page per level.
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
struct schfs_path_ent {
|
||||
uint64_t blocknr;
|
||||
uint32_t crc;
|
||||
int index; // internal: child descended into; leaf: item
|
||||
};
|
||||
|
||||
struct schfs_cursor {
|
||||
struct schfs_btree *t;
|
||||
void *blk; // caller-owned, SCHFS_BLKSIZE bytes
|
||||
int depth; // path[depth] is the leaf
|
||||
int valid; // 0 once the walk has run off either end
|
||||
struct schfs_path_ent path[SCHFS_MAX_LEVEL + 1];
|
||||
};
|
||||
|
||||
// `blk` must point at SCHFS_BLKSIZE bytes that outlive the cursor.
|
||||
void schfs_cursor_init(struct schfs_cursor *c, struct schfs_btree *t, void *blk);
|
||||
|
||||
// Position at the first key >= start, or at the very first key if start is
|
||||
// NULL. Returns 0 if positioned, -1 if the tree holds no such key, -2 on
|
||||
// corruption.
|
||||
int schfs_cursor_seek(struct schfs_cursor *c, const struct schfs_key *start);
|
||||
|
||||
// Step. Return 0, -1 at the end of the tree, -2 on corruption.
|
||||
int schfs_cursor_next(struct schfs_cursor *c);
|
||||
int schfs_cursor_prev(struct schfs_cursor *c);
|
||||
|
||||
// Current record. Both pointers are invalidated by the next seek/next/prev.
|
||||
// Return NULL when the cursor is not positioned.
|
||||
const struct schfs_key *schfs_cursor_key(struct schfs_cursor *c);
|
||||
const void *schfs_cursor_val(struct schfs_cursor *c, unsigned *len);
|
||||
|
||||
// Ordering over keys: (objectid, type, offset). Returns <0, 0, >0.
|
||||
int schfs_key_cmp(const struct schfs_key *a, const struct schfs_key *b);
|
||||
|
||||
uint32_t schfs_crc32c(uint32_t crc, const void *buf, unsigned len);
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Node accessors
|
||||
//
|
||||
// Plain views over the on-disk layout in schfs.h. Exposed because the space
|
||||
// manager walks the tree by block number rather than by key, and a second,
|
||||
// slightly different copy of these three lines is exactly the sort of thing
|
||||
// that ends up disagreeing with the first.
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#define SCHFS_HDR(b) ((struct schfs_node_hdr *)(b))
|
||||
#define SCHFS_KPTRS(b) ((struct schfs_kptr *) \
|
||||
((char *)(b) + sizeof(struct schfs_node_hdr)))
|
||||
#define SCHFS_ITEMS(b) ((struct schfs_item *) \
|
||||
((char *)(b) + sizeof(struct schfs_node_hdr)))
|
||||
|
||||
// Read one tree block into `out` and validate it completely: magic, the block's
|
||||
// self-pointer, its checksum, and — when want_crc is non-zero — that the
|
||||
// checksum is the one its parent recorded. Returns 0 or -2.
|
||||
int schfs_node_read(struct schfs_bdev *dev, uint64_t blk, uint32_t want_crc,
|
||||
void *out);
|
||||
|
||||
// Create an empty tree (a single empty leaf) and set t->root.
|
||||
int schfs_btree_create(struct schfs_btree *t, struct schfs_bdev *dev,
|
||||
struct schfs_ctx *x);
|
||||
|
||||
// Look a key up. On success copies at most *len bytes of the value into buf and
|
||||
// stores the true size in *len. Returns 0 on success, -1 if absent, -2 on a
|
||||
// corrupt or unreadable block. Uses x->a as its block buffer.
|
||||
int schfs_btree_lookup(struct schfs_btree *t, struct schfs_ctx *x,
|
||||
const struct schfs_key *k, void *buf, unsigned *len);
|
||||
|
||||
// Same, through a cursor the caller already has positioned somewhere else.
|
||||
int schfs_btree_lookup_cur(struct schfs_cursor *c, const struct schfs_key *k,
|
||||
void *buf, unsigned *len);
|
||||
|
||||
// Greatest key <= k, which is how an extent covering a file offset is found:
|
||||
// extents are keyed by the file offset they start at, so the record governing
|
||||
// byte N is the last one at or before (ino, EXTENT, N). Leaves the cursor
|
||||
// positioned on it. Returns 0, -1 if every key in the tree is greater, -2 on
|
||||
// corruption.
|
||||
int schfs_btree_lookup_le(struct schfs_cursor *c, const struct schfs_key *k);
|
||||
|
||||
// Insert or replace. Copies the path, so t->root changes on success.
|
||||
// Returns 0, -1 if the value is too large to store inline, -2 on I/O or
|
||||
// corruption, -3 if the device is out of blocks.
|
||||
int schfs_btree_insert(struct schfs_btree *t, struct schfs_ctx *x,
|
||||
const struct schfs_key *k, const void *val, unsigned len);
|
||||
|
||||
// Remove a key. Emptied nodes are freed and the root is collapsed when it drops
|
||||
// to a single child, so a tree that is emptied returns to one block.
|
||||
// Returns 0, -1 if absent, -2 on corruption, -3 out of space.
|
||||
int schfs_btree_delete(struct schfs_btree *t, struct schfs_ctx *x,
|
||||
const struct schfs_key *k);
|
||||
|
||||
// Walk records from `start` (inclusive) onward. This is what a directory read
|
||||
// uses: every entry of directory D is a key in [(D,DIRENT,0), (D,DIRENT,~0)],
|
||||
// so listing a directory costs the size of that directory rather than the size
|
||||
// of the filesystem. fn returns non-zero to stop.
|
||||
//
|
||||
// WARNING: the `val` pointer handed to fn points *into the cursor's block
|
||||
// buffer*. Any other tree operation the callback performs may overwrite it —
|
||||
// including one on a different cursor, if the two were given the same buffer.
|
||||
// A callback that looks anything up must copy what it needs first. The key is
|
||||
// already copied out for you; the value is not, because it can be a kilobyte
|
||||
// and most callbacks consume it immediately.
|
||||
int schfs_btree_range(struct schfs_cursor *c, const struct schfs_key *start,
|
||||
int (*fn)(const struct schfs_key *k,
|
||||
const void *val, unsigned len, void *arg),
|
||||
void *arg);
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Host-only facilities
|
||||
//
|
||||
// These recurse and keep a block per level, which is fine in a tool and fatal
|
||||
// in the kernel. Compiling them out of the kernel makes that a link error
|
||||
// rather than a comment nobody reads.
|
||||
// ---------------------------------------------------------------------------
|
||||
#ifdef SCHFS_HOSTED
|
||||
|
||||
// Walk every record in key order, calling fn for each. A non-zero return from
|
||||
// fn stops the walk and is passed back.
|
||||
int schfs_btree_iterate(struct schfs_btree *t,
|
||||
int (*fn)(const struct schfs_key *k,
|
||||
const void *val, unsigned len, void *arg),
|
||||
void *arg);
|
||||
|
||||
// Full structural check: magic, self-pointer, checksum, level agreement, key
|
||||
// ordering within and across nodes, that values lie inside their block, and
|
||||
// that every child's first key is exactly the key its parent files it under.
|
||||
// Returns 0 if the tree is sound, non-zero on the first problem found.
|
||||
int schfs_btree_check(struct schfs_btree *t);
|
||||
|
||||
#endif /* SCHFS_HOSTED */
|
||||
|
||||
#endif /* SCHFS_BTREE_H */
|
||||
623
kernel/fs/schfs/kmount.c
Normal file
623
kernel/fs/schfs/kmount.c
Normal file
|
|
@ -0,0 +1,623 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
// SchFS in the kernel: block device, mount, and the transaction bracket.
|
||||
//
|
||||
// The filesystem code itself (btree.c, space.c, vol.c, obj.c, objw.c) is the
|
||||
// same portable C the host tools compile, so nothing here re-implements the
|
||||
// format. This file is only the three things that cannot be shared: a block
|
||||
// device backed by the buffer cache, the boot-time mount, and the rule about
|
||||
// when a group of changes becomes durable.
|
||||
//
|
||||
// The running root filesystem is still the v1 code in kernel/fs/fs.c, so a
|
||||
// SchFS volume that fails to mount costs nothing — the system boots exactly as
|
||||
// before and says so on the console.
|
||||
//
|
||||
// ---------------------------------------------------------------------------
|
||||
// Transactions
|
||||
//
|
||||
// Every entry point below that changes anything is one transaction group: it
|
||||
// takes the volume lock, does its work, and commits before returning. That
|
||||
// makes an operation durable the moment it returns, and — because a commit is a
|
||||
// single uberblock write that either lands or does not — indivisible against a
|
||||
// crash. There is no journal to replay and nothing to undo, because none of the
|
||||
// work is reachable until that one block is written.
|
||||
//
|
||||
// The cost is one uberblock write per operation. Batching several operations
|
||||
// into a group would amortise it, and the structure here is deliberately shaped
|
||||
// so that becomes a change to where commit is called rather than a redesign;
|
||||
// but a filesystem that loses the last few operations on a crash is a much
|
||||
// worse default than a slow one.
|
||||
|
||||
#include "types.h"
|
||||
#include "defs.h"
|
||||
#include "param.h"
|
||||
#include "spinlock.h"
|
||||
#include "sleeplock.h"
|
||||
#include "fs.h"
|
||||
#include "buf.h"
|
||||
|
||||
#include "schfs_int.h"
|
||||
|
||||
// A SchFS block is 4096 bytes and a buffer-cache block is BSIZE (1024), so each
|
||||
// filesystem block spans SCHFS_BPB consecutive buffers. Keeping the buffer
|
||||
// cache at its own block size avoids disturbing the v1 filesystem, the log and
|
||||
// the IDE driver, all of which assume BSIZE.
|
||||
#define SCHFS_BPB (SCHFS_BLKSIZE / BSIZE)
|
||||
|
||||
struct schfs_kdev {
|
||||
uint dev;
|
||||
};
|
||||
|
||||
static struct schfs_kdev kdev;
|
||||
static struct schfs_vol kvol;
|
||||
static int kmounted;
|
||||
|
||||
// Block buffers, allocated once at mount.
|
||||
//
|
||||
// Nothing in SchFS may put a block on the stack: a cursor needs a whole
|
||||
// SCHFS_BLKSIZE buffer and the kernel stack is one 4096-byte page (KSTACKSIZE),
|
||||
// so a single such local overflows it. kalloc returns exactly one page, which
|
||||
// is exactly one SchFS block, so each of these fits with nothing to spare.
|
||||
//
|
||||
// kcurblk and kdatablk serve the read paths; kctx* are the three the volume
|
||||
// keeps for mutations. They are separate pages because a write path runs a
|
||||
// cursor of its own inside the volume's buffers while the caller's cursor is
|
||||
// still positioned in kcurblk.
|
||||
static char *kcurblk;
|
||||
static char *kdatablk;
|
||||
static char *kctxa, *kctxb, *kctxc;
|
||||
|
||||
// Those buffers are shared by every caller, so every entry point below holds
|
||||
// this lock for the whole operation. A sleeplock rather than a spinlock because
|
||||
// bread() sleeps waiting on the disk.
|
||||
//
|
||||
// It also serialises transactions, which is not incidental: a group being built
|
||||
// rewrites its own blocks in place, so two operations interleaving inside one
|
||||
// group would each see the other's half-finished tree.
|
||||
static struct sleeplock schfs_lock;
|
||||
|
||||
// Seconds, for inode timestamps. The kernel has no wall clock wired up, so this
|
||||
// matches what fs.c does for the v1 filesystem rather than inventing a second,
|
||||
// differently-wrong answer.
|
||||
static uint64_t
|
||||
know(void)
|
||||
{
|
||||
return (uint64_t)uptime();
|
||||
}
|
||||
|
||||
// Start and finish one transaction. `changed` says whether anything was
|
||||
// written, so a read does not pay for an uberblock write.
|
||||
static void
|
||||
ktx_begin(void)
|
||||
{
|
||||
acquiresleep(&schfs_lock);
|
||||
}
|
||||
|
||||
static int
|
||||
ktx_end(int changed, int r)
|
||||
{
|
||||
if (changed && r >= 0) {
|
||||
int c = schfs_vol_commit(&kvol);
|
||||
if (c != 0)
|
||||
r = c;
|
||||
}
|
||||
releasesleep(&schfs_lock);
|
||||
return r;
|
||||
}
|
||||
|
||||
static int
|
||||
kd_read(void *ctx, uint64_t blk, void *out)
|
||||
{
|
||||
struct schfs_kdev *d = ctx;
|
||||
|
||||
if (blk == 0)
|
||||
return -1;
|
||||
// The buffer cache addresses blocks with a uint, which caps the volume long
|
||||
// before the on-disk format does. Refusing out here beats truncating and
|
||||
// reading some unrelated sector, which would surface as corruption.
|
||||
if (blk > 0xffffffffULL / SCHFS_BPB)
|
||||
return -1;
|
||||
|
||||
for (int i = 0; i < SCHFS_BPB; i++) {
|
||||
struct buf *b = bread(d->dev, (uint)(blk * SCHFS_BPB + i));
|
||||
memmove((char *)out + i * BSIZE, b->data, BSIZE);
|
||||
brelse(b);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
kd_write(void *ctx, uint64_t blk, const void *in)
|
||||
{
|
||||
struct schfs_kdev *d = ctx;
|
||||
|
||||
if (blk == 0)
|
||||
return -1;
|
||||
if (blk > 0xffffffffULL / SCHFS_BPB)
|
||||
return -1;
|
||||
|
||||
// Straight through the buffer cache, with no log. The xv6 log exists to make
|
||||
// multi-block updates atomic; copy-on-write makes them atomic by never
|
||||
// modifying a block the committed tree can reach, so logging every write
|
||||
// would double the I/O to re-solve a problem the format has already solved.
|
||||
//
|
||||
// bread before the overwrite because the cache has no "give me this block
|
||||
// without reading it" entry point. It costs a read the first time a block is
|
||||
// touched and nothing afterwards.
|
||||
for (int i = 0; i < SCHFS_BPB; i++) {
|
||||
struct buf *b = bread(d->dev, (uint)(blk * SCHFS_BPB + i));
|
||||
memmove(b->data, (const char *)in + i * BSIZE, BSIZE);
|
||||
bwrite(b);
|
||||
brelse(b);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Allocation goes through the volume's space manager. These take no ctx of
|
||||
// their own because a schfs_bdev has one ctx shared by every hook and the block
|
||||
// I/O above needs it for the device number.
|
||||
static uint64_t
|
||||
kd_alloc(void *ctx)
|
||||
{
|
||||
(void)ctx;
|
||||
return schfs_vol_alloc(&kvol);
|
||||
}
|
||||
|
||||
static uint64_t
|
||||
kd_alloc_run(void *ctx, uint64_t n)
|
||||
{
|
||||
(void)ctx;
|
||||
return schfs_vol_alloc_run(&kvol, n);
|
||||
}
|
||||
|
||||
static void
|
||||
kd_free(void *ctx, uint64_t b)
|
||||
{
|
||||
(void)ctx;
|
||||
schfs_vol_free(&kvol, b);
|
||||
}
|
||||
|
||||
// Pages for the allocation bitmap: one per 32768 blocks of volume, taken once
|
||||
// at mount and never given back.
|
||||
static void *
|
||||
kd_palloc(void)
|
||||
{
|
||||
return kalloc();
|
||||
}
|
||||
|
||||
// Try to mount a SchFS volume from `dev`. Never panics: a disk that is not
|
||||
// SchFS, or is an unreadable SchFS, simply leaves the volume unmounted.
|
||||
void
|
||||
schfs_kmount(uint dev)
|
||||
{
|
||||
if (kmounted)
|
||||
return;
|
||||
|
||||
initsleeplock(&schfs_lock, "schfs");
|
||||
|
||||
if (kcurblk == 0) kcurblk = kalloc();
|
||||
if (kdatablk == 0) kdatablk = kalloc();
|
||||
if (kctxa == 0) kctxa = kalloc();
|
||||
if (kctxb == 0) kctxb = kalloc();
|
||||
if (kctxc == 0) kctxc = kalloc();
|
||||
if (kcurblk == 0 || kdatablk == 0 ||
|
||||
kctxa == 0 || kctxb == 0 || kctxc == 0) {
|
||||
cprintf("schfs: no memory for block buffers\n");
|
||||
return;
|
||||
}
|
||||
|
||||
kdev.dev = dev;
|
||||
|
||||
struct schfs_bdev bd;
|
||||
bd.ctx = &kdev;
|
||||
bd.bread = kd_read;
|
||||
bd.bwrite = kd_write;
|
||||
bd.balloc = kd_alloc;
|
||||
bd.bfree = kd_free;
|
||||
bd.balloc_run = kd_alloc_run;
|
||||
|
||||
struct schfs_ctx cx;
|
||||
cx.a = kctxa;
|
||||
cx.b = kctxb;
|
||||
cx.c = kctxc;
|
||||
|
||||
// The uberblock scan doubles as the probe: no valid uberblock means this is
|
||||
// simply not a SchFS volume, which is not an error worth reporting loudly.
|
||||
// Opening with a page allocator also builds the allocation bitmap, by walking
|
||||
// the tree — the only mount-time cost SchFS has, and what buys it a mount
|
||||
// that cannot disagree with the filesystem it just adopted.
|
||||
int r = schfs_vol_open(&kvol, &bd, &cx, kd_palloc);
|
||||
if (r == -1)
|
||||
return;
|
||||
if (r == -4) {
|
||||
cprintf("schfs: device %d is larger than %d GB and cannot be mapped\n",
|
||||
dev, (int)(SCHFS_MAX_BLOCKS * SCHFS_BLKSIZE / (1024 * 1024 * 1024)));
|
||||
return;
|
||||
}
|
||||
if (r == -5) {
|
||||
cprintf("schfs: no memory for the allocation map of device %d\n", dev);
|
||||
return;
|
||||
}
|
||||
if (r != 0) {
|
||||
cprintf("schfs: device %d has a SchFS uberblock but an unreadable tree\n",
|
||||
dev);
|
||||
return;
|
||||
}
|
||||
|
||||
kmounted = 1;
|
||||
cprintf("schfs: mounted '%s' on device %d, %d MB, generation %d\n",
|
||||
kvol.label, dev,
|
||||
(int)(kvol.total_blocks * SCHFS_BLKSIZE / (1024 * 1024)),
|
||||
(int)kvol.generation);
|
||||
}
|
||||
|
||||
int
|
||||
schfs_kmounted(void)
|
||||
{
|
||||
return kmounted;
|
||||
}
|
||||
|
||||
// Resolve a path on the mounted volume. Returns the object id, or 0.
|
||||
uint64_t
|
||||
schfs_klookup(const char *path)
|
||||
{
|
||||
if (!kmounted)
|
||||
return 0;
|
||||
acquiresleep(&schfs_lock);
|
||||
struct schfs_cursor c;
|
||||
schfs_cursor_init(&c, &kvol.tree, kcurblk);
|
||||
uint64_t r = schfs_path_lookup(&kvol, &c, path);
|
||||
releasesleep(&schfs_lock);
|
||||
return r;
|
||||
}
|
||||
|
||||
int
|
||||
schfs_kstat(uint64_t ino, struct schfs_inode *out)
|
||||
{
|
||||
if (!kmounted)
|
||||
return -1;
|
||||
acquiresleep(&schfs_lock);
|
||||
struct schfs_cursor c;
|
||||
schfs_cursor_init(&c, &kvol.tree, kcurblk);
|
||||
int r = schfs_inode_get(&kvol, &c, ino, out);
|
||||
releasesleep(&schfs_lock);
|
||||
return r;
|
||||
}
|
||||
|
||||
long
|
||||
schfs_kread(uint64_t ino, uint64_t off, void *buf, unsigned n)
|
||||
{
|
||||
if (!kmounted)
|
||||
return -1;
|
||||
acquiresleep(&schfs_lock);
|
||||
struct schfs_cursor c;
|
||||
schfs_cursor_init(&c, &kvol.tree, kcurblk);
|
||||
long r = schfs_file_read(&kvol, &c, ino, off, buf, n, kdatablk);
|
||||
releasesleep(&schfs_lock);
|
||||
return r;
|
||||
}
|
||||
|
||||
int
|
||||
schfs_kreaddir(uint64_t dir,
|
||||
int (*fn)(uint64_t, uint16_t, const char *, unsigned, void *),
|
||||
void *arg)
|
||||
{
|
||||
if (!kmounted)
|
||||
return -1;
|
||||
// NOTE: fn must not call back into schfs_k* -- the lock is not recursive and
|
||||
// the cursor buffer is shared. Callers that need per-entry detail collect the
|
||||
// entries first, then look them up after the walk.
|
||||
acquiresleep(&schfs_lock);
|
||||
struct schfs_cursor c;
|
||||
schfs_cursor_init(&c, &kvol.tree, kcurblk);
|
||||
int r = schfs_dir_iterate(&kvol, &c, dir, fn, arg);
|
||||
releasesleep(&schfs_lock);
|
||||
return r;
|
||||
}
|
||||
|
||||
struct schfs_vol *
|
||||
schfs_kvol(void)
|
||||
{
|
||||
return kmounted ? &kvol : 0;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Write side */
|
||||
/* */
|
||||
/* Each of these is one transaction: everything it does is invisible */
|
||||
/* until it commits, and a crash part way through leaves the volume at */
|
||||
/* the state before the call. That is the entire failure story — there */
|
||||
/* is no partial state to detect and nothing to roll back. */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
int
|
||||
schfs_kcreate(uint64_t dir, const char *name, unsigned namelen, uint16_t type,
|
||||
uint32_t mode, uint16_t major, uint16_t minor, uint64_t *out)
|
||||
{
|
||||
if (!kmounted || kvol.readonly)
|
||||
return -1;
|
||||
ktx_begin();
|
||||
int r = schfs_create(&kvol, dir, name, namelen, type, mode, major, minor,
|
||||
know(), out);
|
||||
return ktx_end(1, r);
|
||||
}
|
||||
|
||||
long
|
||||
schfs_kwrite(uint64_t ino, uint64_t off, const void *buf, unsigned n)
|
||||
{
|
||||
if (!kmounted || kvol.readonly)
|
||||
return -1;
|
||||
ktx_begin();
|
||||
long r = schfs_file_write(&kvol, ino, off, buf, n, kdatablk);
|
||||
// ktx_end speaks in error codes, and a write speaks in byte counts. Passing
|
||||
// the count straight through told it the transaction had failed with status
|
||||
// 17, which it duly reported -- and a short write is a panic one layer up.
|
||||
int e = ktx_end(1, r < 0 ? (int)r : 0);
|
||||
return e == 0 ? r : (long)e;
|
||||
}
|
||||
|
||||
int
|
||||
schfs_ktruncate(uint64_t ino, uint64_t size)
|
||||
{
|
||||
if (!kmounted || kvol.readonly)
|
||||
return -1;
|
||||
ktx_begin();
|
||||
int r = schfs_file_truncate(&kvol, ino, size, kdatablk);
|
||||
return ktx_end(1, r);
|
||||
}
|
||||
|
||||
int
|
||||
schfs_kunlink(uint64_t dir, const char *name, unsigned namelen)
|
||||
{
|
||||
if (!kmounted || kvol.readonly)
|
||||
return -1;
|
||||
ktx_begin();
|
||||
int r = schfs_unlink(&kvol, dir, name, namelen);
|
||||
return ktx_end(1, r);
|
||||
}
|
||||
|
||||
int
|
||||
schfs_klink(uint64_t dir, const char *name, unsigned namelen, uint64_t ino)
|
||||
{
|
||||
if (!kmounted || kvol.readonly)
|
||||
return -1;
|
||||
ktx_begin();
|
||||
int r = schfs_link(&kvol, dir, name, namelen, ino);
|
||||
return ktx_end(1, r);
|
||||
}
|
||||
|
||||
int
|
||||
schfs_krename(uint64_t olddir, const char *oldname, unsigned oldlen,
|
||||
uint64_t newdir, const char *newname, unsigned newlen)
|
||||
{
|
||||
if (!kmounted || kvol.readonly)
|
||||
return -1;
|
||||
ktx_begin();
|
||||
int r = schfs_rename(&kvol, olddir, oldname, oldlen,
|
||||
newdir, newname, newlen);
|
||||
return ktx_end(1, r);
|
||||
}
|
||||
|
||||
// Write back an inode record the caller has modified — the one way to change
|
||||
// mode, ownership or timestamps. The caller reads it with schfs_kstat, edits
|
||||
// the struct, and hands it back.
|
||||
int
|
||||
schfs_ksetattr(uint64_t ino, const struct schfs_inode *in)
|
||||
{
|
||||
if (!kmounted || kvol.readonly)
|
||||
return -1;
|
||||
ktx_begin();
|
||||
int r = schfs_inode_put(&kvol, ino, in);
|
||||
return ktx_end(1, r);
|
||||
}
|
||||
|
||||
// Flush without changing anything. Nothing needs this yet — every operation
|
||||
// above commits already — but sync(2) has to mean something.
|
||||
int
|
||||
schfs_ksync(void)
|
||||
{
|
||||
if (!kmounted || kvol.readonly)
|
||||
return 0;
|
||||
ktx_begin();
|
||||
return ktx_end(1, 0);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Pieces the VFS needs */
|
||||
/* */
|
||||
/* The VFS keeps its own link count and decides when an object dies, */
|
||||
/* so it needs the directory entry and the object as separate */
|
||||
/* operations rather than the combined create/unlink above. Two layers */
|
||||
/* counting links independently is exactly how a filesystem loses a */
|
||||
/* file that something still has open. */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
// Resolve one name inside a directory. 0 if there is no such entry.
|
||||
uint64_t
|
||||
schfs_klookup_at(uint64_t dir, const char *name, unsigned namelen)
|
||||
{
|
||||
if (!kmounted)
|
||||
return 0;
|
||||
acquiresleep(&schfs_lock);
|
||||
struct schfs_cursor c;
|
||||
schfs_cursor_init(&c, &kvol.tree, kcurblk);
|
||||
uint64_t ino = 0;
|
||||
if (schfs_dir_lookup(&kvol, &c, dir, name, namelen, &ino, 0) != 0)
|
||||
ino = 0;
|
||||
releasesleep(&schfs_lock);
|
||||
return ino;
|
||||
}
|
||||
|
||||
// 1 if the directory holds no entries, 0 if it does, -1 on error.
|
||||
int
|
||||
schfs_kdirempty(uint64_t dir)
|
||||
{
|
||||
if (!kmounted)
|
||||
return -1;
|
||||
acquiresleep(&schfs_lock);
|
||||
int r = schfs_dir_empty(&kvol, dir);
|
||||
releasesleep(&schfs_lock);
|
||||
return r == -2 ? -1 : r;
|
||||
}
|
||||
|
||||
// An object with no name and no links yet. Nothing can reach it until the
|
||||
// caller links it, and nothing reaches the disk until the transaction commits,
|
||||
// so a caller that gives up in between leaves nothing behind.
|
||||
int
|
||||
schfs_kalloc_object(uint16_t type, uint32_t mode, uint64_t *out)
|
||||
{
|
||||
if (!kmounted || kvol.readonly)
|
||||
return -1;
|
||||
ktx_begin();
|
||||
|
||||
uint64_t ino = schfs_alloc_objectid(&kvol);
|
||||
struct schfs_inode in;
|
||||
memset(&in, 0, sizeof(in));
|
||||
in.type = type;
|
||||
in.mode = mode;
|
||||
in.nlink = 0;
|
||||
in.atime = in.mtime = in.ctime = know();
|
||||
in.gen = kvol.tree.generation;
|
||||
|
||||
int r = schfs_inode_put(&kvol, ino, &in);
|
||||
if (r == 0 && out)
|
||||
*out = ino;
|
||||
return ktx_end(1, r);
|
||||
}
|
||||
|
||||
int
|
||||
schfs_kdiradd(uint64_t dir, const char *name, unsigned namelen, uint64_t ino,
|
||||
uint16_t type)
|
||||
{
|
||||
if (!kmounted || kvol.readonly)
|
||||
return -1;
|
||||
ktx_begin();
|
||||
int r = schfs_dir_add(&kvol, dir, name, namelen, ino, type);
|
||||
return ktx_end(1, r);
|
||||
}
|
||||
|
||||
// Remove one directory entry, leaving the object alone. The VFS decrements the
|
||||
// link count itself and calls schfs_kfree when it reaches zero.
|
||||
int
|
||||
schfs_kdirdel(uint64_t dir, const char *name, unsigned namelen)
|
||||
{
|
||||
if (!kmounted || kvol.readonly)
|
||||
return -1;
|
||||
ktx_begin();
|
||||
int r = schfs_dir_del(&kvol, dir, name, namelen);
|
||||
return ktx_end(1, r);
|
||||
}
|
||||
|
||||
// Release an object's data, checksums and inode record. Called from iput once
|
||||
// the last name is gone and nothing has it open.
|
||||
int
|
||||
schfs_kfree(uint64_t ino)
|
||||
{
|
||||
if (!kmounted || kvol.readonly)
|
||||
return -1;
|
||||
ktx_begin();
|
||||
int r = schfs_object_free(&kvol, ino);
|
||||
return ktx_end(1, r);
|
||||
}
|
||||
|
||||
uint64_t
|
||||
schfs_kroot(void)
|
||||
{
|
||||
return kmounted ? kvol.root_objectid : 0;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* /proc/schfs */
|
||||
/* */
|
||||
/* Phase 2 has no VFS integration -- SchFS files are not reachable */
|
||||
/* through open(2) yet -- so this is how the mount is observable from */
|
||||
/* userland at all. It reports the volume and lists the root directory, */
|
||||
/* which is enough to confirm from inside a running Schunix that the */
|
||||
/* kernel really did parse an image mkschfs wrote. */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
static void
|
||||
kp_puts(char *buf, int cap, int *len, const char *s)
|
||||
{
|
||||
while (*s && *len < cap - 1)
|
||||
buf[(*len)++] = *s++;
|
||||
}
|
||||
|
||||
static void
|
||||
kp_putu(char *buf, int cap, int *len, uint64 v)
|
||||
{
|
||||
char tmp[24];
|
||||
int i = 0;
|
||||
if (v == 0) { if (*len < cap - 1) buf[(*len)++] = '0'; return; }
|
||||
while (v && i < (int)sizeof(tmp)) { tmp[i++] = '0' + (v % 10); v /= 10; }
|
||||
while (i--) { if (*len < cap - 1) buf[(*len)++] = tmp[i]; }
|
||||
}
|
||||
|
||||
struct kp_ctx {
|
||||
char *buf;
|
||||
int cap;
|
||||
int *len;
|
||||
int count;
|
||||
};
|
||||
|
||||
static int
|
||||
kp_entry(uint64_t ino, uint16_t type, const char *name, unsigned namelen,
|
||||
void *arg)
|
||||
{
|
||||
struct kp_ctx *c = arg;
|
||||
c->count++;
|
||||
// Only the first handful, and no per-entry stat: schfs_kreaddir holds the
|
||||
// lock across the walk, so calling schfs_kstat from here would deadlock.
|
||||
if (c->count > 16)
|
||||
return 0;
|
||||
kp_puts(c->buf, c->cap, c->len, " ");
|
||||
kp_puts(c->buf, c->cap, c->len,
|
||||
type == SCHFS_T_DIR ? "dir " :
|
||||
type == SCHFS_T_SYMLINK ? "symlink " :
|
||||
type == SCHFS_T_DEV ? "dev " : "file ");
|
||||
for (unsigned i = 0; i < namelen && *c->len < c->cap - 1; i++)
|
||||
c->buf[(*c->len)++] = name[i];
|
||||
kp_puts(c->buf, c->cap, c->len, " #");
|
||||
kp_putu(c->buf, c->cap, c->len, ino);
|
||||
kp_puts(c->buf, c->cap, c->len, "\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
schfs_kinfo(char *buf, int cap)
|
||||
{
|
||||
int len = 0;
|
||||
|
||||
if (!kmounted) {
|
||||
kp_puts(buf, cap, &len, "no SchFS volume mounted\n");
|
||||
return len;
|
||||
}
|
||||
|
||||
kp_puts(buf, cap, &len, "label: ");
|
||||
kp_puts(buf, cap, &len, kvol.label);
|
||||
kp_puts(buf, cap, &len, "\nblocksize: ");
|
||||
kp_putu(buf, cap, &len, SCHFS_BLKSIZE);
|
||||
kp_puts(buf, cap, &len, "\ntotal_blocks: ");
|
||||
kp_putu(buf, cap, &len, kvol.total_blocks);
|
||||
kp_puts(buf, cap, &len, "\nfree_blocks: ");
|
||||
kp_putu(buf, cap, &len, kvol.free_blocks);
|
||||
kp_puts(buf, cap, &len, "\ngeneration: ");
|
||||
kp_putu(buf, cap, &len, kvol.generation);
|
||||
kp_puts(buf, cap, &len, "\ntree_root: ");
|
||||
kp_putu(buf, cap, &len, kvol.tree.root);
|
||||
kp_puts(buf, cap, &len, "\nroot_objectid: ");
|
||||
kp_putu(buf, cap, &len, kvol.root_objectid);
|
||||
kp_puts(buf, cap, &len, "\n\nroot directory:\n");
|
||||
|
||||
struct kp_ctx c = { buf, cap, &len, 0 };
|
||||
if (schfs_kreaddir(kvol.root_objectid, kp_entry, &c) != 0) {
|
||||
kp_puts(buf, cap, &len, " (directory read failed)\n");
|
||||
return len;
|
||||
}
|
||||
if (c.count > 16) {
|
||||
kp_puts(buf, cap, &len, " ... ");
|
||||
kp_putu(buf, cap, &len, (uint64)(c.count - 16));
|
||||
kp_puts(buf, cap, &len, " more\n");
|
||||
}
|
||||
return len;
|
||||
}
|
||||
441
kernel/fs/schfs/obj.c
Normal file
441
kernel/fs/schfs/obj.c
Normal file
|
|
@ -0,0 +1,441 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
// SchFS object layer, read side: inodes, directories and file data.
|
||||
//
|
||||
// Everything here is a record in the one B-tree, which is the point of the
|
||||
// btrfs-style design — there is no inode table, no directory block format and
|
||||
// no indirect-block scheme to get wrong, only key types:
|
||||
//
|
||||
// (ino, INODE, 0) struct schfs_inode
|
||||
// (dir, DIRENT, hash) packed list of schfs_dirent_rec + name
|
||||
// (ino, EXTENT, fileblk) struct schfs_extent
|
||||
// (CSUM, CSUM, diskblk) uint32 CRC32c of that data block
|
||||
//
|
||||
// Kernel code reaches all of this through a caller-supplied cursor, so no
|
||||
// function here puts a 4096-byte block on the stack.
|
||||
|
||||
#include "schfs_int.h"
|
||||
|
||||
#ifdef SCHFS_HOSTED
|
||||
#include <string.h>
|
||||
#else
|
||||
#include "types.h"
|
||||
#include "defs.h"
|
||||
#endif
|
||||
|
||||
// Point a cursor at the volume's current tree, keeping the caller's block
|
||||
// buffer. Called before every operation rather than once at open, because the
|
||||
// tree root moves on every commit under copy-on-write: a cursor that cached the
|
||||
// old root would keep reading a stale — though perfectly valid — filesystem.
|
||||
static void
|
||||
cur_bind(struct schfs_cursor *c, struct schfs_vol *v)
|
||||
{
|
||||
c->t = &v->tree;
|
||||
c->depth = 0;
|
||||
c->valid = 0;
|
||||
}
|
||||
|
||||
void
|
||||
schfs_mkkey(struct schfs_key *k, uint64_t objectid, uint8_t type,
|
||||
uint64_t offset)
|
||||
{
|
||||
memset(k, 0, sizeof(*k));
|
||||
k->objectid = objectid;
|
||||
k->type = type;
|
||||
k->offset = offset;
|
||||
}
|
||||
|
||||
// FNV-1a, 64-bit. Directory lookup is a tree descent on this value, so it wants
|
||||
// to spread short, highly similar names ("ls", "ln", "cp") across the keyspace.
|
||||
// FNV does that, is a dozen lines, and needs no table — worth more here than
|
||||
// the marginally better distribution of something larger.
|
||||
//
|
||||
// Collisions are handled by the packed dirent list rather than assumed away;
|
||||
// see struct schfs_dirent_rec.
|
||||
// Test-only: narrows the hash so collisions become common.
|
||||
//
|
||||
// With a full 64-bit hash, two names in one directory colliding is so unlikely
|
||||
// that the packed-list code which handles it would never run — and untested
|
||||
// code that only executes on rare inputs is exactly the code that loses a file
|
||||
// years later. Setting this to a small mask makes every directory a pile of
|
||||
// collisions, so the round-trip test exercises the path properly. Always ~0
|
||||
// in the kernel and in real images.
|
||||
uint64_t schfs_hash_mask = ~0ULL;
|
||||
|
||||
uint64_t
|
||||
schfs_namehash(const char *name, unsigned len)
|
||||
{
|
||||
uint64_t h = 1469598103934665603ULL; /* FNV offset basis */
|
||||
for (unsigned i = 0; i < len; i++) {
|
||||
h ^= (uint8_t)name[i];
|
||||
h *= 1099511628211ULL; /* FNV prime */
|
||||
}
|
||||
// Key ordering is by this value, so a directory's entries come back in an
|
||||
// order that has nothing to do with their names. That is fine for readdir
|
||||
// (POSIX promises no order) and is what makes lookup one descent.
|
||||
return h & schfs_hash_mask;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Inodes */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
int
|
||||
schfs_inode_get(struct schfs_vol *v, struct schfs_cursor *c,
|
||||
uint64_t ino, struct schfs_inode *out)
|
||||
{
|
||||
struct schfs_key k;
|
||||
schfs_mkkey(&k, ino, SCHFS_KEY_INODE, 0);
|
||||
|
||||
cur_bind(c, v);
|
||||
unsigned len = sizeof(*out);
|
||||
int r = schfs_btree_lookup_cur(c, &k, out, &len);
|
||||
if (r != 0)
|
||||
return r;
|
||||
// A short record means an image written by an older or broken tool. Reject
|
||||
// it rather than acting on a half-filled struct.
|
||||
if (len != sizeof(*out))
|
||||
return -2;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Directories */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
// Walk a packed dirent list, calling fn per entry. Stops early if fn returns
|
||||
// non-zero. Returns fn's value, or -2 if the list is malformed.
|
||||
static int
|
||||
dirent_walk(const void *val, unsigned len,
|
||||
int (*fn)(const struct schfs_dirent_rec *, const char *, void *),
|
||||
void *arg)
|
||||
{
|
||||
unsigned off = 0;
|
||||
while (off < len) {
|
||||
if (len - off < sizeof(struct schfs_dirent_rec))
|
||||
return -2;
|
||||
const struct schfs_dirent_rec *d =
|
||||
(const struct schfs_dirent_rec *)((const char *)val + off);
|
||||
unsigned step = SCHFS_DIRENT_LEN(d->namelen);
|
||||
// Bounds-check before touching the name: a corrupt namelen would
|
||||
// otherwise read past the end of the block.
|
||||
if (d->namelen == 0 || d->namelen > SCHFS_NAME_MAX || off + step > len)
|
||||
return -2;
|
||||
int r = fn(d, (const char *)d + sizeof(*d), arg);
|
||||
if (r)
|
||||
return r;
|
||||
off += step;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct find_ctx {
|
||||
const char *name;
|
||||
unsigned namelen;
|
||||
uint64_t ino;
|
||||
uint16_t type;
|
||||
};
|
||||
|
||||
static int
|
||||
find_one(const struct schfs_dirent_rec *d, const char *nm, void *arg)
|
||||
{
|
||||
struct find_ctx *f = (struct find_ctx *)arg;
|
||||
if (d->namelen != f->namelen)
|
||||
return 0;
|
||||
if (memcmp(nm, f->name, f->namelen) != 0)
|
||||
return 0;
|
||||
f->ino = d->objectid;
|
||||
f->type = d->type;
|
||||
return 1; /* found; stop */
|
||||
}
|
||||
|
||||
int
|
||||
schfs_dir_lookup(struct schfs_vol *v, struct schfs_cursor *c,
|
||||
uint64_t dir, const char *name, unsigned namelen,
|
||||
uint64_t *out_ino, uint16_t *out_type)
|
||||
{
|
||||
if (namelen == 0 || namelen > SCHFS_NAME_MAX)
|
||||
return -1;
|
||||
|
||||
struct schfs_key k;
|
||||
schfs_mkkey(&k, dir, SCHFS_KEY_DIRENT, schfs_namehash(name, namelen));
|
||||
|
||||
cur_bind(c, v);
|
||||
int r = schfs_cursor_seek(c, &k);
|
||||
if (r == -2)
|
||||
return -2;
|
||||
if (r != 0)
|
||||
return -1;
|
||||
|
||||
const struct schfs_key *got = schfs_cursor_key(c);
|
||||
if (!got || schfs_key_cmp(got, &k) != 0)
|
||||
return -1;
|
||||
|
||||
unsigned len;
|
||||
const void *val = schfs_cursor_val(c, &len);
|
||||
if (!val)
|
||||
return -2;
|
||||
|
||||
struct find_ctx f;
|
||||
f.name = name; f.namelen = namelen; f.ino = 0; f.type = 0;
|
||||
r = dirent_walk(val, len, find_one, &f);
|
||||
if (r == -2)
|
||||
return -2;
|
||||
if (r != 1)
|
||||
return -1; /* hash matched, name did not */
|
||||
|
||||
if (out_ino) *out_ino = f.ino;
|
||||
if (out_type) *out_type = f.type;
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct iter_ctx {
|
||||
int (*fn)(uint64_t, uint16_t, const char *, unsigned, void *);
|
||||
void *arg;
|
||||
struct schfs_cursor *cur;
|
||||
uint64_t dir;
|
||||
int stop;
|
||||
};
|
||||
|
||||
// Hand each entry of one packed dirent list to the caller.
|
||||
//
|
||||
// The list lives in the cursor's block buffer, and almost every readdir caller
|
||||
// immediately looks each entry up — to stat it, to recurse into it — through a
|
||||
// cursor that may well share that buffer. Walking the list in place meant the
|
||||
// name and every record after it turned to garbage the moment the callback
|
||||
// touched the filesystem, which is how an extracted tree once ended up with
|
||||
// directories reparented under their own children.
|
||||
//
|
||||
// The obvious fix, copying the whole item aside, costs SCHFS_MAX_INLINE (just
|
||||
// over 1 KB) of stack — a quarter of the kernel's entire 4096-byte stack, on a
|
||||
// path that also runs under procread's 2 KB buffer. So only the current entry
|
||||
// is copied, and the item is re-fetched from the cursor each time round:
|
||||
// schfs_cursor_val re-reads the block, which restores whatever the callback
|
||||
// disturbed.
|
||||
static int
|
||||
iter_item(const struct schfs_key *k, const void *val, unsigned len, void *arg)
|
||||
{
|
||||
struct iter_ctx *ic = (struct iter_ctx *)arg;
|
||||
(void)val;
|
||||
|
||||
// The range scan runs until something stops it, so stopping at the first
|
||||
// key that is no longer this directory's is what bounds the walk.
|
||||
if (k->objectid != ic->dir || k->type != SCHFS_KEY_DIRENT)
|
||||
return 1;
|
||||
if (len > SCHFS_MAX_INLINE)
|
||||
return 1;
|
||||
|
||||
unsigned off = 0;
|
||||
while (off < len) {
|
||||
unsigned now;
|
||||
const char *v = (const char *)schfs_cursor_val(ic->cur, &now);
|
||||
if (!v || now != len) {
|
||||
ic->stop = -2;
|
||||
return 1;
|
||||
}
|
||||
if (len - off < sizeof(struct schfs_dirent_rec)) {
|
||||
ic->stop = -2;
|
||||
return 1;
|
||||
}
|
||||
|
||||
const struct schfs_dirent_rec *d =
|
||||
(const struct schfs_dirent_rec *)(v + off);
|
||||
unsigned namelen = d->namelen;
|
||||
unsigned step = SCHFS_DIRENT_LEN(namelen);
|
||||
// Bounds-check before touching the name: a corrupt namelen would
|
||||
// otherwise read past the end of the block.
|
||||
if (namelen == 0 || namelen > SCHFS_NAME_MAX || off + step > len) {
|
||||
ic->stop = -2;
|
||||
return 1;
|
||||
}
|
||||
|
||||
char name[SCHFS_NAME_MAX];
|
||||
uint64_t oid = d->objectid;
|
||||
uint16_t type = d->type;
|
||||
memmove(name, (const char *)d + sizeof(*d), namelen);
|
||||
|
||||
int r = ic->fn(oid, type, name, namelen, ic->arg);
|
||||
if (r) {
|
||||
ic->stop = r;
|
||||
return 1;
|
||||
}
|
||||
off += step;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
schfs_dir_iterate(struct schfs_vol *v, struct schfs_cursor *c, uint64_t dir,
|
||||
int (*fn)(uint64_t, uint16_t, const char *, unsigned, void *),
|
||||
void *arg)
|
||||
{
|
||||
struct schfs_key start;
|
||||
schfs_mkkey(&start, dir, SCHFS_KEY_DIRENT, 0);
|
||||
|
||||
struct iter_ctx ic;
|
||||
ic.fn = fn; ic.arg = arg; ic.cur = c; ic.dir = dir; ic.stop = 0;
|
||||
|
||||
cur_bind(c, v);
|
||||
int r = schfs_btree_range(c, &start, iter_item, &ic);
|
||||
if (r == -2)
|
||||
return -2;
|
||||
return ic.stop == 1 ? 0 : ic.stop;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* File data */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
// Map a file block to a disk block. Returns 1 if mapped, 0 for a hole, -2 on
|
||||
// corruption. Extents are keyed by the file block they start at, so the record
|
||||
// governing block B is the last one at or before (ino, EXTENT, B).
|
||||
static int
|
||||
extent_map(struct schfs_vol *v, struct schfs_cursor *c, uint64_t ino,
|
||||
uint64_t fblk, uint64_t *out)
|
||||
{
|
||||
struct schfs_key k;
|
||||
schfs_mkkey(&k, ino, SCHFS_KEY_EXTENT, fblk);
|
||||
|
||||
cur_bind(c, v);
|
||||
int r = schfs_btree_lookup_le(c, &k);
|
||||
if (r == -2)
|
||||
return -2;
|
||||
if (r != 0)
|
||||
return 0;
|
||||
|
||||
const struct schfs_key *got = schfs_cursor_key(c);
|
||||
if (!got)
|
||||
return 0;
|
||||
// lookup_le can land on some other object's records entirely; a sparse file
|
||||
// whose first extent starts past this block looks the same way.
|
||||
if (got->objectid != ino || got->type != SCHFS_KEY_EXTENT)
|
||||
return 0;
|
||||
|
||||
unsigned len;
|
||||
const struct schfs_extent *e =
|
||||
(const struct schfs_extent *)schfs_cursor_val(c, &len);
|
||||
if (!e || len != sizeof(*e))
|
||||
return -2;
|
||||
|
||||
if (fblk >= got->offset + e->nblocks)
|
||||
return 0; /* hole past the end of this extent */
|
||||
*out = e->disk_block + (fblk - got->offset);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Verify a data block against its checksum record. Tree nodes carry their
|
||||
// checksum in the parent pointer; data blocks have no parent, so the check must
|
||||
// be an explicit lookup. Returns 0 if good, -2 if it fails, and 0 as well when
|
||||
// no record exists — an image may legitimately predate checksums for a block,
|
||||
// and refusing to read data over a *missing* checksum would turn an upgrade
|
||||
// into a total loss.
|
||||
static int
|
||||
csum_check(struct schfs_vol *v, struct schfs_cursor *c, uint64_t dblk,
|
||||
const void *data)
|
||||
{
|
||||
struct schfs_key k;
|
||||
schfs_mkkey(&k, SCHFS_CSUM_OBJECTID, SCHFS_KEY_CSUM, dblk);
|
||||
|
||||
uint32_t want;
|
||||
unsigned len = sizeof(want);
|
||||
cur_bind(c, v);
|
||||
int r = schfs_btree_lookup_cur(c, &k, &want, &len);
|
||||
if (r == -1)
|
||||
return 0;
|
||||
if (r != 0 || len != sizeof(want))
|
||||
return -2;
|
||||
|
||||
if (schfs_crc32c(0, data, SCHFS_BLKSIZE) != want)
|
||||
return -2;
|
||||
return 0;
|
||||
}
|
||||
|
||||
long
|
||||
schfs_file_read(struct schfs_vol *v, struct schfs_cursor *c, uint64_t ino,
|
||||
uint64_t off, void *buf, unsigned n, void *dblk)
|
||||
{
|
||||
struct schfs_inode in;
|
||||
int r = schfs_inode_get(v, c, ino, &in);
|
||||
if (r != 0)
|
||||
return r;
|
||||
|
||||
if (off >= in.size)
|
||||
return 0;
|
||||
if (off + n > in.size)
|
||||
n = (unsigned)(in.size - off);
|
||||
|
||||
char *dst = (char *)buf;
|
||||
unsigned done = 0;
|
||||
while (done < n) {
|
||||
uint64_t fblk = (off + done) / SCHFS_BLKSIZE;
|
||||
unsigned inblk = (unsigned)((off + done) % SCHFS_BLKSIZE);
|
||||
unsigned want = SCHFS_BLKSIZE - inblk;
|
||||
if (want > n - done)
|
||||
want = n - done;
|
||||
|
||||
uint64_t disk = 0;
|
||||
int m = extent_map(v, c, ino, fblk, &disk);
|
||||
if (m == -2)
|
||||
return -2;
|
||||
|
||||
if (m == 0) {
|
||||
memset(dst + done, 0, want); /* hole reads as zeros */
|
||||
} else {
|
||||
if (v->dev.bread(v->dev.ctx, disk, dblk) != 0)
|
||||
return -2;
|
||||
if (csum_check(v, c, disk, dblk) != 0)
|
||||
return -2;
|
||||
memmove(dst + done, (char *)dblk + inblk, want);
|
||||
}
|
||||
done += want;
|
||||
}
|
||||
return (long)done;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Path resolution */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
uint64_t
|
||||
schfs_path_lookup(struct schfs_vol *v, struct schfs_cursor *c, const char *path)
|
||||
{
|
||||
uint64_t cur = v->root_objectid;
|
||||
|
||||
while (*path == '/')
|
||||
path++;
|
||||
|
||||
while (*path) {
|
||||
const char *s = path;
|
||||
while (*path && *path != '/')
|
||||
path++;
|
||||
unsigned len = (unsigned)(path - s);
|
||||
while (*path == '/')
|
||||
path++;
|
||||
|
||||
if (len == 1 && s[0] == '.')
|
||||
continue;
|
||||
if (len == 2 && s[0] == '.' && s[1] == '.') {
|
||||
// ".." is synthetic: directories store a parent id rather than an
|
||||
// entry, so walking up is an inode read, not a lookup.
|
||||
struct schfs_inode in;
|
||||
if (schfs_inode_get(v, c, cur, &in) != 0)
|
||||
return 0;
|
||||
cur = in.parent ? in.parent : v->root_objectid;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Every component but the last must be a directory. Checking here is
|
||||
// what makes "cat /etc/passwd/x" fail rather than silently succeed.
|
||||
struct schfs_inode in;
|
||||
if (schfs_inode_get(v, c, cur, &in) != 0)
|
||||
return 0;
|
||||
if (in.type != SCHFS_T_DIR)
|
||||
return 0;
|
||||
|
||||
uint64_t next;
|
||||
if (schfs_dir_lookup(v, c, cur, s, len, &next, 0) != 0)
|
||||
return 0;
|
||||
cur = next;
|
||||
}
|
||||
return cur;
|
||||
}
|
||||
932
kernel/fs/schfs/objw.c
Normal file
932
kernel/fs/schfs/objw.c
Normal file
|
|
@ -0,0 +1,932 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
// SchFS object layer, write side: create, link, unlink, rename, write,
|
||||
// truncate.
|
||||
//
|
||||
// The same portable C as the read side, compiled both hosted (mkschfs) and
|
||||
// freestanding (the kernel mount), so an image the tools produce and one the
|
||||
// kernel produces come from a single implementation rather than two that agree
|
||||
// by luck. Everything below is expressed as records in the one B-tree; there is
|
||||
// no inode table, no directory block format and no bitmap to keep in step.
|
||||
//
|
||||
// Two rules run through the whole file.
|
||||
//
|
||||
// Nothing here may put a block on the stack. A SchFS block is 4096 bytes and so
|
||||
// is the entire kernel stack, so every function borrows the volume's three
|
||||
// scratch buffers (v->ctx) instead. Where a cursor is needed, ctx.a doubles as
|
||||
// its buffer — which is safe only because no path below holds a cursor across a
|
||||
// tree mutation, and every one of them copies what it needs out first.
|
||||
//
|
||||
// Nothing reachable from a committed root is ever overwritten. For tree blocks
|
||||
// the B-tree enforces that; for *data* blocks it is enforced here, by
|
||||
// schfs_extent.gen. A run allocated during the group being built may be written
|
||||
// in place, because no published root can see it. Anything older is copied to a
|
||||
// fresh block and the extent record split around it.
|
||||
|
||||
#include "schfs_int.h"
|
||||
|
||||
#ifdef SCHFS_HOSTED
|
||||
#include <string.h>
|
||||
#else
|
||||
#include "types.h"
|
||||
#include "defs.h"
|
||||
#endif
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Inodes */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
uint64_t
|
||||
schfs_alloc_objectid(struct schfs_vol *v)
|
||||
{
|
||||
return v->next_objectid++;
|
||||
}
|
||||
|
||||
int
|
||||
schfs_inode_put(struct schfs_vol *v, uint64_t ino, const struct schfs_inode *in)
|
||||
{
|
||||
struct schfs_key k;
|
||||
schfs_mkkey(&k, ino, SCHFS_KEY_INODE, 0);
|
||||
return schfs_btree_insert(&v->tree, &v->ctx, &k, in, sizeof(*in));
|
||||
}
|
||||
|
||||
// Read an inode using the volume's own scratch block. Every mutation below is a
|
||||
// read-modify-write of one of these, and each one that took a cursor buffer
|
||||
// from its caller was one more place to get the aliasing rules wrong.
|
||||
static int
|
||||
inode_load(struct schfs_vol *v, uint64_t ino, struct schfs_inode *out)
|
||||
{
|
||||
struct schfs_cursor c;
|
||||
schfs_cursor_init(&c, &v->tree, v->ctx.a);
|
||||
return schfs_inode_get(v, &c, ino, out);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Directory entries */
|
||||
/* */
|
||||
/* One key per name hash, holding a packed list of records, because two */
|
||||
/* names in a directory can hash the same and the second insert would */
|
||||
/* otherwise silently replace the first. */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
// Load the packed list for `name`'s hash into v->dirbuf. Returns its length, or
|
||||
// 0 if there is no such key, or a negative error.
|
||||
static int
|
||||
dirlist_load(struct schfs_vol *v, uint64_t dir, const char *name,
|
||||
unsigned namelen, struct schfs_key *k)
|
||||
{
|
||||
schfs_mkkey(k, dir, SCHFS_KEY_DIRENT, schfs_namehash(name, namelen));
|
||||
|
||||
unsigned len = sizeof(v->dirbuf);
|
||||
int r = schfs_btree_lookup(&v->tree, &v->ctx, k, v->dirbuf, &len);
|
||||
if (r == -2)
|
||||
return -2;
|
||||
if (r != 0)
|
||||
return 0;
|
||||
if (len > sizeof(v->dirbuf))
|
||||
return -2; /* stored value larger than can exist */
|
||||
return (int)len;
|
||||
}
|
||||
|
||||
// Offset of `name` within the packed list, or -1 if absent, -2 if malformed.
|
||||
static int
|
||||
dirlist_find(const char *list, unsigned len, const char *name, unsigned namelen)
|
||||
{
|
||||
for (unsigned off = 0; off + sizeof(struct schfs_dirent_rec) <= len; ) {
|
||||
const struct schfs_dirent_rec *d =
|
||||
(const struct schfs_dirent_rec *)(list + off);
|
||||
unsigned step = SCHFS_DIRENT_LEN(d->namelen);
|
||||
if (step == 0 || off + step > len)
|
||||
return -2;
|
||||
if (d->namelen == namelen &&
|
||||
memcmp(list + off + sizeof(*d), name, namelen) == 0)
|
||||
return (int)off;
|
||||
off += step;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int
|
||||
schfs_dir_add(struct schfs_vol *v, uint64_t dir, const char *name,
|
||||
unsigned namelen, uint64_t child, uint16_t type)
|
||||
{
|
||||
if (namelen == 0 || namelen > SCHFS_NAME_MAX)
|
||||
return -1;
|
||||
|
||||
struct schfs_key k;
|
||||
int got = dirlist_load(v, dir, name, namelen, &k);
|
||||
if (got < 0)
|
||||
return got;
|
||||
unsigned len = (unsigned)got;
|
||||
|
||||
// Reject a duplicate outright. Without this, adding a name twice produces a
|
||||
// directory with two entries that lookup can never tell apart.
|
||||
int at = dirlist_find(v->dirbuf, len, name, namelen);
|
||||
if (at == -2)
|
||||
return -2;
|
||||
if (at >= 0)
|
||||
return -1;
|
||||
|
||||
unsigned add = SCHFS_DIRENT_LEN(namelen);
|
||||
if (len + add > sizeof(v->dirbuf))
|
||||
return -4; /* collision list will not fit in one item */
|
||||
|
||||
struct schfs_dirent_rec *d = (struct schfs_dirent_rec *)(v->dirbuf + len);
|
||||
memset(d, 0, add);
|
||||
d->objectid = child;
|
||||
d->type = type;
|
||||
d->namelen = (uint16_t)namelen;
|
||||
memmove(v->dirbuf + len + sizeof(*d), name, namelen);
|
||||
|
||||
return schfs_btree_insert(&v->tree, &v->ctx, &k, v->dirbuf, len + add);
|
||||
}
|
||||
|
||||
int
|
||||
schfs_dir_del(struct schfs_vol *v, uint64_t dir, const char *name,
|
||||
unsigned namelen)
|
||||
{
|
||||
if (namelen == 0 || namelen > SCHFS_NAME_MAX)
|
||||
return -1;
|
||||
|
||||
struct schfs_key k;
|
||||
int got = dirlist_load(v, dir, name, namelen, &k);
|
||||
if (got < 0)
|
||||
return got;
|
||||
unsigned len = (unsigned)got;
|
||||
if (len == 0)
|
||||
return -1;
|
||||
|
||||
int at = dirlist_find(v->dirbuf, len, name, namelen);
|
||||
if (at == -2)
|
||||
return -2;
|
||||
if (at < 0)
|
||||
return -1;
|
||||
|
||||
const struct schfs_dirent_rec *d =
|
||||
(const struct schfs_dirent_rec *)(v->dirbuf + at);
|
||||
unsigned step = SCHFS_DIRENT_LEN(d->namelen);
|
||||
memmove(v->dirbuf + at, v->dirbuf + at + step, len - (unsigned)at - step);
|
||||
len -= step;
|
||||
|
||||
// An emptied list means the key itself goes, not a zero-length value: a key
|
||||
// that exists with nothing in it would make the directory scan stop on a
|
||||
// record readdir cannot describe.
|
||||
if (len == 0)
|
||||
return schfs_btree_delete(&v->tree, &v->ctx, &k);
|
||||
return schfs_btree_insert(&v->tree, &v->ctx, &k, v->dirbuf, len);
|
||||
}
|
||||
|
||||
int
|
||||
schfs_dir_empty(struct schfs_vol *v, uint64_t dir)
|
||||
{
|
||||
struct schfs_key start;
|
||||
schfs_mkkey(&start, dir, SCHFS_KEY_DIRENT, 0);
|
||||
|
||||
struct schfs_cursor c;
|
||||
schfs_cursor_init(&c, &v->tree, v->ctx.a);
|
||||
int r = schfs_cursor_seek(&c, &start);
|
||||
if (r == -2)
|
||||
return -2;
|
||||
if (r != 0)
|
||||
return 1; /* nothing at or after: empty */
|
||||
|
||||
const struct schfs_key *got = schfs_cursor_key(&c);
|
||||
if (!got)
|
||||
return 1;
|
||||
return (got->objectid == dir && got->type == SCHFS_KEY_DIRENT) ? 0 : 1;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Data-block checksums */
|
||||
/* */
|
||||
/* Tree nodes carry their checksum in their parent's pointer. Data */
|
||||
/* blocks have no parent, so theirs live under one reserved object id, */
|
||||
/* exactly as btrfs keeps a checksum tree. */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
static int
|
||||
csum_put(struct schfs_vol *v, uint64_t dblk, const void *data)
|
||||
{
|
||||
struct schfs_key k;
|
||||
schfs_mkkey(&k, SCHFS_CSUM_OBJECTID, SCHFS_KEY_CSUM, dblk);
|
||||
uint32_t c = schfs_crc32c(0, data, SCHFS_BLKSIZE);
|
||||
return schfs_btree_insert(&v->tree, &v->ctx, &k, &c, sizeof(c));
|
||||
}
|
||||
|
||||
static int
|
||||
csum_del(struct schfs_vol *v, uint64_t dblk)
|
||||
{
|
||||
struct schfs_key k;
|
||||
schfs_mkkey(&k, SCHFS_CSUM_OBJECTID, SCHFS_KEY_CSUM, dblk);
|
||||
int r = schfs_btree_delete(&v->tree, &v->ctx, &k);
|
||||
return r == -1 ? 0 : r; /* absent is not a failure */
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Extents */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
static int
|
||||
extent_put(struct schfs_vol *v, uint64_t ino, uint64_t fblk,
|
||||
const struct schfs_extent *e)
|
||||
{
|
||||
struct schfs_key k;
|
||||
schfs_mkkey(&k, ino, SCHFS_KEY_EXTENT, fblk);
|
||||
return schfs_btree_insert(&v->tree, &v->ctx, &k, e, sizeof(*e));
|
||||
}
|
||||
|
||||
static int
|
||||
extent_del(struct schfs_vol *v, uint64_t ino, uint64_t fblk)
|
||||
{
|
||||
struct schfs_key k;
|
||||
schfs_mkkey(&k, ino, SCHFS_KEY_EXTENT, fblk);
|
||||
int r = schfs_btree_delete(&v->tree, &v->ctx, &k);
|
||||
return r == -1 ? 0 : r;
|
||||
}
|
||||
|
||||
// The extent covering file block `fblk`, if any. Extents are keyed by the file
|
||||
// block they start at, so the record governing block B is the last one at or
|
||||
// before (ino, EXTENT, B). Returns 1 and fills *start and *e, 0 for a hole, -2
|
||||
// on corruption.
|
||||
static int
|
||||
extent_find(struct schfs_vol *v, uint64_t ino, uint64_t fblk,
|
||||
uint64_t *start, struct schfs_extent *e)
|
||||
{
|
||||
struct schfs_cursor c;
|
||||
schfs_cursor_init(&c, &v->tree, v->ctx.a);
|
||||
|
||||
struct schfs_key probe;
|
||||
schfs_mkkey(&probe, ino, SCHFS_KEY_EXTENT, fblk);
|
||||
int r = schfs_btree_lookup_le(&c, &probe);
|
||||
if (r == -2)
|
||||
return -2;
|
||||
if (r != 0)
|
||||
return 0;
|
||||
|
||||
const struct schfs_key *got = schfs_cursor_key(&c);
|
||||
unsigned vlen;
|
||||
const struct schfs_extent *found =
|
||||
(const struct schfs_extent *)schfs_cursor_val(&c, &vlen);
|
||||
if (!got || !found)
|
||||
return 0;
|
||||
// lookup_le happily lands on some other object's records; so does a sparse
|
||||
// file whose first extent starts past this block.
|
||||
if (got->objectid != ino || got->type != SCHFS_KEY_EXTENT)
|
||||
return 0;
|
||||
if (vlen != sizeof(*e))
|
||||
return -2;
|
||||
if (fblk >= got->offset + found->nblocks)
|
||||
return 0;
|
||||
|
||||
*start = got->offset;
|
||||
*e = *found;
|
||||
return 1;
|
||||
}
|
||||
|
||||
// The last extent record belonging to `ino`, or 0 if it has none.
|
||||
static int
|
||||
extent_last(struct schfs_vol *v, uint64_t ino, uint64_t *start,
|
||||
struct schfs_extent *e)
|
||||
{
|
||||
struct schfs_cursor c;
|
||||
schfs_cursor_init(&c, &v->tree, v->ctx.a);
|
||||
|
||||
struct schfs_key probe;
|
||||
schfs_mkkey(&probe, ino, SCHFS_KEY_EXTENT, ~0ULL);
|
||||
int r = schfs_btree_lookup_le(&c, &probe);
|
||||
if (r == -2)
|
||||
return -2;
|
||||
if (r != 0)
|
||||
return 0;
|
||||
|
||||
const struct schfs_key *got = schfs_cursor_key(&c);
|
||||
unsigned vlen;
|
||||
const struct schfs_extent *found =
|
||||
(const struct schfs_extent *)schfs_cursor_val(&c, &vlen);
|
||||
if (!got || !found)
|
||||
return 0;
|
||||
if (got->objectid != ino || got->type != SCHFS_KEY_EXTENT)
|
||||
return 0;
|
||||
if (vlen != sizeof(*e))
|
||||
return -2;
|
||||
|
||||
*start = got->offset;
|
||||
*e = *found;
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Record a newly allocated single block, merging it into the preceding extent
|
||||
// when the two are adjacent both in the file and on the disk.
|
||||
//
|
||||
// This is what keeps extents worth having. Sequential writes get consecutive
|
||||
// blocks from the allocator, so without merging a one-megabyte file carries 256
|
||||
// extent records instead of one, and the tree ends up mostly describing where
|
||||
// the data is rather than holding it.
|
||||
static int
|
||||
extent_grow(struct schfs_vol *v, uint64_t ino, uint64_t fblk,
|
||||
const struct schfs_extent *e)
|
||||
{
|
||||
if (fblk > 0) {
|
||||
uint64_t pstart;
|
||||
struct schfs_extent p;
|
||||
int m = extent_find(v, ino, fblk - 1, &pstart, &p);
|
||||
if (m == -2)
|
||||
return -2;
|
||||
if (m == 1 &&
|
||||
pstart + p.nblocks == fblk &&
|
||||
p.disk_block + p.nblocks == e->disk_block &&
|
||||
p.refs == e->refs && p.gen == e->gen) {
|
||||
p.nblocks++;
|
||||
return extent_put(v, ino, pstart, &p);
|
||||
}
|
||||
}
|
||||
return extent_put(v, ino, fblk, e);
|
||||
}
|
||||
|
||||
// Release a run of data blocks and the checksum records that describe them.
|
||||
// Shared runs are left alone: another file still points at them, and proper
|
||||
// refcounting needs an extent tree that nothing creates yet.
|
||||
static int
|
||||
extent_release(struct schfs_vol *v, const struct schfs_extent *e,
|
||||
uint64_t first, uint64_t n)
|
||||
{
|
||||
if (e->refs != 1)
|
||||
return 0;
|
||||
for (uint64_t i = 0; i < n; i++) {
|
||||
int r = csum_del(v, first + i);
|
||||
if (r != 0)
|
||||
return r;
|
||||
v->dev.bfree(v->dev.ctx, first + i);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Drop everything from file block `from` onward. Used by truncate and by the
|
||||
// deletion of an object.
|
||||
static int
|
||||
extents_drop_from(struct schfs_vol *v, uint64_t ino, struct schfs_inode *in,
|
||||
uint64_t from)
|
||||
{
|
||||
for (;;) {
|
||||
uint64_t start;
|
||||
struct schfs_extent e;
|
||||
int m = extent_last(v, ino, &start, &e);
|
||||
if (m == -2)
|
||||
return -2;
|
||||
if (m == 0)
|
||||
return 0; /* no extents left */
|
||||
if (start + e.nblocks <= from)
|
||||
return 0; /* nothing beyond the cut */
|
||||
|
||||
if (start >= from) {
|
||||
int r = extent_release(v, &e, e.disk_block, e.nblocks);
|
||||
if (r != 0)
|
||||
return r;
|
||||
r = extent_del(v, ino, start);
|
||||
if (r != 0)
|
||||
return r;
|
||||
if (in->blocks >= e.nblocks)
|
||||
in->blocks -= e.nblocks;
|
||||
continue; /* there may be earlier ones too */
|
||||
}
|
||||
|
||||
// The cut falls inside this run: keep its head, release its tail.
|
||||
uint64_t keep = from - start;
|
||||
uint64_t tail = e.nblocks - keep;
|
||||
int r = extent_release(v, &e, e.disk_block + keep, tail);
|
||||
if (r != 0)
|
||||
return r;
|
||||
e.nblocks = keep;
|
||||
r = extent_put(v, ino, start, &e);
|
||||
if (r != 0)
|
||||
return r;
|
||||
if (in->blocks >= tail)
|
||||
in->blocks -= tail;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* File data */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
int
|
||||
schfs_file_reserve(struct schfs_vol *v, uint64_t ino, uint64_t nblocks)
|
||||
{
|
||||
if (nblocks == 0)
|
||||
return 0;
|
||||
|
||||
// Take the whole run before touching the tree.
|
||||
//
|
||||
// This is what makes extents worth having when the final size is already
|
||||
// known. Allocating a data block, then inserting its extent record -- which
|
||||
// itself allocates blocks for the copy-on-write path -- then allocating the
|
||||
// next data block interleaves metadata with data, so consecutive file
|
||||
// blocks are never physically adjacent. Grabbing the run up front keeps it
|
||||
// contiguous and a whole file becomes one record.
|
||||
uint64_t first;
|
||||
if (v->dev.balloc_run) {
|
||||
first = v->dev.balloc_run(v->dev.ctx, nblocks);
|
||||
if (first == 0)
|
||||
return -3;
|
||||
} else {
|
||||
first = v->dev.balloc(v->dev.ctx);
|
||||
if (first == 0)
|
||||
return -3;
|
||||
uint64_t prev = first;
|
||||
for (uint64_t i = 1; i < nblocks; i++) {
|
||||
uint64_t b = v->dev.balloc(v->dev.ctx);
|
||||
if (b == 0)
|
||||
return -3;
|
||||
if (b != prev + 1)
|
||||
return -2; /* allocator is not handing out a run */
|
||||
prev = b;
|
||||
}
|
||||
}
|
||||
|
||||
struct schfs_extent e;
|
||||
memset(&e, 0, sizeof(e));
|
||||
e.disk_block = first;
|
||||
e.nblocks = nblocks;
|
||||
e.refs = 1;
|
||||
e.gen = v->tree.generation;
|
||||
|
||||
// Deliberately does not touch the inode. Re-reading and rewriting it here
|
||||
// costs two extra tree inserts per file, and every insert copies its whole
|
||||
// path. The caller knows the block count before it writes the inode the
|
||||
// first time, so it sets in.blocks there instead.
|
||||
return extent_put(v, ino, 0, &e);
|
||||
}
|
||||
|
||||
// Where the next write to file block `fblk` should land.
|
||||
struct wblk {
|
||||
uint64_t disk; // block to write
|
||||
uint64_t src; // block holding the current contents, 0 for a hole
|
||||
int fresh; // a block that did not exist before
|
||||
};
|
||||
|
||||
static int
|
||||
block_for_write(struct schfs_vol *v, uint64_t ino, uint64_t fblk,
|
||||
struct wblk *w)
|
||||
{
|
||||
uint64_t start;
|
||||
struct schfs_extent e;
|
||||
int m = extent_find(v, ino, fblk, &start, &e);
|
||||
if (m == -2)
|
||||
return -2;
|
||||
|
||||
if (m == 0) {
|
||||
uint64_t d = v->dev.balloc(v->dev.ctx);
|
||||
if (d == 0)
|
||||
return -3;
|
||||
|
||||
struct schfs_extent ne;
|
||||
memset(&ne, 0, sizeof(ne));
|
||||
ne.disk_block = d;
|
||||
ne.nblocks = 1;
|
||||
ne.refs = 1;
|
||||
ne.gen = v->tree.generation;
|
||||
|
||||
int r = extent_grow(v, ino, fblk, &ne);
|
||||
if (r != 0)
|
||||
return r;
|
||||
|
||||
w->disk = d;
|
||||
w->src = 0;
|
||||
w->fresh = 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint64_t old = e.disk_block + (fblk - start);
|
||||
|
||||
// Allocated by the group being built, and owned outright: no published root
|
||||
// can reach it, so it may simply be overwritten.
|
||||
if (e.gen == v->tree.generation && e.refs == 1) {
|
||||
w->disk = old;
|
||||
w->src = old;
|
||||
w->fresh = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Otherwise copy this one block out of the run, and split the record around
|
||||
// it: a head covering what came before, the new block, and a tail covering
|
||||
// what came after. No deletion is needed — when the block being replaced is
|
||||
// the run's first, the middle record simply overwrites the original key.
|
||||
uint64_t nd = v->dev.balloc(v->dev.ctx);
|
||||
if (nd == 0)
|
||||
return -3;
|
||||
|
||||
if (fblk > start) {
|
||||
struct schfs_extent head = e;
|
||||
head.nblocks = fblk - start;
|
||||
int r = extent_put(v, ino, start, &head);
|
||||
if (r != 0)
|
||||
return r;
|
||||
}
|
||||
{
|
||||
struct schfs_extent mid;
|
||||
memset(&mid, 0, sizeof(mid));
|
||||
mid.disk_block = nd;
|
||||
mid.nblocks = 1;
|
||||
mid.refs = 1;
|
||||
mid.gen = v->tree.generation;
|
||||
int r = extent_put(v, ino, fblk, &mid);
|
||||
if (r != 0)
|
||||
return r;
|
||||
}
|
||||
if (fblk + 1 < start + e.nblocks) {
|
||||
struct schfs_extent tail = e;
|
||||
tail.disk_block = e.disk_block + (fblk + 1 - start);
|
||||
tail.nblocks = start + e.nblocks - (fblk + 1);
|
||||
int r = extent_put(v, ino, fblk + 1, &tail);
|
||||
if (r != 0)
|
||||
return r;
|
||||
}
|
||||
|
||||
// The block just replaced rejoins the free map at the next commit, not now:
|
||||
// until then the committed tree still points at it and a crash lands there.
|
||||
int r = extent_release(v, &e, old, 1);
|
||||
if (r != 0)
|
||||
return r;
|
||||
|
||||
w->disk = nd;
|
||||
w->src = old;
|
||||
w->fresh = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
long
|
||||
schfs_file_write(struct schfs_vol *v, uint64_t ino, uint64_t off,
|
||||
const void *buf, unsigned n, void *dblk)
|
||||
{
|
||||
struct schfs_inode in;
|
||||
int r = inode_load(v, ino, &in);
|
||||
if (r != 0)
|
||||
return r;
|
||||
|
||||
const char *src = (const char *)buf;
|
||||
unsigned done = 0;
|
||||
|
||||
while (done < n) {
|
||||
uint64_t at = off + done;
|
||||
uint64_t fblk = at / SCHFS_BLKSIZE;
|
||||
unsigned inblk = (unsigned)(at % SCHFS_BLKSIZE);
|
||||
unsigned want = SCHFS_BLKSIZE - inblk;
|
||||
if (want > n - done)
|
||||
want = n - done;
|
||||
|
||||
struct wblk w;
|
||||
r = block_for_write(v, ino, fblk, &w);
|
||||
if (r != 0)
|
||||
return r;
|
||||
if (w.fresh)
|
||||
in.blocks++;
|
||||
|
||||
// A whole-block write needs no read: every byte is about to be
|
||||
// replaced. A partial one has to start from whatever is there, or from
|
||||
// zeros where the file had a hole.
|
||||
if (inblk != 0 || want != SCHFS_BLKSIZE) {
|
||||
if (w.src) {
|
||||
if (v->dev.bread(v->dev.ctx, w.src, dblk) != 0)
|
||||
return -2;
|
||||
} else {
|
||||
memset(dblk, 0, SCHFS_BLKSIZE);
|
||||
}
|
||||
}
|
||||
memmove((char *)dblk + inblk, src + done, want);
|
||||
|
||||
if (v->dev.bwrite(v->dev.ctx, w.disk, dblk) != 0)
|
||||
return -2;
|
||||
// The checksum is rewritten every time the block changes, not just when
|
||||
// it is allocated, or a topped-up tail block fails its own check on the
|
||||
// very next read.
|
||||
r = csum_put(v, w.disk, dblk);
|
||||
if (r != 0)
|
||||
return r;
|
||||
|
||||
done += want;
|
||||
}
|
||||
|
||||
if (off + n > in.size)
|
||||
in.size = off + n;
|
||||
in.gen = v->tree.generation;
|
||||
r = schfs_inode_put(v, ino, &in);
|
||||
if (r != 0)
|
||||
return r;
|
||||
return (long)done;
|
||||
}
|
||||
|
||||
int
|
||||
schfs_file_append(struct schfs_vol *v, uint64_t ino, const void *buf,
|
||||
unsigned n, void *dblk)
|
||||
{
|
||||
struct schfs_inode in;
|
||||
int r = inode_load(v, ino, &in);
|
||||
if (r != 0)
|
||||
return r == -1 ? -2 : r;
|
||||
|
||||
long got = schfs_file_write(v, ino, in.size, buf, n, dblk);
|
||||
if (got < 0)
|
||||
return (int)got;
|
||||
return got == (long)n ? 0 : -2;
|
||||
}
|
||||
|
||||
int
|
||||
schfs_file_truncate(struct schfs_vol *v, uint64_t ino, uint64_t size,
|
||||
void *dblk)
|
||||
{
|
||||
struct schfs_inode in;
|
||||
int r = inode_load(v, ino, &in);
|
||||
if (r != 0)
|
||||
return r;
|
||||
|
||||
if (size < in.size) {
|
||||
uint64_t keep = (size + SCHFS_BLKSIZE - 1) / SCHFS_BLKSIZE;
|
||||
r = extents_drop_from(v, ino, &in, keep);
|
||||
if (r != 0)
|
||||
return r;
|
||||
|
||||
// Whole blocks past the new end are gone; the block the new size falls
|
||||
// *inside* has to have its tail zeroed. It is tempting not to bother,
|
||||
// since a read stops at in.size and can never see those bytes — but
|
||||
// truncating down and then growing again is required to read back
|
||||
// zeros, and without this it reads back whatever the file used to hold.
|
||||
unsigned tail = (unsigned)(size % SCHFS_BLKSIZE);
|
||||
if (tail != 0) {
|
||||
uint64_t fblk = size / SCHFS_BLKSIZE;
|
||||
uint64_t start;
|
||||
struct schfs_extent e;
|
||||
int m = extent_find(v, ino, fblk, &start, &e);
|
||||
if (m == -2)
|
||||
return -2;
|
||||
if (m == 1) { /* a hole is already zeros */
|
||||
struct wblk w;
|
||||
r = block_for_write(v, ino, fblk, &w);
|
||||
if (r != 0)
|
||||
return r;
|
||||
if (w.src) {
|
||||
if (v->dev.bread(v->dev.ctx, w.src, dblk) != 0)
|
||||
return -2;
|
||||
} else {
|
||||
memset(dblk, 0, SCHFS_BLKSIZE);
|
||||
}
|
||||
memset((char *)dblk + tail, 0, SCHFS_BLKSIZE - tail);
|
||||
if (v->dev.bwrite(v->dev.ctx, w.disk, dblk) != 0)
|
||||
return -2;
|
||||
r = csum_put(v, w.disk, dblk);
|
||||
if (r != 0)
|
||||
return r;
|
||||
if (w.fresh)
|
||||
in.blocks++;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Growing needs nothing at all: the range with no extent records reads back
|
||||
// as zeros, which is a sparse file for free.
|
||||
|
||||
in.size = size;
|
||||
in.gen = v->tree.generation;
|
||||
return schfs_inode_put(v, ino, &in);
|
||||
}
|
||||
|
||||
// Release an object entirely: its data, its checksums and its inode record.
|
||||
int
|
||||
schfs_object_free(struct schfs_vol *v, uint64_t ino)
|
||||
{
|
||||
struct schfs_inode in;
|
||||
int r = inode_load(v, ino, &in);
|
||||
if (r == -1)
|
||||
return 0; /* already gone */
|
||||
if (r != 0)
|
||||
return r;
|
||||
|
||||
r = extents_drop_from(v, ino, &in, 0);
|
||||
if (r != 0)
|
||||
return r;
|
||||
|
||||
struct schfs_key k;
|
||||
schfs_mkkey(&k, ino, SCHFS_KEY_INODE, 0);
|
||||
r = schfs_btree_delete(&v->tree, &v->ctx, &k);
|
||||
return r == -1 ? 0 : r;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Namespace */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
// A directory's link count is 2 (itself and its own ".") plus one per
|
||||
// subdirectory, because each subdirectory's ".." points back at it. Neither
|
||||
// entry is stored, but the count still has to match what POSIX tools expect or
|
||||
// "find -type d" prunes real directories and du walks the tree wrongly.
|
||||
static int
|
||||
parent_nlink_adjust(struct schfs_vol *v, uint64_t dir, int delta)
|
||||
{
|
||||
struct schfs_inode in;
|
||||
int r = inode_load(v, dir, &in);
|
||||
if (r != 0)
|
||||
return r;
|
||||
if (delta < 0 && in.nlink < (uint32_t)(-delta))
|
||||
return -2;
|
||||
in.nlink = (uint32_t)((int64_t)in.nlink + delta);
|
||||
return schfs_inode_put(v, dir, &in);
|
||||
}
|
||||
|
||||
int
|
||||
schfs_create(struct schfs_vol *v, uint64_t dir, const char *name,
|
||||
unsigned namelen, uint16_t type, uint32_t mode,
|
||||
uint16_t rdev_major, uint16_t rdev_minor, uint64_t now,
|
||||
uint64_t *out_ino)
|
||||
{
|
||||
if (namelen == 0 || namelen > SCHFS_NAME_MAX)
|
||||
return -1;
|
||||
|
||||
struct schfs_inode parent;
|
||||
int r = inode_load(v, dir, &parent);
|
||||
if (r != 0)
|
||||
return r;
|
||||
if (parent.type != SCHFS_T_DIR)
|
||||
return -1;
|
||||
|
||||
uint64_t ino = schfs_alloc_objectid(v);
|
||||
|
||||
struct schfs_inode in;
|
||||
memset(&in, 0, sizeof(in));
|
||||
in.type = type;
|
||||
in.parent = dir;
|
||||
in.mode = mode;
|
||||
in.nlink = type == SCHFS_T_DIR ? 2 : 1;
|
||||
in.rdev_major = rdev_major;
|
||||
in.rdev_minor = rdev_minor;
|
||||
in.atime = in.mtime = in.ctime = now;
|
||||
in.gen = v->tree.generation;
|
||||
|
||||
r = schfs_inode_put(v, ino, &in);
|
||||
if (r != 0)
|
||||
return r;
|
||||
|
||||
// The entry goes in last, so a failure leaves an unreferenced object rather
|
||||
// than a name pointing at nothing. Neither survives a crash — none of this
|
||||
// is visible until the transaction commits — but an unreachable inode is
|
||||
// the harmless one of the two if a later step in the same group fails.
|
||||
r = schfs_dir_add(v, dir, name, namelen, ino, type);
|
||||
if (r != 0)
|
||||
return r;
|
||||
|
||||
if (type == SCHFS_T_DIR) {
|
||||
r = parent_nlink_adjust(v, dir, +1);
|
||||
if (r != 0)
|
||||
return r;
|
||||
}
|
||||
|
||||
if (out_ino)
|
||||
*out_ino = ino;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
schfs_link(struct schfs_vol *v, uint64_t dir, const char *name,
|
||||
unsigned namelen, uint64_t ino)
|
||||
{
|
||||
struct schfs_inode in;
|
||||
int r = inode_load(v, ino, &in);
|
||||
if (r != 0)
|
||||
return r;
|
||||
// Directories are not hard-linkable. Allowing it makes cycles possible, and
|
||||
// a cycle turns every recursive walk into an infinite one.
|
||||
if (in.type == SCHFS_T_DIR)
|
||||
return -1;
|
||||
|
||||
r = schfs_dir_add(v, dir, name, namelen, ino, in.type);
|
||||
if (r != 0)
|
||||
return r;
|
||||
|
||||
in.nlink++;
|
||||
in.ctime = in.mtime;
|
||||
return schfs_inode_put(v, ino, &in);
|
||||
}
|
||||
|
||||
int
|
||||
schfs_unlink(struct schfs_vol *v, uint64_t dir, const char *name,
|
||||
unsigned namelen)
|
||||
{
|
||||
uint64_t ino;
|
||||
uint16_t type;
|
||||
|
||||
{
|
||||
struct schfs_cursor c;
|
||||
schfs_cursor_init(&c, &v->tree, v->ctx.a);
|
||||
int r = schfs_dir_lookup(v, &c, dir, name, namelen, &ino, &type);
|
||||
if (r != 0)
|
||||
return r;
|
||||
}
|
||||
|
||||
struct schfs_inode in;
|
||||
int r = inode_load(v, ino, &in);
|
||||
if (r != 0)
|
||||
return r;
|
||||
|
||||
if (in.type == SCHFS_T_DIR) {
|
||||
int e = schfs_dir_empty(v, ino);
|
||||
if (e == -2)
|
||||
return -2;
|
||||
if (e == 0)
|
||||
return -5; /* directory not empty */
|
||||
}
|
||||
|
||||
r = schfs_dir_del(v, dir, name, namelen);
|
||||
if (r != 0)
|
||||
return r;
|
||||
|
||||
if (in.type == SCHFS_T_DIR) {
|
||||
r = parent_nlink_adjust(v, dir, -1);
|
||||
if (r != 0)
|
||||
return r;
|
||||
return schfs_object_free(v, ino);
|
||||
}
|
||||
|
||||
if (in.nlink > 1) {
|
||||
in.nlink--;
|
||||
return schfs_inode_put(v, ino, &in);
|
||||
}
|
||||
return schfs_object_free(v, ino);
|
||||
}
|
||||
|
||||
int
|
||||
schfs_rename(struct schfs_vol *v, uint64_t olddir, const char *oldname,
|
||||
unsigned oldlen, uint64_t newdir, const char *newname,
|
||||
unsigned newlen)
|
||||
{
|
||||
uint64_t ino;
|
||||
uint16_t type;
|
||||
|
||||
{
|
||||
struct schfs_cursor c;
|
||||
schfs_cursor_init(&c, &v->tree, v->ctx.a);
|
||||
int r = schfs_dir_lookup(v, &c, olddir, oldname, oldlen, &ino, &type);
|
||||
if (r != 0)
|
||||
return r;
|
||||
}
|
||||
|
||||
// Renaming a directory into its own subtree would detach that subtree from
|
||||
// the root and leave it reachable only from itself. Walking up from the new
|
||||
// parent is the cheap way to catch it, since every directory records its
|
||||
// own parent.
|
||||
if (type == SCHFS_T_DIR && newdir != olddir) {
|
||||
uint64_t up = newdir;
|
||||
for (int i = 0; i <= SCHFS_MAX_LEVEL * 8 && up != 0; i++) {
|
||||
if (up == ino)
|
||||
return -1;
|
||||
struct schfs_inode t;
|
||||
if (inode_load(v, up, &t) != 0)
|
||||
break;
|
||||
if (up == v->root_objectid)
|
||||
break;
|
||||
up = t.parent;
|
||||
}
|
||||
}
|
||||
|
||||
// A target that already exists is replaced, which means removing it first —
|
||||
// and refusing when that would silently discard a non-empty directory.
|
||||
{
|
||||
struct schfs_cursor c;
|
||||
schfs_cursor_init(&c, &v->tree, v->ctx.a);
|
||||
uint64_t victim;
|
||||
if (schfs_dir_lookup(v, &c, newdir, newname, newlen, &victim, 0) == 0) {
|
||||
if (victim == ino)
|
||||
return 0; /* renaming a name onto itself */
|
||||
int r = schfs_unlink(v, newdir, newname, newlen);
|
||||
if (r != 0)
|
||||
return r;
|
||||
}
|
||||
}
|
||||
|
||||
int r = schfs_dir_add(v, newdir, newname, newlen, ino, type);
|
||||
if (r != 0)
|
||||
return r;
|
||||
r = schfs_dir_del(v, olddir, oldname, oldlen);
|
||||
if (r != 0)
|
||||
return r;
|
||||
|
||||
if (newdir != olddir) {
|
||||
if (type == SCHFS_T_DIR) {
|
||||
// ".." is synthesised from the inode's parent field, so moving a
|
||||
// directory means rewriting that field and moving the link its ".."
|
||||
// contributes to each parent's count.
|
||||
struct schfs_inode in;
|
||||
r = inode_load(v, ino, &in);
|
||||
if (r != 0)
|
||||
return r;
|
||||
in.parent = newdir;
|
||||
r = schfs_inode_put(v, ino, &in);
|
||||
if (r != 0)
|
||||
return r;
|
||||
r = parent_nlink_adjust(v, olddir, -1);
|
||||
if (r != 0)
|
||||
return r;
|
||||
r = parent_nlink_adjust(v, newdir, +1);
|
||||
if (r != 0)
|
||||
return r;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
277
kernel/fs/schfs/schfs_int.h
Normal file
277
kernel/fs/schfs/schfs_int.h
Normal file
|
|
@ -0,0 +1,277 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
// SchFS internals: the volume (uberblock ring, block allocation) and the object
|
||||
// layer (inodes, directories, file data) that sit on top of the B-tree.
|
||||
//
|
||||
// Split three ways so the kernel links only what it needs:
|
||||
//
|
||||
// vol.c open / format / commit — the uberblock ring
|
||||
// obj.c read paths — kernel needs these in phase 2
|
||||
// objw.c write paths — host tools only, until phase 3
|
||||
//
|
||||
// Everything here is portable C compiled both hosted (mkfs, fsck) and freestanding
|
||||
// (kernel), so a bug is found on the host, where a test takes a second rather
|
||||
// than a boot.
|
||||
|
||||
#ifndef SCHFS_INT_H
|
||||
#define SCHFS_INT_H
|
||||
|
||||
#include "btree.h"
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// space.c — the allocation bitmap
|
||||
//
|
||||
// One bit per block, held in RAM and derived from the committed tree at mount
|
||||
// rather than stored on disk. See space.c for why.
|
||||
//
|
||||
// The bitmap is a vector of pages because the kernel allocates a page at a time
|
||||
// and gives no promise that two are adjacent. That vector is fixed-size, and
|
||||
// its length is therefore the hard ceiling on how large a volume this build can
|
||||
// mount: SCHFS_SPACE_PAGES pages x SCHFS_SPACE_BITS_PER_PAGE blocks each.
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#define SCHFS_SPACE_BITS_PER_PAGE ((uint64_t)SCHFS_BLKSIZE * 8) /* 32768 */
|
||||
#define SCHFS_SPACE_PAGES 512
|
||||
#define SCHFS_MAX_BLOCKS \
|
||||
(SCHFS_SPACE_BITS_PER_PAGE * (uint64_t)SCHFS_SPACE_PAGES) /* 64 GiB */
|
||||
|
||||
struct schfs_space {
|
||||
uint64_t *inuse[SCHFS_SPACE_PAGES]; // 1 = allocated
|
||||
uint64_t *pend [SCHFS_SPACE_PAGES]; // 1 = freed, but not yet reusable
|
||||
int npages;
|
||||
uint64_t nblocks;
|
||||
uint64_t free_blocks;
|
||||
uint64_t pending; // bits set in pend[], counted
|
||||
uint64_t hint; // rotating search start
|
||||
};
|
||||
|
||||
// A mounted (or under-construction) volume.
|
||||
//
|
||||
// NOT copyable: tree.dev points at this struct's own dev field, so a copy
|
||||
// leaves the tree talking to whichever struct it was built in. Open into the
|
||||
// storage it will live in.
|
||||
struct schfs_vol {
|
||||
struct schfs_bdev dev;
|
||||
struct schfs_btree tree;
|
||||
|
||||
// The three block buffers every mutation borrows, handed in at open or
|
||||
// format and owned by the caller for the life of the volume. Nothing in
|
||||
// SchFS may put a SCHFS_BLKSIZE buffer on the stack: a block is 4096 bytes
|
||||
// and so is the whole kernel stack.
|
||||
//
|
||||
// The object layer also uses ctx.a as a cursor buffer, which is safe on the
|
||||
// one condition that no cursor is still live when a tree mutation runs.
|
||||
// Every write path here reads what it needs out of the cursor first.
|
||||
struct schfs_ctx ctx;
|
||||
struct schfs_space space;
|
||||
|
||||
uint64_t total_blocks;
|
||||
uint64_t free_blocks;
|
||||
uint64_t next_objectid;
|
||||
uint64_t root_objectid;
|
||||
uint64_t generation; // last *committed* transaction group
|
||||
|
||||
int uber_slot; // ring slot the last commit used
|
||||
int readonly;
|
||||
char label[SCHFS_LABEL_MAX];
|
||||
uint8_t uuid[16];
|
||||
|
||||
// Staging for a directory's packed dirent list. Deliberately not one of the
|
||||
// ctx buffers: a split reuses ctx.b and ctx.c while the value being
|
||||
// inserted is still being read from, so the value needs storage of its own.
|
||||
char dirbuf[SCHFS_MAX_INLINE];
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// vol.c
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// Scan the whole uberblock ring and adopt the highest generation that both
|
||||
// checksums and points at a readable root. This *is* the crash recovery story:
|
||||
// there is no journal to replay, because a torn update simply leaves an older
|
||||
// slot as the newest valid one. Returns 0, -1 if no valid uberblock exists
|
||||
// (not a SchFS volume), -2 on I/O error.
|
||||
//
|
||||
// `x` supplies the volume's three scratch blocks; they are kept for its whole
|
||||
// lifetime, so they must outlive it and must not alias one another. `palloc`
|
||||
// yields pages for the allocation bitmap and may be NULL for a read-only mount,
|
||||
// which then cannot allocate at all.
|
||||
int schfs_vol_open(struct schfs_vol *v, struct schfs_bdev *dev,
|
||||
struct schfs_ctx *x, void *(*palloc)(void));
|
||||
|
||||
// Lay down an empty volume: one empty tree and one uberblock.
|
||||
int schfs_vol_format(struct schfs_vol *v, struct schfs_bdev *dev,
|
||||
uint64_t total_blocks, const char *label,
|
||||
struct schfs_ctx *x, void *(*palloc)(void));
|
||||
|
||||
// Publish the current tree by writing the next slot in the ring. Until this
|
||||
// returns, everything written since the last commit is invisible and a crash
|
||||
// discards it — which is exactly what makes the format crash-safe.
|
||||
int schfs_vol_commit(struct schfs_vol *v);
|
||||
|
||||
// Block allocation. Returns 0 when the volume is full.
|
||||
//
|
||||
// Not themselves schfs_bdev callbacks: a bdev has one ctx shared by all of its
|
||||
// hooks, and the caller's block I/O needs that slot for its own state (a file
|
||||
// handle, a device number). Callers wrap these in a one-line adapter instead.
|
||||
uint64_t schfs_vol_alloc(struct schfs_vol *v);
|
||||
uint64_t schfs_vol_alloc_run(struct schfs_vol *v, uint64_t n);
|
||||
void schfs_vol_free(struct schfs_vol *v, uint64_t blk);
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// space.c
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// Build an empty map covering `nblocks`, with everything below
|
||||
// SCHFS_FIRST_BLOCK already reserved. `palloc` yields SCHFS_BLKSIZE-sized
|
||||
// pages; the map keeps them for the volume's lifetime. Returns 0, -3 if the
|
||||
// volume is too small to hold anything, -4 if it is larger than this build can
|
||||
// map (see SCHFS_MAX_BLOCKS), -5 if palloc ran out.
|
||||
int schfs_space_init(struct schfs_space *s, uint64_t nblocks,
|
||||
void *(*palloc)(void));
|
||||
|
||||
// Record a run as allocated. Idempotent, because an extent range may be shared
|
||||
// by several files.
|
||||
void schfs_space_mark(struct schfs_space *s, uint64_t blk, uint64_t n);
|
||||
|
||||
uint64_t schfs_space_alloc(struct schfs_space *s);
|
||||
uint64_t schfs_space_alloc_run(struct schfs_space *s, uint64_t n);
|
||||
|
||||
// Release a block — but only for the *next* transaction group. Until the
|
||||
// commit that supersedes it, the last committed tree still points here and a
|
||||
// crash would land on it.
|
||||
void schfs_space_free(struct schfs_space *s, uint64_t blk);
|
||||
|
||||
// Fold this group's deferred frees back into the free map. Called by
|
||||
// schfs_vol_commit once the new uberblock is on the disk, never before.
|
||||
void schfs_space_commit(struct schfs_space *s);
|
||||
|
||||
// Walk the committed tree and mark every block it reaches. This is what makes
|
||||
// the map correct without storing it. Returns 0 or -2 on an unreadable tree.
|
||||
int schfs_space_scan(struct schfs_vol *v);
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// obj.c — read side
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
uint64_t schfs_namehash(const char *name, unsigned len);
|
||||
|
||||
// Test-only knob; see obj.c. Narrowing this forces directory-hash collisions
|
||||
// so the packed-dirent path actually runs. Never changed outside tools/.
|
||||
extern uint64_t schfs_hash_mask;
|
||||
|
||||
void schfs_mkkey(struct schfs_key *k, uint64_t objectid, uint8_t type,
|
||||
uint64_t offset);
|
||||
|
||||
// Fetch an inode record. Returns 0, -1 if there is no such object, -2 corrupt.
|
||||
int schfs_inode_get(struct schfs_vol *v, struct schfs_cursor *c,
|
||||
uint64_t ino, struct schfs_inode *out);
|
||||
|
||||
// Resolve one name inside a directory. Returns 0 and fills *out_ino/*out_type,
|
||||
// -1 if absent, -2 corrupt.
|
||||
int schfs_dir_lookup(struct schfs_vol *v, struct schfs_cursor *c,
|
||||
uint64_t dir, const char *name, unsigned namelen,
|
||||
uint64_t *out_ino, uint16_t *out_type);
|
||||
|
||||
// Iterate a directory's real entries in hash order. "." and ".." are not
|
||||
// stored and are not reported; callers that need them synthesise them.
|
||||
// A non-zero return from fn stops the walk and is passed back.
|
||||
int schfs_dir_iterate(struct schfs_vol *v, struct schfs_cursor *c, uint64_t dir,
|
||||
int (*fn)(uint64_t ino, uint16_t type, const char *name,
|
||||
unsigned namelen, void *arg),
|
||||
void *arg);
|
||||
|
||||
// Read from a file. Short reads at end-of-file are normal; returns the byte
|
||||
// count, or -1 on a missing object, -2 on corruption or a failed data checksum.
|
||||
// `dblk` is a caller-owned SCHFS_BLKSIZE scratch buffer for data blocks.
|
||||
long schfs_file_read(struct schfs_vol *v, struct schfs_cursor *c, uint64_t ino,
|
||||
uint64_t off, void *buf, unsigned n, void *dblk);
|
||||
|
||||
// Walk an absolute path. Returns the object id, or 0 if any component is
|
||||
// missing or is not a directory.
|
||||
uint64_t schfs_path_lookup(struct schfs_vol *v, struct schfs_cursor *c,
|
||||
const char *path);
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// objw.c — write side
|
||||
//
|
||||
// Shared error codes throughout: 0 success, -1 absent or not permitted,
|
||||
// -2 corruption or I/O failure, -3 out of space, -4 a directory hash-collision
|
||||
// list that will not fit in one item, -5 directory not empty.
|
||||
//
|
||||
// None of it is visible to a reader until schfs_vol_commit publishes the tree,
|
||||
// which is what lets a failure part way through a multi-record operation be
|
||||
// abandoned rather than unwound.
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
uint64_t schfs_alloc_objectid(struct schfs_vol *v);
|
||||
|
||||
int schfs_inode_put(struct schfs_vol *v, uint64_t ino,
|
||||
const struct schfs_inode *in);
|
||||
|
||||
// Add one entry, appending to the packed list if another name already hashes
|
||||
// the same. -1 if the name is already present.
|
||||
int schfs_dir_add(struct schfs_vol *v, uint64_t dir, const char *name,
|
||||
unsigned namelen, uint64_t child, uint16_t type);
|
||||
|
||||
// Remove one entry. -1 if there is no such name.
|
||||
int schfs_dir_del(struct schfs_vol *v, uint64_t dir, const char *name,
|
||||
unsigned namelen);
|
||||
|
||||
// 1 if the directory holds no entries, 0 if it holds some, -2 on corruption.
|
||||
int schfs_dir_empty(struct schfs_vol *v, uint64_t dir);
|
||||
|
||||
// Claim a contiguous run of data blocks for a file whose final size is already
|
||||
// known, and record the whole run as ONE extent. Worth calling before a bulk
|
||||
// write: without it, data and tree blocks interleave, and only the merging in
|
||||
// schfs_file_write keeps the extent count down.
|
||||
int schfs_file_reserve(struct schfs_vol *v, uint64_t ino, uint64_t nblocks);
|
||||
|
||||
// Write at an arbitrary offset, allocating blocks, extent records and data
|
||||
// checksums, copying any block that a committed tree can still reach, and
|
||||
// splitting the extent record around it. Writing past the end extends the file;
|
||||
// a gap left behind stays a hole and reads back as zeros. Returns the byte
|
||||
// count written, or a negative error.
|
||||
long schfs_file_write(struct schfs_vol *v, uint64_t ino, uint64_t off,
|
||||
const void *buf, unsigned n, void *dblk);
|
||||
|
||||
// Append at the current end of file. A convenience over schfs_file_write, kept
|
||||
// because image builders write nothing else.
|
||||
int schfs_file_append(struct schfs_vol *v, uint64_t ino, const void *buf,
|
||||
unsigned n, void *dblk);
|
||||
|
||||
// Set a file's length, releasing whole blocks past the new end and zeroing what
|
||||
// is left of the block the new end falls inside — truncating down and growing
|
||||
// again has to read back zeros. Growing alone costs nothing: a range with no
|
||||
// extent records is a hole. `dblk` is a caller-owned SCHFS_BLKSIZE buffer.
|
||||
int schfs_file_truncate(struct schfs_vol *v, uint64_t ino, uint64_t size,
|
||||
void *dblk);
|
||||
|
||||
// Release an object's data, checksums and inode record. The caller must already
|
||||
// have removed every name pointing at it.
|
||||
int schfs_object_free(struct schfs_vol *v, uint64_t ino);
|
||||
|
||||
// Create an object and the entry naming it. `now` is the timestamp to stamp all
|
||||
// three times with — SchFS has no clock of its own.
|
||||
int schfs_create(struct schfs_vol *v, uint64_t dir, const char *name,
|
||||
unsigned namelen, uint16_t type, uint32_t mode,
|
||||
uint16_t rdev_major, uint16_t rdev_minor, uint64_t now,
|
||||
uint64_t *out_ino);
|
||||
|
||||
// A second name for an existing object. -1 for a directory: hard-linking one
|
||||
// makes cycles possible, and a cycle turns every recursive walk infinite.
|
||||
int schfs_link(struct schfs_vol *v, uint64_t dir, const char *name,
|
||||
unsigned namelen, uint64_t ino);
|
||||
|
||||
// Remove a name, and the object with it once the last name is gone. Handles
|
||||
// directories too, refusing with -5 if the directory still has entries.
|
||||
int schfs_unlink(struct schfs_vol *v, uint64_t dir, const char *name,
|
||||
unsigned namelen);
|
||||
|
||||
// Move a name, replacing the target if one exists. Refuses to move a directory
|
||||
// into its own subtree, which would detach that subtree from the root.
|
||||
int schfs_rename(struct schfs_vol *v, uint64_t olddir, const char *oldname,
|
||||
unsigned oldlen, uint64_t newdir, const char *newname,
|
||||
unsigned newlen);
|
||||
|
||||
#endif /* SCHFS_INT_H */
|
||||
314
kernel/fs/schfs/space.c
Normal file
314
kernel/fs/schfs/space.c
Normal file
|
|
@ -0,0 +1,314 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
// SchFS space manager: which blocks are in use.
|
||||
//
|
||||
// There is no free-space map on disk, and that is the interesting decision
|
||||
// here. The committed tree already records exactly which blocks are live —
|
||||
// every tree node is named by its parent's pointer, every data block by an
|
||||
// extent record — so the map is *derived* at mount by walking the tree once,
|
||||
// held in RAM, and never written back.
|
||||
//
|
||||
// The alternative, persisting it, buys a fast mount and costs the one problem
|
||||
// copy-on-write is otherwise free of: the map has to be updated atomically with
|
||||
// the tree it describes, and writing the map needs blocks, which changes the
|
||||
// map. ZFS spends real complexity on that recursion and btrfs resolves it with
|
||||
// a fixpoint loop. Deriving the map instead cannot disagree with the filesystem
|
||||
// no matter what a crash interrupts, because it is computed from it — there is
|
||||
// no second source of truth to get out of step.
|
||||
//
|
||||
// What it costs is a metadata scan at mount, proportional to the number of tree
|
||||
// blocks rather than to the size of the volume. Nothing is written, so it is
|
||||
// also safe on a read-only mount. If mount time ever becomes the thing that
|
||||
// hurts, the map can be cached on disk later purely as a hint, validated
|
||||
// against the uberblock's generation and discarded when it does not match — an
|
||||
// addition, not a format change.
|
||||
//
|
||||
// ---------------------------------------------------------------------------
|
||||
// Deferred frees
|
||||
//
|
||||
// A block released during the transaction group being built is still reachable
|
||||
// from the last *committed* root, which is where a crash lands. Handing it
|
||||
// straight back would let the filesystem overwrite the very copy it is supposed
|
||||
// to fall back to. So frees go into a second bitmap and only rejoin the free
|
||||
// map when the commit that supersedes them lands. One transaction of delay, no
|
||||
// more: once group N is published, group N-1's tree is no longer anybody's
|
||||
// fallback.
|
||||
|
||||
#include "schfs_int.h"
|
||||
|
||||
#ifdef SCHFS_HOSTED
|
||||
#include <string.h>
|
||||
#else
|
||||
#include "types.h"
|
||||
#include "defs.h"
|
||||
#endif
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Bit vectors, paged */
|
||||
/* */
|
||||
/* The bitmap has to be one flat array to be indexable, and the kernel */
|
||||
/* hands out memory a page at a time with no promise that two pages */
|
||||
/* are adjacent. So it is a vector of page pointers instead: one page */
|
||||
/* covers SCHFS_SPACE_BITS_PER_PAGE blocks, and the page vector itself */
|
||||
/* is a fixed-size array, which is what puts a hard ceiling on volume */
|
||||
/* size. See docs/architecture/schfs.md for the resulting limits. */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
static int
|
||||
bit_test(uint64_t *const *pages, uint64_t b)
|
||||
{
|
||||
uint64_t pg = b / SCHFS_SPACE_BITS_PER_PAGE;
|
||||
uint64_t off = b % SCHFS_SPACE_BITS_PER_PAGE;
|
||||
return (int)((pages[pg][off / 64] >> (off % 64)) & 1);
|
||||
}
|
||||
|
||||
static void
|
||||
bit_set(uint64_t *const *pages, uint64_t b)
|
||||
{
|
||||
uint64_t pg = b / SCHFS_SPACE_BITS_PER_PAGE;
|
||||
uint64_t off = b % SCHFS_SPACE_BITS_PER_PAGE;
|
||||
pages[pg][off / 64] |= 1ULL << (off % 64);
|
||||
}
|
||||
|
||||
// The whole 64-block word containing b, so a scan can skip a fully used region
|
||||
// sixty-four blocks at a time instead of one. On a nearly full volume that is
|
||||
// the difference between an allocation costing microseconds and milliseconds.
|
||||
static uint64_t
|
||||
bit_word(uint64_t *const *pages, uint64_t b)
|
||||
{
|
||||
uint64_t pg = b / SCHFS_SPACE_BITS_PER_PAGE;
|
||||
uint64_t off = b % SCHFS_SPACE_BITS_PER_PAGE;
|
||||
return pages[pg][off / 64];
|
||||
}
|
||||
|
||||
static int
|
||||
popcount64(uint64_t v)
|
||||
{
|
||||
int n = 0;
|
||||
while (v) { v &= v - 1; n++; }
|
||||
return n;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Setup */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
int
|
||||
schfs_space_init(struct schfs_space *s, uint64_t nblocks,
|
||||
void *(*palloc)(void))
|
||||
{
|
||||
memset(s, 0, sizeof(*s));
|
||||
|
||||
if (nblocks <= SCHFS_FIRST_BLOCK)
|
||||
return -3;
|
||||
if (nblocks > SCHFS_MAX_BLOCKS)
|
||||
return -4; /* larger than this build can map */
|
||||
|
||||
uint64_t npages = (nblocks + SCHFS_SPACE_BITS_PER_PAGE - 1)
|
||||
/ SCHFS_SPACE_BITS_PER_PAGE;
|
||||
|
||||
for (uint64_t i = 0; i < npages; i++) {
|
||||
void *p = palloc();
|
||||
if (p == 0)
|
||||
return -5; /* caller frees what was taken */
|
||||
memset(p, 0, SCHFS_BLKSIZE);
|
||||
s->inuse[i] = (uint64_t *)p;
|
||||
|
||||
p = palloc();
|
||||
if (p == 0)
|
||||
return -5;
|
||||
memset(p, 0, SCHFS_BLKSIZE);
|
||||
s->pend[i] = (uint64_t *)p;
|
||||
s->npages = (int)(i + 1);
|
||||
}
|
||||
|
||||
s->nblocks = nblocks;
|
||||
s->free_blocks = nblocks - SCHFS_FIRST_BLOCK;
|
||||
s->hint = SCHFS_FIRST_BLOCK;
|
||||
|
||||
// The boot block and the uberblock ring are never allocatable, and neither
|
||||
// is anything past the end of the volume: the last bitmap page covers more
|
||||
// blocks than exist, and a stray allocation out there would be written to a
|
||||
// sector the device does not have.
|
||||
for (uint64_t b = 0; b < SCHFS_FIRST_BLOCK; b++)
|
||||
bit_set(s->inuse, b);
|
||||
for (uint64_t b = nblocks; b < (uint64_t)npages * SCHFS_SPACE_BITS_PER_PAGE; b++)
|
||||
bit_set(s->inuse, b);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void
|
||||
schfs_space_mark(struct schfs_space *s, uint64_t blk, uint64_t n)
|
||||
{
|
||||
for (uint64_t i = 0; i < n; i++) {
|
||||
uint64_t b = blk + i;
|
||||
if (b < SCHFS_FIRST_BLOCK || b >= s->nblocks)
|
||||
continue;
|
||||
if (bit_test(s->inuse, b))
|
||||
continue; /* already counted; extents may be shared */
|
||||
bit_set(s->inuse, b);
|
||||
s->free_blocks--;
|
||||
}
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Allocation */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
uint64_t
|
||||
schfs_space_alloc_run(struct schfs_space *s, uint64_t n)
|
||||
{
|
||||
if (n == 0 || n > s->free_blocks)
|
||||
return 0;
|
||||
|
||||
// Two sweeps: from the rotating hint to the end of the volume, then from
|
||||
// the start back to it. Starting every search at block 0 would make the
|
||||
// front of the volume the only place anything is ever written and turn
|
||||
// allocation into a linear scan of everything already used.
|
||||
for (int pass = 0; pass < 2; pass++) {
|
||||
uint64_t lo = pass == 0 ? s->hint : SCHFS_FIRST_BLOCK;
|
||||
uint64_t hi = pass == 0 ? s->nblocks : s->hint;
|
||||
uint64_t run = 0, first = 0;
|
||||
|
||||
for (uint64_t b = lo; b < hi; ) {
|
||||
if (b % 64 == 0 && bit_word(s->inuse, b) == ~0ULL) {
|
||||
run = 0;
|
||||
b += 64;
|
||||
continue;
|
||||
}
|
||||
if (bit_test(s->inuse, b)) {
|
||||
run = 0;
|
||||
b++;
|
||||
continue;
|
||||
}
|
||||
if (run == 0)
|
||||
first = b;
|
||||
run++;
|
||||
b++;
|
||||
if (run == n) {
|
||||
for (uint64_t i = 0; i < n; i++)
|
||||
bit_set(s->inuse, first + i);
|
||||
s->free_blocks -= n;
|
||||
s->hint = first + n < s->nblocks ? first + n : SCHFS_FIRST_BLOCK;
|
||||
return first;
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint64_t
|
||||
schfs_space_alloc(struct schfs_space *s)
|
||||
{
|
||||
return schfs_space_alloc_run(s, 1);
|
||||
}
|
||||
|
||||
void
|
||||
schfs_space_free(struct schfs_space *s, uint64_t blk)
|
||||
{
|
||||
if (blk < SCHFS_FIRST_BLOCK || blk >= s->nblocks)
|
||||
return;
|
||||
if (!bit_test(s->inuse, blk))
|
||||
return; /* never allocated, or freed twice */
|
||||
if (bit_test(s->pend, blk))
|
||||
return; /* already queued this group */
|
||||
bit_set(s->pend, blk);
|
||||
s->pending++;
|
||||
}
|
||||
|
||||
void
|
||||
schfs_space_commit(struct schfs_space *s)
|
||||
{
|
||||
uint64_t words = SCHFS_SPACE_BITS_PER_PAGE / 64;
|
||||
for (int p = 0; p < s->npages; p++) {
|
||||
uint64_t *pend = s->pend[p];
|
||||
uint64_t *inuse = s->inuse[p];
|
||||
for (uint64_t w = 0; w < words; w++) {
|
||||
if (pend[w] == 0)
|
||||
continue;
|
||||
s->free_blocks += popcount64(pend[w] & inuse[w]);
|
||||
inuse[w] &= ~pend[w];
|
||||
pend[w] = 0;
|
||||
}
|
||||
}
|
||||
s->pending = 0;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Deriving the map from the committed tree */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
// Every extent record in a leaf names a run of data blocks. A range shared by
|
||||
// several files (a reflink, a snapshot) appears more than once, which is why
|
||||
// schfs_space_mark is idempotent rather than counting.
|
||||
static void
|
||||
mark_leaf_extents(struct schfs_space *s, const void *blk)
|
||||
{
|
||||
const struct schfs_item *it = SCHFS_ITEMS(blk);
|
||||
int n = SCHFS_HDR(blk)->nritems;
|
||||
|
||||
for (int i = 0; i < n; i++) {
|
||||
if (it[i].key.type != SCHFS_KEY_EXTENT)
|
||||
continue;
|
||||
if (it[i].size != sizeof(struct schfs_extent))
|
||||
continue;
|
||||
if ((int)it[i].offset + (int)it[i].size > SCHFS_BLKSIZE)
|
||||
continue;
|
||||
const struct schfs_extent *e =
|
||||
(const struct schfs_extent *)((const char *)blk + it[i].offset);
|
||||
schfs_space_mark(s, e->disk_block, e->nblocks);
|
||||
}
|
||||
}
|
||||
|
||||
// Walk the whole tree, marking every block it reaches.
|
||||
//
|
||||
// Iterative, with one block buffer and a path of block numbers, for the same
|
||||
// reason everything else here is: a recursive walk keeps a block per level and
|
||||
// the kernel stack is one block in total. Climbing re-reads the parent, so an
|
||||
// internal node is read once per child — mount-time cost that the buffer cache
|
||||
// largely absorbs, in exchange for bounded memory.
|
||||
int
|
||||
schfs_space_scan(struct schfs_vol *v)
|
||||
{
|
||||
struct schfs_space *s = &v->space;
|
||||
void *blk = v->ctx.a;
|
||||
|
||||
struct {
|
||||
uint64_t nr;
|
||||
uint32_t crc;
|
||||
int idx; /* next child to descend into */
|
||||
} path[SCHFS_MAX_LEVEL + 1];
|
||||
|
||||
int d = 0;
|
||||
path[0].nr = v->tree.root;
|
||||
path[0].crc = v->tree.root_crc;
|
||||
path[0].idx = 0;
|
||||
schfs_space_mark(s, v->tree.root, 1);
|
||||
|
||||
for (;;) {
|
||||
if (schfs_node_read(&v->dev, path[d].nr, path[d].crc, blk) != 0)
|
||||
return -2;
|
||||
|
||||
if (SCHFS_HDR(blk)->level == 0) {
|
||||
mark_leaf_extents(s, blk);
|
||||
d--;
|
||||
} else if (path[d].idx < SCHFS_HDR(blk)->nritems) {
|
||||
struct schfs_kptr *kp = &SCHFS_KPTRS(blk)[path[d].idx];
|
||||
path[d].idx++;
|
||||
if (d + 1 > SCHFS_MAX_LEVEL)
|
||||
return -2;
|
||||
d++;
|
||||
path[d].nr = kp->blocknr;
|
||||
path[d].crc = kp->crc;
|
||||
path[d].idx = 0;
|
||||
schfs_space_mark(s, path[d].nr, 1);
|
||||
continue;
|
||||
} else {
|
||||
d--; /* every child visited */
|
||||
}
|
||||
|
||||
if (d < 0)
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
414
kernel/fs/schfs/vfs.c
Normal file
414
kernel/fs/schfs/vfs.c
Normal file
|
|
@ -0,0 +1,414 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
// SchFS behind the VFS: the fs_ops table that makes SchFS files reachable
|
||||
// through open(2), read(2), write(2) and the rest.
|
||||
//
|
||||
// Everything below is translation, not filesystem logic. The filesystem lives
|
||||
// in btree.c/space.c/vol.c/obj.c/objw.c and is driven through the schfs_k*
|
||||
// entry points in kmount.c, each of which is one transaction. This file's whole
|
||||
// job is turning a `struct inode` into an object id and back.
|
||||
//
|
||||
// ---------------------------------------------------------------------------
|
||||
// The two models, and where they disagree
|
||||
//
|
||||
// The VFS inherited xv6's shape: allocate an inode, fill it in, then link a
|
||||
// name to it. SchFS has no free-standing inode — an object exists because a
|
||||
// record with its id exists — so schfs_ialloc writes an inode record with
|
||||
// nlink 0 and no name at all. Nothing can reach it until dirlink adds an entry.
|
||||
// If the caller dies in between, the record is unreferenced but harmless, and
|
||||
// it never reaches the disk anyway: nothing is durable until the transaction
|
||||
// that created it commits.
|
||||
//
|
||||
// The other disagreement is `nlink`. The VFS adjusts it itself (create sets it
|
||||
// to 1, link bumps it, unlink drops it) and then calls iupdate. So the object
|
||||
// layer's own link accounting is deliberately bypassed here: schfs_dir_add and
|
||||
// schfs_dir_del are used rather than schfs_link and schfs_unlink, and iput's
|
||||
// ifree hook is what releases an object once the VFS has decided it has no
|
||||
// names left. Two pieces of code counting links independently is precisely how
|
||||
// a filesystem loses a file that is still referenced.
|
||||
|
||||
#include "types.h"
|
||||
#include "x86.h"
|
||||
#include "defs.h"
|
||||
#include "param.h"
|
||||
#include "stat.h"
|
||||
#include "spinlock.h"
|
||||
#include "proc.h" /* `proc` — copyin/copyout need the current pgdir */
|
||||
#include "sleeplock.h"
|
||||
#include "fs.h"
|
||||
#include "buf.h"
|
||||
#include "file.h"
|
||||
|
||||
#include "schfs_int.h"
|
||||
|
||||
// The VFS talks in T_*; the disk format uses SCHFS_T_*. Neither is going to
|
||||
// change to suit the other, so the mapping lives here, once, in both
|
||||
// directions rather than as an assumption that the numbers happen to line up.
|
||||
static short
|
||||
type_to_vfs(uint16_t t)
|
||||
{
|
||||
switch (t) {
|
||||
case SCHFS_T_DIR: return T_DIR;
|
||||
case SCHFS_T_DEV: return T_DEV;
|
||||
case SCHFS_T_SYMLINK: return T_SYMLINK;
|
||||
default: return T_FILE;
|
||||
}
|
||||
}
|
||||
|
||||
static uint16_t
|
||||
type_from_vfs(short t)
|
||||
{
|
||||
switch (t) {
|
||||
case T_DIR: return SCHFS_T_DIR;
|
||||
case T_DEV: return SCHFS_T_DEV;
|
||||
case T_SYMLINK: return SCHFS_T_SYMLINK;
|
||||
default: return SCHFS_T_FILE;
|
||||
}
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Inode load / store / free */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
static void
|
||||
schfs_vfs_iload(struct inode *ip)
|
||||
{
|
||||
struct schfs_inode in;
|
||||
|
||||
if (schfs_kstat(ip->inum, &in) != 0) {
|
||||
// An unreadable inode on the root filesystem is a panic in v1 because the
|
||||
// system cannot continue without it. Here it is a mounted volume that may
|
||||
// simply be damaged, so report an empty file and let the caller fail
|
||||
// normally rather than taking the machine down with it.
|
||||
ip->type = T_FILE;
|
||||
ip->nlink = 0;
|
||||
ip->size = 0;
|
||||
ip->valid = 1;
|
||||
return;
|
||||
}
|
||||
|
||||
ip->type = type_to_vfs(in.type);
|
||||
ip->major = (short)in.rdev_major;
|
||||
ip->minor = (short)in.rdev_minor;
|
||||
ip->nlink = (short)in.nlink;
|
||||
ip->size = in.size;
|
||||
ip->rwmode = RW;
|
||||
ip->supermode = 0;
|
||||
ip->showmode = 1;
|
||||
ip->atime = (uint)in.atime;
|
||||
ip->mtime = (uint)in.mtime;
|
||||
ip->ctime = (uint)in.ctime;
|
||||
memset(ip->addrs, 0, sizeof(ip->addrs));
|
||||
ip->valid = 1;
|
||||
}
|
||||
|
||||
static void
|
||||
schfs_vfs_istore(struct inode *ip)
|
||||
{
|
||||
struct schfs_inode in;
|
||||
|
||||
// Read-modify-write rather than build-from-scratch: the on-disk record holds
|
||||
// fields the VFS has no slot for (uid, gid, the real mode bits, the
|
||||
// generation), and rebuilding it here would quietly zero every one of them.
|
||||
if (schfs_kstat(ip->inum, &in) != 0) {
|
||||
memset(&in, 0, sizeof(in));
|
||||
in.mode = ip->type == T_DIR ? 0755 : 0644;
|
||||
}
|
||||
|
||||
in.type = type_from_vfs(ip->type);
|
||||
in.rdev_major = (uint16_t)ip->major;
|
||||
in.rdev_minor = (uint16_t)ip->minor;
|
||||
in.nlink = (uint32_t)ip->nlink;
|
||||
in.size = ip->size;
|
||||
in.atime = ip->atime;
|
||||
in.mtime = ip->mtime;
|
||||
in.ctime = ip->ctime;
|
||||
|
||||
schfs_ksetattr(ip->inum, &in);
|
||||
}
|
||||
|
||||
static void
|
||||
schfs_vfs_ifree(struct inode *ip)
|
||||
{
|
||||
schfs_kfree(ip->inum);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Data */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
// Reads and writes are staged through a bounce buffer rather than going
|
||||
// straight to user memory, because the filesystem takes a sleeplock and walks
|
||||
// the tree while a copyout could fault. One page at a time keeps that buffer
|
||||
// off the 4096-byte kernel stack -- it is kalloc'd -- and bounds how long the
|
||||
// volume lock is held.
|
||||
#define BOUNCE 2048
|
||||
|
||||
static int
|
||||
schfs_vfs_read(struct inode *ip, int user_dst, uint64 dst, uint64 off, uint n)
|
||||
{
|
||||
char *tmp = kalloc();
|
||||
if (tmp == 0)
|
||||
return -1;
|
||||
|
||||
uint done = 0;
|
||||
while (done < n) {
|
||||
uint want = n - done;
|
||||
if (want > BOUNCE)
|
||||
want = BOUNCE;
|
||||
|
||||
long got = schfs_kread(ip->inum, off + done, tmp, want);
|
||||
if (got < 0) { kfree(tmp); return done ? (int)done : -1; }
|
||||
if (got == 0) break; /* end of file */
|
||||
|
||||
if (user_dst) {
|
||||
if (copyout(myproc()->pgdir, dst + done, tmp, (uint)got) < 0)
|
||||
break;
|
||||
} else {
|
||||
memmove((char *)(dst + done), tmp, (uint)got);
|
||||
}
|
||||
done += (uint)got;
|
||||
if ((uint)got < want)
|
||||
break; /* short read: end of file */
|
||||
}
|
||||
|
||||
kfree(tmp);
|
||||
return (int)done;
|
||||
}
|
||||
|
||||
static int
|
||||
schfs_vfs_write(struct inode *ip, int user_src, uint64 src, uint64 off, uint n)
|
||||
{
|
||||
char *tmp = kalloc();
|
||||
if (tmp == 0)
|
||||
return -1;
|
||||
|
||||
uint done = 0;
|
||||
while (done < n) {
|
||||
uint want = n - done;
|
||||
if (want > BOUNCE)
|
||||
want = BOUNCE;
|
||||
|
||||
if (user_src) {
|
||||
if (copyin(myproc()->pgdir, (addr_t)(src + done), tmp, (uint64)want) < 0)
|
||||
break;
|
||||
} else {
|
||||
memmove(tmp, (char *)(src + done), want);
|
||||
}
|
||||
|
||||
long put = schfs_kwrite(ip->inum, off + done, tmp, want);
|
||||
if (put <= 0) break;
|
||||
done += (uint)put;
|
||||
if ((uint)put < want) break;
|
||||
}
|
||||
|
||||
kfree(tmp);
|
||||
if (off + done > ip->size)
|
||||
ip->size = off + done;
|
||||
return (int)done;
|
||||
}
|
||||
|
||||
static void
|
||||
schfs_vfs_truncate(struct inode *ip, uint64 newsize)
|
||||
{
|
||||
if (schfs_ktruncate(ip->inum, newsize) == 0)
|
||||
ip->size = newsize;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Directories */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
static struct inode *
|
||||
schfs_vfs_dirlookup(struct inode *dp, char *name, uint *poff)
|
||||
{
|
||||
// "." and ".." are not stored: a directory costs nothing until it has real
|
||||
// entries. Both are answered from what the VFS already knows, except ".."
|
||||
// at the mount root, which the mount layer redirects before we get here.
|
||||
if (name[0] == '.' && name[1] == '\0') {
|
||||
if (poff) *poff = 0;
|
||||
return iget(dp->dev, dp->inum);
|
||||
}
|
||||
|
||||
unsigned namelen = 0;
|
||||
while (name[namelen]) namelen++;
|
||||
|
||||
if (namelen == 2 && name[0] == '.' && name[1] == '.') {
|
||||
struct schfs_inode in;
|
||||
if (schfs_kstat(dp->inum, &in) != 0)
|
||||
return 0;
|
||||
if (poff) *poff = 0;
|
||||
return iget(dp->dev, in.parent ? in.parent : dp->inum);
|
||||
}
|
||||
|
||||
uint64 ino = schfs_klookup_at(dp->inum, name, namelen);
|
||||
if (ino == 0)
|
||||
return 0;
|
||||
// poff is v1's byte offset into the directory file. SchFS finds an entry by
|
||||
// name, so there is nothing meaningful to hand back; dirunlink below takes
|
||||
// the name and ignores it.
|
||||
if (poff) *poff = 0;
|
||||
return iget(dp->dev, ino);
|
||||
}
|
||||
|
||||
static int
|
||||
schfs_vfs_dirlink(struct inode *dp, char *name, uint64 inum)
|
||||
{
|
||||
unsigned namelen = 0;
|
||||
while (name[namelen]) namelen++;
|
||||
if (namelen == 0 || namelen > SCHFS_NAME_MAX)
|
||||
return -1;
|
||||
|
||||
struct schfs_inode in;
|
||||
if (schfs_kstat(inum, &in) != 0)
|
||||
return -1;
|
||||
|
||||
int r = schfs_kdiradd(dp->inum, name, namelen, inum, in.type);
|
||||
if (r != 0)
|
||||
return -1;
|
||||
|
||||
// A subdirectory's ".." is synthesised from its parent field, so moving or
|
||||
// creating one has to set that field or the new directory reports the wrong
|
||||
// parent -- and every ".." walk out of it lands somewhere else entirely.
|
||||
if (in.type == SCHFS_T_DIR && in.parent != dp->inum) {
|
||||
in.parent = dp->inum;
|
||||
schfs_ksetattr(inum, &in);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
schfs_vfs_dirunlink(struct inode *dp, char *name, uint off)
|
||||
{
|
||||
(void)off;
|
||||
unsigned namelen = 0;
|
||||
while (name[namelen]) namelen++;
|
||||
return schfs_kunlink(dp->inum, name, namelen) == 0 ? 0 : -1;
|
||||
}
|
||||
|
||||
static int
|
||||
schfs_vfs_dirempty(struct inode *dp)
|
||||
{
|
||||
return schfs_kdirempty(dp->inum);
|
||||
}
|
||||
|
||||
static struct inode *
|
||||
schfs_vfs_ialloc(uint dev, short type)
|
||||
{
|
||||
uint64 ino = 0;
|
||||
if (schfs_kalloc_object(type_from_vfs(type), type == T_DIR ? 0755 : 0644,
|
||||
&ino) != 0)
|
||||
return 0;
|
||||
return iget(dev, ino);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* getdents64 */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
// One record's worth of state for the callback below.
|
||||
//
|
||||
// Entries come back in name-hash order, which has nothing to do with their
|
||||
// names -- POSIX promises no order, and hash order is what makes a lookup one
|
||||
// tree descent instead of a scan. The position handed back to userland is
|
||||
// therefore an *index*: entry N of the walk. Resuming means walking to N again,
|
||||
// which readdir already does linearly, so a full listing stays linear overall
|
||||
// only because the common case is resuming exactly where the last call stopped.
|
||||
struct gd_ctx {
|
||||
char *out;
|
||||
int cap;
|
||||
int written;
|
||||
uint64 skip; // entries to step over before emitting
|
||||
uint64 index; // entries seen so far
|
||||
int full;
|
||||
};
|
||||
|
||||
static int
|
||||
gd_entry(uint64 ino, unsigned short type, const char *name, unsigned namelen,
|
||||
void *arg)
|
||||
{
|
||||
struct gd_ctx *g = arg;
|
||||
|
||||
if (g->index++ < g->skip)
|
||||
return 0;
|
||||
if (g->full)
|
||||
return 1;
|
||||
|
||||
if (namelen > 255)
|
||||
namelen = 255;
|
||||
int reclen = (8 + 8 + 2 + 1 + (int)namelen + 1 + 7) & ~7;
|
||||
if (g->written + reclen > g->cap) {
|
||||
g->full = 1;
|
||||
return 1; /* stop; the caller will come back */
|
||||
}
|
||||
|
||||
char *k = g->out + g->written;
|
||||
uint64 *p64 = (uint64 *)k;
|
||||
p64[0] = ino;
|
||||
p64[1] = g->index; /* resume position: entries consumed */
|
||||
*(uint16 *)(k + 16) = (uint16)reclen;
|
||||
*(uint8 *)(k + 18) = type == SCHFS_T_DIR ? 4 /* DT_DIR */
|
||||
: type == SCHFS_T_DEV ? 2 /* DT_CHR */
|
||||
: type == SCHFS_T_SYMLINK ? 10 /* DT_LNK */
|
||||
: 8; /* DT_REG */
|
||||
memmove(k + 19, name, namelen);
|
||||
memset(k + 19 + namelen, 0, reclen - 19 - namelen);
|
||||
g->written += reclen;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
schfs_vfs_getdents(struct inode *dp, char *out, int cap, uint64 *poff)
|
||||
{
|
||||
struct gd_ctx g;
|
||||
g.out = out;
|
||||
g.cap = cap;
|
||||
g.written = 0;
|
||||
g.skip = *poff;
|
||||
g.index = 0;
|
||||
g.full = 0;
|
||||
|
||||
// "." and ".." are synthesised: they are not records, so a walk would never
|
||||
// produce them, and every readdir caller expects both.
|
||||
static const char *dots[2] = { ".", ".." };
|
||||
for (int i = 0; i < 2; i++) {
|
||||
if (g.index++ < g.skip)
|
||||
continue;
|
||||
int reclen = (8 + 8 + 2 + 1 + (i + 1) + 1 + 7) & ~7;
|
||||
if (g.written + reclen > cap)
|
||||
return g.written;
|
||||
char *k = out + g.written;
|
||||
uint64 *p64 = (uint64 *)k;
|
||||
p64[0] = dp->inum; /* close enough for ".."; nothing reads it */
|
||||
p64[1] = g.index;
|
||||
*(uint16 *)(k + 16) = (uint16)reclen;
|
||||
*(uint8 *)(k + 18) = 4; /* DT_DIR */
|
||||
memmove(k + 19, dots[i], i + 1);
|
||||
memset(k + 19 + i + 1, 0, reclen - 19 - i - 1);
|
||||
g.written += reclen;
|
||||
}
|
||||
|
||||
if (schfs_kreaddir(dp->inum, gd_entry, &g) != 0 && g.written == 0)
|
||||
return -1;
|
||||
|
||||
*poff = g.index;
|
||||
return g.written;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
const struct fs_ops schfs_ops = {
|
||||
.name = "schfs",
|
||||
.iload = schfs_vfs_iload,
|
||||
.istore = schfs_vfs_istore,
|
||||
.ifree = schfs_vfs_ifree,
|
||||
.read = schfs_vfs_read,
|
||||
.write = schfs_vfs_write,
|
||||
.truncate = schfs_vfs_truncate,
|
||||
.dirlookup = schfs_vfs_dirlookup,
|
||||
.dirlink = schfs_vfs_dirlink,
|
||||
.dirunlink = schfs_vfs_dirunlink,
|
||||
.dirempty = schfs_vfs_dirempty,
|
||||
.ialloc = schfs_vfs_ialloc,
|
||||
.getdents = schfs_vfs_getdents,
|
||||
};
|
||||
299
kernel/fs/schfs/vol.c
Normal file
299
kernel/fs/schfs/vol.c
Normal file
|
|
@ -0,0 +1,299 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
// SchFS volume layer: the uberblock ring.
|
||||
//
|
||||
// A ring of SCHFS_UBER_COUNT slots is written round-robin, each stamped with a
|
||||
// transaction number and a checksum. Mounting reads every slot and adopts the
|
||||
// highest-numbered one that validates.
|
||||
//
|
||||
// That single rule replaces journalling entirely. A commit writes one block; it
|
||||
// either lands whole or it does not. If it does not, its checksum fails, the
|
||||
// previous slot is still the highest valid one, and the volume comes up at the
|
||||
// previous transaction with no replay and no fsck. Rotating through many slots
|
||||
// rather than overwriting one means a run of bad sectors under the current slot
|
||||
// cannot take the volume with it.
|
||||
|
||||
#include "schfs_int.h"
|
||||
|
||||
#ifdef SCHFS_HOSTED
|
||||
#include <string.h>
|
||||
#else
|
||||
#include "types.h"
|
||||
#include "defs.h"
|
||||
#endif
|
||||
|
||||
// Checksum an uberblock with its own crc field zeroed, the same convention the
|
||||
// tree nodes use.
|
||||
static uint32_t
|
||||
uber_crc(struct schfs_uberblock *u)
|
||||
{
|
||||
uint32_t saved = u->crc;
|
||||
u->crc = 0;
|
||||
uint32_t c = schfs_crc32c(0, u, sizeof(*u));
|
||||
u->crc = saved;
|
||||
return c;
|
||||
}
|
||||
|
||||
static int
|
||||
uber_valid(struct schfs_uberblock *u)
|
||||
{
|
||||
if (u->magic != SCHFS_MAGIC)
|
||||
return 0;
|
||||
if (u->version != SCHFS_VERSION)
|
||||
return 0;
|
||||
// A volume made with a different block size would otherwise be read with
|
||||
// every offset wrong, which looks exactly like corruption.
|
||||
if (u->blocksize != SCHFS_BLKSIZE)
|
||||
return 0;
|
||||
if (uber_crc(u) != u->crc)
|
||||
return 0;
|
||||
// Self-consistency: a root inside the ring or past the end of the device is
|
||||
// a corrupt uberblock that happens to checksum, not a usable one.
|
||||
if (u->root < SCHFS_FIRST_BLOCK || u->root >= u->total_blocks)
|
||||
return 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void
|
||||
vol_from_uber(struct schfs_vol *v, struct schfs_uberblock *u, int slot)
|
||||
{
|
||||
v->tree.dev = &v->dev;
|
||||
v->tree.root = u->root;
|
||||
v->tree.root_crc = u->root_crc;
|
||||
v->tree.root_gen = u->root_generation;
|
||||
// The tree's `generation` is the transaction group being *built*, one past
|
||||
// the newest committed one. Setting it to the committed root's generation
|
||||
// instead would tell the tree that every block of the mounted filesystem
|
||||
// belongs to the open transaction, and it would then rewrite the live tree
|
||||
// in place — losing the very thing copy-on-write exists to preserve.
|
||||
v->tree.generation = u->generation + 1;
|
||||
|
||||
v->total_blocks = u->total_blocks;
|
||||
v->free_blocks = u->free_blocks;
|
||||
v->next_objectid = u->next_objectid;
|
||||
v->root_objectid = u->root_objectid;
|
||||
v->generation = u->generation;
|
||||
v->uber_slot = slot;
|
||||
|
||||
memmove(v->uuid, u->uuid, sizeof(v->uuid));
|
||||
memmove(v->label, u->label, sizeof(v->label));
|
||||
v->label[SCHFS_LABEL_MAX - 1] = '\0';
|
||||
}
|
||||
|
||||
int
|
||||
schfs_vol_open(struct schfs_vol *v, struct schfs_bdev *dev,
|
||||
struct schfs_ctx *x, void *(*palloc)(void))
|
||||
{
|
||||
struct schfs_uberblock *u = (struct schfs_uberblock *)x->a;
|
||||
int best_slot = -1;
|
||||
uint64_t best_gen = 0;
|
||||
int io_errors = 0;
|
||||
|
||||
v->dev = *dev;
|
||||
v->ctx = *x;
|
||||
v->tree.dev = &v->dev;
|
||||
|
||||
// Remember which slot won, not a copy of it.
|
||||
//
|
||||
// An uberblock is a whole SCHFS_BLKSIZE block, and keeping one as a local
|
||||
// put 4096 bytes on a kernel stack that is 4096 bytes in total — the canary
|
||||
// in kalloc'd stacks caught it as "kstack overflow" on the very first boot.
|
||||
// Re-reading the winner costs one block read, once, at mount.
|
||||
for (int i = 0; i < SCHFS_UBER_COUNT; i++) {
|
||||
if (v->dev.bread(v->dev.ctx, SCHFS_UBER_START + i, u) != 0) {
|
||||
io_errors++;
|
||||
continue; // an unreadable slot is not fatal: try the rest
|
||||
}
|
||||
if (!uber_valid(u))
|
||||
continue;
|
||||
if (best_slot < 0 || u->generation > best_gen) {
|
||||
best_gen = u->generation;
|
||||
best_slot = i;
|
||||
}
|
||||
}
|
||||
|
||||
if (best_slot < 0)
|
||||
return io_errors == SCHFS_UBER_COUNT ? -2 : -1;
|
||||
|
||||
if (v->dev.bread(v->dev.ctx, SCHFS_UBER_START + best_slot, u) != 0)
|
||||
return -2;
|
||||
if (!uber_valid(u))
|
||||
return -2; /* changed under us, or a flaky device */
|
||||
|
||||
vol_from_uber(v, u, best_slot);
|
||||
v->readonly = 1; // until an allocation map exists, below
|
||||
|
||||
// The newest uberblock passing its own checksum does not prove the tree it
|
||||
// names is readable — a misdirected write could have landed on the root.
|
||||
// Check now, while there is still the option of falling back, rather than
|
||||
// discovering it on the first lookup.
|
||||
if (v->dev.bread(v->dev.ctx, v->tree.root, x->a) != 0)
|
||||
return -2;
|
||||
{
|
||||
struct schfs_node_hdr *h = (struct schfs_node_hdr *)x->a;
|
||||
if (h->magic != SCHFS_NODE_MAGIC || h->blocknr != v->tree.root)
|
||||
return -2;
|
||||
}
|
||||
|
||||
// Rebuild the allocation map from the tree just adopted. Without a palloc
|
||||
// the volume stays read-only, which is exactly right for a tool that only
|
||||
// inspects an image: it can then never allocate, rather than allocating
|
||||
// from a map that says everything is free.
|
||||
if (palloc) {
|
||||
int sr = schfs_space_init(&v->space, v->total_blocks, palloc);
|
||||
if (sr != 0)
|
||||
return sr;
|
||||
if (schfs_space_scan(v) != 0)
|
||||
return -2;
|
||||
v->free_blocks = v->space.free_blocks;
|
||||
v->readonly = 0;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
schfs_vol_commit(struct schfs_vol *v)
|
||||
{
|
||||
struct schfs_uberblock *u = (struct schfs_uberblock *)v->ctx.a;
|
||||
|
||||
v->generation++;
|
||||
v->uber_slot = (v->uber_slot + 1) % SCHFS_UBER_COUNT;
|
||||
|
||||
memset(u, 0, sizeof(*u));
|
||||
u->magic = SCHFS_MAGIC;
|
||||
u->version = SCHFS_VERSION;
|
||||
u->blocksize = SCHFS_BLKSIZE;
|
||||
u->generation = v->generation;
|
||||
u->root = v->tree.root;
|
||||
u->root_crc = v->tree.root_crc;
|
||||
u->root_generation = v->tree.root_gen;
|
||||
u->total_blocks = v->total_blocks;
|
||||
// Blocks this group released count as free *as of this uberblock*: nothing
|
||||
// the root below reaches touches them. They are only still marked in use in
|
||||
// memory because the group being superseded is what a crash would land on
|
||||
// until this very block is written.
|
||||
u->free_blocks = v->space.nblocks
|
||||
? v->space.free_blocks + v->space.pending
|
||||
: v->free_blocks;
|
||||
u->next_objectid = v->next_objectid;
|
||||
u->root_objectid = v->root_objectid;
|
||||
memmove(u->uuid, v->uuid, sizeof(u->uuid));
|
||||
memmove(u->label, v->label, sizeof(u->label));
|
||||
u->crc = uber_crc(u);
|
||||
|
||||
if (v->dev.bwrite(v->dev.ctx, SCHFS_UBER_START + v->uber_slot, u) != 0)
|
||||
return -2;
|
||||
|
||||
// Order matters here, and it is the whole crash-safety argument in three
|
||||
// lines. Only now that the new uberblock is on the disk is the group this
|
||||
// freed blocks in superseded, so only now may those blocks be handed out
|
||||
// again; releasing them before the write would let a crash come back to a
|
||||
// tree whose blocks had already been reused.
|
||||
if (v->space.nblocks) {
|
||||
schfs_space_commit(&v->space);
|
||||
v->free_blocks = v->space.free_blocks;
|
||||
}
|
||||
|
||||
// Everything written from here on belongs to the next transaction group, so
|
||||
// the blocks just published become read-only to the tree again and the
|
||||
// copy-on-write rule resumes. Advancing this is the whole reason a commit
|
||||
// is a barrier rather than just another block write.
|
||||
v->tree.generation = v->generation + 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
schfs_vol_format(struct schfs_vol *v, struct schfs_bdev *dev,
|
||||
uint64_t total_blocks, const char *label,
|
||||
struct schfs_ctx *x, void *(*palloc)(void))
|
||||
{
|
||||
if (total_blocks <= SCHFS_FIRST_BLOCK)
|
||||
return -3;
|
||||
|
||||
memset(v, 0, sizeof(*v));
|
||||
v->dev = *dev;
|
||||
v->ctx = *x;
|
||||
v->tree.dev = &v->dev;
|
||||
v->total_blocks = total_blocks;
|
||||
v->root_objectid = SCHFS_ROOT_OBJECTID;
|
||||
|
||||
// Nothing is allocated on a fresh volume beyond the boot block and the
|
||||
// ring, so the map starts empty rather than being scanned into existence.
|
||||
{
|
||||
int sr = schfs_space_init(&v->space, total_blocks, palloc);
|
||||
if (sr != 0)
|
||||
return sr;
|
||||
}
|
||||
v->free_blocks = v->space.free_blocks;
|
||||
// The root's object id is consumed by the root itself, so allocation must
|
||||
// start *past* it. Starting at SCHFS_FIRST_OBJECTID instead handed the
|
||||
// first directory created the same id as the root: that directory then
|
||||
// silently became the root, so it contained itself and any tree walk
|
||||
// recursed until the stack ran out.
|
||||
v->next_objectid = SCHFS_ROOT_OBJECTID + 1;
|
||||
v->generation = 0; /* nothing committed yet */
|
||||
v->tree.generation = 1; /* ...so this is the group being built */
|
||||
v->uber_slot = -1;
|
||||
v->readonly = 0;
|
||||
|
||||
if (label) {
|
||||
unsigned i = 0;
|
||||
for (; i < SCHFS_LABEL_MAX - 1 && label[i]; i++)
|
||||
v->label[i] = label[i];
|
||||
v->label[i] = '\0';
|
||||
}
|
||||
|
||||
// A UUID from the block count and a fixed salt. Not random — mkfs is
|
||||
// deliberately reproducible, so two runs over the same tree produce
|
||||
// byte-identical images and a diff means a real change.
|
||||
{
|
||||
uint32_t a = schfs_crc32c(0, &total_blocks, sizeof(total_blocks));
|
||||
uint32_t b = schfs_crc32c(a, v->label, SCHFS_LABEL_MAX);
|
||||
for (int i = 0; i < 16; i++)
|
||||
v->uuid[i] = (uint8_t)((i & 1 ? a : b) >> ((i % 4) * 8));
|
||||
}
|
||||
|
||||
// Zero the whole ring so a stale uberblock from a previous filesystem on
|
||||
// this device cannot outrank ours and be mounted instead.
|
||||
memset(x->a, 0, SCHFS_BLKSIZE);
|
||||
for (int i = 0; i < SCHFS_UBER_COUNT; i++)
|
||||
if (v->dev.bwrite(v->dev.ctx, SCHFS_UBER_START + i, x->a) != 0)
|
||||
return -2;
|
||||
|
||||
if (schfs_btree_create(&v->tree, &v->dev, x) != 0)
|
||||
return -2;
|
||||
|
||||
return schfs_vol_commit(v);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Allocation */
|
||||
/* */
|
||||
/* Thin adapters over the space manager, so callers wiring up a */
|
||||
/* schfs_bdev have something with the right shape to point at. The */
|
||||
/* readonly check lives here rather than in space.c because "this */
|
||||
/* volume may not be written" is a property of the mount, not of the */
|
||||
/* bitmap. */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
uint64_t
|
||||
schfs_vol_alloc(struct schfs_vol *v)
|
||||
{
|
||||
if (v->readonly)
|
||||
return 0;
|
||||
return schfs_space_alloc(&v->space);
|
||||
}
|
||||
|
||||
uint64_t
|
||||
schfs_vol_alloc_run(struct schfs_vol *v, uint64_t n)
|
||||
{
|
||||
if (v->readonly)
|
||||
return 0;
|
||||
return schfs_space_alloc_run(&v->space, n);
|
||||
}
|
||||
|
||||
void
|
||||
schfs_vol_free(struct schfs_vol *v, uint64_t blk)
|
||||
{
|
||||
schfs_space_free(&v->space, blk);
|
||||
}
|
||||
104
kernel/kernel.ld
Normal file
104
kernel/kernel.ld
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
/* Simple linker script for the JOS kernel.
|
||||
See the GNU ld 'info' manual ("info ld") to learn the syntax. */
|
||||
|
||||
/* OUTPUT_FORMAT("elf32-i386", "elf32-i386", "elf32-i386") */
|
||||
/* OUTPUT_FORMAT("elf64-x86-64", "elf64-x86-64", "elf64-x86-64") */
|
||||
OUTPUT_FORMAT("elf64-x86-64")
|
||||
OUTPUT_ARCH(i386:x86-64)
|
||||
ENTRY(_start)
|
||||
|
||||
mboot_load_addr = 0x00100000;
|
||||
|
||||
SECTIONS
|
||||
{
|
||||
/* Link the kernel at this address: "." means the current address */
|
||||
/* Must be equal to KERNLINK */
|
||||
. = 0xFFFF800000100000;
|
||||
|
||||
PROVIDE(begin = .);
|
||||
|
||||
/* The .l* section names are the x86-64 psABI's "large" sections, and they
|
||||
* are not optional decoration: with -mcmodel=large both LLVM and GCC 13+
|
||||
* put nearly all code and data into .ltext/.lrodata/.ldata/.lbss rather
|
||||
* than the classic names.
|
||||
*
|
||||
* A script that does not name them still links. The large sections become
|
||||
* orphans placed after everything the script did name, which puts them past
|
||||
* PROVIDE(end = .) -- so kinit1(end, ...) hands the page allocator a megabyte
|
||||
* of live .lbss and the kernel destroys itself within a few instructions of
|
||||
* its first printf. That is exactly how this was found: the boot printed one
|
||||
* line and triple-faulted in a loop.
|
||||
*
|
||||
* Matched alongside their small counterparts so the layout is unchanged for
|
||||
* a toolchain that does not use them. */
|
||||
.text : AT(mboot_load_addr) {
|
||||
*(.text .rela.text .stub .text.* .gnu.linkonce.t.*)
|
||||
*(.ltext .ltext.*)
|
||||
}
|
||||
|
||||
PROVIDE(etext = .); /* Define the 'etext' symbol to this value */
|
||||
|
||||
.rodata : {
|
||||
*(.rodata .rodata.* .gnu.linkonce.r.*)
|
||||
*(.lrodata .lrodata.*)
|
||||
}
|
||||
|
||||
/* Thread-local storage.
|
||||
*
|
||||
* The kernel really does use TLS: `__thread struct cpu *cpu` and
|
||||
* `__thread struct proc *proc` (kernel/arch/x86_64/vm.c), reached through
|
||||
* FS_BASE, which seginit() points at kernel_fs_base.
|
||||
*
|
||||
* These output sections are what make the linker emit a PT_TLS segment, and
|
||||
* without a PT_TLS the local-exec relocations have nothing to resolve
|
||||
* against: lld left every access as a bare `movq %fs:0x0`, so `cpu` and
|
||||
* `proc` both aliased the thread pointer itself and the kernel died a few
|
||||
* instructions into seginit. GNU ld happened to place an orphan .tbss well
|
||||
* enough to work; that was luck, not correctness.
|
||||
*
|
||||
* Order matters and is checked by more than C: trapasm.S hardcodes `proc` at
|
||||
* %fs:(-8). With cpu at TLS offset 0 and proc at 8 in a 16-byte block, the
|
||||
* local-exec offsets come out as -16 and -8, which is the layout that file
|
||||
* assumes. */
|
||||
.tdata : { *(.tdata .tdata.* .gnu.linkonce.td.*) }
|
||||
.tbss : { *(.tbss .tbss.* .gnu.linkonce.tb.*) *(.tcommon) }
|
||||
|
||||
/* Adjust the address for the data segment to the next page */
|
||||
. = ALIGN(0x1000);
|
||||
|
||||
/* Conventionally, Unix linkers provide pseudo-symbols
|
||||
* etext, edata, and end, at the end of the text, data, and bss.
|
||||
* For the kernel mapping, we need the address at the beginning
|
||||
* of the data section, but that's not one of the conventional
|
||||
* symbols, because the convention started before there was a
|
||||
* read-only rodata section between text and data. */
|
||||
PROVIDE(data = .);
|
||||
|
||||
/* The data segment */
|
||||
.data : {
|
||||
*(.data .data.*)
|
||||
*(.ldata .ldata.*)
|
||||
}
|
||||
|
||||
. = ALIGN(0x1000);
|
||||
|
||||
PROVIDE(edata = .);
|
||||
|
||||
.bss : {
|
||||
*(.bss .bss.*)
|
||||
*(.lbss .lbss.*)
|
||||
*(COMMON)
|
||||
}
|
||||
|
||||
. = ALIGN(0x1000);
|
||||
|
||||
PROVIDE(end = .);
|
||||
|
||||
/DISCARD/ : {
|
||||
*(.eh_frame .rela.eh_frame .note.GNU-stack)
|
||||
}
|
||||
}
|
||||
|
||||
mboot_load_end = mboot_load_addr + (edata - begin);
|
||||
mboot_bss_end = mboot_load_addr + (end - begin);
|
||||
mboot_entry_addr = mboot_load_addr + (mboot_entry - begin);
|
||||
441
kernel/net/unix.c
Normal file
441
kernel/net/unix.c
Normal file
|
|
@ -0,0 +1,441 @@
|
|||
// Unix domain sockets.
|
||||
//
|
||||
// A connected pair is two buffers, not one. Writing to a socket appends to
|
||||
// its *peer's* buffer and reading drains its own, which is what makes a
|
||||
// socket bidirectional where a pipe is not, without needing two of anything.
|
||||
//
|
||||
// **One lock covers the whole table**, rather than a lock per socket. A pipe
|
||||
// gets away with a lock per pipe because every operation touches exactly one
|
||||
// object; a socket operation touches two, and two-object locking needs an
|
||||
// ordering rule that every path obeys or it deadlocks. Worse, the wakeup
|
||||
// races are subtle: a reader that tests `peer_closed` under its own lock while
|
||||
// the closer sets it under the peer's has no interlock at all, and sleeps
|
||||
// through the wakeup that was meant for it. With one lock the condition and
|
||||
// the sleep are atomic with respect to every writer of that condition, which
|
||||
// is the property sleep/wakeup actually requires. Thirty-two sockets carrying
|
||||
// GUI messages will not notice the contention.
|
||||
//
|
||||
// **Names are held in a table here, not in the filesystem.** A real Unix
|
||||
// socket appears in a directory listing as a special file, and binding one
|
||||
// creates that node. Doing that properly means a new inode type threaded
|
||||
// through both filesystems, iget, dirlookup and stat -- a VFS change, not a
|
||||
// socket change. The table gives every behaviour that matters: a client
|
||||
// finds a server by a name it agreed in advance, which is the whole job.
|
||||
// What is lost is `ls -l /tmp/schcom` showing an `s`, and `unlink()` of a
|
||||
// socket path failing with ENOENT. Both are worth fixing when the VFS next
|
||||
// gets opened up; neither blocks anything.
|
||||
|
||||
#include "types.h"
|
||||
#include "defs.h"
|
||||
#include "param.h"
|
||||
#include "mmu.h"
|
||||
#include "proc.h"
|
||||
#include "fs.h"
|
||||
#include "spinlock.h"
|
||||
#include "sleeplock.h"
|
||||
#include "file.h"
|
||||
#include "posix.h"
|
||||
#include "errno.h"
|
||||
|
||||
#define _KERNEL_SOCKET_INTERNALS
|
||||
#include "socket.h"
|
||||
|
||||
static struct {
|
||||
struct spinlock lock;
|
||||
struct socket sock[NSOCK];
|
||||
} stab;
|
||||
|
||||
void
|
||||
sockinit(void)
|
||||
{
|
||||
initlock(&stab.lock, "socktab");
|
||||
}
|
||||
|
||||
// Caller holds stab.lock.
|
||||
static struct socket *
|
||||
alloc_locked(int type)
|
||||
{
|
||||
struct socket *s;
|
||||
|
||||
for(s = stab.sock; s < stab.sock + NSOCK; s++){
|
||||
if(s->state == SS_FREE && s->ref == 0){
|
||||
memset(s, 0, sizeof(*s));
|
||||
s->ref = 1;
|
||||
s->type = type;
|
||||
s->state = SS_UNBOUND;
|
||||
return s;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Find a socket listening on this name. Caller holds stab.lock.
|
||||
static struct socket *
|
||||
find_listener(const char *name)
|
||||
{
|
||||
struct socket *s;
|
||||
|
||||
for(s = stab.sock; s < stab.sock + NSOCK; s++)
|
||||
if(s->state == SS_LISTENING && strncmp(s->name, name, UNIX_PATH_MAX) == 0)
|
||||
return s;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Detach s from its peer and tell the peer about it. Caller holds stab.lock.
|
||||
static void
|
||||
orphan_peer(struct socket *s)
|
||||
{
|
||||
struct socket *peer = s->peer;
|
||||
|
||||
if(peer == 0)
|
||||
return;
|
||||
peer->peer_closed = 1;
|
||||
peer->peer = 0;
|
||||
s->peer = 0;
|
||||
|
||||
// Both channels, because the peer may be blocked reading (waiting for data
|
||||
// that will now never come) or writing (waiting for room in a buffer nobody
|
||||
// will ever drain).
|
||||
wakeup(&peer->nread);
|
||||
wakeup(&peer->nwrite);
|
||||
}
|
||||
|
||||
void
|
||||
sockclose(struct socket *s)
|
||||
{
|
||||
int i;
|
||||
|
||||
if(s == 0)
|
||||
return;
|
||||
|
||||
acquire(&stab.lock);
|
||||
if(--s->ref > 0){
|
||||
release(&stab.lock);
|
||||
return;
|
||||
}
|
||||
|
||||
orphan_peer(s);
|
||||
|
||||
// A listener that goes away takes its unaccepted connections with it. Each
|
||||
// queued entry is the *server* side of a connection whose client is already
|
||||
// live and may be writing into it; that client has to be told, or it hangs
|
||||
// on a socket that will never be read.
|
||||
for(i = 0; i < s->nqueue; i++){
|
||||
struct socket *q = s->queue[i];
|
||||
if(q == 0)
|
||||
continue;
|
||||
orphan_peer(q);
|
||||
q->state = SS_FREE;
|
||||
q->ref = 0;
|
||||
}
|
||||
s->nqueue = 0;
|
||||
|
||||
s->state = SS_FREE;
|
||||
s->ref = 0;
|
||||
wakeup(&s->nqueue); // an accept() blocked on this listener
|
||||
release(&stab.lock);
|
||||
}
|
||||
|
||||
void
|
||||
sockdup(struct socket *s)
|
||||
{
|
||||
acquire(&stab.lock);
|
||||
s->ref++;
|
||||
release(&stab.lock);
|
||||
}
|
||||
|
||||
const char *
|
||||
sockname(struct socket *s)
|
||||
{
|
||||
return s->name;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Data
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
int
|
||||
sockread(struct socket *s, char *addr, int n)
|
||||
{
|
||||
int i;
|
||||
|
||||
if(n < 0)
|
||||
return -EINVAL;
|
||||
|
||||
acquire(&stab.lock);
|
||||
while(s->nread == s->nwrite && !s->peer_closed){
|
||||
if(myproc()->killed || s->state == SS_FREE){
|
||||
release(&stab.lock);
|
||||
return -EINTR;
|
||||
}
|
||||
sleep(&s->nread, &stab.lock);
|
||||
}
|
||||
|
||||
for(i = 0; i < n; i++){
|
||||
if(s->nread == s->nwrite)
|
||||
break;
|
||||
addr[i] = s->data[s->nread++ % SOCKBUF];
|
||||
}
|
||||
|
||||
// Waking the writer matters even when nothing was read: it may be blocked
|
||||
// on a full buffer that this call has just made room in.
|
||||
wakeup(&s->nwrite);
|
||||
release(&stab.lock);
|
||||
return i;
|
||||
}
|
||||
|
||||
int
|
||||
sockwrite(struct socket *s, char *addr, int n)
|
||||
{
|
||||
struct socket *dst;
|
||||
int i;
|
||||
|
||||
if(n < 0)
|
||||
return -EINVAL;
|
||||
|
||||
acquire(&stab.lock);
|
||||
|
||||
// Data goes into the peer's buffer, because that is where the peer will
|
||||
// look for it. Same contract as a pipe with no reader: a signal, then an
|
||||
// error. A process that writes to a dead connection and is not told about
|
||||
// it spins forever believing it is making progress.
|
||||
if(s->peer == 0){
|
||||
release(&stab.lock);
|
||||
sigsend(myproc()->pid, SIGPIPE);
|
||||
return -EPIPE;
|
||||
}
|
||||
|
||||
for(i = 0; i < n; i++){
|
||||
// Re-read every iteration: the peer can go away while we sleep below.
|
||||
dst = s->peer;
|
||||
while(dst && dst->nwrite == dst->nread + SOCKBUF){
|
||||
if(myproc()->killed){
|
||||
release(&stab.lock);
|
||||
return i > 0 ? i : -EINTR;
|
||||
}
|
||||
wakeup(&dst->nread);
|
||||
sleep(&dst->nwrite, &stab.lock);
|
||||
dst = s->peer;
|
||||
}
|
||||
if(dst == 0){
|
||||
release(&stab.lock);
|
||||
sigsend(myproc()->pid, SIGPIPE);
|
||||
return i > 0 ? i : -EPIPE; // a short write is still a write
|
||||
}
|
||||
dst->data[dst->nwrite++ % SOCKBUF] = addr[i];
|
||||
}
|
||||
|
||||
wakeup(&s->peer->nread);
|
||||
release(&stab.lock);
|
||||
return i;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Connection setup
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
struct socket *
|
||||
socknew(int domain, int type)
|
||||
{
|
||||
struct socket *s;
|
||||
|
||||
if(domain != AF_UNIX)
|
||||
return 0;
|
||||
if(type != SOCK_STREAM)
|
||||
return 0;
|
||||
|
||||
acquire(&stab.lock);
|
||||
s = alloc_locked(type);
|
||||
release(&stab.lock);
|
||||
return s;
|
||||
}
|
||||
|
||||
// A connected pair with no name and no listener, for socketpair(2).
|
||||
int
|
||||
sockpair(struct socket **pa, struct socket **pb)
|
||||
{
|
||||
struct socket *a, *b;
|
||||
|
||||
acquire(&stab.lock);
|
||||
if((a = alloc_locked(SOCK_STREAM)) == 0){
|
||||
release(&stab.lock);
|
||||
return -ENFILE;
|
||||
}
|
||||
if((b = alloc_locked(SOCK_STREAM)) == 0){
|
||||
a->state = SS_FREE;
|
||||
a->ref = 0;
|
||||
release(&stab.lock);
|
||||
return -ENFILE;
|
||||
}
|
||||
a->peer = b; a->state = SS_CONNECTED;
|
||||
b->peer = a; b->state = SS_CONNECTED;
|
||||
release(&stab.lock);
|
||||
|
||||
*pa = a;
|
||||
*pb = b;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
sockbind(struct socket *s, const char *path)
|
||||
{
|
||||
struct socket *other;
|
||||
|
||||
if(path[0] == '\0')
|
||||
return -EINVAL;
|
||||
|
||||
acquire(&stab.lock);
|
||||
if(s->state != SS_UNBOUND){
|
||||
release(&stab.lock);
|
||||
return -EINVAL;
|
||||
}
|
||||
for(other = stab.sock; other < stab.sock + NSOCK; other++){
|
||||
if(other != s && other->state >= SS_BOUND &&
|
||||
strncmp(other->name, path, UNIX_PATH_MAX) == 0){
|
||||
release(&stab.lock);
|
||||
return -EADDRINUSE;
|
||||
}
|
||||
}
|
||||
safestrcpy(s->name, path, UNIX_PATH_MAX);
|
||||
s->state = SS_BOUND;
|
||||
release(&stab.lock);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
socklisten(struct socket *s, int backlog)
|
||||
{
|
||||
(void)backlog; // the queue is a fixed NBACKLOG either way
|
||||
|
||||
acquire(&stab.lock);
|
||||
if(s->state != SS_BOUND && s->state != SS_LISTENING){
|
||||
release(&stab.lock);
|
||||
return -EINVAL;
|
||||
}
|
||||
s->state = SS_LISTENING;
|
||||
release(&stab.lock);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Returns the server-side socket of a new connection with a reference already
|
||||
// held, or 0 on error.
|
||||
struct socket *
|
||||
sockaccept(struct socket *s)
|
||||
{
|
||||
struct socket *c;
|
||||
int i;
|
||||
|
||||
acquire(&stab.lock);
|
||||
if(s->state != SS_LISTENING){
|
||||
release(&stab.lock);
|
||||
return 0;
|
||||
}
|
||||
|
||||
while(s->nqueue == 0){
|
||||
if(myproc()->killed || s->state != SS_LISTENING){
|
||||
release(&stab.lock);
|
||||
return 0;
|
||||
}
|
||||
sleep(&s->nqueue, &stab.lock);
|
||||
}
|
||||
|
||||
c = s->queue[0];
|
||||
for(i = 0; i + 1 < s->nqueue; i++)
|
||||
s->queue[i] = s->queue[i+1];
|
||||
s->nqueue--;
|
||||
release(&stab.lock);
|
||||
|
||||
return c;
|
||||
}
|
||||
|
||||
int
|
||||
sockconnect(struct socket *s, const char *path)
|
||||
{
|
||||
struct socket *srv, *peer;
|
||||
|
||||
acquire(&stab.lock);
|
||||
if(s->state != SS_UNBOUND && s->state != SS_BOUND){
|
||||
release(&stab.lock);
|
||||
return -EINVAL;
|
||||
}
|
||||
srv = find_listener(path);
|
||||
if(srv == 0){
|
||||
release(&stab.lock);
|
||||
return -ECONNREFUSED;
|
||||
}
|
||||
if(srv->nqueue >= NBACKLOG){
|
||||
release(&stab.lock);
|
||||
return -EAGAIN;
|
||||
}
|
||||
|
||||
// Every connection needs a socket at each end. The listener is not one of
|
||||
// them -- it stays listening, and accept() hands back this new one.
|
||||
peer = alloc_locked(s->type);
|
||||
if(peer == 0){
|
||||
release(&stab.lock);
|
||||
return -ENFILE;
|
||||
}
|
||||
|
||||
peer->peer = s;
|
||||
peer->state = SS_CONNECTED;
|
||||
s->peer = peer;
|
||||
s->state = SS_CONNECTED;
|
||||
|
||||
srv->queue[srv->nqueue++] = peer;
|
||||
wakeup(&srv->nqueue); // an accept() may be blocked on this
|
||||
|
||||
release(&stab.lock);
|
||||
|
||||
// Deliberately does *not* wait for accept(). On Unix a queued connection is
|
||||
// already established: the client may write, and the bytes sit in the
|
||||
// server-side buffer until somebody accepts and reads them. Blocking here
|
||||
// instead would deadlock any server that connects to itself, and would make
|
||||
// a busy server's backlog useless.
|
||||
return 0;
|
||||
}
|
||||
|
||||
// shutdown(2). There is one buffer per direction and no per-direction flag,
|
||||
// so a half-close is approximated: SHUT_RD stops this end reading anything
|
||||
// further, and SHUT_WR/SHUT_RDWR give the peer end-of-file. What programs
|
||||
// actually use shutdown for -- "I have finished sending, read until EOF and
|
||||
// then reply" -- works; what is missing is the ability to keep reading after
|
||||
// SHUT_WR, since the connection is torn down rather than half-closed.
|
||||
int
|
||||
sock_shutdown(struct socket *s, int how)
|
||||
{
|
||||
acquire(&stab.lock);
|
||||
if(s->state != SS_CONNECTED){
|
||||
release(&stab.lock);
|
||||
return -ENOTCONN;
|
||||
}
|
||||
|
||||
if(how == SHUT_RD || how == SHUT_RDWR){
|
||||
s->peer_closed = 1;
|
||||
wakeup(&s->nread);
|
||||
}
|
||||
if(how == SHUT_WR || how == SHUT_RDWR)
|
||||
orphan_peer(s);
|
||||
release(&stab.lock);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Readiness, for poll/select
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
int
|
||||
sockreadable(struct socket *s)
|
||||
{
|
||||
if(s->state == SS_LISTENING)
|
||||
return s->nqueue > 0;
|
||||
return s->nread != s->nwrite || s->peer_closed;
|
||||
}
|
||||
|
||||
// The peer is gone and everything it sent has been read.
|
||||
int
|
||||
sock_hup(struct socket *s)
|
||||
{
|
||||
if(s->state == SS_LISTENING)
|
||||
return 0;
|
||||
return s->peer == 0 && s->nread == s->nwrite;
|
||||
}
|
||||
838
kernel/proc/proc.c
Normal file
838
kernel/proc/proc.c
Normal file
|
|
@ -0,0 +1,838 @@
|
|||
#include "types.h"
|
||||
#include "defs.h"
|
||||
#include "param.h"
|
||||
#include "memlayout.h"
|
||||
#include "mmu.h"
|
||||
#include "x86.h"
|
||||
#include "traps.h"
|
||||
#include "proc.h"
|
||||
#include "spinlock.h"
|
||||
#include "sleeplock.h"
|
||||
#include "fs.h"
|
||||
#include "file.h"
|
||||
#include "posix.h"
|
||||
#include "errno.h"
|
||||
|
||||
// Verify myproc()->fs_base offset matches the hardcoded 0x688 in trapasm.S.
|
||||
_Static_assert(__builtin_offsetof(struct proc, fs_base) == 0x18,
|
||||
"myproc()->fs_base offset changed — update trapasm.S WRITE_USER_FS macro (must be 0x18)");
|
||||
|
||||
struct {
|
||||
struct spinlock lock;
|
||||
struct proc proc[NPROC];
|
||||
} ptable;
|
||||
|
||||
static struct proc *initproc;
|
||||
|
||||
int nextpid = 1;
|
||||
extern void forkret(void);
|
||||
extern void syscall_trapret(void);
|
||||
|
||||
static void wakeup1(void *chan);
|
||||
|
||||
void
|
||||
kstack_check_canary(const char *where)
|
||||
{
|
||||
if(myproc() == 0 || myproc()->kstack == 0)
|
||||
return;
|
||||
if(*(uint64*)(myproc()->kstack) != KSTACK_CANARY){
|
||||
cprintf("kstack canary corrupted for pid %d (%s) at %s: got 0x%p\n",
|
||||
myproc()->pid, myproc()->name, where, *(uint64*)(myproc()->kstack));
|
||||
panic("kstack overflow");
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
pinit(void)
|
||||
{
|
||||
initlock(&ptable.lock, "ptable");
|
||||
}
|
||||
|
||||
static struct proc*
|
||||
allocproc(void)
|
||||
{
|
||||
struct proc *p;
|
||||
char *sp;
|
||||
|
||||
acquire(&ptable.lock);
|
||||
|
||||
for(p = ptable.proc; p < &ptable.proc[NPROC]; p++)
|
||||
if(p->state == UNUSED)
|
||||
goto found;
|
||||
|
||||
release(&ptable.lock);
|
||||
return 0;
|
||||
|
||||
found:
|
||||
p->state = EMBRYO;
|
||||
p->pid = nextpid++;
|
||||
|
||||
// ptable slots get reused once a previous occupant is reaped, but
|
||||
// nothing previously cleared the signal-delivery fields added for
|
||||
// SIGCHLD (sig_handler/sig_restorer/pending_signals/signal_mask/
|
||||
// in_signal/saved_sigtf) — a freshly allocated process would silently
|
||||
// inherit whatever a totally unrelated earlier process left behind,
|
||||
// including stale handler addresses. deliver_signals() (syscall.c)
|
||||
// would then jump to garbage on this new process's very first syscall
|
||||
// return, faulting at whatever bogus rip happened to be sitting there.
|
||||
memset(p->sig_handler, 0, sizeof(p->sig_handler));
|
||||
memset(p->sig_restorer, 0, sizeof(p->sig_restorer));
|
||||
p->pending_signals = 0;
|
||||
p->signal_mask = 0;
|
||||
p->in_signal = 0;
|
||||
p->sigsusp_oldmask = 0;
|
||||
p->sigsusp_active = 0;
|
||||
|
||||
// Same reasoning as the signal fields above: a recycled slot must not
|
||||
// inherit a dead process's identity or exit status.
|
||||
p->xstate = 0;
|
||||
p->killsig = 0;
|
||||
p->pgid = 0;
|
||||
p->sid = 0;
|
||||
p->uid = p->euid = p->suid = 0;
|
||||
p->gid = p->egid = p->sgid = 0;
|
||||
|
||||
release(&ptable.lock);
|
||||
|
||||
if((p->kstack = kalloc()) == 0){
|
||||
p->state = UNUSED;
|
||||
return 0;
|
||||
}
|
||||
// Canary at the very bottom of the kstack page: if a deep/nested trap
|
||||
// ever overflows this 4096-byte stack, this is the first thing clobbered.
|
||||
// Checked on every trap()/syscall() entry (kstack_check_canary()).
|
||||
*(uint64*)(p->kstack) = KSTACK_CANARY;
|
||||
sp = p->kstack + KSTACKSIZE;
|
||||
|
||||
sp -= sizeof(struct trapframe);
|
||||
p->tf = (struct trapframe*)sp;
|
||||
|
||||
sp -= sizeof(addr_t);
|
||||
*(addr_t*)sp = (addr_t)syscall_trapret;
|
||||
|
||||
sp -= sizeof(struct context);
|
||||
p->context = (struct context*)sp;
|
||||
memset(p->context, 0, sizeof *p->context);
|
||||
p->context->rip = (addr_t)forkret;
|
||||
|
||||
p->syscall_map = 0;
|
||||
p->map_size = 0;
|
||||
p->mmap_next = 0;
|
||||
|
||||
memset(p->ofile_flags, 0, sizeof(p->ofile_flags));
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
void
|
||||
sinit(void)
|
||||
{
|
||||
struct proc *p;
|
||||
extern char _binary_initcode_start[], _binary_initcode_size[];
|
||||
|
||||
p = allocproc();
|
||||
initproc = p;
|
||||
|
||||
if((p->pgdir = setupkvm()) == 0)
|
||||
panic("sinit: setupkvm failed");
|
||||
|
||||
inituvm(p->pgdir, _binary_initcode_start, (uint64)_binary_initcode_size);
|
||||
|
||||
if(allocuvm(p->pgdir, PGSIZE, 32 * PGSIZE) == 0)
|
||||
panic("sinit: allocuvm failed");
|
||||
|
||||
p->sz = 32 * PGSIZE;
|
||||
|
||||
memset(p->tf, 0, sizeof(*p->tf));
|
||||
p->tf->cs = (SEG_UCODE << 3) | DPL_USER;
|
||||
p->tf->ss = (SEG_UDATA << 3) | DPL_USER;
|
||||
p->tf->rflags = FL_IF;
|
||||
p->tf->rsp = p->sz;
|
||||
p->tf->rip = 0;
|
||||
|
||||
// PID 1 leads its own session and process group, and runs as root.
|
||||
p->pgid = p->pid;
|
||||
p->sid = p->pid;
|
||||
p->uid = p->euid = p->suid = 0;
|
||||
p->gid = p->egid = p->sgid = 0;
|
||||
|
||||
safestrcpy(p->name, "sinit", sizeof(p->name));
|
||||
p->cwd = namei("/");
|
||||
p->cwd_path[0] = '/'; p->cwd_path[1] = 0;
|
||||
p->fs_base = 0;
|
||||
|
||||
p->state = RUNNABLE;
|
||||
}
|
||||
|
||||
int
|
||||
growproc(int64 n)
|
||||
{
|
||||
addr_t sz;
|
||||
|
||||
sz = myproc()->sz;
|
||||
if(n > 0){
|
||||
if((sz = allocuvm(myproc()->pgdir, sz, sz + n)) == 0)
|
||||
return -1;
|
||||
} else if(n < 0){
|
||||
if((sz = deallocuvm(myproc()->pgdir, sz, sz + n)) == 0)
|
||||
return -1;
|
||||
}
|
||||
myproc()->sz = sz;
|
||||
switchuvm(myproc());
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
fork(void)
|
||||
{
|
||||
int i, pid;
|
||||
struct proc *np;
|
||||
|
||||
if((np = allocproc()) == 0)
|
||||
return -1;
|
||||
|
||||
if((np->pgdir = copyuvm(myproc()->pgdir, myproc()->sz)) == 0){
|
||||
kfree(np->kstack);
|
||||
np->kstack = 0;
|
||||
np->state = UNUSED;
|
||||
return -1;
|
||||
}
|
||||
np->sz = myproc()->sz;
|
||||
np->parent = myproc();
|
||||
|
||||
// copyuvm() only duplicates [0, sz) — mmap()'d regions live above that
|
||||
// and were never copied, leaving the child's vma[] bookkeeping (stale
|
||||
// from whichever process last occupied this ptable slot) pointing at
|
||||
// memory that isn't actually mapped in np->pgdir. Reset and rebuild it
|
||||
// from the parent's real, current mappings.
|
||||
memset(np->vma, 0, sizeof(np->vma));
|
||||
for(i = 0; i < NVMA; i++){
|
||||
if(!myproc()->vma[i].used)
|
||||
continue;
|
||||
if(copy_vma_range(np->pgdir, myproc()->pgdir, myproc()->vma[i].addr,
|
||||
myproc()->vma[i].length, myproc()->vma[i].no_free) < 0){
|
||||
freevm(np->pgdir);
|
||||
kfree(np->kstack);
|
||||
np->kstack = 0;
|
||||
np->state = UNUSED;
|
||||
return -1;
|
||||
}
|
||||
np->vma[i] = myproc()->vma[i];
|
||||
if(np->vma[i].shmid >= 0)
|
||||
shm_ref(np->vma[i].shmid);
|
||||
}
|
||||
np->mmap_next = myproc()->mmap_next;
|
||||
|
||||
memmove(np->tf, myproc()->tf, sizeof(struct trapframe));
|
||||
|
||||
np->syscall_map = myproc()->syscall_map;
|
||||
np->map_size = myproc()->map_size;
|
||||
|
||||
// fork() preserves the parent's signal handlers and mask (POSIX); the
|
||||
// child starts with no pending signals of its own, though — those
|
||||
// aren't inherited. allocproc() already zeroed pending_signals/
|
||||
// in_signal/saved_sigtf for this fresh slot.
|
||||
memmove(np->sig_handler, myproc()->sig_handler, sizeof(np->sig_handler));
|
||||
memmove(np->sig_restorer, myproc()->sig_restorer, sizeof(np->sig_restorer));
|
||||
np->signal_mask = myproc()->signal_mask;
|
||||
|
||||
np->tf->rax = 0;
|
||||
|
||||
for(i = 0; i < NOFILE; i++)
|
||||
if(myproc()->ofile[i]) {
|
||||
np->ofile[i] = filedup(myproc()->ofile[i]);
|
||||
np->ofile_flags[i] = myproc()->ofile_flags[i];
|
||||
}
|
||||
// POSIX: the child inherits process group, session and all four ids.
|
||||
np->pgid = myproc()->pgid;
|
||||
np->sid = myproc()->sid;
|
||||
np->uid = myproc()->uid; np->euid = myproc()->euid; np->suid = myproc()->suid;
|
||||
np->gid = myproc()->gid; np->egid = myproc()->egid; np->sgid = myproc()->sgid;
|
||||
|
||||
np->cwd = idup(myproc()->cwd);
|
||||
memmove(np->cwd_path, myproc()->cwd_path, sizeof(myproc()->cwd_path));
|
||||
np->fs_base = myproc()->fs_base;
|
||||
|
||||
safestrcpy(np->name, myproc()->name, sizeof(myproc()->name));
|
||||
|
||||
pid = np->pid;
|
||||
|
||||
__sync_synchronize();
|
||||
np->state = RUNNABLE;
|
||||
|
||||
return pid;
|
||||
}
|
||||
|
||||
// status is already wait(2)-encoded -- see W_EXITCODE() in proc.h. Callers
|
||||
// that die from a fault or signal pass W_EXITCODE(0, sig); a normal _exit(n)
|
||||
// passes W_EXITCODE(n, 0).
|
||||
void
|
||||
exit(int status)
|
||||
{
|
||||
struct proc *p;
|
||||
int fd;
|
||||
|
||||
if(myproc() == initproc)
|
||||
panic("init exiting");
|
||||
|
||||
myproc()->xstate = status;
|
||||
|
||||
for(fd = 0; fd < NOFILE; fd++){
|
||||
if(myproc()->ofile[fd]){
|
||||
fileclose(myproc()->ofile[fd]);
|
||||
myproc()->ofile[fd] = 0;
|
||||
myproc()->ofile_flags[fd] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
begin_op();
|
||||
iput(myproc()->cwd);
|
||||
end_op();
|
||||
myproc()->cwd = 0;
|
||||
|
||||
acquire(&ptable.lock);
|
||||
|
||||
// Tell the parent a child exited — needed for mksh's sigsuspend()-based
|
||||
// job-control wait loop to have any chance of making progress (without
|
||||
// this, sigsuspend() can never return for the right reason and the
|
||||
// parent hangs unconditionally). Signal delivery itself is verified
|
||||
// working (handler invocation + sigreturn round-trip is clean); mksh's
|
||||
// own job-control bookkeeping has further gaps (see docs/architecture/
|
||||
// kernel-history.md) that this alone doesn't fully resolve, but
|
||||
// disabling it guarantees no progress, whereas leaving it enabled at
|
||||
// least allows the cases that don't hit those gaps to work.
|
||||
if (myproc()->parent)
|
||||
myproc()->parent->pending_signals |= (1ULL << SIGCHLD);
|
||||
wakeup1(myproc()->parent);
|
||||
|
||||
for(p = ptable.proc; p < &ptable.proc[NPROC]; p++){
|
||||
if(p->parent == myproc()){
|
||||
p->parent = initproc;
|
||||
if(p->state == ZOMBIE)
|
||||
wakeup1(initproc);
|
||||
}
|
||||
}
|
||||
|
||||
myproc()->state = ZOMBIE;
|
||||
sched();
|
||||
panic("zombie exit");
|
||||
}
|
||||
|
||||
int
|
||||
wait(void)
|
||||
{
|
||||
return waitpid(-1, 0, 0);
|
||||
}
|
||||
|
||||
// waitpid(2)/wait4(2) proper: a specific pid (-1 = any child, matching
|
||||
// wait()'s old behavior), an optional status-out pointer (NULL is valid —
|
||||
// POSIX callers commonly do waitpid(pid, NULL, WNOHANG)), and WNOHANG.
|
||||
//
|
||||
// This used to be missing: sys_waitpid()/sys_wait4() both ignored pid and
|
||||
// options and called the any-child, always-blocking wait() above. A NULL
|
||||
// status pointer additionally got rejected by argptr() (page 0 is
|
||||
// unmapped) before ever reaching here. Together, a caller doing
|
||||
// waitpid(child, NULL, WNOHANG) right after a child exits — exactly what
|
||||
// mksh's job control does — always failed instead of immediately reaping
|
||||
// the already-zombie child. mksh then fell back to sigsuspend(), which
|
||||
// this kernel can't wake for a real SIGCHLD (no signal delivery), so it
|
||||
// silently retried forever: a real, low-CPU hang with no error printed.
|
||||
int
|
||||
waitpid(int pid, int *status, int options)
|
||||
{
|
||||
struct proc *p;
|
||||
int havekids;
|
||||
|
||||
acquire(&ptable.lock);
|
||||
for(;;){
|
||||
havekids = 0;
|
||||
for(p = ptable.proc; p < &ptable.proc[NPROC]; p++){
|
||||
if(p->parent != myproc())
|
||||
continue;
|
||||
// POSIX pid selection: >0 one child, 0 = caller's group,
|
||||
// -1 = any child, < -1 = the process group with id -pid.
|
||||
if(pid > 0 && p->pid != pid)
|
||||
continue;
|
||||
if(pid == 0 && p->pgid != myproc()->pgid)
|
||||
continue;
|
||||
if(pid < -1 && p->pgid != -pid)
|
||||
continue;
|
||||
havekids = 1;
|
||||
if(p->state == ZOMBIE){
|
||||
int rpid = p->pid;
|
||||
int rstatus = p->xstate; // copy out before the slot is recycled
|
||||
kfree(p->kstack);
|
||||
p->kstack = 0;
|
||||
// Give back anything shared before freevm() decides it owns the
|
||||
// pages. See proc_release_shared() in kernel/sys/sys_mem.c.
|
||||
proc_release_shared(p);
|
||||
freevm(p->pgdir);
|
||||
p->pid = 0;
|
||||
p->parent = 0;
|
||||
p->name[0] = 0;
|
||||
p->killed = 0;
|
||||
p->xstate = 0;
|
||||
p->killsig = 0;
|
||||
p->pgid = 0;
|
||||
p->sid = 0;
|
||||
p->state = UNUSED;
|
||||
release(&ptable.lock);
|
||||
if (status) *status = rstatus;
|
||||
return rpid;
|
||||
}
|
||||
}
|
||||
|
||||
if(!havekids || myproc()->killed){
|
||||
release(&ptable.lock);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (options & 1) { // WNOHANG
|
||||
release(&ptable.lock);
|
||||
return 0;
|
||||
}
|
||||
|
||||
sleep(myproc(), &ptable.lock);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
scheduler(void)
|
||||
{
|
||||
struct proc *p;
|
||||
int skipped = 0;
|
||||
for(;;){
|
||||
sti();
|
||||
acquire(&ptable.lock);
|
||||
for(p = ptable.proc; p < &ptable.proc[NPROC]; p++){
|
||||
if(p->state != RUNNABLE) {
|
||||
skipped++;
|
||||
continue;
|
||||
}
|
||||
skipped = 0;
|
||||
|
||||
proc_set(p);
|
||||
switchuvm(p);
|
||||
p->state = RUNNING;
|
||||
swtch(&mycpu()->scheduler, p->context);
|
||||
switchkvm();
|
||||
|
||||
proc_set(0);
|
||||
}
|
||||
release(&ptable.lock);
|
||||
if (skipped > NPROC) {
|
||||
hlt();
|
||||
skipped = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
sched(void)
|
||||
{
|
||||
int intena;
|
||||
|
||||
if(!holding(&ptable.lock))
|
||||
panic("sched ptable.lock");
|
||||
if(mycpu()->ncli != 1)
|
||||
panic("sched locks");
|
||||
if(myproc()->state == RUNNING)
|
||||
panic("sched running");
|
||||
if(readeflags()&FL_IF)
|
||||
panic("sched interruptible");
|
||||
intena = mycpu()->intena;
|
||||
swtch(&myproc()->context, mycpu()->scheduler);
|
||||
mycpu()->intena = intena;
|
||||
}
|
||||
|
||||
void
|
||||
yield(void)
|
||||
{
|
||||
acquire(&ptable.lock);
|
||||
myproc()->state = RUNNABLE;
|
||||
sched();
|
||||
release(&ptable.lock);
|
||||
}
|
||||
|
||||
void
|
||||
forkret(void) {
|
||||
static int first = 1;
|
||||
release(&ptable.lock);
|
||||
|
||||
if (first) {
|
||||
first = 0;
|
||||
iinit();
|
||||
mountinit();
|
||||
|
||||
// SchFS is the root filesystem.
|
||||
//
|
||||
// Mounted here rather than in main() because mounting reads blocks,
|
||||
// bread() sleeps, and sleeping needs a process context.
|
||||
//
|
||||
// schfs_kmount() reports failure by leaving the volume unmounted rather
|
||||
// than by returning -- it is a probe elsewhere, where "not SchFS" is an
|
||||
// ordinary answer. Here it is fatal, so ask afterwards.
|
||||
schfs_kmount(ROOTDEV);
|
||||
if (!schfs_kmounted())
|
||||
panic("schfs root: no SchFS volume on the root device");
|
||||
// Re-point pid 1's cwd at the real root, now that there is one.
|
||||
//
|
||||
// sinit() sets cwd = namei("/") back in main(), which is before this runs.
|
||||
// The old v1 root survived that because iget(ROOTDEV, ROOTINO) is two
|
||||
// compile-time constants and touches no disk -- the inode is filled in
|
||||
// lazily at the first ilock(). SchFS has no constant to offer: the root
|
||||
// object id comes off the uberblock, so namei("/") that early returns an
|
||||
// inode built from whatever schfs_kroot() reads out of an unmounted
|
||||
// volume.
|
||||
//
|
||||
// Nothing complains, because absolute paths never consult cwd -- exec
|
||||
// "/bin/init" works fine. It is the first *relative* lookup that fails,
|
||||
// and in init that is open("console"): no console means no fd 0/1/2, so
|
||||
// every shell it spawns exits immediately and init respawns it forever.
|
||||
// The symptom is a silent boot with no init: ready, which reads like a
|
||||
// hang and is not one.
|
||||
{
|
||||
struct inode *stale = myproc()->cwd;
|
||||
myproc()->cwd = iget(SCHFS_DEV, schfs_kroot());
|
||||
if (stale)
|
||||
iput(stale);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
sleep(void *chan, struct spinlock *lk)
|
||||
{
|
||||
if(myproc() == 0)
|
||||
panic("sleep");
|
||||
|
||||
if(lk == 0)
|
||||
panic("sleep without lk");
|
||||
|
||||
if(lk != &ptable.lock){
|
||||
acquire(&ptable.lock);
|
||||
release(lk);
|
||||
}
|
||||
|
||||
myproc()->chan = chan;
|
||||
myproc()->state = SLEEPING;
|
||||
sched();
|
||||
|
||||
myproc()->chan = 0;
|
||||
|
||||
if(lk != &ptable.lock){
|
||||
release(&ptable.lock);
|
||||
acquire(lk);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
wakeup1(void *chan)
|
||||
{
|
||||
struct proc *p;
|
||||
|
||||
for(p = ptable.proc; p < &ptable.proc[NPROC]; p++)
|
||||
if(p->state == SLEEPING && p->chan == chan)
|
||||
p->state = RUNNABLE;
|
||||
}
|
||||
|
||||
void
|
||||
wakeup(void *chan)
|
||||
{
|
||||
acquire(&ptable.lock);
|
||||
wakeup1(chan);
|
||||
release(&ptable.lock);
|
||||
}
|
||||
|
||||
// Raise a signal on a process: set the pending bit and wake it if it's
|
||||
// sleeping so blocking syscalls (sigsuspend, pause, read) re-check their
|
||||
// conditions promptly. Signals with no handler installed fall back to the
|
||||
// default action — termination for the fatal set, dropped otherwise.
|
||||
// mksh depends on kill(getpid(), SIGCHLD) genuinely re-raising SIGCHLD
|
||||
// (its held_sigchld recovery in j_startjob); the previous sys_kill
|
||||
// silently discarded every non-fatal signal.
|
||||
int
|
||||
sigsend(int pid, int sig)
|
||||
{
|
||||
struct proc *p;
|
||||
|
||||
if(sig <= 0 || sig >= 32)
|
||||
return -1;
|
||||
|
||||
acquire(&ptable.lock);
|
||||
for(p = ptable.proc; p < &ptable.proc[NPROC]; p++){
|
||||
if(p->pid == pid && p->state != UNUSED && p->state != ZOMBIE){
|
||||
p->pending_signals |= 1ULL << sig;
|
||||
if(p->sig_handler[sig] == 0 &&
|
||||
(sig == SIGKILL || sig == SIGTERM || sig == SIGABRT ||
|
||||
sig == SIGSEGV || sig == SIGFPE || sig == SIGBUS ||
|
||||
sig == SIGILL || sig == SIGINT || sig == SIGQUIT ||
|
||||
sig == SIGHUP || sig == SIGPIPE || sig == SIGUSR1 ||
|
||||
sig == SIGUSR2 || sig == SIGALRM))
|
||||
p->killed = 1;
|
||||
if(sig == SIGKILL)
|
||||
p->killed = 1;
|
||||
if(p->killed && p->killsig == 0)
|
||||
p->killsig = sig; // so exit() can report the real cause
|
||||
if(p->state == SLEEPING)
|
||||
p->state = RUNNABLE;
|
||||
release(&ptable.lock);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
release(&ptable.lock);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Deliver a signal to every member of a process group. This is what job
|
||||
// control actually needs: a shell sends SIGINT/SIGTSTP/SIGHUP to a *group*,
|
||||
// never to individual pids. sys_kill() with pid <= 0 routes here.
|
||||
//
|
||||
// Returns 0 if at least one process was signalled, -1 if the group is empty.
|
||||
int
|
||||
sigsend_pgrp(int pgid, int sig)
|
||||
{
|
||||
struct proc *p;
|
||||
int found = 0;
|
||||
|
||||
if(sig < 0 || sig >= 32)
|
||||
return -1;
|
||||
|
||||
acquire(&ptable.lock);
|
||||
for(p = ptable.proc; p < &ptable.proc[NPROC]; p++){
|
||||
if(p->state == UNUSED || p->state == ZOMBIE)
|
||||
continue;
|
||||
if(p->pgid != pgid)
|
||||
continue;
|
||||
found = 1;
|
||||
if(sig == 0)
|
||||
continue; // signal 0 is an existence probe only
|
||||
p->pending_signals |= 1ULL << sig;
|
||||
if(p->sig_handler[sig] == 0 &&
|
||||
(sig == SIGKILL || sig == SIGTERM || sig == SIGABRT ||
|
||||
sig == SIGSEGV || sig == SIGFPE || sig == SIGBUS ||
|
||||
sig == SIGILL || sig == SIGINT || sig == SIGQUIT ||
|
||||
sig == SIGHUP || sig == SIGPIPE || sig == SIGUSR1 ||
|
||||
sig == SIGUSR2 || sig == SIGALRM)){
|
||||
p->killed = 1;
|
||||
if(p->killsig == 0)
|
||||
p->killsig = sig;
|
||||
}
|
||||
if(sig == SIGKILL){
|
||||
p->killed = 1;
|
||||
if(p->killsig == 0)
|
||||
p->killsig = sig;
|
||||
}
|
||||
if(p->state == SLEEPING)
|
||||
p->state = RUNNABLE;
|
||||
}
|
||||
release(&ptable.lock);
|
||||
|
||||
return found ? 0 : -1;
|
||||
}
|
||||
|
||||
// Look up a process's process-group id. Returns -1 if no such live process.
|
||||
int
|
||||
proc_pgid(int pid)
|
||||
{
|
||||
struct proc *p;
|
||||
int r = -1;
|
||||
|
||||
acquire(&ptable.lock);
|
||||
for(p = ptable.proc; p < &ptable.proc[NPROC]; p++){
|
||||
if(p->pid == pid && p->state != UNUSED && p->state != ZOMBIE){
|
||||
r = p->pgid;
|
||||
break;
|
||||
}
|
||||
}
|
||||
release(&ptable.lock);
|
||||
return r;
|
||||
}
|
||||
|
||||
// setpgid(pid, pgid) with POSIX's argument shorthands: pid 0 means the caller,
|
||||
// pgid 0 means "use pid as the group id" (i.e. become a group leader).
|
||||
int
|
||||
proc_setpgid(int pid, int pgid)
|
||||
{
|
||||
struct proc *p;
|
||||
int r = -ESRCH;
|
||||
|
||||
if(pid < 0 || pgid < 0)
|
||||
return -EINVAL;
|
||||
if(pid == 0)
|
||||
pid = myproc()->pid;
|
||||
if(pgid == 0)
|
||||
pgid = pid;
|
||||
|
||||
acquire(&ptable.lock);
|
||||
for(p = ptable.proc; p < &ptable.proc[NPROC]; p++){
|
||||
if(p->pid != pid || p->state == UNUSED || p->state == ZOMBIE)
|
||||
continue;
|
||||
// A caller may only move itself or one of its own children, and never
|
||||
// across a session boundary.
|
||||
if(p != myproc() && p->parent != myproc())
|
||||
r = -ESRCH;
|
||||
else if(p->sid != myproc()->sid)
|
||||
r = -EPERM;
|
||||
else {
|
||||
p->pgid = pgid;
|
||||
r = 0;
|
||||
}
|
||||
break;
|
||||
}
|
||||
release(&ptable.lock);
|
||||
return r;
|
||||
}
|
||||
|
||||
// setsid(): become leader of a brand-new session and process group. Fails if
|
||||
// the caller already leads a process group, since that group would otherwise
|
||||
// end up split across two sessions.
|
||||
int
|
||||
proc_setsid(void)
|
||||
{
|
||||
struct proc *p;
|
||||
int r;
|
||||
|
||||
acquire(&ptable.lock);
|
||||
for(p = ptable.proc; p < &ptable.proc[NPROC]; p++){
|
||||
if(p->state != UNUSED && p->state != ZOMBIE && p->pgid == myproc()->pid){
|
||||
release(&ptable.lock);
|
||||
return -EPERM;
|
||||
}
|
||||
}
|
||||
myproc()->sid = myproc()->pid;
|
||||
myproc()->pgid = myproc()->pid;
|
||||
r = myproc()->sid;
|
||||
release(&ptable.lock);
|
||||
return r;
|
||||
}
|
||||
|
||||
int
|
||||
kill(int pid)
|
||||
{
|
||||
struct proc *p;
|
||||
|
||||
acquire(&ptable.lock);
|
||||
for(p = ptable.proc; p < &ptable.proc[NPROC]; p++){
|
||||
if(p->pid == pid){
|
||||
p->killed = 1;
|
||||
if(p->state == SLEEPING)
|
||||
p->state = RUNNABLE;
|
||||
release(&ptable.lock);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
release(&ptable.lock);
|
||||
return -1;
|
||||
}
|
||||
|
||||
void
|
||||
procdump(void)
|
||||
{
|
||||
static char *states[] = {
|
||||
[UNUSED] "unused",
|
||||
[EMBRYO] "embryo",
|
||||
[SLEEPING] "sleep ",
|
||||
[RUNNABLE] "runble",
|
||||
[RUNNING] "run ",
|
||||
[ZOMBIE] "zombie"
|
||||
};
|
||||
int i;
|
||||
struct proc *p;
|
||||
char *state;
|
||||
addr_t pc[10];
|
||||
|
||||
for(p = ptable.proc; p < &ptable.proc[NPROC]; p++){
|
||||
if(p->state == UNUSED)
|
||||
continue;
|
||||
if(p->state >= 0 && p->state < NELEM(states) && states[p->state])
|
||||
state = states[p->state];
|
||||
else
|
||||
state = "???";
|
||||
cprintf("%d %s %s", p->pid, state, p->name);
|
||||
if(p->state == SLEEPING){
|
||||
getstackpcs((addr_t*)p->context->rbp, pc);
|
||||
for(i=0; i<10 && pc[i] != 0; i++)
|
||||
cprintf(" %p", pc[i]);
|
||||
}
|
||||
cprintf("\n");
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Snapshots, for /proc
|
||||
//
|
||||
// procfs must not walk ptable itself: it would need the lock, the struct
|
||||
// layout, and a rule about what happens when a process exits between the
|
||||
// readdir that listed it and the open that reads it. These two copy what is
|
||||
// needed out from under the lock and hand back a value, so a process that dies
|
||||
// mid-listing produces a stale line rather than a use-after-free.
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// Fill `out` with the `idx`-th live process (0-based, in table order).
|
||||
// Returns 0 on success, -1 when idx is past the end.
|
||||
int
|
||||
proc_snapshot(int idx, struct procinfo *out)
|
||||
{
|
||||
struct proc *p;
|
||||
int n = 0;
|
||||
|
||||
acquire(&ptable.lock);
|
||||
for (p = ptable.proc; p < &ptable.proc[NPROC]; p++) {
|
||||
if (p->state == UNUSED)
|
||||
continue;
|
||||
if (n++ != idx)
|
||||
continue;
|
||||
out->pid = p->pid;
|
||||
out->ppid = p->parent ? p->parent->pid : 0;
|
||||
out->pgid = p->pgid;
|
||||
out->sid = p->sid;
|
||||
out->uid = p->uid;
|
||||
out->state = p->state;
|
||||
out->sz = p->sz;
|
||||
out->killed = p->killed;
|
||||
safestrcpy(out->name, p->name, sizeof(out->name));
|
||||
safestrcpy(out->exe, p->exe, sizeof(out->exe));
|
||||
release(&ptable.lock);
|
||||
return 0;
|
||||
}
|
||||
release(&ptable.lock);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// The same, by pid rather than index.
|
||||
int
|
||||
proc_snapshot_pid(int pid, struct procinfo *out)
|
||||
{
|
||||
int i;
|
||||
struct procinfo pi;
|
||||
|
||||
for (i = 0; proc_snapshot(i, &pi) == 0; i++)
|
||||
if (pi.pid == pid) { *out = pi; return 0; }
|
||||
return -1;
|
||||
}
|
||||
|
||||
int proc_num(void)
|
||||
{
|
||||
struct proc *p;
|
||||
int num = 0;
|
||||
acquire(&ptable.lock);
|
||||
for (p = ptable.proc; p < &ptable.proc[NPROC]; p++)
|
||||
if (p->state != UNUSED) num++;
|
||||
release(&ptable.lock);
|
||||
return num;
|
||||
}
|
||||
|
||||
int proc_exists(int pid)
|
||||
{
|
||||
struct proc *p;
|
||||
acquire(&ptable.lock);
|
||||
for (p = ptable.proc; p < &ptable.proc[NPROC]; p++) {
|
||||
if (p->pid == pid && p->state != UNUSED && p->state != ZOMBIE) {
|
||||
release(&ptable.lock);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
release(&ptable.lock);
|
||||
return 0;
|
||||
}
|
||||
33
kernel/sys/linux_abi.c
Normal file
33
kernel/sys/linux_abi.c
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Linux x86_64 syscall number -> Schunix syscall number translation.
|
||||
// Enabled per process by sys_enable_linux_abi (see kernel/sys/sys_compat.c).
|
||||
//
|
||||
// This file used to be the second half of a two-place registration scheme: a
|
||||
// hand-written `#define LINUX_foo N` for every Linux number, plus a hand-written
|
||||
// map entry pointing it at SYS_foo. Adding a syscall meant three edits across
|
||||
// two files, and forgetting this one left the syscall unreachable from every
|
||||
// musl-linked program while looking perfectly registered in table.inc.
|
||||
//
|
||||
// The map is now generated from kernel/sys/table.inc, which carries the Linux
|
||||
// number alongside the native one. There is nothing to hand-edit here.
|
||||
|
||||
#include "types.h"
|
||||
#include "syscall.h"
|
||||
|
||||
#define LINUX_NSYSCALL NSYSCALLS
|
||||
|
||||
// Index: Linux x86_64 syscall number. Value: Schunix SYS_* number, or 0 for
|
||||
// "not implemented" (syscall() then returns -ENOSYS).
|
||||
//
|
||||
// Syscalls declared NO_LINUX in table.inc all land on the single scratch index
|
||||
// NO_LINUX. They overwrite each other there, which is harmless: no real Linux
|
||||
// syscall uses that number, so the slot is never dispatched.
|
||||
int linux_syscall_map[LINUX_NSYSCALL] = {
|
||||
#define SYSCALL(nr, lnr, name) [lnr] = SYS_##name,
|
||||
#define SYSCALL_ALIAS(lnr, name) [lnr] = SYS_##name,
|
||||
#include "table.inc"
|
||||
#undef SYSCALL
|
||||
#undef SYSCALL_ALIAS
|
||||
};
|
||||
|
||||
int linux_syscall_map_size = LINUX_NSYSCALL;
|
||||
162
kernel/sys/sys_compat.c
Normal file
162
kernel/sys/sys_compat.c
Normal file
|
|
@ -0,0 +1,162 @@
|
|||
// POSIX / Linux-ABI compatibility syscalls.
|
||||
// These are stubs or thin wrappers that make musl and ksh happy
|
||||
// without implementing full semantics.
|
||||
|
||||
#include "types.h"
|
||||
#include "defs.h"
|
||||
#include "param.h"
|
||||
#include "memlayout.h"
|
||||
#include "mmu.h"
|
||||
#include "traps.h"
|
||||
#include "proc.h"
|
||||
#include "x86.h"
|
||||
#include "syscall.h"
|
||||
#include "posix.h"
|
||||
#include "uaccess.h"
|
||||
#include "errno.h"
|
||||
|
||||
extern int linux_syscall_map[];
|
||||
extern int linux_syscall_map_size;
|
||||
|
||||
// enable_linux_abi: switch this process to Linux syscall numbering.
|
||||
addr_t
|
||||
sys_enable_linux_abi(void)
|
||||
{
|
||||
myproc()->syscall_map = linux_syscall_map;
|
||||
myproc()->map_size = linux_syscall_map_size;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// set_tid_address: musl calls this at startup to register its TID location.
|
||||
addr_t
|
||||
sys_set_tid_address(void)
|
||||
{
|
||||
addr_t tidp;
|
||||
argaddr(0, &tidp);
|
||||
if (tidp)
|
||||
put_user(myproc()->pid, tidp);
|
||||
return myproc()->pid;
|
||||
}
|
||||
|
||||
// set_robust_list: robust mutex cleanup on exit — stub.
|
||||
addr_t sys_set_robust_list(void) { return 0; }
|
||||
|
||||
// futex: used by musl for single-threaded lock primitives.
|
||||
addr_t
|
||||
sys_futex(void)
|
||||
{
|
||||
int op;
|
||||
argint(1, &op);
|
||||
op &= ~(FUTEX_WAIT_PRIVATE - FUTEX_WAIT);
|
||||
if (op == FUTEX_WAKE || op == FUTEX_WAKE_PRIVATE)
|
||||
return 0;
|
||||
return -EAGAIN; // no blocking, musl retries
|
||||
}
|
||||
|
||||
// arch_prctl: save FS_BASE for TLS.
|
||||
addr_t
|
||||
sys_arch_prctl(void)
|
||||
{
|
||||
int code;
|
||||
addr_t addr;
|
||||
argint(0, &code);
|
||||
argaddr(1, &addr);
|
||||
if (code == ARCH_SET_FS) {
|
||||
myproc()->fs_base = addr;
|
||||
return 0;
|
||||
}
|
||||
if (code == ARCH_GET_FS) {
|
||||
if (put_user(myproc()->fs_base, addr) < 0)
|
||||
return -EFAULT;
|
||||
return 0;
|
||||
}
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
addr_t
|
||||
sys_getrlimit(void)
|
||||
{
|
||||
int resource;
|
||||
struct rlimit *rlp;
|
||||
argint(0, &resource);
|
||||
if (argptr(1, (char**)&rlp, sizeof(*rlp)) < 0)
|
||||
return -1;
|
||||
rlp->rlim_cur = (uint64)-1;
|
||||
rlp->rlim_max = (uint64)-1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
addr_t
|
||||
sys_prlimit(void)
|
||||
{
|
||||
struct rlimit *oldrlp;
|
||||
if (argptr(3, (char**)&oldrlp, sizeof(*oldrlp)) == 0 && oldrlp) {
|
||||
oldrlp->rlim_cur = (uint64)-1;
|
||||
oldrlp->rlim_max = (uint64)-1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// uname: return fake but plausible kernel/host info
|
||||
addr_t
|
||||
sys_uname(void)
|
||||
{
|
||||
struct utsname {
|
||||
char sysname[65];
|
||||
char nodename[65];
|
||||
char release[65];
|
||||
char version[65];
|
||||
char machine[65];
|
||||
char domainname[65];
|
||||
};
|
||||
addr_t ubuf;
|
||||
argaddr(0, &ubuf);
|
||||
struct utsname *u = (struct utsname*)user_ptr(ubuf, sizeof(*u));
|
||||
if (!u) return -EFAULT;
|
||||
memset(u, 0, sizeof(*u));
|
||||
memmove(u->sysname, "Schunix", 8);
|
||||
memmove(u->nodename, "schunix", 8);
|
||||
memmove(u->release, "0.1.0", 6);
|
||||
memmove(u->version, "#1", 3);
|
||||
memmove(u->machine, "x86_64", 7);
|
||||
memmove(u->domainname, "(none)", 7);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// socket/bind/listen/accept/connect live in sys_net.c now that they do
|
||||
// something.
|
||||
|
||||
// syslog: kernel logging facility — no-op for user processes.
|
||||
addr_t sys_syslog(void) { return 0; }
|
||||
|
||||
// getrandom: fill buffer with pseudo-random bytes.
|
||||
addr_t sys_getrandom(void)
|
||||
{
|
||||
addr_t bufaddr;
|
||||
int len, flags;
|
||||
argaddr(0, &bufaddr); argint(1, &len); argint(2, &flags);
|
||||
if (len <= 0) return 0;
|
||||
uint8 *buf = (uint8*)user_ptr(bufaddr, (uint64)len);
|
||||
if (!buf) return -EFAULT;
|
||||
// LCG seeded from proc pointer + pid; no lock needed (read-only fields).
|
||||
uint rseed = (uint)(uintptr_t)myproc() ^ (uint)myproc()->pid;
|
||||
for (int i = 0; i < len; i++) {
|
||||
rseed = rseed * 1664525u + 1013904223u;
|
||||
buf[i] = (uint8)(rseed >> 16);
|
||||
}
|
||||
return len;
|
||||
}
|
||||
|
||||
// pselect6 / ppoll live in sys_poll.c alongside select/poll.
|
||||
|
||||
// epoll: not implemented.
|
||||
addr_t sys_epoll_create1(void) { return -ENOSYS; }
|
||||
addr_t sys_epoll_ctl(void) { return -ENOSYS; }
|
||||
addr_t sys_epoll_wait(void) { return -ENOSYS; }
|
||||
|
||||
// signalfd / eventfd / timerfd: not implemented.
|
||||
addr_t sys_signalfd4(void) { return -ENOSYS; }
|
||||
addr_t sys_eventfd2(void) { return -ENOSYS; }
|
||||
addr_t sys_timerfd_create(void) { return -ENOSYS; }
|
||||
addr_t sys_timerfd_settime(void){ return -ENOSYS; }
|
||||
addr_t sys_timerfd_gettime(void){ return -ENOSYS; }
|
||||
1413
kernel/sys/sys_fs.c
Normal file
1413
kernel/sys/sys_fs.c
Normal file
File diff suppressed because it is too large
Load diff
479
kernel/sys/sys_mem.c
Normal file
479
kernel/sys/sys_mem.c
Normal file
|
|
@ -0,0 +1,479 @@
|
|||
#include "types.h"
|
||||
#include "defs.h"
|
||||
#include "param.h"
|
||||
#include "stat.h"
|
||||
#include "memlayout.h"
|
||||
#include "mmu.h"
|
||||
#include "spinlock.h"
|
||||
#include "sleeplock.h"
|
||||
#include "proc.h"
|
||||
#include "fs.h"
|
||||
#include "file.h"
|
||||
#include "syscall.h"
|
||||
#include "errno.h"
|
||||
|
||||
extern int mappages(pml4e_t *pgdir, void *va, uint64 size, uint64 pa, int perm);
|
||||
|
||||
#define MAP_SHARED 0x01
|
||||
#define MAP_PRIVATE 0x02
|
||||
#define MAP_FIXED 0x10
|
||||
#define MAP_ANONYMOUS 0x20
|
||||
#define PROT_NONE 0
|
||||
#define PROT_WRITE 0x2
|
||||
|
||||
// Reject user mapping requests that fall outside user space. Without this,
|
||||
// MAP_FIXED at an arbitrary address could create user-accessible PTEs over
|
||||
// (or vm_unmap_range/kfree pages belonging to) the kernel half of the
|
||||
// address space.
|
||||
static int
|
||||
bad_user_range(uint64 va, uint64 length)
|
||||
{
|
||||
return length == 0 || va + length < va || va + length > KERNBASE;
|
||||
}
|
||||
|
||||
addr_t
|
||||
sys_sbrk(void)
|
||||
{
|
||||
int n;
|
||||
if (argint(0, &n) < 0)
|
||||
return -1;
|
||||
addr_t addr = myproc()->sz;
|
||||
if (growproc(n) < 0)
|
||||
return -1;
|
||||
return addr;
|
||||
}
|
||||
|
||||
addr_t
|
||||
sys_brk(void)
|
||||
{
|
||||
addr_t addr;
|
||||
if (argaddr(0, &addr) < 0 || addr == 0)
|
||||
return myproc()->sz;
|
||||
if (addr > myproc()->sz && growproc(addr - myproc()->sz) < 0)
|
||||
return myproc()->sz;
|
||||
return myproc()->sz;
|
||||
}
|
||||
|
||||
addr_t
|
||||
sys_mmap(void)
|
||||
{
|
||||
uint64 addr, length, offset;
|
||||
int prot, flags, fd;
|
||||
struct file *f = 0;
|
||||
|
||||
if (argaddr(0, &addr) < 0 || argaddr(1, &length) < 0 ||
|
||||
argint(2, &prot) < 0 || argint(3, &flags) < 0 ||
|
||||
argint(4, &fd) < 0 || argaddr(5, &offset) < 0)
|
||||
return -1;
|
||||
|
||||
if (fd >= 0 && fd < NOFILE)
|
||||
f = myproc()->ofile[fd];
|
||||
|
||||
length = PGROUNDUP(length);
|
||||
uint64 va = addr;
|
||||
if (length == 0 || length > KERNBASE)
|
||||
return -EINVAL;
|
||||
|
||||
if (flags & MAP_FIXED) {
|
||||
if (bad_user_range(va, length) || (va % PGSIZE))
|
||||
return -EINVAL;
|
||||
vm_unmap_range(myproc()->pgdir, va, length);
|
||||
if (prot == PROT_NONE)
|
||||
return va;
|
||||
for (uint64 a = va; a < va + length; a += PGSIZE) {
|
||||
char *mem = kalloc();
|
||||
if (!mem) return -1;
|
||||
memset(mem, 0, PGSIZE);
|
||||
if (mappages(myproc()->pgdir, (void*)a, PGSIZE, V2P((uint64)mem), PTE_W | PTE_U) < 0)
|
||||
return -1;
|
||||
}
|
||||
return va;
|
||||
}
|
||||
|
||||
struct vma *v = 0;
|
||||
for (int i = 0; i < NVMA; i++) {
|
||||
if (!myproc()->vma[i].used) { v = &myproc()->vma[i]; break; }
|
||||
}
|
||||
if (!v) return -1;
|
||||
|
||||
// Ignore non-zero hint addresses (not MAP_FIXED); always pick a fresh VA.
|
||||
// Bump-allocated: each mapping gets its own disjoint, exactly-length-sized
|
||||
// range, so a single mmap() can never run into a neighboring live mapping
|
||||
// regardless of size (the old scheme assigned a fixed 8MB window per vma[]
|
||||
// slot index, which a single allocation bigger than 8MB — e.g. a large
|
||||
// realloc() growth — could map straight past, panicking mappages("remap")
|
||||
// when it hit an already-present PTE in the next slot's territory).
|
||||
// munmap()'d ranges are not reclaimed for reuse; that's fine at this scale.
|
||||
if (myproc()->mmap_next == 0)
|
||||
myproc()->mmap_next = 0x40000000;
|
||||
va = myproc()->mmap_next;
|
||||
if (bad_user_range(va, length))
|
||||
return -ENOMEM;
|
||||
myproc()->mmap_next += length;
|
||||
|
||||
int is_fb = 0;
|
||||
int shmid = -1;
|
||||
// File-backed mapping of a regular file.
|
||||
//
|
||||
// This used to be ignored entirely: any file mapping silently returned
|
||||
// zeroed anonymous pages, so a program that mmap'd a font or a config
|
||||
// file got a buffer full of nothing and no error to explain it. Reading
|
||||
// the contents in is not demand paging and never will be without a page
|
||||
// cache -- but it is the difference between a wrong answer and a right
|
||||
// one.
|
||||
//
|
||||
// MAP_SHARED that is also writable is refused rather than faked. Its
|
||||
// whole contract is that stores land in the file and other mappers see
|
||||
// them, and there is nothing here that could make that true; quietly
|
||||
// giving back a private copy would be the same lie in a new place.
|
||||
if (f && f->type == FD_INODE && f->ip->type != T_DEV) {
|
||||
if ((flags & MAP_SHARED) && (prot & PROT_WRITE))
|
||||
return -ENODEV;
|
||||
if (!f->readable)
|
||||
return -EACCES;
|
||||
|
||||
for (uint64 a = va; a < va + length; a += PGSIZE) {
|
||||
char *mem = kalloc();
|
||||
if (!mem) return -ENOMEM;
|
||||
memset(mem, 0, PGSIZE);
|
||||
if (mappages(myproc()->pgdir, (void*)a, PGSIZE, V2P((uint64)mem), PTE_W | PTE_U) < 0) {
|
||||
kfree(mem);
|
||||
return -ENOMEM;
|
||||
}
|
||||
}
|
||||
|
||||
// Read through the file's own offset-independent path, so this does
|
||||
// not disturb a descriptor the caller is also reading normally.
|
||||
ilock(f->ip);
|
||||
readi(f->ip, 0, va, offset, length);
|
||||
iunlock(f->ip);
|
||||
|
||||
v->addr = va; v->length = length; v->used = 1;
|
||||
v->no_free = 0; v->shmid = -1;
|
||||
return va;
|
||||
}
|
||||
|
||||
if (f && f->type == FD_INODE && f->ip->type == T_DEV && f->ip->major == 2) {
|
||||
// Map the framebuffer where it actually is. This used to map a
|
||||
// hardcoded 0xfd000000 for a hardcoded length, so a request for more
|
||||
// than the screen holds handed out user-writable PTEs to whatever
|
||||
// MMIO sits above the framebuffer, and any machine that put the LFB
|
||||
// somewhere else got a window onto the wrong device entirely.
|
||||
uint64 fb = vga_fb_phys();
|
||||
uint64 fbsz = PGROUNDUP(vga_fb_size());
|
||||
if (fb == 0 || fbsz == 0)
|
||||
return -ENODEV;
|
||||
if (length > fbsz)
|
||||
length = fbsz;
|
||||
if (mappages(myproc()->pgdir, (void*)va, length, fb, PTE_W | PTE_U) < 0)
|
||||
return -1;
|
||||
is_fb = 1;
|
||||
// From here the drawing happens with no further syscalls, so this is
|
||||
// the kernel's only chance to notice that the screen no longer belongs
|
||||
// to the text console. fbclose() puts it back.
|
||||
fb_mapped();
|
||||
} else {
|
||||
for (uint64 a = va; a < va + length; a += PGSIZE) {
|
||||
char *mem = kalloc();
|
||||
if (!mem) return -1;
|
||||
memset(mem, 0, PGSIZE);
|
||||
if (mappages(myproc()->pgdir, (void*)a, PGSIZE, V2P((uint64)mem), PTE_W | PTE_U) < 0)
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
v->addr = va; v->length = length; v->used = 1;
|
||||
v->no_free = is_fb; v->shmid = shmid;
|
||||
return va;
|
||||
}
|
||||
|
||||
addr_t
|
||||
sys_munmap(void)
|
||||
{
|
||||
uint64 addr, length;
|
||||
if (argaddr(0, &addr) < 0 || argaddr(1, &length) < 0)
|
||||
return -1;
|
||||
length = PGROUNDUP(length);
|
||||
if (bad_user_range(addr, length))
|
||||
return -EINVAL;
|
||||
for (int i = 0; i < NVMA; i++) {
|
||||
if (myproc()->vma[i].used && myproc()->vma[i].addr == addr) {
|
||||
if (myproc()->vma[i].shmid >= 0)
|
||||
shm_detach(myproc()->vma[i].shmid);
|
||||
if (myproc()->vma[i].no_free)
|
||||
vm_unmap_norelease(myproc()->pgdir, addr, myproc()->vma[i].length);
|
||||
else
|
||||
vm_unmap_range(myproc()->pgdir, addr, myproc()->vma[i].length);
|
||||
myproc()->vma[i].used = myproc()->vma[i].addr = myproc()->vma[i].length = 0;
|
||||
myproc()->vma[i].no_free = 0;
|
||||
myproc()->vma[i].shmid = -1;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
// Partial / untracked unmap — free pages anyway
|
||||
vm_unmap_range(myproc()->pgdir, addr, length);
|
||||
return 0;
|
||||
}
|
||||
|
||||
addr_t sys_mprotect(void) { return 0; }
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Shared memory
|
||||
//
|
||||
// Two processes mapping the same physical pages. This is what a display
|
||||
// server and its clients need: without it every frame is a copy through a
|
||||
// pipe, and the copy is the entire cost of drawing anything.
|
||||
//
|
||||
// Deliberately simple. Pages are allocated up front rather than on demand,
|
||||
// there is no swapping, and a segment lives until the last attachment goes
|
||||
// away. What it does have is the property that matters: a store by one
|
||||
// process is visible to the other, because it is the same page.
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#define NSHM 16 // segments
|
||||
#define SHM_MAXPAGES 512 // 2 MB each -- a 800x600x32 surface is 1.9 MB
|
||||
#define IPC_PRIVATE 0
|
||||
#define IPC_CREAT 01000
|
||||
#define IPC_EXCL 02000
|
||||
|
||||
struct shmseg {
|
||||
int used;
|
||||
int key;
|
||||
uint64 size; // rounded up to a page
|
||||
int npages;
|
||||
int nattach; // live attachments across all processes
|
||||
char *pages[SHM_MAXPAGES]; // kernel virtual addresses
|
||||
};
|
||||
|
||||
static struct shmseg shm[NSHM];
|
||||
static struct spinlock shmlock;
|
||||
static int shm_ready;
|
||||
|
||||
static void
|
||||
shm_init_once(void)
|
||||
{
|
||||
if (!shm_ready) {
|
||||
initlock(&shmlock, "shm");
|
||||
shm_ready = 1;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
shm_free(struct shmseg *s)
|
||||
{
|
||||
for (int i = 0; i < s->npages; i++)
|
||||
if (s->pages[i])
|
||||
kfree(s->pages[i]);
|
||||
memset(s, 0, sizeof(*s));
|
||||
}
|
||||
|
||||
// fork() gives the child its own attachment to the same segment. Without
|
||||
// this the count would say one attachment while two processes had it mapped,
|
||||
// and the first to exit would free the pages under the other.
|
||||
void
|
||||
shm_ref(int id)
|
||||
{
|
||||
if (id < 0 || id >= NSHM)
|
||||
return;
|
||||
shm_init_once();
|
||||
acquire(&shmlock);
|
||||
if (shm[id].used)
|
||||
shm[id].nattach++;
|
||||
release(&shmlock);
|
||||
}
|
||||
|
||||
// Called from munmap and from exit, once per detached mapping.
|
||||
void
|
||||
shm_detach(int id)
|
||||
{
|
||||
if (id < 0 || id >= NSHM)
|
||||
return;
|
||||
shm_init_once();
|
||||
acquire(&shmlock);
|
||||
if (shm[id].used && shm[id].nattach > 0) {
|
||||
if (--shm[id].nattach == 0)
|
||||
shm_free(&shm[id]);
|
||||
}
|
||||
release(&shmlock);
|
||||
}
|
||||
|
||||
addr_t
|
||||
sys_shmget(void)
|
||||
{
|
||||
int key, flags;
|
||||
addr_t size;
|
||||
|
||||
if (argint(0, &key) < 0 || argaddr(1, &size) < 0 || argint(2, &flags) < 0)
|
||||
return -EINVAL;
|
||||
|
||||
size = PGROUNDUP(size);
|
||||
if (size == 0 || size > (uint64)SHM_MAXPAGES * PGSIZE)
|
||||
return -EINVAL;
|
||||
|
||||
shm_init_once();
|
||||
acquire(&shmlock);
|
||||
|
||||
if (key != IPC_PRIVATE) {
|
||||
for (int i = 0; i < NSHM; i++) {
|
||||
if (shm[i].used && shm[i].key == key) {
|
||||
if ((flags & IPC_CREAT) && (flags & IPC_EXCL)) {
|
||||
release(&shmlock);
|
||||
return -EEXIST;
|
||||
}
|
||||
if (shm[i].size < size) {
|
||||
release(&shmlock);
|
||||
return -EINVAL;
|
||||
}
|
||||
release(&shmlock);
|
||||
return i;
|
||||
}
|
||||
}
|
||||
if (!(flags & IPC_CREAT)) {
|
||||
release(&shmlock);
|
||||
return -ENOENT;
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < NSHM; i++) {
|
||||
if (shm[i].used)
|
||||
continue;
|
||||
struct shmseg *s = &shm[i];
|
||||
s->npages = (int)(size / PGSIZE);
|
||||
for (int pg = 0; pg < s->npages; pg++) {
|
||||
s->pages[pg] = kalloc();
|
||||
if (!s->pages[pg]) {
|
||||
for (int j = 0; j < pg; j++) kfree(s->pages[j]);
|
||||
memset(s, 0, sizeof(*s));
|
||||
release(&shmlock);
|
||||
return -ENOMEM;
|
||||
}
|
||||
// Zeroed, because a fresh segment handing out the last tenant's
|
||||
// bytes would leak them between processes.
|
||||
memset(s->pages[pg], 0, PGSIZE);
|
||||
}
|
||||
s->used = 1;
|
||||
s->key = key;
|
||||
s->size = size;
|
||||
s->nattach = 0;
|
||||
release(&shmlock);
|
||||
return i;
|
||||
}
|
||||
|
||||
release(&shmlock);
|
||||
return -ENOSPC;
|
||||
}
|
||||
|
||||
addr_t
|
||||
sys_shmat(void)
|
||||
{
|
||||
int id, flags;
|
||||
addr_t addr;
|
||||
|
||||
if (argint(0, &id) < 0 || argaddr(1, &addr) < 0 || argint(2, &flags) < 0)
|
||||
return -EINVAL;
|
||||
if (id < 0 || id >= NSHM)
|
||||
return -EINVAL;
|
||||
|
||||
shm_init_once();
|
||||
acquire(&shmlock);
|
||||
if (!shm[id].used) {
|
||||
release(&shmlock);
|
||||
return -EINVAL;
|
||||
}
|
||||
uint64 len = shm[id].size;
|
||||
int npages = shm[id].npages;
|
||||
shm[id].nattach++; // claimed before the lock is dropped
|
||||
release(&shmlock);
|
||||
|
||||
struct vma *v = 0;
|
||||
for (int i = 0; i < NVMA; i++)
|
||||
if (!myproc()->vma[i].used) { v = &myproc()->vma[i]; break; }
|
||||
if (!v) { shm_detach(id); return -ENOMEM; }
|
||||
|
||||
// Ignore the hint and take a fresh range, the same way mmap does.
|
||||
if (myproc()->mmap_next == 0)
|
||||
myproc()->mmap_next = 0x40000000;
|
||||
uint64 va = myproc()->mmap_next;
|
||||
if (bad_user_range(va, len)) { shm_detach(id); return -ENOMEM; }
|
||||
myproc()->mmap_next += len;
|
||||
|
||||
for (int pg = 0; pg < npages; pg++) {
|
||||
if (mappages(myproc()->pgdir, (void*)(va + (uint64)pg * PGSIZE), PGSIZE,
|
||||
V2P((uint64)shm[id].pages[pg]), PTE_W | PTE_U) < 0) {
|
||||
vm_unmap_norelease(myproc()->pgdir, va, (uint64)pg * PGSIZE);
|
||||
shm_detach(id);
|
||||
return -ENOMEM;
|
||||
}
|
||||
}
|
||||
|
||||
v->addr = va; v->length = len; v->used = 1;
|
||||
v->no_free = 1; // the segment owns these pages
|
||||
v->shmid = id;
|
||||
return va;
|
||||
}
|
||||
|
||||
// Drop every mapping this process holds that it does not own the pages of,
|
||||
// before its page tables are torn down.
|
||||
//
|
||||
// freevm() frees any mapped page below PHYSTOP, which is right for the heap
|
||||
// and the stack and wrong for anything shared. The framebuffer survived only
|
||||
// because it sits above PHYSTOP and was skipped by accident; shared memory is
|
||||
// kalloc'd, sits below it, and would be handed to kfree while another process
|
||||
// was still using it -- the worst kind of bug, silent and remote.
|
||||
//
|
||||
// Called from exit() and from exec() before the old address space goes away.
|
||||
void
|
||||
proc_release_shared(struct proc *p)
|
||||
{
|
||||
for (int i = 0; i < NVMA; i++) {
|
||||
if (!p->vma[i].used || !p->vma[i].no_free)
|
||||
continue;
|
||||
vm_unmap_norelease(p->pgdir, p->vma[i].addr, p->vma[i].length);
|
||||
if (p->vma[i].shmid >= 0)
|
||||
shm_detach(p->vma[i].shmid);
|
||||
p->vma[i].used = 0;
|
||||
p->vma[i].no_free = 0;
|
||||
p->vma[i].shmid = -1;
|
||||
}
|
||||
}
|
||||
|
||||
addr_t
|
||||
sys_shmdt(void)
|
||||
{
|
||||
addr_t addr;
|
||||
if (argaddr(0, &addr) < 0)
|
||||
return -EINVAL;
|
||||
|
||||
for (int i = 0; i < NVMA; i++) {
|
||||
if (myproc()->vma[i].used && myproc()->vma[i].addr == addr &&
|
||||
myproc()->vma[i].shmid >= 0) {
|
||||
vm_unmap_norelease(myproc()->pgdir, addr, myproc()->vma[i].length);
|
||||
shm_detach(myproc()->vma[i].shmid);
|
||||
myproc()->vma[i].used = myproc()->vma[i].addr = myproc()->vma[i].length = 0;
|
||||
myproc()->vma[i].no_free = 0;
|
||||
myproc()->vma[i].shmid = -1;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
// Memory advice/hints — all no-ops; kernel decides on its own.
|
||||
addr_t sys_madvise(void) { return 0; }
|
||||
addr_t sys_msync(void) { return 0; }
|
||||
addr_t sys_mincore(void) { return 0; }
|
||||
addr_t sys_mlock(void) { return 0; }
|
||||
addr_t sys_munlock(void) { return 0; }
|
||||
addr_t sys_mlockall(void) { return 0; }
|
||||
addr_t sys_munlockall(void){ return 0; }
|
||||
addr_t sys_fadvise64(void) { return 0; }
|
||||
|
||||
// mremap: we don't support region relocation; return ENOMEM.
|
||||
addr_t sys_mremap(void) { return -ENOMEM; }
|
||||
|
||||
// Resource limits: always report unlimited.
|
||||
addr_t sys_setrlimit(void) { return 0; }
|
||||
|
||||
// Process priority: always report 0.
|
||||
addr_t sys_getpriority(void) { return 0; }
|
||||
addr_t sys_setpriority(void) { return 0; }
|
||||
346
kernel/sys/sys_net.c
Normal file
346
kernel/sys/sys_net.c
Normal file
|
|
@ -0,0 +1,346 @@
|
|||
// Socket syscalls.
|
||||
//
|
||||
// The kernel side is in kernel/net/unix.c; this file is only the translation
|
||||
// between a syscall's user-supplied `struct sockaddr` and the path string the
|
||||
// socket layer actually wants, plus the file-descriptor bookkeeping.
|
||||
//
|
||||
// Only AF_UNIX is answered. AF_INET is recognised and refused with
|
||||
// EAFNOSUPPORT rather than EINVAL, because a program that probes for IPv6 and
|
||||
// falls back to IPv4 needs to be able to tell "this address family does not
|
||||
// exist here" from "you passed nonsense".
|
||||
|
||||
#include "types.h"
|
||||
#include "defs.h"
|
||||
#include "param.h"
|
||||
#include "mmu.h"
|
||||
#include "proc.h"
|
||||
#include "fs.h"
|
||||
#include "spinlock.h"
|
||||
#include "sleeplock.h"
|
||||
#include "file.h"
|
||||
#include "posix.h"
|
||||
#include "syscall.h"
|
||||
#include "uaccess.h"
|
||||
#include "errno.h"
|
||||
#include "socket.h"
|
||||
|
||||
// Pull the sun_path out of a user `struct sockaddr_un`, NUL-terminated.
|
||||
//
|
||||
// The length the caller gives is checked rather than trusted: musl passes
|
||||
// `offsetof(sockaddr_un, sun_path) + strlen(path) + 1`, which is shorter than
|
||||
// the full struct, so reading sizeof(struct sockaddr_un) from user memory
|
||||
// would fault on a path that sits near the end of a page.
|
||||
static int
|
||||
fetch_sun_path(addr_t uaddr, int ulen, char *out)
|
||||
{
|
||||
struct sockaddr_un sa;
|
||||
int plen;
|
||||
|
||||
if (ulen < (int)sizeof(sa.sun_family) || ulen > (int)sizeof(sa))
|
||||
return -EINVAL;
|
||||
|
||||
memset(&sa, 0, sizeof(sa));
|
||||
if (copy_from_user(&sa, uaddr, (uint64)ulen) < 0)
|
||||
return -EFAULT;
|
||||
|
||||
if (sa.sun_family == AF_INET)
|
||||
return -EAFNOSUPPORT;
|
||||
if (sa.sun_family != AF_UNIX)
|
||||
return -EAFNOSUPPORT;
|
||||
|
||||
plen = ulen - (int)sizeof(sa.sun_family);
|
||||
if (plen <= 0)
|
||||
return -EINVAL;
|
||||
if (plen > UNIX_PATH_MAX - 1)
|
||||
plen = UNIX_PATH_MAX - 1;
|
||||
|
||||
memmove(out, sa.sun_path, (uint)plen);
|
||||
out[plen] = '\0';
|
||||
|
||||
// A NUL inside the copied region is fine -- it just ends the path early,
|
||||
// which is what a caller passing the full struct length does.
|
||||
out[UNIX_PATH_MAX - 1] = '\0';
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Wrap an established socket in a file and a descriptor. Consumes the
|
||||
// socket's reference on success; on failure the caller still owns it.
|
||||
static int
|
||||
sock_to_fd(struct socket *s)
|
||||
{
|
||||
struct file *f;
|
||||
int fd;
|
||||
|
||||
if ((f = filealloc()) == 0)
|
||||
return -ENFILE;
|
||||
if ((fd = fdalloc(f)) < 0) {
|
||||
f->ref = 0;
|
||||
return -EMFILE;
|
||||
}
|
||||
f->type = FD_SOCKET;
|
||||
f->sock = s;
|
||||
f->readable = 1;
|
||||
f->writable = 1;
|
||||
f->off = 0;
|
||||
return fd;
|
||||
}
|
||||
|
||||
// Resolve a descriptor to the socket behind it.
|
||||
static int
|
||||
argsock(int n, struct socket **ps)
|
||||
{
|
||||
int fd;
|
||||
struct file *f;
|
||||
|
||||
if (argint(n, &fd) < 0)
|
||||
return -EBADF;
|
||||
if (fd < 0 || fd >= NOFILE || !(f = myproc()->ofile[fd]))
|
||||
return -EBADF;
|
||||
if (f->type != FD_SOCKET)
|
||||
return -ENOTSOCK;
|
||||
*ps = f->sock;
|
||||
return 0;
|
||||
}
|
||||
|
||||
SYSCALL_DEFINE(socket)
|
||||
{
|
||||
int domain, type, protocol;
|
||||
struct socket *s;
|
||||
int fd;
|
||||
|
||||
argint(0, &domain);
|
||||
argint(1, &type);
|
||||
argint(2, &protocol);
|
||||
|
||||
if (domain != AF_UNIX)
|
||||
return -EAFNOSUPPORT;
|
||||
|
||||
// SOCK_CLOEXEC / SOCK_NONBLOCK ride in the high bits of `type`. Neither
|
||||
// is honoured yet, but masking them off means a musl program that always
|
||||
// sets SOCK_CLOEXEC still gets a socket instead of EINVAL.
|
||||
type &= 0xFF;
|
||||
if (type != SOCK_STREAM)
|
||||
return -EPROTOTYPE;
|
||||
if (protocol != 0)
|
||||
return -EPROTONOSUPPORT;
|
||||
|
||||
if ((s = socknew(domain, type)) == 0)
|
||||
return -ENFILE;
|
||||
|
||||
if ((fd = sock_to_fd(s)) < 0) {
|
||||
sockclose(s);
|
||||
return fd;
|
||||
}
|
||||
return fd;
|
||||
}
|
||||
|
||||
SYSCALL_DEFINE(bind)
|
||||
{
|
||||
struct socket *s;
|
||||
addr_t uaddr;
|
||||
int ulen, r;
|
||||
char path[UNIX_PATH_MAX];
|
||||
|
||||
if ((r = argsock(0, &s)) < 0)
|
||||
return r;
|
||||
argaddr(1, &uaddr);
|
||||
argint(2, &ulen);
|
||||
|
||||
if ((r = fetch_sun_path(uaddr, ulen, path)) < 0)
|
||||
return r;
|
||||
return sockbind(s, path);
|
||||
}
|
||||
|
||||
SYSCALL_DEFINE(listen)
|
||||
{
|
||||
struct socket *s;
|
||||
int backlog, r;
|
||||
|
||||
if ((r = argsock(0, &s)) < 0)
|
||||
return r;
|
||||
argint(1, &backlog);
|
||||
return socklisten(s, backlog);
|
||||
}
|
||||
|
||||
SYSCALL_DEFINE(connect)
|
||||
{
|
||||
struct socket *s;
|
||||
addr_t uaddr;
|
||||
int ulen, r;
|
||||
char path[UNIX_PATH_MAX];
|
||||
|
||||
if ((r = argsock(0, &s)) < 0)
|
||||
return r;
|
||||
argaddr(1, &uaddr);
|
||||
argint(2, &ulen);
|
||||
|
||||
if ((r = fetch_sun_path(uaddr, ulen, path)) < 0)
|
||||
return r;
|
||||
return sockconnect(s, path);
|
||||
}
|
||||
|
||||
SYSCALL_DEFINE(accept)
|
||||
{
|
||||
struct socket *s, *c;
|
||||
addr_t uaddr, ulenp;
|
||||
int r, fd;
|
||||
|
||||
if ((r = argsock(0, &s)) < 0)
|
||||
return r;
|
||||
argaddr(1, &uaddr);
|
||||
argaddr(2, &ulenp);
|
||||
|
||||
if ((c = sockaccept(s)) == 0)
|
||||
return -EINVAL;
|
||||
|
||||
if ((fd = sock_to_fd(c)) < 0) {
|
||||
sockclose(c);
|
||||
return fd;
|
||||
}
|
||||
|
||||
// The peer address of a Unix socket that never bound a name is the empty
|
||||
// one: family AF_UNIX and a zero-length path. Reporting it is optional
|
||||
// (both pointers may be null) but a caller that asks must not be left
|
||||
// with an uninitialised struct it then prints.
|
||||
if (uaddr && ulenp) {
|
||||
struct sockaddr_un sa;
|
||||
int len = (int)sizeof(sa.sun_family);
|
||||
int want = 0;
|
||||
|
||||
memset(&sa, 0, sizeof(sa));
|
||||
sa.sun_family = AF_UNIX;
|
||||
safestrcpy(sa.sun_path, sockname(c), UNIX_PATH_MAX);
|
||||
len += (int)strlen(sa.sun_path) + 1;
|
||||
|
||||
if (get_user(want, ulenp) < 0)
|
||||
return fd; // the fd is already live; do not fail it
|
||||
if (want > len)
|
||||
want = len;
|
||||
if (want > 0)
|
||||
copy_to_user(uaddr, &sa, (uint64)want);
|
||||
put_user(len, ulenp);
|
||||
}
|
||||
return fd;
|
||||
}
|
||||
|
||||
SYSCALL_DEFINE(socketpair)
|
||||
{
|
||||
int domain, type, protocol;
|
||||
addr_t usv;
|
||||
struct socket *a, *b;
|
||||
int fd0, fd1, r;
|
||||
|
||||
argint(0, &domain);
|
||||
argint(1, &type);
|
||||
argint(2, &protocol);
|
||||
argaddr(3, &usv);
|
||||
|
||||
if (domain != AF_UNIX)
|
||||
return -EAFNOSUPPORT;
|
||||
type &= 0xFF;
|
||||
if (type != SOCK_STREAM)
|
||||
return -EPROTOTYPE;
|
||||
|
||||
if ((r = sockpair(&a, &b)) < 0)
|
||||
return r;
|
||||
|
||||
if ((fd0 = sock_to_fd(a)) < 0) {
|
||||
sockclose(a);
|
||||
sockclose(b);
|
||||
return fd0;
|
||||
}
|
||||
if ((fd1 = sock_to_fd(b)) < 0) {
|
||||
myproc()->ofile[fd0] = 0;
|
||||
sockclose(a);
|
||||
sockclose(b);
|
||||
return fd1;
|
||||
}
|
||||
|
||||
int sv[2] = { fd0, fd1 };
|
||||
if (copy_to_user(usv, sv, sizeof(sv)) < 0) {
|
||||
myproc()->ofile[fd0] = 0;
|
||||
myproc()->ofile[fd1] = 0;
|
||||
sockclose(a);
|
||||
sockclose(b);
|
||||
return -EFAULT;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// send/recv with no flags are read/write. Implementing them here rather than
|
||||
// leaving them ENOSYS costs four lines and saves every ported program from
|
||||
// having to be rewritten to use write().
|
||||
SYSCALL_DEFINE(sendto)
|
||||
{
|
||||
struct socket *s;
|
||||
addr_t ubuf;
|
||||
int len, flags, r;
|
||||
|
||||
if ((r = argsock(0, &s)) < 0)
|
||||
return r;
|
||||
argaddr(1, &ubuf);
|
||||
argint(2, &len);
|
||||
argint(3, &flags);
|
||||
|
||||
if (len < 0)
|
||||
return -EINVAL;
|
||||
char *p = (char *)user_ptr(ubuf, (uint64)len);
|
||||
if (len && !p)
|
||||
return -EFAULT;
|
||||
return sockwrite(s, p, len);
|
||||
}
|
||||
|
||||
SYSCALL_DEFINE(recvfrom)
|
||||
{
|
||||
struct socket *s;
|
||||
addr_t ubuf;
|
||||
int len, flags, r;
|
||||
|
||||
if ((r = argsock(0, &s)) < 0)
|
||||
return r;
|
||||
argaddr(1, &ubuf);
|
||||
argint(2, &len);
|
||||
argint(3, &flags);
|
||||
|
||||
if (len < 0)
|
||||
return -EINVAL;
|
||||
char *p = (char *)user_ptr(ubuf, (uint64)len);
|
||||
if (len && !p)
|
||||
return -EFAULT;
|
||||
return sockread(s, p, len);
|
||||
}
|
||||
|
||||
// shutdown(2). Half-close is not modelled -- there is one buffer per
|
||||
// direction and no per-direction flag -- so SHUT_WR and SHUT_RDWR both mean
|
||||
// "the peer sees end-of-file", which is the behaviour programs depend on.
|
||||
SYSCALL_DEFINE(sockshutdown)
|
||||
{
|
||||
struct socket *s;
|
||||
int how, r;
|
||||
|
||||
if ((r = argsock(0, &s)) < 0)
|
||||
return r;
|
||||
argint(1, &how);
|
||||
return sock_shutdown(s, how);
|
||||
}
|
||||
|
||||
// getsockopt/setsockopt: accepted and ignored. A server that sets SO_REUSEADDR
|
||||
// and treats failure as fatal is common enough to be worth not failing.
|
||||
SYSCALL_DEFINE(setsockopt) { return 0; }
|
||||
|
||||
SYSCALL_DEFINE(getsockopt)
|
||||
{
|
||||
addr_t uval, ulen;
|
||||
int zero = 0, len = 0;
|
||||
|
||||
argaddr(3, &uval);
|
||||
argaddr(4, &ulen);
|
||||
if (uval && ulen) {
|
||||
if (get_user(len, ulen) == 0 && len >= (int)sizeof(int)) {
|
||||
put_user(zero, uval);
|
||||
len = sizeof(int);
|
||||
put_user(len, ulen);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
292
kernel/sys/sys_poll.c
Normal file
292
kernel/sys/sys_poll.c
Normal file
|
|
@ -0,0 +1,292 @@
|
|||
// poll(2) / select(2) — real readiness-based I/O multiplexing over pipes,
|
||||
// the console tty, and regular files (via file_poll()). This is the
|
||||
// foundation every event-loop program (dash, toybox netcat, eventually a
|
||||
// display server) sits on; the previous stubs returned -ENOSYS, which made
|
||||
// anything poll-driven bail out instantly.
|
||||
//
|
||||
// Blocking strategy: scan all fds; if nothing is ready and the timeout
|
||||
// allows, sleep until the next timer tick (10ms granularity at HZ=100) and
|
||||
// rescan. A pending unblocked signal or kill interrupts the wait with
|
||||
// -EINTR so handlers get a chance to run, matching POSIX.
|
||||
|
||||
#include "types.h"
|
||||
#include "defs.h"
|
||||
#include "param.h"
|
||||
#include "spinlock.h"
|
||||
#include "sleeplock.h"
|
||||
#include "fs.h"
|
||||
#include "file.h"
|
||||
#include "proc.h"
|
||||
#include "syscall.h"
|
||||
#include "posix.h"
|
||||
#include "uaccess.h"
|
||||
#include "errno.h"
|
||||
|
||||
#define POLL_MAX_FDS 64
|
||||
|
||||
// Sleep one timer tick, or until killed/signaled. Returns 0 to keep
|
||||
// waiting, -EINTR if the wait must be abandoned.
|
||||
static int
|
||||
poll_block_one_tick(void)
|
||||
{
|
||||
if (myproc()->killed)
|
||||
return -EINTR;
|
||||
if (myproc()->pending_signals & ~myproc()->signal_mask)
|
||||
return -EINTR;
|
||||
acquire(&tickslock);
|
||||
uint t0 = ticks;
|
||||
while (ticks == t0 && !myproc()->killed &&
|
||||
!(myproc()->pending_signals & ~myproc()->signal_mask))
|
||||
sleep(&ticks, &tickslock);
|
||||
release(&tickslock);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Shared core: fds[] already in kernel memory. timeout_ticks < 0 means
|
||||
// wait forever. Returns number of fds with nonzero revents, 0 on timeout,
|
||||
// -EINTR if interrupted.
|
||||
static int
|
||||
do_poll(struct pollfd *fds, int nfds, int timeout_ticks)
|
||||
{
|
||||
for (;;) {
|
||||
int nready = 0;
|
||||
for (int i = 0; i < nfds; i++) {
|
||||
fds[i].revents = 0;
|
||||
if (fds[i].fd < 0)
|
||||
continue; // negative fd: ignore, per poll(2)
|
||||
if (fds[i].fd >= NOFILE || !myproc()->ofile[fds[i].fd]) {
|
||||
fds[i].revents = POLLNVAL;
|
||||
nready++;
|
||||
continue;
|
||||
}
|
||||
int rev = file_poll(myproc()->ofile[fds[i].fd], fds[i].events);
|
||||
if (rev) {
|
||||
fds[i].revents = (short)rev;
|
||||
nready++;
|
||||
}
|
||||
}
|
||||
if (nready > 0 || timeout_ticks == 0)
|
||||
return nready;
|
||||
if (poll_block_one_tick() < 0)
|
||||
return -EINTR;
|
||||
if (timeout_ticks > 0)
|
||||
timeout_ticks--;
|
||||
}
|
||||
}
|
||||
|
||||
// poll(fds, nfds, timeout_ms)
|
||||
addr_t
|
||||
sys_poll(void)
|
||||
{
|
||||
addr_t ufds;
|
||||
int nfds, timeout_ms;
|
||||
struct pollfd fds[POLL_MAX_FDS];
|
||||
|
||||
argaddr(0, &ufds);
|
||||
argint(1, &nfds);
|
||||
argint(2, &timeout_ms);
|
||||
|
||||
if (nfds < 0 || nfds > POLL_MAX_FDS)
|
||||
return -EINVAL;
|
||||
if (nfds > 0 && copy_from_user(fds, ufds, nfds * sizeof(struct pollfd)) < 0)
|
||||
return -EFAULT;
|
||||
|
||||
int timeout_ticks = timeout_ms < 0 ? -1 : (timeout_ms + 9) / 10;
|
||||
int r = do_poll(fds, nfds, timeout_ticks);
|
||||
|
||||
if (r >= 0 && nfds > 0 &&
|
||||
copy_to_user(ufds, fds, nfds * sizeof(struct pollfd)) < 0)
|
||||
return -EFAULT;
|
||||
return r;
|
||||
}
|
||||
|
||||
// ppoll(fds, nfds, timespec*, sigmask) — sigmask is ignored (poll already
|
||||
// breaks out for any deliverable signal); NULL timespec = wait forever.
|
||||
addr_t
|
||||
sys_ppoll(void)
|
||||
{
|
||||
addr_t ufds, utsp;
|
||||
int nfds;
|
||||
struct pollfd fds[POLL_MAX_FDS];
|
||||
|
||||
argaddr(0, &ufds);
|
||||
argint(1, &nfds);
|
||||
argaddr(2, &utsp);
|
||||
|
||||
if (nfds < 0 || nfds > POLL_MAX_FDS)
|
||||
return -EINVAL;
|
||||
if (nfds > 0 && copy_from_user(fds, ufds, nfds * sizeof(struct pollfd)) < 0)
|
||||
return -EFAULT;
|
||||
|
||||
int timeout_ticks = -1;
|
||||
if (utsp) {
|
||||
int64 ts[2]; // {tv_sec, tv_nsec}
|
||||
if (copy_from_user(ts, utsp, sizeof(ts)) < 0)
|
||||
return -EFAULT;
|
||||
timeout_ticks = (int)(ts[0] * 100 + ts[1] / 10000000);
|
||||
}
|
||||
|
||||
int r = do_poll(fds, nfds, timeout_ticks);
|
||||
|
||||
if (r >= 0 && nfds > 0 &&
|
||||
copy_to_user(ufds, fds, nfds * sizeof(struct pollfd)) < 0)
|
||||
return -EFAULT;
|
||||
return r;
|
||||
}
|
||||
|
||||
// select(nfds, readfds, writefds, exceptfds, timeval*). fd_set is a bit
|
||||
// array; with NOFILE=128 the interesting part fits in two 64-bit words.
|
||||
#define FDSET_WORDS (NOFILE / 64)
|
||||
|
||||
static int
|
||||
fdset_fetch(addr_t uaddr, uint64 *set)
|
||||
{
|
||||
if (!uaddr) {
|
||||
set[0] = set[1] = 0;
|
||||
return 0;
|
||||
}
|
||||
return copy_from_user(set, uaddr, FDSET_WORDS * sizeof(uint64));
|
||||
}
|
||||
|
||||
static int
|
||||
fdset_isset(uint64 *set, int fd) { return (set[fd / 64] >> (fd % 64)) & 1; }
|
||||
static void
|
||||
fdset_set(uint64 *set, int fd) { set[fd / 64] |= 1ULL << (fd % 64); }
|
||||
|
||||
addr_t
|
||||
sys_select(void)
|
||||
{
|
||||
int nfds;
|
||||
addr_t urd, uwr, uex, utv;
|
||||
uint64 rd[FDSET_WORDS], wr[FDSET_WORDS], ex[FDSET_WORDS];
|
||||
uint64 ord[FDSET_WORDS] = {0}, owr[FDSET_WORDS] = {0};
|
||||
|
||||
argint(0, &nfds);
|
||||
argaddr(1, &urd);
|
||||
argaddr(2, &uwr);
|
||||
argaddr(3, &uex);
|
||||
argaddr(4, &utv);
|
||||
|
||||
if (nfds < 0 || nfds > NOFILE)
|
||||
return -EINVAL;
|
||||
if (fdset_fetch(urd, rd) < 0 || fdset_fetch(uwr, wr) < 0 ||
|
||||
fdset_fetch(uex, ex) < 0)
|
||||
return -EFAULT;
|
||||
|
||||
int timeout_ticks = -1;
|
||||
if (utv) {
|
||||
int64 tv[2]; // {tv_sec, tv_usec}
|
||||
if (copy_from_user(tv, utv, sizeof(tv)) < 0)
|
||||
return -EFAULT;
|
||||
timeout_ticks = (int)(tv[0] * 100 + tv[1] / 10000);
|
||||
}
|
||||
|
||||
for (;;) {
|
||||
int nready = 0;
|
||||
for (int fd = 0; fd < nfds; fd++) {
|
||||
int want_r = fdset_isset(rd, fd);
|
||||
int want_w = fdset_isset(wr, fd);
|
||||
if (!want_r && !want_w)
|
||||
continue;
|
||||
struct file *f = myproc()->ofile[fd];
|
||||
if (!f)
|
||||
return -EBADF;
|
||||
int rev = file_poll(f, (want_r ? POLLIN : 0) | (want_w ? POLLOUT : 0));
|
||||
// Hangup/error count as readable: a read() will not block
|
||||
// (it returns EOF/error immediately) — same rule Linux uses.
|
||||
if (want_r && (rev & (POLLIN | POLLHUP | POLLERR))) {
|
||||
fdset_set(ord, fd);
|
||||
nready++;
|
||||
}
|
||||
if (want_w && (rev & (POLLOUT | POLLERR))) {
|
||||
fdset_set(owr, fd);
|
||||
nready++;
|
||||
}
|
||||
}
|
||||
if (nready > 0 || timeout_ticks == 0) {
|
||||
if (urd && copy_to_user(urd, ord, sizeof(ord)) < 0) return -EFAULT;
|
||||
if (uwr && copy_to_user(uwr, owr, sizeof(owr)) < 0) return -EFAULT;
|
||||
if (uex) { // no exceptional conditions exist here; report none
|
||||
uint64 zero[FDSET_WORDS] = {0};
|
||||
if (copy_to_user(uex, zero, sizeof(zero)) < 0) return -EFAULT;
|
||||
}
|
||||
return nready;
|
||||
}
|
||||
if (poll_block_one_tick() < 0)
|
||||
return -EINTR;
|
||||
if (timeout_ticks > 0)
|
||||
timeout_ticks--;
|
||||
}
|
||||
}
|
||||
|
||||
// pselect6: select with a timespec and (ignored) sigmask.
|
||||
addr_t
|
||||
sys_pselect6(void)
|
||||
{
|
||||
// Delegate: args 0-3 line up with select's; arg 4 is a timespec instead
|
||||
// of a timeval and arg 5 the sigmask. Reuse sys_select's field reads but
|
||||
// treat the timeout as nanoseconds.
|
||||
int nfds;
|
||||
addr_t urd, uwr, uex, utsp;
|
||||
argint(0, &nfds);
|
||||
argaddr(1, &urd);
|
||||
argaddr(2, &uwr);
|
||||
argaddr(3, &uex);
|
||||
argaddr(4, &utsp);
|
||||
|
||||
// Convert the timespec (if any) into a timeval in place on the user
|
||||
// stack? No — simplest correct route: temporarily run the same logic.
|
||||
// sys_select re-reads the same registers, so just fix up the timeout
|
||||
// interpretation here by patching r8 semantics is not possible; instead
|
||||
// duplicate the small tail:
|
||||
uint64 rd[FDSET_WORDS], wr[FDSET_WORDS], ex[FDSET_WORDS];
|
||||
uint64 ord[FDSET_WORDS] = {0}, owr[FDSET_WORDS] = {0};
|
||||
if (nfds < 0 || nfds > NOFILE)
|
||||
return -EINVAL;
|
||||
if (fdset_fetch(urd, rd) < 0 || fdset_fetch(uwr, wr) < 0 ||
|
||||
fdset_fetch(uex, ex) < 0)
|
||||
return -EFAULT;
|
||||
|
||||
int timeout_ticks = -1;
|
||||
if (utsp) {
|
||||
int64 ts[2];
|
||||
if (copy_from_user(ts, utsp, sizeof(ts)) < 0)
|
||||
return -EFAULT;
|
||||
timeout_ticks = (int)(ts[0] * 100 + ts[1] / 10000000);
|
||||
}
|
||||
|
||||
for (;;) {
|
||||
int nready = 0;
|
||||
for (int fd = 0; fd < nfds; fd++) {
|
||||
int want_r = fdset_isset(rd, fd);
|
||||
int want_w = fdset_isset(wr, fd);
|
||||
if (!want_r && !want_w)
|
||||
continue;
|
||||
struct file *f = myproc()->ofile[fd];
|
||||
if (!f)
|
||||
return -EBADF;
|
||||
int rev = file_poll(f, (want_r ? POLLIN : 0) | (want_w ? POLLOUT : 0));
|
||||
if (want_r && (rev & (POLLIN | POLLHUP | POLLERR))) {
|
||||
fdset_set(ord, fd);
|
||||
nready++;
|
||||
}
|
||||
if (want_w && (rev & (POLLOUT | POLLERR))) {
|
||||
fdset_set(owr, fd);
|
||||
nready++;
|
||||
}
|
||||
}
|
||||
if (nready > 0 || timeout_ticks == 0) {
|
||||
if (urd && copy_to_user(urd, ord, sizeof(ord)) < 0) return -EFAULT;
|
||||
if (uwr && copy_to_user(uwr, owr, sizeof(owr)) < 0) return -EFAULT;
|
||||
if (uex) {
|
||||
uint64 zero[FDSET_WORDS] = {0};
|
||||
if (copy_to_user(uex, zero, sizeof(zero)) < 0) return -EFAULT;
|
||||
}
|
||||
return nready;
|
||||
}
|
||||
if (poll_block_one_tick() < 0)
|
||||
return -EINTR;
|
||||
if (timeout_ticks > 0)
|
||||
timeout_ticks--;
|
||||
}
|
||||
}
|
||||
451
kernel/sys/sys_proc.c
Normal file
451
kernel/sys/sys_proc.c
Normal file
|
|
@ -0,0 +1,451 @@
|
|||
#include "types.h"
|
||||
#include "defs.h"
|
||||
#include "param.h"
|
||||
#include "memlayout.h"
|
||||
#include "mmu.h"
|
||||
#include "proc.h"
|
||||
#include "x86.h"
|
||||
#include "syscall.h"
|
||||
#include "posix.h"
|
||||
#include "uaccess.h"
|
||||
#include "errno.h"
|
||||
|
||||
addr_t sys_fork(void) { return fork(); }
|
||||
addr_t sys_wait(void) { return wait(); }
|
||||
|
||||
// _exit(status)/exit_group(status): the low 8 bits of the argument are what
|
||||
// wait() reports back through WEXITSTATUS.
|
||||
addr_t
|
||||
sys_exit(void)
|
||||
{
|
||||
int code = 0;
|
||||
argint(0, &code);
|
||||
exit(W_EXITCODE(code, 0));
|
||||
return 0;
|
||||
}
|
||||
addr_t sys_exit_group(void) { return sys_exit(); }
|
||||
|
||||
|
||||
addr_t
|
||||
sys_kill(void)
|
||||
{
|
||||
int pid, sig;
|
||||
argint(0, &pid);
|
||||
argint(1, &sig);
|
||||
|
||||
// POSIX target selection:
|
||||
// pid > 0 -> that process
|
||||
// pid == 0 -> every process in the caller's own group
|
||||
// pid == -1 -> every process we may signal (approximated as our group)
|
||||
// pid < -1 -> the process group with id -pid
|
||||
if (pid == 0 || pid == -1)
|
||||
return sigsend_pgrp(myproc()->pgid, sig) < 0 ? -ESRCH : 0;
|
||||
if (pid < -1)
|
||||
return sigsend_pgrp(-pid, sig) < 0 ? -ESRCH : 0;
|
||||
|
||||
// sig == 0: existence check only
|
||||
if (sig == 0)
|
||||
return proc_exists(pid) ? 0 : -ESRCH;
|
||||
|
||||
// Raise the signal for real: pending bit + wakeup, handler delivery on
|
||||
// the target's next syscall boundary, default action if unhandled.
|
||||
return sigsend(pid, sig) < 0 ? -ESRCH : 0;
|
||||
}
|
||||
|
||||
addr_t sys_getpid(void) { return myproc()->pid; }
|
||||
addr_t sys_getppid(void) { return myproc()->parent ? myproc()->parent->pid : 1; }
|
||||
addr_t sys_gettid(void) { return myproc()->pid; } // single-threaded: tid == pid
|
||||
|
||||
addr_t
|
||||
sys_waitpid(void)
|
||||
{
|
||||
int pid, options;
|
||||
addr_t statusp;
|
||||
// status is optional (NULL is standard, valid usage — waitpid(pid, NULL,
|
||||
// WNOHANG) — so this must NOT go through argptr(), which rejects a NULL/
|
||||
// unmapped pointer outright).
|
||||
if (argint(0, &pid) < 0 || argaddr(1, &statusp) < 0 || argint(2, &options) < 0)
|
||||
return -1;
|
||||
int status;
|
||||
int wpid = waitpid(pid, &status, options);
|
||||
if (wpid > 0 && statusp)
|
||||
put_user(status, statusp);
|
||||
return wpid;
|
||||
}
|
||||
|
||||
addr_t
|
||||
sys_wait4(void)
|
||||
{
|
||||
int pid, options;
|
||||
addr_t statusp;
|
||||
argint(0, &pid);
|
||||
argaddr(1, &statusp);
|
||||
argint(2, &options);
|
||||
int status;
|
||||
int wpid = waitpid(pid, &status, options);
|
||||
if (wpid <= 0)
|
||||
return wpid;
|
||||
if (statusp)
|
||||
put_user(status, statusp);
|
||||
return wpid;
|
||||
}
|
||||
|
||||
addr_t
|
||||
sys_clone(void)
|
||||
{
|
||||
int flags;
|
||||
argint(0, &flags);
|
||||
if (flags & (CLONE_VM | CLONE_THREAD | CLONE_SIGHAND))
|
||||
return -ENOSYS;
|
||||
return fork();
|
||||
}
|
||||
|
||||
addr_t
|
||||
sys_sleep(void)
|
||||
{
|
||||
int n;
|
||||
uint ticks0;
|
||||
if (argint(0, &n) < 0)
|
||||
return -1;
|
||||
acquire(&tickslock);
|
||||
ticks0 = ticks;
|
||||
while (ticks - ticks0 < (uint)n) {
|
||||
if (myproc()->killed) { release(&tickslock); return -1; }
|
||||
sleep(&ticks, &tickslock);
|
||||
}
|
||||
release(&tickslock);
|
||||
return 0;
|
||||
}
|
||||
|
||||
addr_t
|
||||
sys_uptime(void)
|
||||
{
|
||||
uint xticks;
|
||||
acquire(&tickslock);
|
||||
xticks = ticks;
|
||||
release(&tickslock);
|
||||
return xticks;
|
||||
}
|
||||
|
||||
addr_t sys_reboot(void)
|
||||
{
|
||||
cprintf("System is rebooting...\n");
|
||||
outb(0xCF9, 0x06);
|
||||
outb(0x64, 0xFE);
|
||||
return 0;
|
||||
}
|
||||
|
||||
addr_t sys_shutdown(void)
|
||||
{
|
||||
cprintf("System is shutting down...\n");
|
||||
outw(0x604, 0x2000);
|
||||
outw(0xB004, 0x2000);
|
||||
outw(0x4004, 0x3400);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// --- Sessions and process groups ---------------------------------------
|
||||
// Job control is defined entirely in terms of these. They used to be stubs
|
||||
// returning the caller's own pid, which made every process look like its own
|
||||
// group leader and left a shell's wait loop unable to make progress.
|
||||
|
||||
addr_t sys_setsid(void) { return proc_setsid(); }
|
||||
|
||||
addr_t
|
||||
sys_getsid(void)
|
||||
{
|
||||
int pid;
|
||||
argint(0, &pid);
|
||||
if (pid == 0 || pid == myproc()->pid)
|
||||
return myproc()->sid;
|
||||
return proc_exists(pid) ? (addr_t)myproc()->sid : (addr_t)-ESRCH;
|
||||
}
|
||||
|
||||
addr_t
|
||||
sys_getpgid(void)
|
||||
{
|
||||
int pid;
|
||||
argint(0, &pid);
|
||||
if (pid == 0 || pid == myproc()->pid)
|
||||
return myproc()->pgid;
|
||||
int g = proc_pgid(pid);
|
||||
return g < 0 ? (addr_t)-ESRCH : (addr_t)g;
|
||||
}
|
||||
|
||||
addr_t
|
||||
sys_setpgid(void)
|
||||
{
|
||||
int pid, pgid;
|
||||
argint(0, &pid);
|
||||
argint(1, &pgid);
|
||||
return proc_setpgid(pid, pgid);
|
||||
}
|
||||
|
||||
addr_t sys_getpgrp(void) { return myproc()->pgid; }
|
||||
|
||||
// --- User and group identity -------------------------------------------
|
||||
// Tracked for real now. Nothing *enforces* permissions with these yet -- the
|
||||
// on-disk format has no owner field until SchFS -- but they are no longer
|
||||
// invented, so getuid() after setuid() reports what actually happened.
|
||||
|
||||
addr_t sys_getuid(void) { return myproc()->uid; }
|
||||
addr_t sys_geteuid(void) { return myproc()->euid; }
|
||||
addr_t sys_getgid(void) { return myproc()->gid; }
|
||||
addr_t sys_getegid(void) { return myproc()->egid; }
|
||||
|
||||
// Privileged (euid 0) callers may set any id; an unprivileged caller may only
|
||||
// switch between its real and saved ids. Standard POSIX rule.
|
||||
addr_t
|
||||
sys_setuid(void)
|
||||
{
|
||||
int uid;
|
||||
argint(0, &uid);
|
||||
if (uid < 0)
|
||||
return -EINVAL;
|
||||
if (myproc()->euid == 0) {
|
||||
myproc()->uid = myproc()->euid = myproc()->suid = (uint)uid;
|
||||
return 0;
|
||||
}
|
||||
if ((uint)uid != myproc()->uid && (uint)uid != myproc()->suid)
|
||||
return -EPERM;
|
||||
myproc()->euid = (uint)uid;
|
||||
return 0;
|
||||
}
|
||||
|
||||
addr_t
|
||||
sys_setgid(void)
|
||||
{
|
||||
int gid;
|
||||
argint(0, &gid);
|
||||
if (gid < 0)
|
||||
return -EINVAL;
|
||||
if (myproc()->euid == 0) {
|
||||
myproc()->gid = myproc()->egid = myproc()->sgid = (uint)gid;
|
||||
return 0;
|
||||
}
|
||||
if ((uint)gid != myproc()->gid && (uint)gid != myproc()->sgid)
|
||||
return -EPERM;
|
||||
myproc()->egid = (uint)gid;
|
||||
return 0;
|
||||
}
|
||||
|
||||
addr_t sys_umask(void) { return 022; }
|
||||
|
||||
addr_t
|
||||
sys_setreuid(void)
|
||||
{
|
||||
int ruid, euid;
|
||||
argint(0, &ruid); argint(1, &euid);
|
||||
if (myproc()->euid != 0)
|
||||
return -EPERM;
|
||||
if (ruid >= 0) myproc()->uid = (uint)ruid;
|
||||
if (euid >= 0) myproc()->euid = (uint)euid;
|
||||
myproc()->suid = myproc()->euid;
|
||||
return 0;
|
||||
}
|
||||
|
||||
addr_t
|
||||
sys_setregid(void)
|
||||
{
|
||||
int rgid, egid;
|
||||
argint(0, &rgid); argint(1, &egid);
|
||||
if (myproc()->euid != 0)
|
||||
return -EPERM;
|
||||
if (rgid >= 0) myproc()->gid = (uint)rgid;
|
||||
if (egid >= 0) myproc()->egid = (uint)egid;
|
||||
myproc()->sgid = myproc()->egid;
|
||||
return 0;
|
||||
}
|
||||
|
||||
addr_t
|
||||
sys_setresuid(void)
|
||||
{
|
||||
int r, e, sv;
|
||||
argint(0, &r); argint(1, &e); argint(2, &sv);
|
||||
if (myproc()->euid != 0)
|
||||
return -EPERM;
|
||||
if (r >= 0) myproc()->uid = (uint)r;
|
||||
if (e >= 0) myproc()->euid = (uint)e;
|
||||
if (sv >= 0) myproc()->suid = (uint)sv;
|
||||
return 0;
|
||||
}
|
||||
|
||||
addr_t
|
||||
sys_setresgid(void)
|
||||
{
|
||||
int r, e, sv;
|
||||
argint(0, &r); argint(1, &e); argint(2, &sv);
|
||||
if (myproc()->euid != 0)
|
||||
return -EPERM;
|
||||
if (r >= 0) myproc()->gid = (uint)r;
|
||||
if (e >= 0) myproc()->egid = (uint)e;
|
||||
if (sv >= 0) myproc()->sgid = (uint)sv;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Linux-specific; the fs id follows the effective id here.
|
||||
addr_t sys_setfsuid(void) { return myproc()->euid; }
|
||||
addr_t sys_setfsgid(void) { return myproc()->egid; }
|
||||
|
||||
addr_t
|
||||
sys_getresuid(void)
|
||||
{
|
||||
addr_t rp, ep, sp;
|
||||
argaddr(0, &rp); argaddr(1, &ep); argaddr(2, &sp);
|
||||
if (rp) put_user(myproc()->uid, rp);
|
||||
if (ep) put_user(myproc()->euid, ep);
|
||||
if (sp) put_user(myproc()->suid, sp);
|
||||
return 0;
|
||||
}
|
||||
|
||||
addr_t
|
||||
sys_getresgid(void)
|
||||
{
|
||||
addr_t rp, ep, sp;
|
||||
argaddr(0, &rp); argaddr(1, &ep); argaddr(2, &sp);
|
||||
if (rp) put_user(myproc()->gid, rp);
|
||||
if (ep) put_user(myproc()->egid, ep);
|
||||
if (sp) put_user(myproc()->sgid, sp);
|
||||
return 0;
|
||||
}
|
||||
|
||||
addr_t sys_getgroups(void)
|
||||
{
|
||||
int size;
|
||||
argint(0, &size);
|
||||
return 0; // no supplementary groups
|
||||
}
|
||||
addr_t sys_setgroups(void) { return 0; }
|
||||
|
||||
// --- Process/scheduling extensions ---
|
||||
|
||||
addr_t sys_sched_yield(void) { return 0; }
|
||||
|
||||
addr_t sys_sched_getaffinity(void)
|
||||
{
|
||||
int pid, cpusetsize;
|
||||
addr_t maskp;
|
||||
argint(0, &pid); argint(1, &cpusetsize); argaddr(2, &maskp);
|
||||
if (maskp && cpusetsize > 0) {
|
||||
char *m = (char*)user_ptr(maskp, (uint64)cpusetsize);
|
||||
if (m) { memset(m, 0, cpusetsize); m[0] = 1; } // CPU 0 only
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
addr_t sys_sched_setaffinity(void) { return 0; }
|
||||
|
||||
addr_t sys_prctl(void)
|
||||
{
|
||||
int op;
|
||||
addr_t arg2;
|
||||
argint(0, &op);
|
||||
argaddr(1, &arg2);
|
||||
if (op == PR_SET_NAME) {
|
||||
char *name;
|
||||
if (argstr(1, &name) < 0) return -1;
|
||||
safestrcpy(myproc()->name, name, sizeof(myproc()->name));
|
||||
return 0;
|
||||
}
|
||||
if (op == PR_GET_NAME) {
|
||||
char *buf = (char*)user_ptr(arg2, 16);
|
||||
if (!buf) return -EFAULT;
|
||||
safestrcpy(buf, myproc()->name, 16);
|
||||
return 0;
|
||||
}
|
||||
return 0; // all other prctl ops: silently succeed
|
||||
}
|
||||
|
||||
addr_t sys_tgkill(void)
|
||||
{
|
||||
int tgid, tid, sig;
|
||||
argint(0, &tgid); argint(1, &tid); argint(2, &sig);
|
||||
// In our single-threaded model tgid == tid == pid.
|
||||
if (tgid <= 0) return 0;
|
||||
if (sig == 0) return proc_exists(tgid) ? 0 : -ESRCH;
|
||||
return sigsend(tgid, sig) < 0 ? -ESRCH : 0;
|
||||
}
|
||||
|
||||
addr_t sys_tkill(void)
|
||||
{
|
||||
int tid, sig;
|
||||
argint(0, &tid); argint(1, &sig);
|
||||
if (tid <= 0) return 0;
|
||||
if (sig == 0) return proc_exists(tid) ? 0 : -ESRCH;
|
||||
return sigsend(tid, sig) < 0 ? -ESRCH : 0;
|
||||
}
|
||||
|
||||
addr_t sys_pause(void)
|
||||
{
|
||||
// Sleep until next timer tick and return -EINTR (no real signal delivery).
|
||||
acquire(&tickslock);
|
||||
uint t0 = ticks;
|
||||
while (ticks == t0 && !myproc()->killed)
|
||||
sleep(&ticks, &tickslock);
|
||||
release(&tickslock);
|
||||
return -EINTR;
|
||||
}
|
||||
|
||||
addr_t sys_getrusage(void)
|
||||
{
|
||||
int who;
|
||||
struct rusage *ru;
|
||||
argint(0, &who);
|
||||
if (argptr(1, (char**)&ru, sizeof(*ru)) < 0) return -1;
|
||||
memset(ru, 0, sizeof(*ru));
|
||||
acquire(&tickslock);
|
||||
uint xt = ticks;
|
||||
release(&tickslock);
|
||||
ru->ru_utime.tv_sec = (long)(xt / 100);
|
||||
ru->ru_utime.tv_usec = (long)((xt % 100) * 10000);
|
||||
return 0;
|
||||
}
|
||||
|
||||
addr_t sys_times(void)
|
||||
{
|
||||
addr_t bufaddr;
|
||||
argaddr(0, &bufaddr);
|
||||
if (bufaddr) {
|
||||
struct tms *t = (struct tms*)user_ptr(bufaddr, sizeof(*t));
|
||||
if (t) memset(t, 0, sizeof(*t));
|
||||
}
|
||||
acquire(&tickslock);
|
||||
uint xt = ticks;
|
||||
release(&tickslock);
|
||||
return (addr_t)xt; // return clock ticks since boot
|
||||
}
|
||||
|
||||
addr_t sys_sysinfo(void)
|
||||
{
|
||||
addr_t bufaddr;
|
||||
argaddr(0, &bufaddr);
|
||||
struct sysinfo *si = (struct sysinfo*)user_ptr(bufaddr, sizeof(*si));
|
||||
if (!si) return -EFAULT;
|
||||
memset(si, 0, sizeof(*si));
|
||||
acquire(&tickslock);
|
||||
uint xt = ticks;
|
||||
release(&tickslock);
|
||||
si->uptime = (long)(xt / 100);
|
||||
// Real values: PHYSTOP is the top of usable physical memory and the page
|
||||
// allocator knows exactly how much of it is still on the free list. These
|
||||
// used to be the constants 256MB/128MB, invented and always wrong.
|
||||
si->totalram = (uint64)PHYSTOP;
|
||||
si->freeram = kalloc_free_bytes();
|
||||
si->mem_unit = 1;
|
||||
si->procs = (uint16)proc_num();
|
||||
return 0;
|
||||
}
|
||||
|
||||
addr_t sys_waitid(void)
|
||||
{
|
||||
// Simplified: wait for any child, ignore idtype/id/options.
|
||||
int wpid = wait();
|
||||
if (wpid < 0) return -ECHILD;
|
||||
// siginfo_t pointer (arg 2) — zero-fill if provided.
|
||||
addr_t infop;
|
||||
argaddr(2, &infop);
|
||||
if (infop) {
|
||||
char *p = (char*)user_ptr(infop, 128); // sizeof(siginfo_t)
|
||||
if (p) memset(p, 0, 128);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
296
kernel/sys/sys_sig.c
Normal file
296
kernel/sys/sys_sig.c
Normal file
|
|
@ -0,0 +1,296 @@
|
|||
#include "types.h"
|
||||
#include "defs.h"
|
||||
#include "param.h"
|
||||
#include "proc.h"
|
||||
#include "syscall.h"
|
||||
#include "uaccess.h"
|
||||
#include "errno.h"
|
||||
|
||||
// Linux sigset_t puts signal N at bit N-1; kernel-internal masks
|
||||
// (pending_signals / signal_mask / deliver_signals) put signal N at bit N.
|
||||
// Convert at the user boundary — storing user sets raw silently shifted
|
||||
// every mask by one signal (blocking SIGCHLD actually blocked SIGCONT),
|
||||
// which let mksh's SIGCHLD handler run inside the very critical sections
|
||||
// sigprocmask() was guarding, wrecking its job table.
|
||||
static uint64 sigset_from_user(uint64 uset) { return uset << 1; }
|
||||
static uint64 sigset_to_user(uint64 kset) { return kset >> 1; }
|
||||
|
||||
addr_t
|
||||
sys_sigfillset(void)
|
||||
{
|
||||
uint64 *set;
|
||||
if (argptr(0, (char**)&set, sizeof(uint64)) < 0)
|
||||
return -1;
|
||||
if (set) *set = ~0ULL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
addr_t
|
||||
sys_sigprocmask(void)
|
||||
{
|
||||
int how;
|
||||
addr_t setp, oldsetp;
|
||||
argint(0, &how);
|
||||
argaddr(1, &setp);
|
||||
argaddr(2, &oldsetp);
|
||||
|
||||
// oldset is optional (NULL = don't return previous mask)
|
||||
if (oldsetp) {
|
||||
uint64 old = sigset_to_user(myproc()->signal_mask);
|
||||
put_user(old, oldsetp);
|
||||
}
|
||||
// set is optional (NULL = just query)
|
||||
if (setp) {
|
||||
uint64 uset;
|
||||
if (copy_from_user(&uset, setp, sizeof(uset)) == 0) {
|
||||
uint64 set = sigset_from_user(uset);
|
||||
if (how == 0) myproc()->signal_mask |= set; // SIG_BLOCK
|
||||
else if (how == 1) myproc()->signal_mask &= ~set; // SIG_UNBLOCK
|
||||
else myproc()->signal_mask = set; // SIG_SETMASK
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
addr_t
|
||||
sys_sigwait(void)
|
||||
{
|
||||
uint64 *set_ptr;
|
||||
int *sig_ptr;
|
||||
if (argptr(0, (char**)&set_ptr, sizeof(uint64)) < 0 ||
|
||||
argptr(1, (char**)&sig_ptr, sizeof(int)) < 0)
|
||||
return -1;
|
||||
uint64 set = sigset_from_user(*set_ptr);
|
||||
acquire(&tickslock);
|
||||
while (!(myproc()->pending_signals & set)) {
|
||||
if (myproc()->killed) { release(&tickslock); return -1; }
|
||||
sleep(myproc(), &tickslock);
|
||||
}
|
||||
for (int i = 0; i < 64; i++) {
|
||||
if ((myproc()->pending_signals & set) & (1ULL << i)) {
|
||||
myproc()->pending_signals &= ~(1ULL << i);
|
||||
*sig_ptr = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
release(&tickslock);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// musl's struct sigaction (musl 1.2.5 signal.h) for this target:
|
||||
// sa_handler (8 bytes) at offset 0, then a 128-byte sigset_t (musl uses
|
||||
// 128/sizeof(long) = 16 longs regardless of NSIG), sa_flags (4 bytes) +
|
||||
// padding, then sa_restorer (8 bytes) at offset 144. We only care about
|
||||
// those two 8-byte fields.
|
||||
#define SA_HANDLER_OFF 0
|
||||
#define SA_RESTORER_OFF 144
|
||||
|
||||
// rt_sigaction: store the handler + restorer so deliver_signals() (called
|
||||
// from syscall()'s tail, kernel/sys/syscall.c) can actually jump to user
|
||||
// code instead of silently doing nothing. sig 0 is invalid (matches
|
||||
// sigaction(2)); we don't validate against NSIG beyond our own array size.
|
||||
//
|
||||
// User accesses go through copy_from_user/put_user (uaccess.h), which are
|
||||
// page-safe. An earlier version bulk-copied the ~152-byte struct through a
|
||||
// single uva2ka() translation of its start address; when the user's
|
||||
// act/oldact pointer landed close enough to a page boundary, the write ran
|
||||
// past the mapped page into an unrelated physical page, silently crashing
|
||||
// every freshly exec'd /bin/sh (mksh calls sigaction() during startup).
|
||||
addr_t
|
||||
sys_rt_sigaction(void)
|
||||
{
|
||||
int sig;
|
||||
addr_t actp, oldactp;
|
||||
argint(0, &sig);
|
||||
argaddr(1, &actp);
|
||||
argaddr(2, &oldactp);
|
||||
if (sig <= 0 || sig >= 32) return -1;
|
||||
|
||||
if (oldactp) {
|
||||
put_user(myproc()->sig_handler[sig], oldactp + SA_HANDLER_OFF);
|
||||
put_user(myproc()->sig_restorer[sig], oldactp + SA_RESTORER_OFF);
|
||||
}
|
||||
if (actp) {
|
||||
copy_from_user(&myproc()->sig_handler[sig], actp + SA_HANDLER_OFF,
|
||||
sizeof(myproc()->sig_handler[sig]));
|
||||
copy_from_user(&myproc()->sig_restorer[sig], actp + SA_RESTORER_OFF,
|
||||
sizeof(myproc()->sig_restorer[sig]));
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// rt_sigreturn: entered via `syscall` (rax=15) from musl's __restore_rt
|
||||
// trampoline, which is exactly what we set as the manufactured return
|
||||
// address in deliver_signals() below — the handler function's own `ret`
|
||||
// lands here once it's done. Restore the trapframe deliver_signals()
|
||||
// stashed before redirecting to the handler, so syscall()'s normal
|
||||
// sysretq path resumes the interrupted code exactly where it left off.
|
||||
addr_t
|
||||
sys_rt_sigreturn(void)
|
||||
{
|
||||
addr_t saved_rax = myproc()->saved_sigtf.rax;
|
||||
*myproc()->tf = myproc()->saved_sigtf;
|
||||
myproc()->in_signal = 0;
|
||||
// If this handler interrupted a sigsuspend(), put its pre-suspend
|
||||
// signal mask back now that the handler has finished.
|
||||
if (myproc()->sigsusp_active) {
|
||||
myproc()->signal_mask = myproc()->sigsusp_oldmask;
|
||||
myproc()->sigsusp_active = 0;
|
||||
}
|
||||
// syscall() does `tf->rax = <our return value>` right after this
|
||||
// returns — hand back the already-restored rax so that assignment is
|
||||
// a harmless no-op instead of clobbering what we just restored.
|
||||
return saved_rax;
|
||||
}
|
||||
|
||||
// Called from syscall()'s tail (kernel/sys/syscall.c), right before
|
||||
// returning to user mode, for every syscall a process makes (this is the
|
||||
// only "check point" we have without also hooking the interrupt-return
|
||||
// path in trap.c — sufficient for what actually needed this: a process
|
||||
// blocked in a syscall, like sigsuspend(), waiting for its own next
|
||||
// syscall return to notice a signal arrived).
|
||||
//
|
||||
// If there's a pending, unblocked signal with a registered handler,
|
||||
// redirect tf to it: rdi = signal number (SysV ABI first arg), rip =
|
||||
// handler.
|
||||
//
|
||||
// The return address (what the handler's own `ret` lands on) can't be
|
||||
// sig_restorer[sig] — this musl build has no SA_RESTORER defined at all
|
||||
// (checked: not defined anywhere in musl 1.2.5), so sigaction() never
|
||||
// fills it in; it's always 0. Pushing 0 as a return address is exactly
|
||||
// what crashed every mksh instance the first time a signal was ever
|
||||
// actually delivered (handler ran, `ret` jumped to address 0). Instead,
|
||||
// inject a tiny trampoline directly onto the user's own stack — 7 bytes
|
||||
// of machine code for `mov eax, 15; syscall` (rt_sigreturn) — below the
|
||||
// current stack pointer (clear of the SysV red zone) and point the
|
||||
// return address at that, the standard way a kernel supplies sigreturn
|
||||
// when the C library doesn't hand it one itself.
|
||||
void
|
||||
deliver_signals(struct trapframe *tf)
|
||||
{
|
||||
if (myproc()->in_signal) return; // no nested delivery
|
||||
uint64 deliverable = myproc()->pending_signals & ~myproc()->signal_mask;
|
||||
if (!deliverable) return;
|
||||
|
||||
for (int sig = 1; sig < 32; sig++) {
|
||||
if (!(deliverable & (1ULL << sig))) continue;
|
||||
|
||||
// SIG_DFL is 0 and SIG_IGN is 1 -- neither is an address to jump to.
|
||||
//
|
||||
// Only the 0 case was checked here. A process that had asked to
|
||||
// ignore a signal got its handler "called" at address 1, which is a
|
||||
// page fault at rip=0x1 and a dead process. Nothing sent SIGPIPE
|
||||
// until pipes learned to, at which point every shell that ignores
|
||||
// SIGPIPE -- which is every shell -- died the first time a pipeline
|
||||
// ended early. `cat big | head -c 5` was enough.
|
||||
//
|
||||
// The default action for a signal with no handler has already been
|
||||
// applied by sigsend(); all that is left here is to stop it being
|
||||
// pending, which the old `continue` never did, so an ignored signal
|
||||
// was re-examined on the way out of every syscall forever after.
|
||||
if (myproc()->sig_handler[sig] <= 1) {
|
||||
myproc()->pending_signals &= ~(1ULL << sig);
|
||||
continue;
|
||||
}
|
||||
|
||||
uint64 tramp_addr = (tf->rsp - 256) & ~0xFULL;
|
||||
uint8 *tramp = (uint8*)uva2ka(myproc()->pgdir, (char*)tramp_addr);
|
||||
if (!tramp) continue; // can't deliver safely; try another pending signal
|
||||
|
||||
uint64 sp = tramp_addr - 8;
|
||||
uint64 *retaddr = (uint64*)uva2ka(myproc()->pgdir, (char*)sp);
|
||||
if (!retaddr) continue;
|
||||
|
||||
// mov eax, 15 ; syscall
|
||||
tramp[0] = 0xb8; tramp[1] = 0x0f; tramp[2] = 0x00;
|
||||
tramp[3] = 0x00; tramp[4] = 0x00;
|
||||
tramp[5] = 0x0f; tramp[6] = 0x05;
|
||||
*retaddr = tramp_addr;
|
||||
|
||||
myproc()->pending_signals &= ~(1ULL << sig);
|
||||
myproc()->saved_sigtf = *tf;
|
||||
myproc()->in_signal = 1;
|
||||
|
||||
tf->rsp = sp;
|
||||
tf->rdi = (uint64)sig;
|
||||
// Both return paths must point at the handler: iretq-based returns
|
||||
// use tf->rip, but syscall_trapret exits via sysretq, which takes
|
||||
// the user RIP from RCX and *discards* tf->rip (trapasm.S pops rcx
|
||||
// from the frame, then `addq $40` throws rip/cs/rflags away). With
|
||||
// only tf->rip set, the "delivered" handler never actually ran on
|
||||
// the syscall path: user code resumed at the old rcx with the
|
||||
// shifted rsp, and its ret fell into the sigreturn trampoline,
|
||||
// silently undoing everything. exec() handles this same quirk by
|
||||
// setting tf->rcx = elf.entry.
|
||||
tf->rip = myproc()->sig_handler[sig];
|
||||
tf->rcx = myproc()->sig_handler[sig];
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// sigaltstack: set/get alternate signal stack.
|
||||
// We don't deliver signals, so just store/return zeros.
|
||||
addr_t sys_sigaltstack(void) { return 0; }
|
||||
|
||||
// rt_sigpending: write the set of pending signals to user buffer.
|
||||
addr_t sys_rt_sigpending(void)
|
||||
{
|
||||
addr_t setp;
|
||||
argaddr(0, &setp);
|
||||
if (setp) {
|
||||
uint64 pend = sigset_to_user(myproc()->pending_signals);
|
||||
put_user(pend, setp);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// rt_sigtimedwait: wait for a signal from the set; returns -EAGAIN immediately.
|
||||
addr_t sys_rt_sigtimedwait(void) { return -EAGAIN; }
|
||||
|
||||
// rt_sigsuspend: atomically replace the signal mask and suspend until a
|
||||
// signal not blocked by the *temporary* mask is pending, then return -EINTR.
|
||||
//
|
||||
// The atomic mask swap is the entire point of sigsuspend(2): mksh blocks
|
||||
// SIGCHLD while it inspects its job table, then calls sigsuspend() with the
|
||||
// old (SIGCHLD-unblocked) mask to wait for a child. The previous stub
|
||||
// ignored the mask argument and just slept one tick — SIGCHLD stayed
|
||||
// blocked, deliver_signals() could never fire the handler, and the shell
|
||||
// hung forever after running any external command (unreaped zombie child,
|
||||
// shell parked in sigsuspend: the "can't type after a command" hang).
|
||||
//
|
||||
// The temporary mask must still be in effect when deliver_signals() runs at
|
||||
// syscall()'s tail (that's what allows the delivery), so the old mask can't
|
||||
// be restored here — sys_rt_sigreturn() restores it once the handler
|
||||
// finishes, via sigsusp_oldmask/sigsusp_active.
|
||||
addr_t sys_rt_sigsuspend(void)
|
||||
{
|
||||
addr_t maskp;
|
||||
uint64 newmask;
|
||||
argaddr(0, &maskp);
|
||||
|
||||
uint64 oldmask = myproc()->signal_mask;
|
||||
if (maskp && copy_from_user(&newmask, maskp, sizeof(newmask)) == 0)
|
||||
myproc()->signal_mask = sigset_from_user(newmask);
|
||||
|
||||
acquire(&tickslock);
|
||||
while (!(myproc()->pending_signals & ~myproc()->signal_mask) && !myproc()->killed)
|
||||
sleep(&ticks, &tickslock);
|
||||
release(&tickslock);
|
||||
|
||||
// Will any pending signal actually reach a handler? If so, the mask is
|
||||
// restored by sigreturn after the handler runs; otherwise (no handler
|
||||
// installed, or we were killed) restore it right now.
|
||||
uint64 deliverable = myproc()->pending_signals & ~myproc()->signal_mask;
|
||||
int will_deliver = 0;
|
||||
for (int sig = 1; sig < 32; sig++)
|
||||
if ((deliverable & (1ULL << sig)) && myproc()->sig_handler[sig])
|
||||
{ will_deliver = 1; break; }
|
||||
|
||||
if (will_deliver && !myproc()->in_signal) {
|
||||
myproc()->sigsusp_oldmask = oldmask;
|
||||
myproc()->sigsusp_active = 1;
|
||||
} else {
|
||||
myproc()->signal_mask = oldmask;
|
||||
}
|
||||
return -EINTR;
|
||||
}
|
||||
139
kernel/sys/sys_time.c
Normal file
139
kernel/sys/sys_time.c
Normal file
|
|
@ -0,0 +1,139 @@
|
|||
#include "types.h"
|
||||
#include "defs.h"
|
||||
#include "param.h"
|
||||
#include "date.h"
|
||||
#include "spinlock.h"
|
||||
#include "proc.h"
|
||||
#include "x86.h"
|
||||
#include "syscall.h"
|
||||
#include "posix.h"
|
||||
#include "uaccess.h"
|
||||
#include "errno.h"
|
||||
|
||||
addr_t
|
||||
sys_alarm(void)
|
||||
{
|
||||
int seconds;
|
||||
if (argint(0, &seconds) < 0)
|
||||
return -1;
|
||||
myproc()->alarm_ticks = seconds * 100;
|
||||
return 0;
|
||||
}
|
||||
|
||||
addr_t
|
||||
sys_gettimeofday(void)
|
||||
{
|
||||
struct timeval *tv;
|
||||
if (argptr(0, (char**)&tv, sizeof(*tv)) < 0)
|
||||
return -1;
|
||||
if (tv) {
|
||||
// Real time, from the hardware clock read at boot plus uptime -- not
|
||||
// uptime alone, which is what this used to return and why every machine
|
||||
// thought it was 1970. See kernel/core/time.c.
|
||||
uint64 sec, usec;
|
||||
wallclock(&sec, &usec);
|
||||
tv->tv_sec = (long)sec;
|
||||
tv->tv_usec = (long)usec;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
addr_t
|
||||
sys_nanosleep(void)
|
||||
{
|
||||
addr_t tsaddr, raw;
|
||||
if (argaddr(0, &tsaddr) < 0 || fetchaddr(tsaddr, &raw) < 0)
|
||||
return -1;
|
||||
int64 sec = (int64)raw;
|
||||
if (sec > 0) {
|
||||
acquire(&tickslock);
|
||||
uint t0 = ticks;
|
||||
while (ticks - t0 < (uint)(sec * 100)) {
|
||||
if (myproc()->killed) { release(&tickslock); return -EINTR; }
|
||||
sleep(&ticks, &tickslock);
|
||||
}
|
||||
release(&tickslock);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
addr_t
|
||||
sys_clock_gettime(void)
|
||||
{
|
||||
int clkid;
|
||||
struct timespec *tp;
|
||||
argint(0, &clkid);
|
||||
if (argptr(1, (char**)&tp, sizeof(*tp)) < 0)
|
||||
return -1;
|
||||
// CLOCK_MONOTONIC (1) counts from boot; everything else is treated as a
|
||||
// realtime clock and counts from the epoch. Returning boot-relative values
|
||||
// for CLOCK_REALTIME is what made timestamps land in 1970.
|
||||
uint64 sec, usec;
|
||||
wallclock(&sec, &usec);
|
||||
if (clkid == 1)
|
||||
sec -= boot_epoch;
|
||||
tp->tv_sec = (long)sec;
|
||||
tp->tv_nsec = (long)(usec * 1000);
|
||||
return 0;
|
||||
}
|
||||
|
||||
addr_t sys_getitimer(void) { return 0; } // stub
|
||||
|
||||
addr_t
|
||||
sys_setitimer(void)
|
||||
{
|
||||
int which;
|
||||
addr_t newp;
|
||||
argint(0, &which); argaddr(1, &newp);
|
||||
// We don't implement interval timers; just track the alarm if it's ITIMER_REAL.
|
||||
if (which == 0 && newp) {
|
||||
struct itimerval iv;
|
||||
if (copy_from_user(&iv, newp, sizeof(iv)) == 0)
|
||||
myproc()->alarm_ticks = (int)(iv.it_value.tv_sec * 100 +
|
||||
iv.it_value.tv_usec / 10000);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
addr_t
|
||||
sys_time(void)
|
||||
{
|
||||
addr_t tloc;
|
||||
argaddr(0, &tloc);
|
||||
uint64 sec;
|
||||
wallclock(&sec, 0);
|
||||
long t = (long)sec;
|
||||
if (tloc)
|
||||
put_user(t, tloc);
|
||||
return (addr_t)t;
|
||||
}
|
||||
|
||||
addr_t
|
||||
sys_clock_getres(void)
|
||||
{
|
||||
struct timespec *tp;
|
||||
if (argptr(1, (char**)&tp, sizeof(*tp)) < 0) return 0;
|
||||
if (tp) { tp->tv_sec = 0; tp->tv_nsec = 10000000L; } // 10ms resolution
|
||||
return 0;
|
||||
}
|
||||
|
||||
addr_t
|
||||
sys_clock_nanosleep(void)
|
||||
{
|
||||
// Treat the same as nanosleep: arg2 is the timespec, arg1 is flags.
|
||||
// Linux: clock_nanosleep(clockid, flags, rqtp, rmtp)
|
||||
addr_t tsaddr, raw;
|
||||
if (argaddr(2, &tsaddr) < 0 || fetchaddr(tsaddr, &raw) < 0)
|
||||
return -1;
|
||||
int64 sec = (int64)raw;
|
||||
if (sec > 0) {
|
||||
acquire(&tickslock);
|
||||
uint t0 = ticks;
|
||||
while (ticks - t0 < (uint)(sec * 100)) {
|
||||
if (myproc()->killed) { release(&tickslock); return -EINTR; }
|
||||
sleep(&ticks, &tickslock);
|
||||
}
|
||||
release(&tickslock);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
141
kernel/sys/syscall.c
Normal file
141
kernel/sys/syscall.c
Normal file
|
|
@ -0,0 +1,141 @@
|
|||
#include "types.h"
|
||||
#include "defs.h"
|
||||
#include "param.h"
|
||||
#include "memlayout.h"
|
||||
#include "mmu.h"
|
||||
#include "traps.h"
|
||||
#include "proc.h"
|
||||
#include "x86.h"
|
||||
#include "syscall.h"
|
||||
#include "uaccess.h"
|
||||
#include "errno.h"
|
||||
#include "posix.h"
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Argument helpers — read syscall args from the saved register frame.
|
||||
// x86-64 SysV ABI: rdi, rsi, rdx, r10, r8, r9
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
// Note: address validation goes through uaccess.h (page-table presence +
|
||||
// user bit on every page of the range). There is deliberately no
|
||||
// `addr < myproc()->sz` check: that would assume every valid user pointer
|
||||
// lives in the heap — false for stack addresses and mmap()'d buffers
|
||||
// (both live above myproc()->sz), which made syscalls fail whenever a
|
||||
// Linux-ABI program passed a string/pointer from either region.
|
||||
//
|
||||
// Validated pointers are returned as raw user VAs — the process page
|
||||
// table is live in kernel mode, so they dereference correctly across
|
||||
// page boundaries (unlike a uva2ka() alias, which is only good within
|
||||
// the one page it translated).
|
||||
|
||||
int
|
||||
fetchint(addr_t addr, int *ip)
|
||||
{
|
||||
return copy_from_user(ip, addr, sizeof(*ip)) ? -1 : 0;
|
||||
}
|
||||
|
||||
int
|
||||
fetchaddr(addr_t addr, addr_t *ip)
|
||||
{
|
||||
return copy_from_user(ip, addr, sizeof(*ip)) ? -1 : 0;
|
||||
}
|
||||
|
||||
int
|
||||
fetchstr(addr_t addr, char **pp)
|
||||
{
|
||||
long len = strlen_user(addr);
|
||||
if (len < 0)
|
||||
return -1;
|
||||
*pp = (char*)addr;
|
||||
return len;
|
||||
}
|
||||
|
||||
static addr_t
|
||||
fetcharg(int n)
|
||||
{
|
||||
switch (n) {
|
||||
case 0: return myproc()->tf->rdi;
|
||||
case 1: return myproc()->tf->rsi;
|
||||
case 2: return myproc()->tf->rdx;
|
||||
case 3: return myproc()->tf->r10;
|
||||
case 4: return myproc()->tf->r8;
|
||||
case 5: return myproc()->tf->r9;
|
||||
}
|
||||
panic("fetcharg: bad n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
int argint(int n, int *ip) { *ip = (int)fetcharg(n); return 0; }
|
||||
int argaddr(int n, addr_t *ip) { *ip = fetcharg(n); return 0; }
|
||||
|
||||
int
|
||||
argptr(int n, char **pp, int size)
|
||||
{
|
||||
addr_t i;
|
||||
if (argaddr(n, &i) < 0)
|
||||
return -1;
|
||||
if (size < 0)
|
||||
return -1;
|
||||
char *p = user_ptr(i, (uint64)size);
|
||||
if (!p)
|
||||
return -1;
|
||||
*pp = p;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
argstr(int n, char **pp)
|
||||
{
|
||||
addr_t addr;
|
||||
if (argaddr(n, &addr) < 0)
|
||||
return -1;
|
||||
return fetchstr(addr, pp);
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Dispatch table — auto-generated from table.inc
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
#define SYSCALL(nr, lnr, name) extern addr_t sys_##name(void);
|
||||
#define SYSCALL_ALIAS(lnr, name)
|
||||
#include "table.inc"
|
||||
#undef SYSCALL
|
||||
#undef SYSCALL_ALIAS
|
||||
|
||||
static addr_t (*syscalls[NSYSCALLS])(void) = {
|
||||
#define SYSCALL(nr, lnr, name) [SYS_##name] = sys_##name,
|
||||
#define SYSCALL_ALIAS(lnr, name)
|
||||
#include "table.inc"
|
||||
#undef SYSCALL
|
||||
#undef SYSCALL_ALIAS
|
||||
};
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Main entry point called from trap.c
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
void
|
||||
syscall(struct trapframe *tf)
|
||||
{
|
||||
kstack_check_canary("syscall entry");
|
||||
myproc()->tf = tf;
|
||||
uint64 orig = myproc()->tf->rax;
|
||||
uint64 num = orig;
|
||||
|
||||
if (myproc()->syscall_map) {
|
||||
num = (num < (uint64)myproc()->map_size) ? myproc()->syscall_map[num] : 0;
|
||||
}
|
||||
|
||||
if (num > 0 && num < NSYSCALLS && syscalls[num]) {
|
||||
tf->rax = syscalls[num]();
|
||||
} else {
|
||||
tf->rax = -ENOSYS;
|
||||
}
|
||||
|
||||
if (myproc()->killed)
|
||||
exit(W_EXITCODE(0, myproc()->killsig ? myproc()->killsig : SIGKILL));
|
||||
|
||||
deliver_signals(tf);
|
||||
|
||||
kstack_check_canary("syscall exit");
|
||||
}
|
||||
282
kernel/sys/table.inc
Normal file
282
kernel/sys/table.inc
Normal file
|
|
@ -0,0 +1,282 @@
|
|||
// Segregated Kernel -- central syscall table. ONE line per syscall.
|
||||
//
|
||||
// Format: SYSCALL(nr, linux_nr, name)
|
||||
// nr Schunix native syscall number
|
||||
// linux_nr Linux x86_64 syscall number, or NO_LINUX if none
|
||||
// name implemented as sys_<name>() in kernel/sys/sys_*.c
|
||||
//
|
||||
// To add a syscall:
|
||||
// 1. Add ONE line here.
|
||||
// 2. Write the body with SYSCALL_DEFINE(<name>) in a kernel/sys/sys_*.c.
|
||||
//
|
||||
// Everything else is generated from this file: the SYS_* constants
|
||||
// (include/syscall.h), the dispatch table (kernel/sys/syscall.c), and the
|
||||
// Linux ABI translation map (kernel/sys/linux_abi.c). The Linux number used
|
||||
// to live in a second hand-maintained table in linux_abi.c; it does not any
|
||||
// more, so a syscall can no longer be half-registered.
|
||||
//
|
||||
// A syscall reachable under more than one Linux number (e.g. stat/newstat)
|
||||
// gets extra SYSCALL_ALIAS(linux_nr, name) lines.
|
||||
|
||||
|
||||
// --- process ---
|
||||
SYSCALL( 1, 57, fork)
|
||||
SYSCALL( 2, 60, exit)
|
||||
SYSCALL( 3, NO_LINUX, wait)
|
||||
SYSCALL( 6, 62, kill)
|
||||
SYSCALL( 7, 59, exec)
|
||||
SYSCALL( 11, 39, getpid)
|
||||
SYSCALL( 13, NO_LINUX, sleep)
|
||||
SYSCALL( 14, NO_LINUX, uptime)
|
||||
SYSCALL( 29, NO_LINUX, waitpid)
|
||||
SYSCALL( 30, 112, setsid)
|
||||
SYSCALL( 31, NO_LINUX, reboot)
|
||||
SYSCALL( 32, NO_LINUX, shutdown)
|
||||
SYSCALL( 66, 56, clone)
|
||||
SYSCALL( 67, 231, exit_group)
|
||||
SYSCALL( 68, 61, wait4)
|
||||
SYSCALL( 83, 105, setuid)
|
||||
SYSCALL( 84, 106, setgid)
|
||||
SYSCALL( 85, 124, getsid)
|
||||
SYSCALL( 89, 110, getppid)
|
||||
SYSCALL( 91, 121, getpgid)
|
||||
SYSCALL( 94, 186, gettid)
|
||||
SYSCALL( 92, 109, setpgid)
|
||||
SYSCALL( 93, 111, getpgrp)
|
||||
|
||||
// --- identity ---
|
||||
SYSCALL( 47, 102, getuid)
|
||||
SYSCALL( 48, 107, geteuid)
|
||||
SYSCALL( 69, 104, getgid)
|
||||
SYSCALL( 70, 108, getegid)
|
||||
SYSCALL( 71, 95, umask)
|
||||
|
||||
// --- memory ---
|
||||
SYSCALL( 12, NO_LINUX, sbrk)
|
||||
SYSCALL( 35, 9, mmap)
|
||||
SYSCALL( 36, 11, munmap)
|
||||
// Shared memory. These carried NO_LINUX while they were ENOSYS stubs, which
|
||||
// meant a musl-linked program calling shmget() never reached them at all --
|
||||
// it got "bad syscall" from the ABI translation rather than an answer. Now
|
||||
// that they do something, they need the numbers programs actually use.
|
||||
SYSCALL( 37, 29, shmget)
|
||||
SYSCALL( 38, 30, shmat)
|
||||
SYSCALL( 39, 67, shmdt)
|
||||
SYSCALL( 59, 10, mprotect)
|
||||
SYSCALL( 74, 12, brk)
|
||||
|
||||
// --- filesystem ---
|
||||
SYSCALL( 4, 22, pipe)
|
||||
SYSCALL( 5, 0, read)
|
||||
SYSCALL( 8, 5, fstat)
|
||||
SYSCALL( 9, 80, chdir)
|
||||
SYSCALL( 10, 32, dup)
|
||||
SYSCALL( 15, 2, open)
|
||||
SYSCALL( 16, 1, write)
|
||||
SYSCALL( 17, 133, mknod)
|
||||
SYSCALL( 18, 87, unlink)
|
||||
SYSCALL( 19, 86, link)
|
||||
SYSCALL( 20, 83, mkdir)
|
||||
SYSCALL( 21, 3, close)
|
||||
SYSCALL( 24, 8, lseek)
|
||||
SYSCALL( 33, 33, dup2)
|
||||
SYSCALL( 34, 72, fcntl)
|
||||
// `delete` -- the recycle bin. It hides a file instead of removing it, so the
|
||||
// blocks are never freed and a program that deletes a temp file gets no space
|
||||
// back. Inherited from the third-party filesystem in kernel/fs/fs.c and
|
||||
// orphaned by the decision to keep only Linux syscall numbering, so nothing
|
||||
// reaches it. Its effect on read and write has been removed; the entry stays
|
||||
// only because sys_delete() is still compiled, and both die with fs.c.
|
||||
SYSCALL( 51, NO_LINUX, delete)
|
||||
SYSCALL( 52, NO_LINUX, show)
|
||||
SYSCALL( 53, NO_LINUX, showstat)
|
||||
SYSCALL( 54, 77, ftruncate)
|
||||
SYSCALL( 55, 76, truncate)
|
||||
SYSCALL( 56, 82, rename)
|
||||
SYSCALL( 57, 88, symlink)
|
||||
SYSCALL( 58, 89, readlink)
|
||||
SYSCALL( 61, 79, getcwd)
|
||||
SYSCALL( 73, 19, readv)
|
||||
SYSCALL( 72, 20, writev)
|
||||
SYSCALL( 76, 4, stat)
|
||||
SYSCALL( 77, 6, lstat)
|
||||
SYSCALL( 78, 21, access)
|
||||
SYSCALL( 79, 257, openat)
|
||||
SYSCALL( 80, 81, fchdir)
|
||||
SYSCALL( 86, 292, dup3)
|
||||
SYSCALL( 87, 293, pipe2)
|
||||
|
||||
// --- device / I/O ---
|
||||
SYSCALL( 22, NO_LINUX, vga_sync)
|
||||
SYSCALL( 23, NO_LINUX, getmouse)
|
||||
SYSCALL( 45, 23, select)
|
||||
SYSCALL( 46, 7, poll)
|
||||
SYSCALL( 60, 16, ioctl)
|
||||
|
||||
// --- sockets ---
|
||||
//
|
||||
// These carried NO_LINUX while they were ENOSYS stubs, which meant a
|
||||
// musl-linked program calling socket() never reached them at all -- the ABI
|
||||
// translation answered "bad syscall" before the stub could answer ENOSYS.
|
||||
// Now that they do something they need the numbers programs actually use.
|
||||
//
|
||||
// `shutdown` is deliberately not the name here: Schunix already has a native
|
||||
// shutdown() that powers the machine off (syscall 32), and two syscalls
|
||||
// cannot share a sys_ function name. Linux 48 maps to sockshutdown instead.
|
||||
SYSCALL( 40, 41, socket)
|
||||
SYSCALL( 41, 49, bind)
|
||||
SYSCALL( 42, 50, listen)
|
||||
SYSCALL( 43, 43, accept)
|
||||
SYSCALL( 44, 42, connect)
|
||||
SYSCALL(187, 44, sendto)
|
||||
SYSCALL(188, 45, recvfrom)
|
||||
SYSCALL(189, 48, sockshutdown)
|
||||
SYSCALL(190, 53, socketpair)
|
||||
SYSCALL(191, 54, setsockopt)
|
||||
SYSCALL(192, 55, getsockopt)
|
||||
|
||||
// --- time ---
|
||||
SYSCALL( 28, 37, alarm)
|
||||
SYSCALL( 49, 96, gettimeofday)
|
||||
SYSCALL( 50, 35, nanosleep)
|
||||
SYSCALL( 88, 228, clock_gettime)
|
||||
|
||||
// --- signals ---
|
||||
SYSCALL( 25, NO_LINUX, sigfillset)
|
||||
SYSCALL( 26, 14, sigprocmask)
|
||||
SYSCALL( 27, NO_LINUX, sigwait)
|
||||
SYSCALL( 90, 13, rt_sigaction)
|
||||
|
||||
// --- POSIX / Linux-ABI compat ---
|
||||
SYSCALL( 62, 158, arch_prctl)
|
||||
SYSCALL( 95, 63, uname)
|
||||
SYSCALL( 96, 217, getdents64)
|
||||
SYSCALL( 63, 218, set_tid_address)
|
||||
SYSCALL( 64, 273, set_robust_list)
|
||||
SYSCALL( 65, 202, futex)
|
||||
SYSCALL( 75, 302, prlimit)
|
||||
SYSCALL( 81, NO_LINUX, enable_linux_abi)
|
||||
SYSCALL( 82, 97, getrlimit)
|
||||
|
||||
// --- signal extensions ---
|
||||
SYSCALL( 97, 15, rt_sigreturn)
|
||||
SYSCALL( 98, 131, sigaltstack)
|
||||
SYSCALL( 99, 127, rt_sigpending)
|
||||
SYSCALL(100, 128, rt_sigtimedwait)
|
||||
SYSCALL(101, 130, rt_sigsuspend)
|
||||
|
||||
// --- process extensions ---
|
||||
SYSCALL(102, 157, prctl)
|
||||
SYSCALL(103, 234, tgkill)
|
||||
SYSCALL(104, 200, tkill)
|
||||
SYSCALL(105, 34, pause)
|
||||
SYSCALL(106, NO_LINUX, getitimer)
|
||||
SYSCALL(107, NO_LINUX, setitimer)
|
||||
SYSCALL(108, 98, getrusage)
|
||||
SYSCALL(109, 100, times)
|
||||
SYSCALL(110, 99, sysinfo)
|
||||
SYSCALL(111, 24, sched_yield)
|
||||
SYSCALL(112, 204, sched_getaffinity)
|
||||
SYSCALL(113, 203, sched_setaffinity)
|
||||
SYSCALL(114, 247, waitid)
|
||||
|
||||
// --- identity extensions ---
|
||||
SYSCALL(115, 113, setreuid)
|
||||
SYSCALL(116, 114, setregid)
|
||||
SYSCALL(117, 118, getresuid)
|
||||
SYSCALL(118, 120, getresgid)
|
||||
SYSCALL(119, 117, setresuid)
|
||||
SYSCALL(120, 119, setresgid)
|
||||
SYSCALL(121, 115, getgroups)
|
||||
SYSCALL(122, 116, setgroups)
|
||||
SYSCALL(123, 122, setfsuid)
|
||||
SYSCALL(124, 123, setfsgid)
|
||||
|
||||
// --- filesystem extensions ---
|
||||
SYSCALL(125, 90, chmod)
|
||||
SYSCALL(126, 91, fchmod)
|
||||
SYSCALL(127, 92, chown)
|
||||
SYSCALL(128, 93, fchown)
|
||||
SYSCALL(129, 94, lchown)
|
||||
SYSCALL(130, 84, rmdir)
|
||||
SYSCALL(131, NO_LINUX, fsync)
|
||||
SYSCALL(132, NO_LINUX, fdatasync)
|
||||
SYSCALL(133, 162, sync)
|
||||
SYSCALL(134, 17, pread)
|
||||
SYSCALL(135, 18, pwrite)
|
||||
SYSCALL(148, 132, utime)
|
||||
SYSCALL(149, 235, utimes)
|
||||
SYSCALL(150, 261, utimensat)
|
||||
SYSCALL(151, 187, sendfile)
|
||||
SYSCALL(152, NO_LINUX, preadv2)
|
||||
SYSCALL(153, NO_LINUX, pwritev2)
|
||||
SYSCALL(154, 285, fallocate)
|
||||
SYSCALL(155, 137, statfs)
|
||||
SYSCALL(156, 138, fstatfs)
|
||||
SYSCALL(157, 161, chroot)
|
||||
|
||||
// --- memory extensions ---
|
||||
SYSCALL(136, 28, madvise)
|
||||
SYSCALL(137, NO_LINUX, mremap)
|
||||
SYSCALL(138, NO_LINUX, msync)
|
||||
SYSCALL(139, NO_LINUX, mincore)
|
||||
SYSCALL(140, 149, mlock)
|
||||
SYSCALL(141, 150, munlock)
|
||||
SYSCALL(142, 151, mlockall)
|
||||
SYSCALL(143, 152, munlockall)
|
||||
SYSCALL(144, 221, fadvise64)
|
||||
SYSCALL(145, 160, setrlimit)
|
||||
SYSCALL(146, NO_LINUX, getpriority)
|
||||
SYSCALL(147, NO_LINUX, setpriority)
|
||||
|
||||
// --- at-syscalls ---
|
||||
SYSCALL(158, 258, mkdirat)
|
||||
SYSCALL(159, 259, mknodat)
|
||||
SYSCALL(160, 263, unlinkat)
|
||||
SYSCALL(161, 264, renameat)
|
||||
SYSCALL(162, 265, linkat)
|
||||
SYSCALL(163, 266, symlinkat)
|
||||
SYSCALL(164, 267, readlinkat)
|
||||
SYSCALL(165, 268, fchmodat)
|
||||
SYSCALL(166, 269, faccessat)
|
||||
SYSCALL(167, 262, fstatat)
|
||||
SYSCALL(168, 260, fchownat)
|
||||
SYSCALL(169, 316, renameat2)
|
||||
|
||||
// --- time extensions ---
|
||||
SYSCALL(170, 201, time)
|
||||
SYSCALL(171, 229, clock_getres)
|
||||
SYSCALL(172, 230, clock_nanosleep)
|
||||
|
||||
// --- misc ---
|
||||
SYSCALL(173, 103, syslog)
|
||||
SYSCALL(174, 318, getrandom)
|
||||
SYSCALL(175, 270, pselect6)
|
||||
SYSCALL(176, 271, ppoll)
|
||||
|
||||
// --- epoll / eventfd / signalfd / timerfd ---
|
||||
SYSCALL(177, 291, epoll_create1)
|
||||
SYSCALL(178, 233, epoll_ctl)
|
||||
SYSCALL(179, 232, epoll_wait)
|
||||
SYSCALL(180, 282, signalfd4)
|
||||
SYSCALL(181, 284, eventfd2)
|
||||
SYSCALL(182, 283, timerfd_create)
|
||||
SYSCALL(183, 286, timerfd_settime)
|
||||
SYSCALL(184, 287, timerfd_gettime)
|
||||
|
||||
// --- mounts ---
|
||||
SYSCALL(185, 165, mount)
|
||||
SYSCALL(186, 166, umount2)
|
||||
|
||||
|
||||
// --- additional Linux numbers routed to an existing handler ---
|
||||
SYSCALL_ALIAS( 58, fork)
|
||||
SYSCALL_ALIAS( 280, utimensat)
|
||||
SYSCALL_ALIAS( 281, epoll_wait)
|
||||
SYSCALL_ALIAS( 289, signalfd4)
|
||||
SYSCALL_ALIAS( 290, eventfd2)
|
||||
SYSCALL_ALIAS( 295, readv)
|
||||
SYSCALL_ALIAS( 296, writev)
|
||||
// accept4() differs from accept() only in a flags argument naming
|
||||
// SOCK_CLOEXEC/SOCK_NONBLOCK, neither of which is honoured yet.
|
||||
SYSCALL_ALIAS( 288, accept)
|
||||
Loading…
Reference in a new issue