diff --git a/Makefile b/Makefile index 780cc8c..47b878a 100644 --- a/Makefile +++ b/Makefile @@ -26,7 +26,7 @@ KERN_CORE = $(addprefix $(BUILD)/kernel/core/, \ KERN_DEV = $(addprefix $(BUILD)/kernel/dev/, \ bio.o blkdev.o cdev.o device.o disk.o fb.o ide.o input.o irq.o kbd.o keymap.o mouse.o \ pci.o pciuser.o text.o ttf.o uart.o gfx.o vt.o ramdisk.o procfs.o \ - dbg.o netuser.o audio.o) + dbg.o netuser.o audio.o dmi.o) # Drivers for hardware that has to be *found* rather than assumed. Anything # discovered by walking a bus belongs here; the fixed-address legacy devices @@ -55,7 +55,7 @@ KERN_DRIVERS += $(BUILD)/kernel/drivers/usb/storage/usb_storage.o KERN_DRIVERS += $(BUILD)/kernel/drivers/usb/hub/usb_hub.o KERN_FS = $(addprefix $(BUILD)/kernel/fs/, \ - exec.o file.o fs.o log.o pipe.o procfs.o devfs.o) + exec.o file.o fs.o log.o pipe.o procfs.o devfs.o sysfs.o) # SchFS. Every one of these is the very same source the host tools compile, so # the kernel and mkschfs cannot disagree about the on-disk format: there is one @@ -128,7 +128,7 @@ KERN_NOENTRY = $(filter-out $(BUILD)/kernel/arch/x86_64/entry.o, $(KERNOBJS)) # Third-party software does NOT go here — see ports/. # ----------------------------------------------------------------------- ULIB = $(addprefix $(BUILD)/lib/, start.o glue.o usys.o) -CMDS = schinit schinitd fbinfo shmtest socktest inputtest nettest reflink schattr ping chvt loadkeys sigtest memfdtest pcitest vgpud schdbg schdbgd resolvconf httpd schmode netecho ldlsd tone +CMDS = schinit schinitd fbinfo shmtest socktest inputtest nettest reflink schattr ping chvt loadkeys sigtest memfdtest pcitest vgpud schdbg schdbgd resolvconf httpd schmode netecho ldlsd tone hwreport CMDBINS = $(addprefix $(BUILD)/cmd/, $(CMDS)) # ----------------------------------------------------------------------- @@ -478,10 +478,10 @@ RAMDISK_BLOCKS ?= 8000 # This is not hypothetical: a 33 MB test payload once landed here and overflowed # the 6 MB image, and mkfs's "increase FSSIZE" message then pointed at a constant # that had nothing to do with it. -# usb.ids is 730 KB of a 6 MB image -- 12% of the ramdisk root -- and nothing -# needs it to boot. It is a lookup table for a userspace tool, so it stays on -# the disk image and out of the kernel binary. -RAMDISK_EXCLUDE = usr/share/usb.ids +# usb.ids and pci.ids are 2 MB of a 6 MB image and nothing needs them to boot. +# They are lookup tables for userspace tools, so they stay on the disk image +# and out of the kernel binary. +RAMDISK_EXCLUDE = usr/share/hwdata RAMDISK_EXCLUDE_ARGS = $(foreach p,$(RAMDISK_EXCLUDE),--exclude $(p)) ifdef RAMROOT @@ -1282,6 +1282,12 @@ etc-install: $(SCHINIT_JOBS) $(ETC_PLAIN) $(ETC_ZSH) $(LOGO_ASCII) $(SRV_FILES) @cp $(ETC_ZSH) $(SYSROOT)/etc/zsh/ @cp $(LOGO_ASCII) $(SYSROOT)/usr/share/schos/logo.txt @cp $(SRV_FILES) $(SYSROOT)/srv/ + @# The device-name databases, where lspci and lsusb look for them: the + @# hwdata package's directory. They were at /usr/share/usb.ids, which no + @# tool searches, so lsusb printed numbers only. + @mkdir -p $(SYSROOT)/usr/share/hwdata + @rm -f $(SYSROOT)/usr/share/usb.ids + @for f in usb.ids pci.ids; do [ -f dist/$$f ] && cp dist/$$f $(SYSROOT)/usr/share/hwdata/; done; true @echo "installed $(words $(ETC_PLAIN)) /etc file(s), $(words $(ETC_ZSH)) zsh file(s) and $(words $(SCHINIT_JOBS)) schinit manifest(s)" # The mount points. Created here rather than by init, because a mkdir on a @@ -1515,20 +1521,28 @@ logo: # critical path of `make`. dist/ exists precisely so a rebuild never needs the # network -- see docs/layout.md. `make usb-ids` is a deliberate act; the file it # writes is committed and dated, so what shipped is always knowable. +# +# pci.ids is the same arrangement for PCI, from the PCI ID Repository, under +# the same two licences. `make hwdata` refreshes both. USB_IDS_URL = http://www.linux-usb.org/usb.ids +PCI_IDS_URL = https://pci-ids.ucw.cz/v2.2/pci.ids -usb-ids: - @echo "==> fetching $(USB_IDS_URL)" - @curl -sfL --max-time 60 -o dist/usb.ids.new "$(USB_IDS_URL)" || \ - { echo " fetch failed; keeping the cached copy"; rm -f dist/usb.ids.new; exit 1; } +usb-ids pci-ids: + @url="$(if $(filter usb-ids,$@),$(USB_IDS_URL),$(PCI_IDS_URL))"; \ + f=$(subst -,.,$@); \ + echo "==> fetching $$url"; \ + curl -sfL --max-time 120 -o dist/$$f.new "$$url" || \ + { echo " fetch failed; keeping the cached copy"; rm -f dist/$$f.new; exit 1; }; \ + grep -q "^#.*Version:" dist/$$f.new || \ + { echo " that is not a $$f file; keeping the cached copy"; \ + rm -f dist/$$f.new; exit 1; }; \ + mv dist/$$f.new dist/$$f; \ + mkdir -p $(SYSROOT)/usr/share/hwdata; cp dist/$$f $(SYSROOT)/usr/share/hwdata/; \ + grep -m1 "^#.*Version:" dist/$$f @# A truncated or error-page download would otherwise silently replace a - @# good database with rubbish. Check it looks like what it claims to be. - @grep -q "^# Version:" dist/usb.ids.new || \ - { echo " that is not a usb.ids file; keeping the cached copy"; \ - rm -f dist/usb.ids.new; exit 1; } - @mv dist/usb.ids.new dist/usb.ids - @cp dist/usb.ids $(SYSROOT)/usr/share/usb.ids - @grep -m1 "^# Version:" dist/usb.ids + @# good database with rubbish, which is what the Version check is for. + +hwdata: usb-ids pci-ids # Build the kernel's QR encoder on the host and check it. # @@ -1559,7 +1573,7 @@ qrcheck: $(QRTEST) .PHONY: disk all kernel ramroot cmd ports projects sysroot-install qemu qemu-console qemu-kvm schcom-test \ schfs-test schfs-objtest schfs-image-test schfs-check kmem-test mkschfs \ schfs-fsck crashshot qemu-schfs iso qemu-iso clean distclean \ - logo qrtest qrcheck crashqr usb-ids ovmf esp uefi + logo qrtest qrcheck crashqr usb-ids pci-ids hwdata ovmf esp uefi .PRECIOUS: $(BUILD)/%.o # 'clean' removes generated output only. dist/ (upstream tarballs) survives, diff --git a/NOTICE b/NOTICE index a38b485..333b4f6 100644 --- a/NOTICE +++ b/NOTICE @@ -181,9 +181,22 @@ Public License version 2 or later, *or* the 3-clause BSD Licence. SchOS takes the BSD option, which was chosen while SchOS was Apache-2.0; either option is compatible now, and the BSD one is kept because it asks less of anyone. -Shipped verbatim as /usr/share/usb.ids. Cached in dist/ and refreshed with -`make usb-ids`, never automatically -- the version and date are in the file's -own header, so what shipped is always knowable. +Shipped verbatim as /usr/share/hwdata/usb.ids. Cached in dist/ and refreshed +with `make usb-ids`, never automatically -- the version and date are in the +file's own header, so what shipped is always knowable. + +-------------------------------------------------------------------------------- +pci.ids -- PCI vendor and device database BSD 3-Clause (dual-licensed) +-------------------------------------------------------------------------------- +Maintained by Albert Pool, Martin Mares and the PCI ID Project volunteers +https://pci-ids.ucw.cz/ + +Distributed under the terms of *either* the GNU General Public License version +2 or later, *or* the 3-clause BSD Licence, as its header states. SchOS takes +the BSD option, as for usb.ids. + +Shipped verbatim as /usr/share/hwdata/pci.ids. Cached in dist/ and refreshed +with `make pci-ids`, never automatically. -------------------------------------------------------------------------------- Roboto Apache-2.0 diff --git a/boot/uefi/efi.h b/boot/uefi/efi.h index 17cd2e5..7cad782 100644 --- a/boot/uefi/efi.h +++ b/boot/uefi/efi.h @@ -208,6 +208,10 @@ typedef struct { { 0x8868E871, 0xE4F1, 0x11D3, { 0xBC, 0x22, 0x00, 0x80, 0xC7, 0x3C, 0x88, 0x81 } } #define EFI_ACPI_10_TABLE_GUID \ { 0xEB9D2D30, 0x2D88, 0x11D3, { 0x9A, 0x16, 0x00, 0x90, 0x27, 0x3F, 0xC1, 0x4D } } +#define SMBIOS_TABLE_GUID \ + { 0xEB9D2D31, 0x2D88, 0x11D3, { 0x9A, 0x16, 0x00, 0x90, 0x27, 0x3F, 0xC1, 0x4D } } +#define SMBIOS3_TABLE_GUID \ + { 0xF2FD1544, 0x9794, 0x4A2C, { 0x99, 0x2E, 0xE5, 0xBB, 0xCF, 0x20, 0xE3, 0x94 } } typedef struct { EFI_GUID VendorGuid; diff --git a/boot/uefi/main.c b/boot/uefi/main.c index f49df61..4f849b3 100644 --- a/boot/uefi/main.c +++ b/boot/uefi/main.c @@ -1674,23 +1674,32 @@ static UINT32 build_e820(const UINT8 *mapbuf, UINTN mapsize, UINTN descsize, struct e820entry *out) { - UINT32 nent = 0; + // Sorted by address first. The specification does not promise an ordered + // map, firmware that hands one over unsorted exists, and merging only + // finds neighbours that sit next to each other in the list. + static struct e820entry raw[1024]; + UINT32 nraw = 0, nent = 0; - for (UINTN off = 0; off + descsize <= mapsize && nent < E820_MAXENTRIES; + for (UINTN off = 0; off + descsize <= mapsize && nraw < 1024; off += descsize) { EFI_MEMORY_DESCRIPTOR *d = (EFI_MEMORY_DESCRIPTOR *)(mapbuf + off); - UINT32 type = efi_type_to_e820(d->Type); + struct e820entry e = { d->PhysicalStart, d->NumberOfPages * 0x1000, + efi_type_to_e820(d->Type), 1 }; + UINT32 i = nraw++; + while (i > 0 && raw[i - 1].base > e.base) { + raw[i] = raw[i - 1]; + i--; + } + raw[i] = e; + } - if (nent > 0 && out[nent-1].type == type && - out[nent-1].base + out[nent-1].length == d->PhysicalStart) { - out[nent-1].length += d->NumberOfPages * 0x1000; + for (UINT32 i = 0; i < nraw && nent < E820_MAXENTRIES; i++) { + if (nent > 0 && out[nent-1].type == raw[i].type && + out[nent-1].base + out[nent-1].length == raw[i].base) { + out[nent-1].length += raw[i].length; continue; } - out[nent].base = d->PhysicalStart; - out[nent].length = d->NumberOfPages * 0x1000; - out[nent].type = type; - out[nent].acpi_ext = 1; - nent++; + out[nent++] = raw[i]; } return nent; } @@ -1724,6 +1733,26 @@ find_rsdp(void) return fallback; } +// The SMBIOS entry point, the same way: 3.0's 64-bit one first, since a +// table above 4 GB is out of reach of the 2.x one. +static UINT64 +find_smbios(void) +{ + EFI_GUID sm3 = SMBIOS3_TABLE_GUID; + EFI_GUID sm2 = SMBIOS_TABLE_GUID; + EFI_CONFIGURATION_TABLE *t = + (EFI_CONFIGURATION_TABLE *)ST->ConfigurationTable; + UINT64 fallback = 0; + + for (UINTN i = 0; i < ST->NumberOfTableEntries; i++) { + if (guid_eq(&t[i].VendorGuid, &sm3)) + return (UINT64)(UINTN)t[i].VendorTable; + if (guid_eq(&t[i].VendorGuid, &sm2)) + fallback = (UINT64)(UINTN)t[i].VendorTable; + } + return fallback; +} + EFI_STATUS EfiMain(EFI_HANDLE image, EFI_SYSTEM_TABLE *systab) { @@ -1769,6 +1798,7 @@ EfiMain(EFI_HANDLE image, EFI_SYSTEM_TABLE *systab) // of it, for the same reason the e820 map is. UINT64 rsdp = find_rsdp(); vprint(L"schos: acpi rsdp "); vprint_hex(rsdp); vprint(L"\r\n"); + UINT64 smbios = find_smbios(); // The memory map, and then out. GetMemoryMap must be the last thing before // ExitBootServices: the key it returns is invalidated by any allocation, @@ -1813,6 +1843,21 @@ EfiMain(EFI_HANDLE image, EFI_SYSTEM_TABLE *systab) static struct e820entry e820[E820_MAXENTRIES]; UINT32 nent = build_e820(mapbuf, mapsize, descsize, e820); + // The hand-off block is written after ExitBootServices into memory nobody + // allocated, which is safe only while the firmware keeps nothing there + // that outlives it. Say so if it does, since the alternative is a kernel + // that dies in a way nothing points back at. + for (UINT32 i = 0; i < nent; i++) + if (e820[i].type != E820_USABLE && + e820[i].base < SCHBOOT_E820_MAP + SCHBOOT_E820_MAX * 24 && + e820[i].base + e820[i].length > SCHBOOT_BASE) { + print(L"schos: WARNING: firmware memory overlaps the hand-off " + L"block at 0x30000\r\n"); + ui_progress_label("Firmware memory at 0x30000 -- may not boot"); + BS->Stall(2000000); + break; + } + // Truncation is silent data loss: phystop comes from the largest usable // region, so dropping the tail of the map can leave the kernel believing a // 16 GB machine has a fraction of that. Say so while there is still a @@ -1863,6 +1908,8 @@ EfiMain(EFI_HANDLE image, EFI_SYSTEM_TABLE *systab) copy((void *)SCHBOOT_E820_MAP, e820, nent * sizeof e820[0]); *(UINT32 *)SCHBOOT_E820_COUNT = nent; *(UINT64 *)SCHBOOT_RSDP_ADDR = rsdp; + *(UINT64 *)SCHBOOT_SMBIOS_ADDR = smbios; + *(UINT32 *)SCHBOOT_EFI_FLAG = SCHBOOT_EFI_MAGIC; *(UINT8 *)SCHBOOT_VERBOSE_FLAG = (UINT8)VERBOSE_BOOT; *(UINT8 *)SCHBOOT_DEBUG_FLAG = (UINT8)DEBUG_BOOT; diff --git a/cmd/hwreport/main.c b/cmd/hwreport/main.c new file mode 100644 index 0000000..bb07a38 --- /dev/null +++ b/cmd/hwreport/main.c @@ -0,0 +1,938 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +// +// hwreport -- write down what this machine is, onto something you can carry. +// +// hwreport onto the first FAT volume mounted read-write, or the +// first USB stick it can mount, or /root +// hwreport into +// +// It exists for the machines that do not work. A laptop SchOS half-supports +// has no network driver more often than not, and photos of a screen are a poor +// way to carry a boot log and the tables that explain it. So this collects +// what anybody debugging the port would ask for first -- the log, the PCI and +// USB devices and what claimed each, the ACPI tables, the memory map, the +// machine's name -- into one folder on a stick that opens on any other system. +// +// The layout is Linux's where Linux has one: the ACPI tables are the raw .dat +// files `iasl -d` disassembles, and lspci.txt holds what `lspci -nnk -xxx` +// would, so a report from here can be compared line for line with one taken +// on Linux on the same machine. +// +// What it leaves out on purpose: serial numbers and the system UUID (the +// kernel does not publish them), and the MSDM and SLIC tables, which hold a +// Windows licence key. A report is meant to be handed to somebody. + +#include "types.h" +#include "user.h" +#include "fcntl.h" +#include "posix.h" +#include "stat.h" + +// --------------------------------------------------------------------------- +// Small things a libc would have +// --------------------------------------------------------------------------- + +static int +slen(const char *s) +{ + int n = 0; + while (s[n]) n++; + return n; +} + +static int +seq(const char *a, const char *b) +{ + while (*a && *a == *b) { a++; b++; } + return *a == *b; +} + +static int +sprefix(const char *s, const char *p) +{ + while (*p && *s == *p) { s++; p++; } + return *p == 0; +} + +static void +scpy(char *d, const char *s, int cap) +{ + int n = 0; + while (s[n] && n < cap - 1) { d[n] = s[n]; n++; } + d[n] = 0; +} + +// An output buffer, flushed to a file in one write -- each write to FAT is a +// whole operation with its own flush, so fewer is much faster. +struct buf { char *p; int n, cap; }; + +static char textmem[512 * 1024]; +static struct buf T = { textmem, 0, sizeof textmem }; + +static void bc(struct buf *b, char c) { if (b->n < b->cap) b->p[b->n++] = c; } +static void bs(struct buf *b, const char *s) { while (*s) bc(b, *s++); } +static void bsn(struct buf *b, const char *s, int n) { while (n-- > 0) bc(b, *s++); } + +static void +bu(struct buf *b, unsigned long long v) +{ + char t[24]; + int k = 0; + do { t[k++] = '0' + v % 10; v /= 10; } while (v); + while (k) bc(b, t[--k]); +} + +static void +bx(struct buf *b, unsigned long long v, int digits) +{ + static const char h[] = "0123456789abcdef"; + char t[16]; + int k = 0; + do { t[k++] = h[v & 0xF]; v >>= 4; } while (v || k < digits); + while (k) bc(b, t[--k]); +} + +static void +bpad(struct buf *b, const char *s, int w) +{ + int n = slen(s); + bs(b, s); + while (n++ < w) bc(b, ' '); +} + +static void +say(const char *s) +{ + write(1, (void *)s, slen(s)); +} + +static void +saybuf(struct buf *b) +{ + write(1, b->p, b->n); + b->n = 0; +} + +// --------------------------------------------------------------------------- +// Files +// --------------------------------------------------------------------------- + +static char iobuf[64 * 1024]; + +// The whole of a small file into `out`, terminated. Length, or -1. +static int +slurp(const char *path, char *out, int cap) +{ + int fd = open((char *)path, O_RDONLY), n = 0, r; + if (fd < 0) + return -1; + while (n < cap - 1 && (r = read(fd, out + n, cap - 1 - n)) > 0) + n += r; + close(fd); + out[n] = 0; + return n; +} + +// One line of a file, trailing newline dropped; "" when it is missing. +static char * +line(const char *path) +{ + static char l[8][256]; + static int k; + char *s = l[k++ & 7]; + if (slurp(path, s, 256) < 0) + s[0] = 0; + for (int i = 0; s[i]; i++) + if (s[i] == '\n') { s[i] = 0; break; } + return s; +} + +static char dir[512]; +static int nfiles, nfail; + +static void +path_in(char *out, const char *name) +{ + int n = 0; + for (const char *p = dir; *p && n < 500; p++) out[n++] = *p; + out[n++] = '/'; + for (const char *p = name; *p && n < 510; p++) out[n++] = *p; + out[n] = 0; +} + +static int +put_file(const char *name, const char *data, int len) +{ + char p[520]; + path_in(p, name); + int fd = open(p, O_WRONLY | O_CREATE | O_TRUNC); + if (fd < 0) { + nfail++; + return -1; + } + int done = 0; + while (done < len) { + int r = write(fd, (void *)(data + done), len - done); + if (r <= 0) + break; + done += r; + } + close(fd); + if (done != len) { + nfail++; + return -1; + } + nfiles++; + return 0; +} + +static void +put_text(const char *name) +{ + put_file(name, T.p, T.n); + T.n = 0; +} + +// Stream `src` to `name`, for files of any size. +static long +copy_to(const char *src, const char *name) +{ + char p[520]; + int in = open((char *)src, O_RDONLY); + if (in < 0) + return -1; + path_in(p, name); + int fd = open(p, O_WRONLY | O_CREATE | O_TRUNC); + if (fd < 0) { + close(in); + nfail++; + return -1; + } + long total = 0; + int r; + while ((r = read(in, iobuf, sizeof iobuf)) > 0) { + if (write(fd, iobuf, r) != r) { + total = -1; + break; + } + total += r; + } + close(in); + close(fd); + if (total < 0) + nfail++; + else + nfiles++; + return total; +} + +// Every entry of a directory but the dots, sorted, into names[]. +#define MAXNAMES 256 +static char names[MAXNAMES][64]; + +static int +list(const char *path) +{ + static char dbuf[4096]; + int fd = open((char *)path, O_RDONLY | O_DIRECTORY), n = 0, r; + if (fd < 0) + return 0; + while ((r = getdents64(fd, dbuf, sizeof dbuf)) > 0) + for (int off = 0; off < r; ) { + struct dirent64 *d = (struct dirent64 *)(dbuf + off); + off += d->d_reclen; + if (seq(d->d_name, ".") || seq(d->d_name, "..") || n >= MAXNAMES) + continue; + scpy(names[n++], d->d_name, 64); + } + close(fd); + for (int i = 1; i < n; i++) /* insertion sort; n is small */ + for (int j = i; j > 0; j--) { + const char *a = names[j - 1], *b = names[j]; + while (*a && *a == *b) { a++; b++; } + if ((unsigned char)*a <= (unsigned char)*b) + break; + char t[64]; + scpy(t, names[j], 64); + scpy(names[j], names[j - 1], 64); + scpy(names[j - 1], t, 64); + } + return n; +} + +static void +join(char *out, const char *a, const char *b) +{ + int n = 0; + while (*a && n < 250) out[n++] = *a++; + out[n++] = '/'; + while (*b && n < 510) out[n++] = *b++; + out[n] = 0; +} + +// --------------------------------------------------------------------------- +// Where to write +// --------------------------------------------------------------------------- + +static int mounted_here; +static const char *mnt = "/mnt/hwreport"; + +// The first read-write FAT mount in /proc/mounts. +static int +find_fat_mount(char *out, int cap) +{ + static char m[4096]; + if (slurp("/proc/mounts", m, sizeof m) < 0) + return -1; + for (char *l = m; *l; ) { + char *f[4] = { 0 }; + int nf = 0; + char *e = l; + while (*e && *e != '\n') e++; + char save = *e; + *e = 0; + for (char *p = l; *p && nf < 4; ) { + while (*p == ' ') *p++ = 0; + if (!*p) break; + f[nf++] = p; + while (*p && *p != ' ') p++; + } + if (nf == 4 && seq(f[2], "vfat") && !sprefix(f[3], "ro")) { + scpy(out, f[1], cap); + return 0; + } + l = save ? e + 1 : e; + } + return -1; +} + +// Mount the first USB disk or partition that takes FAT. Partitions first, +// since a stick almost always has one and its whole-disk node is the table. +static int +mount_usb(char *out, int cap) +{ + int n = list("/dev"); + mkdir("/mnt"); + mkdir((char *)mnt); + for (int pass = 0; pass < 2; pass++) + for (int i = 0; i < n; i++) { + const char *nm = names[i]; + if (!sprefix(nm, "usb")) + continue; + int haspart = 0; + for (const char *p = nm + 3; *p; p++) + if (*p == 'p') haspart = 1; + if (haspart != (pass == 0)) + continue; + char dev[80]; + join(dev, "/dev", nm); + if (mount(dev, mnt, "vfat", 0, 0) == 0) { + say("hwreport: mounted "); + say(dev); + say(" on "); + say(mnt); + say("\n"); + mounted_here = 1; + scpy(out, mnt, cap); + return 0; + } + } + return -1; +} + +// "hwreport-20261023-142501", or "hwreport-N" when the clock is not set. +static void +make_dir(const char *base) +{ + struct timespec ts; + struct buf b = { dir, 0, sizeof dir - 1 }; + + bs(&b, base); + if (b.n > 1 || base[0] != '/') + bc(&b, '/'); + bs(&b, "hwreport-"); + if (clock_gettime(CLOCK_REALTIME, &ts) == 0 && ts.tv_sec > 946684800) { + // Days to a civil date: Howard Hinnant's algorithm. + long long s = ts.tv_sec, days = s / 86400, rem = s % 86400; + long long z = days + 719468, era = z / 146097; + long long doe = z - era * 146097; + long long yoe = (doe - doe / 1460 + doe / 36524 - doe / 146096) / 365; + long long y = yoe + era * 400, doy = doe - (365 * yoe + yoe / 4 - yoe / 100); + long long mp = (5 * doy + 2) / 153, d = doy - (153 * mp + 2) / 5 + 1; + long long mo = mp < 10 ? mp + 3 : mp - 9; + if (mo <= 2) y++; + bu(&b, (unsigned long long)y); + if (mo < 10) bc(&b, '0'); + bu(&b, (unsigned long long)mo); + if (d < 10) bc(&b, '0'); + bu(&b, (unsigned long long)d); + bc(&b, '-'); + long long hh = rem / 3600, mm = rem / 60 % 60, ss = rem % 60; + if (hh < 10) bc(&b, '0'); + bu(&b, (unsigned long long)hh); + if (mm < 10) bc(&b, '0'); + bu(&b, (unsigned long long)mm); + if (ss < 10) bc(&b, '0'); + bu(&b, (unsigned long long)ss); + dir[b.n] = 0; + if (mkdir(dir) == 0) + return; + bc(&b, '-'); + } + int keep = b.n; + for (int i = 1; i < 1000; i++) { + b.n = keep; + bu(&b, (unsigned long long)i); + dir[b.n] = 0; + if (mkdir(dir) == 0) + return; + } + dir[0] = 0; +} + +// --------------------------------------------------------------------------- +// The report +// --------------------------------------------------------------------------- + +static const char * +chassis_name(int t) +{ + static const char *const n[] = { + 0, "Other", "Unknown", "Desktop", "Low Profile Desktop", "Pizza Box", + "Mini Tower", "Tower", "Portable", "Laptop", "Notebook", "Hand Held", + "Docking Station", "All in One", "Sub Notebook", "Space-saving", + "Lunch Box", "Main Server Chassis", "Expansion Chassis", "SubChassis", + "Bus Expansion Chassis", "Peripheral Chassis", "RAID Chassis", + "Rack Mount Chassis", "Sealed-case PC", "Multi-system chassis", + "Compact PCI", "AdvancedTCA", "Blade", "Blade Enclosure", "Tablet", + "Convertible", "Detachable", "IoT Gateway", "Embedded PC", "Mini PC", + "Stick PC", + }; + return t > 0 && t < (int)(sizeof n / sizeof n[0]) ? n[t] : "?"; +} + +static int +atoi_(const char *s) +{ + int v = 0; + while (*s >= '0' && *s <= '9') v = v * 10 + (*s++ - '0'); + return v; +} + +static unsigned +hexv(const char *s) +{ + unsigned v = 0; + if (s[0] == '0' && s[1] == 'x') s += 2; + for (;; s++) { + int c = *s; + if (c >= '0' && c <= '9') v = v * 16 + (unsigned)(c - '0'); + else if (c >= 'a' && c <= 'f') v = v * 16 + (unsigned)(c - 'a' + 10); + else if (c >= 'A' && c <= 'F') v = v * 16 + (unsigned)(c - 'A' + 10); + else break; + } + return v; +} + +// The value of KEY= in a uevent, or "". +static const char * +uevent_get(const char *ev, const char *key, char *out, int cap) +{ + int kl = slen(key); + out[0] = 0; + for (const char *l = ev; *l; ) { + if (sprefix(l, key) && l[kl] == '=') { + int n = 0; + for (const char *p = l + kl + 1; *p && *p != '\n' && n < cap - 1; p++) + out[n++] = *p; + out[n] = 0; + break; + } + while (*l && *l != '\n') l++; + if (*l) l++; + } + return out; +} + +static struct buf S = { 0, 0, 0 }; // summary.txt, built alongside +static char summem[64 * 1024]; + +static void +dmi_section(void) +{ + static const char *const f[] = { + "sys_vendor", "product_name", "product_version", "product_family", + "product_sku", "board_vendor", "board_name", "board_version", + "bios_vendor", "bios_version", "bios_date", "bios_release", + "chassis_vendor", "chassis_type", "chassis_version", + }; + char p[128]; + for (int i = 0; i < (int)(sizeof f / sizeof f[0]); i++) { + join(p, "/sys/class/dmi/id", f[i]); + bpad(&T, f[i], 17); + bs(&T, line(p)); + bc(&T, '\n'); + } + put_text("dmi.txt"); + + char a[256], b[256]; + scpy(a, line("/sys/class/dmi/id/sys_vendor"), 256); + scpy(b, line("/sys/class/dmi/id/product_name"), 256); + bs(&S, "Machine: "); + bs(&S, a[0] || b[0] ? a : "(no SMBIOS table)"); + bc(&S, ' '); + bs(&S, b); + const char *v = line("/sys/class/dmi/id/product_version"); + if (v[0]) { bs(&S, " / "); bs(&S, v); } + const char *fam = line("/sys/class/dmi/id/product_family"); + if (fam[0]) { bs(&S, " ("); bs(&S, fam); bc(&S, ')'); } + bs(&S, "\nBoard: "); + bs(&S, line("/sys/class/dmi/id/board_vendor")); bc(&S, ' '); + bs(&S, line("/sys/class/dmi/id/board_name")); bc(&S, ' '); + bs(&S, line("/sys/class/dmi/id/board_version")); + bs(&S, "\nFirmware: "); + bs(&S, line("/sys/class/dmi/id/bios_vendor")); bc(&S, ' '); + bs(&S, line("/sys/class/dmi/id/bios_version")); bc(&S, ' '); + bs(&S, line("/sys/class/dmi/id/bios_date")); + { + struct stat st; + bs(&S, stat("/sys/firmware/efi", &st) == 0 ? ", booted by UEFI" : + ", booted by BIOS"); + } + const char *ct = line("/sys/class/dmi/id/chassis_type"); + if (ct[0]) { + bs(&S, "\nChassis: "); + bs(&S, chassis_name(atoi_(ct))); + bs(&S, " (type "); + bs(&S, ct); + bc(&S, ')'); + } + bc(&S, '\n'); +} + +static void +cpu_mem_section(void) +{ + static char ci[16 * 1024]; + int ncpu = 0; + char model[128] = ""; + + if (slurp("/proc/cpuinfo", ci, sizeof ci) >= 0) + for (char *l = ci; *l; ) { + if (sprefix(l, "processor")) + ncpu++; + if (!model[0] && sprefix(l, "model name")) { + char *p = l; + while (*p && *p != ':') p++; + if (*p) p++; + while (*p == ' ') p++; + int n = 0; + while (p[n] && p[n] != '\n' && n < 127) { model[n] = p[n]; n++; } + model[n] = 0; + } + while (*l && *l != '\n') l++; + if (*l) l++; + } + bs(&S, "CPU: "); + bs(&S, model[0] ? model : "?"); + bs(&S, ", "); + bu(&S, (unsigned long long)ncpu); + bs(&S, ncpu == 1 ? " processor running\n" : " processors running\n"); + bs(&S, "Memory: "); + char *mt = line("/proc/meminfo"); + while (*mt && *mt != ':') mt++; + if (*mt) mt++; + while (*mt == ' ') mt++; + bs(&S, mt); + bs(&S, " usable\nSchOS: "); + bs(&S, line("/proc/version")); + bc(&S, '\n'); +} + +static const char * +pci_class_name(unsigned cls) +{ + switch (cls >> 8) { + case 0x0100: return "SCSI"; case 0x0101: return "IDE"; + case 0x0104: return "RAID"; case 0x0106: return "SATA"; + case 0x0108: return "NVMe"; case 0x0200: return "Ethernet"; + case 0x0280: return "Network (Wi-Fi?)"; case 0x0300: return "VGA"; + case 0x0302: return "3D controller"; case 0x0380: return "Display"; + case 0x0401: return "Audio"; case 0x0403: return "HD Audio"; + case 0x0480: return "Multimedia"; case 0x0600: return "Host bridge"; + case 0x0601: return "ISA bridge"; case 0x0604: return "PCI bridge"; + case 0x0680: return "Bridge"; case 0x0805: return "SD host"; + case 0x0880: return "System peripheral";case 0x0c03: return "USB"; + case 0x0c05: return "SMBus"; case 0x0c80: return "Serial bus"; + case 0x0d00: return "IrDA"; case 0x0d11: return "Bluetooth"; + case 0x1180: return "Signal processing";case 0x0c09: return "CANbus"; + case 0x0c10: return "Thunderbolt"; case 0x0780: return "Communication"; + case 0x0700: return "Serial"; case 0x0c00: return "FireWire"; + } + switch (cls >> 16) { + case 0x01: return "Storage"; case 0x02: return "Network"; + case 0x03: return "Display"; case 0x04: return "Multimedia"; + case 0x06: return "Bridge"; case 0x08: return "System"; + case 0x0c: return "Serial bus"; + } + return "Other"; +} + +static void +pci_section(void) +{ + static char ev[1024], cfg[256]; + char p[256], q[256], drv[32], id[32]; + int n = list("/sys/bus/pci/devices"), nodrv = 0; + struct buf L = { 0, 0, 0 }; + static char lmem[160 * 1024]; + L.p = lmem; L.cap = sizeof lmem; + + // Copy the names: list() is reused below. + static char devs[MAXNAMES][64]; + for (int i = 0; i < n; i++) + scpy(devs[i], names[i], 64); + + bs(&S, "\nPCI devices ("); + bu(&S, (unsigned long long)n); + bs(&S, "):\n"); + for (int i = 0; i < n; i++) { + join(p, "/sys/bus/pci/devices", devs[i]); + join(q, p, "uevent"); + if (slurp(q, ev, sizeof ev) < 0) + continue; + join(q, p, "class"); + unsigned cls = hexv(line(q)); + join(q, p, "revision"); + unsigned rev = hexv(line(q)); + uevent_get(ev, "DRIVER", drv, sizeof drv); + uevent_get(ev, "PCI_ID", id, sizeof id); + for (char *c = id; *c; c++) + if (*c >= 'A' && *c <= 'F') *c += 'a' - 'A'; + char sub[32]; + uevent_get(ev, "PCI_SUBSYS_ID", sub, sizeof sub); + for (char *c = sub; *c; c++) + if (*c >= 'A' && *c <= 'F') *c += 'a' - 'A'; + + // lspci -nnk shape. + const char *bdf = devs[i] + 5; + bs(&L, bdf); + bc(&L, ' '); + bs(&L, pci_class_name(cls)); + bs(&L, " ["); + bx(&L, cls >> 8, 4); + bs(&L, "]: ["); + bs(&L, id); + bc(&L, ']'); + if (rev) { bs(&L, " (rev "); bx(&L, rev, 2); bc(&L, ')'); } + bs(&L, "\n\tSubsystem: ["); + bs(&L, sub); + bs(&L, "]\n"); + if (drv[0]) { bs(&L, "\tKernel driver in use: "); bs(&L, drv); bc(&L, '\n'); } + join(q, p, "irq"); + bs(&L, "\tIRQ: "); + bs(&L, line(q)); + bc(&L, '\n'); + join(q, p, "config"); + int cl = 0, fd = open(q, O_RDONLY); + if (fd >= 0) { cl = read(fd, cfg, 256); close(fd); } + for (int o = 0; o < cl; o += 16) { + bx(&L, (unsigned)o, 2); + bc(&L, ':'); + for (int k = 0; k < 16 && o + k < cl; k++) { + bc(&L, ' '); + bx(&L, (unsigned char)cfg[o + k], 2); + } + bc(&L, '\n'); + } + bc(&L, '\n'); + + bs(&S, " "); + bs(&S, bdf); + bc(&S, ' '); + bs(&S, id); + bc(&S, ' '); + bpad(&S, pci_class_name(cls), 18); + if (drv[0]) { + bs(&S, drv); + } else { + bs(&S, "-- no driver"); + nodrv++; + } + bc(&S, '\n'); + } + if (n == 0) + bs(&S, " (none found; is /sys mounted?)\n"); + else if (nodrv) { + bs(&S, " "); + bu(&S, (unsigned long long)nodrv); + bs(&S, " without a driver.\n"); + } + put_file("lspci.txt", L.p, L.n); +} + +static void +usb_section(void) +{ + static char ev[1024]; + char p[256], q[256], drv[32], prod[32]; + int n = list("/sys/bus/usb/devices"); + static char devs[MAXNAMES][64]; + for (int i = 0; i < n; i++) + scpy(devs[i], names[i], 64); + + bs(&S, "\nUSB devices ("); + bu(&S, (unsigned long long)n); + bs(&S, "):\n"); + for (int i = 0; i < n; i++) { + join(p, "/sys/bus/usb/devices", devs[i]); + join(q, p, "uevent"); + if (slurp(q, ev, sizeof ev) < 0) + continue; + uevent_get(ev, "DRIVER", drv, sizeof drv); + uevent_get(ev, "PRODUCT", prod, sizeof prod); + join(q, p, "idVendor"); + const char *vid = line(q); + join(q, p, "idProduct"); + const char *pid = line(q); + join(q, p, "speed"); + const char *spd = line(q); + + bs(&T, devs[i]); + bs(&T, ": ID "); + bs(&T, vid); bc(&T, ':'); bs(&T, pid); + bs(&T, ", "); + bs(&T, spd); + bs(&T, " Mb/s, driver "); + bs(&T, drv[0] ? drv : "(none)"); + bs(&T, "\n"); + bsn(&T, ev, slen(ev)); + bc(&T, '\n'); + + bs(&S, " "); + bpad(&S, devs[i], 8); + bs(&S, vid); bc(&S, ':'); bs(&S, pid); + bs(&S, " "); + bpad(&S, spd, 5); + bs(&S, " Mb/s "); + bs(&S, drv[0] ? drv : "-- no driver"); + bc(&S, '\n'); + } + put_text("lsusb.txt"); +} + +static void +memmap_section(void) +{ + char p[256], q[256]; + int n = list("/sys/firmware/memmap"); + static char ent[MAXNAMES][64]; + unsigned long long ram = 0; + + for (int i = 0; i < n; i++) + scpy(ent[i], names[i], 64); + // Numerically, not as the names sort. + for (int want = 0; want < n; want++) { + char nm[16]; + struct buf b = { nm, 0, 15 }; + bu(&b, (unsigned long long)want); + nm[b.n] = 0; + join(p, "/sys/firmware/memmap", nm); + join(q, p, "start"); + char s[64]; + scpy(s, line(q), 64); + join(q, p, "end"); + char e[64]; + scpy(e, line(q), 64); + join(q, p, "type"); + const char *t = line(q); + bpad(&T, s, 20); + bpad(&T, e, 20); + bs(&T, t); + bc(&T, '\n'); + if (seq(t, "System RAM")) { + unsigned long long a = 0, z = 0; + for (const char *c = s + 2; *c; c++) + a = a * 16 + (unsigned long long)(*c <= '9' ? *c - '0' : (*c | 32) - 'a' + 10); + for (const char *c = e + 2; *c; c++) + z = z * 16 + (unsigned long long)(*c <= '9' ? *c - '0' : (*c | 32) - 'a' + 10); + ram += z - a + 1; + } + } + put_text("memmap.txt"); + bs(&S, "Firmware memory map: "); + bu(&S, (unsigned long long)n); + bs(&S, " entries, "); + bu(&S, ram >> 20); + bs(&S, " MB of RAM\n"); +} + +static void +acpi_section(void) +{ + char p[256], q[256]; + static unsigned char hdr[36]; + int n = list("/sys/firmware/acpi/tables"), skipped = 0, copied = 0; + static char tabs[MAXNAMES][64]; + for (int i = 0; i < n; i++) + scpy(tabs[i], names[i], 64); + + mkdir((path_in(p, "acpi"), p)); + bs(&T, "name length rev oem oem table oem rev\n"); + for (int i = 0; i < n; i++) { + join(p, "/sys/firmware/acpi/tables", tabs[i]); + int fd = open(p, O_RDONLY), h = 0; + if (fd >= 0) { h = read(fd, hdr, 36); close(fd); } + + // A Windows licence key, which belongs to the machine's owner. + if (sprefix(tabs[i], "MSDM") || sprefix(tabs[i], "SLIC")) { + bpad(&T, tabs[i], 9); + bs(&T, "(left out: holds a Windows licence key)\n"); + skipped++; + continue; + } + if (h >= 36) { + unsigned len = hdr[4] | hdr[5] << 8 | hdr[6] << 16 | (unsigned)hdr[7] << 24; + bpad(&T, tabs[i], 9); + char num[16]; + struct buf b = { num, 0, 15 }; + bu(&b, len); + num[b.n] = 0; + bpad(&T, num, 8); + bu(&T, hdr[8]); + bs(&T, hdr[8] < 10 ? " " : " "); + bsn(&T, (char *)hdr + 10, 6); + bs(&T, " "); + bsn(&T, (char *)hdr + 16, 8); + bs(&T, " 0x"); + bx(&T, hdr[24] | hdr[25] << 8 | hdr[26] << 16 | (unsigned)hdr[27] << 24, 8); + bc(&T, '\n'); + } else { + bpad(&T, tabs[i], 9); + bs(&T, "(no header)\n"); + } + char dst[80]; + struct buf b = { dst, 0, 70 }; + bs(&b, "acpi/"); + bs(&b, tabs[i]); + bs(&b, ".dat"); + dst[b.n] = 0; + (void)q; + if (copy_to(p, dst) >= 0) + copied++; + } + put_text("acpi/tables.txt"); + + bs(&S, "ACPI tables: "); + bu(&S, (unsigned long long)copied); + bs(&S, " saved in acpi/"); + if (skipped) { + bs(&S, " ("); + bu(&S, (unsigned long long)skipped); + bs(&S, " left out: Windows licence key)"); + } + if (n == 0) + bs(&S, " -- none found, so no ACPI at all"); + bc(&S, '\n'); +} + +static void +proc_copies(void) +{ + static const char *const f[] = { + "cpuinfo", "meminfo", "interrupts", "devices", "partitions", "mounts", + "pci", "netif", "version", "bootmode", "filesystems", "fsstat", "stat", + }; + char src[64], dst[64]; + mkdir((path_in(dst, "proc"), dst)); + for (int i = 0; i < (int)(sizeof f / sizeof f[0]); i++) { + struct buf a = { src, 0, 60 }, b = { dst, 0, 60 }; + bs(&a, "/proc/"); bs(&a, f[i]); src[a.n] = 0; + bs(&b, "proc/"); bs(&b, f[i]); bs(&b, ".txt"); dst[b.n] = 0; + copy_to(src, dst); + } + + bs(&S, "\nDisks and partitions:\n"); + static char pt[4096]; + if (slurp("/proc/partitions", pt, sizeof pt) > 0) + bs(&S, pt); +} + +int +main(int argc, char **argv) +{ + char base[256]; + + S.p = summem; + S.cap = sizeof summem; + + if (argc > 1 && (seq(argv[1], "-h") || seq(argv[1], "--help"))) { + say("usage: hwreport [dir]\n" + "Writes a folder describing this machine: the boot log, PCI and USB\n" + "devices, ACPI tables, memory map and firmware identity. With no dir,\n" + "onto a mounted FAT volume or the first USB stick that mounts.\n"); + exit(); + } + + if (argc > 1) + scpy(base, argv[1], sizeof base); + else if (find_fat_mount(base, sizeof base) != 0 && + mount_usb(base, sizeof base) != 0) { + say("hwreport: no FAT volume mounted and no USB stick would mount;\n" + " writing to /root instead. Plug a FAT32 stick in and run\n" + " it again, or give a directory: hwreport /mnt\n"); + scpy(base, "/root", sizeof base); + } + + make_dir(base); + if (!dir[0]) { + say("hwreport: cannot create a folder in "); + say(base); + say("\n"); + exit(); + } + say("hwreport: writing "); + say(dir); + say("\n"); + + bs(&S, "SchOS hardware report\n=====================\n\n"); + dmi_section(); + cpu_mem_section(); + memmap_section(); + acpi_section(); + pci_section(); + usb_section(); + proc_copies(); + + long kl = copy_to("/proc/kmsg", "dmesg.txt"); + bs(&S, "\nFiles: summary.txt, dmesg.txt (the boot log"); + if (kl >= 0) { bs(&S, ", "); bu(&S, (unsigned long long)kl); bs(&S, " bytes"); } + bs(&S, "), dmi.txt, lspci.txt, lsusb.txt,\n" + "memmap.txt, acpi/*.dat (iasl -d disassembles them), proc/*.txt\n"); + put_file("summary.txt", S.p, S.n); + + // On the screen too, for a machine whose stick is about to be pulled. + saybuf(&S); + + sync(); + if (mounted_here) { + if (umount2(mnt, 0) == 0) + say("\nhwreport: unmounted; the stick can be pulled out.\n"); + else + say("\nhwreport: written and synced, but the unmount failed.\n"); + } + + struct buf r = { iobuf, 0, 200 }; + bs(&r, "hwreport: "); + bu(&r, (unsigned long long)nfiles); + bs(&r, " files in "); + bs(&r, dir); + if (nfail) { + bs(&r, ", "); + bu(&r, (unsigned long long)nfail); + bs(&r, " could not be written"); + } + bs(&r, "\n"); + saybuf(&r); + exit(); +} diff --git a/cmd/schinit/main.c b/cmd/schinit/main.c index c64f6df..3977197 100644 --- a/cmd/schinit/main.c +++ b/cmd/schinit/main.c @@ -220,6 +220,10 @@ bootstrap(void) // whole SchFS transaction. mkdir("/sys"); mkdir("/run"); + + // The hardware, at Linux's paths, which is where lspci, lsusb and + // hwreport look for it. See kernel/fs/sysfs.c. + mount("sysfs", "/sys", "sysfs", 0, 0); } // --------------------------------------------------------------------------- diff --git a/docs/README.md b/docs/README.md index ebd0843..69dca39 100644 --- a/docs/README.md +++ b/docs/README.md @@ -104,6 +104,7 @@ good chance it is in there. | [testing.md](guides/testing.md) | The harnesses, and what each one actually proves. | | [layout.md](guides/layout.md) | Repository map, and how to add a program or a port. | | [adding-a-driver.md](guides/adding-a-driver.md) | Writing one, binding it, publishing its class vtable. | +| [hardware-report.md](guides/hardware-report.md) | `hwreport`: describing a machine onto a USB stick, for whoever fixes it. | ## branding/ — the marks diff --git a/docs/architecture/boot.md b/docs/architecture/boot.md index 8989ffb..833b14e 100644 --- a/docs/architecture/boot.md +++ b/docs/architecture/boot.md @@ -52,8 +52,11 @@ and `entry.S` so the two cannot disagree. | `+0x000` | VBE-shaped mode info: LFB address, stride, width, height, depth | | `+0x100` | Verbose-boot flag | | `+0x101` | Boot medium (`1` = CD; `0` = not stated) | -| `+0x200` | E820 entry count, then up to 40 × 24-byte entries | +| `+0x200` | E820 entry count | | `+0x600` | ACPI RSDP physical address, or 0 | +| `+0x638` | SMBIOS entry point physical address, or 0 | +| `+0x640` | `"EFI!"` when the UEFI loader booted the kernel | +| `+0x1000` | the E820 entries, up to 256 × 24 bytes | Two of these exist because of specific failures worth knowing about. @@ -65,8 +68,14 @@ interrupt routing — and the machine boots to a prompt on polled serial output and then ignores every keystroke, which looks like a broken console rather than a missing table. -**The E820 cap is 40 entries, not 32.** Real UEFI firmware produces far more -descriptors than QEMU's, and the space was already there. +**The E820 entries have an area of their own**, room for 256. They used to sit +between the count and the RSDP, which held 40, and a laptop's UEFI map is 60 to +150 descriptors. The firmware lists RAM above 4 GB last, so that was what fell +off: a 16 GB machine came up with 2 or 3 GB of it. + +**The SMBIOS entry point is passed the same way as the RSDP**, from the +configuration table, preferring the 64-bit `_SM3_` one. It names the machine in +the boot log's `DMI:` line and in `/sys/class/dmi/id`. ### Choosing a display, on a machine that has more than one @@ -174,13 +183,14 @@ schos: memory map larger than the loader's buffer (status 0x8000000000000005) QEMU's own map needs 5664 bytes — 118 descriptors — so the real number on hardware is larger still, and 256 KB is 5461 descriptors.) -**A second limit is nearby and is only a warning.** `SCHBOOT_E820_MAX` is 40 -entries, fixed by the hand-off block's layout. `build_e820()` merges adjacent -regions of the same type, so 40 survives a long map — but if it ever truncates, -what gets dropped is the *high* end (the map arrives sorted, and `phystop` is -capped below 4 GB regardless). It now says so on screen as well as on the -serial line, because by that point `print()` goes to a port a laptop does not -have. +**A second limit is nearby and is only a warning.** `SCHBOOT_E820_MAX` is 256 +entries. `build_e820()` sorts the descriptors by address first (the +specification does not promise an ordered map) and then merges neighbours of +the same type, so a long map fits easily. If one ever truncates, it says so on +screen as well as on the serial line, because by that point `print()` goes to a +port a laptop does not have. The kernel keeps at most 32 usable regions; past +that, the smallest give way to larger ones, so what is lost is crumbs rather +than whatever was listed last. ### Two signals for a machine that cannot speak diff --git a/docs/architecture/filesystem.md b/docs/architecture/filesystem.md index f90f31b..a4aa433 100644 --- a/docs/architecture/filesystem.md +++ b/docs/architecture/filesystem.md @@ -119,8 +119,8 @@ during path resolution. `/proc` is mounted by init: mount("proc", "/proc", "proc", 0, 0); ``` -The filesystem types are `proc`, `devtmpfs`, `schfs` and `vfat` (also accepted -as `msdos` and `fat`). Unmounting a FAT volume while anything on it is open, or +The filesystem types are `proc`, `devtmpfs`, `sysfs`, `schfs` and `vfat` (also +accepted as `msdos` and `fat`). Unmounting a FAT volume while anything on it is open, or is some process's working directory, fails with `EBUSY`. ## procfs @@ -222,6 +222,46 @@ selftest, `t` text specimen, `n` the netdev ARP selftest. --- + +## sysfs + +`/sys`, in Linux's layout for the parts that exist here. `kernel/fs/sysfs.c`, +mounted by init with `mount("sysfs", "/sys", "sysfs", 0, 0)`. + +| path | contents | +|------|----------| +| `/sys/bus/pci/devices/0000:BB:DD.F/` | `vendor device class revision subsystem_vendor subsystem_device irq resource config uevent` | +| `/sys/bus/usb/devices/B-P[.P...]/` | `idVendor idProduct bcdDevice busnum devnum speed bDeviceClass bDeviceSubClass bDeviceProtocol bNumConfigurations uevent` | +| `/sys/firmware/acpi/tables/` | each ACPI table, raw; a repeated signature is numbered from 1 (`SSDT1`, `SSDT2`) | +| `/sys/firmware/dmi/tables/` | `smbios_entry_point` and `DMI`, raw | +| `/sys/firmware/memmap/N/` | `start end type`, the firmware's map as the loader handed it over | +| `/sys/firmware/efi/` | present when UEFI booted the kernel; empty | +| `/sys/class/dmi/id/` | `sys_vendor product_name product_version board_name bios_version`, and the rest Linux has, except serials and the UUID | + +**Linux's layout because everything that reads hardware reads it from there.** +toybox's `lspci` and `lsusb` walk `/sys/bus/*/devices` and parse each `uevent` +(`DRIVER=`, `PCI_ID=`, `PRODUCT=`, `BUSNUM=`), so with these files they work +unmodified, with names from `/usr/share/hwdata/pci.ids` and `usb.ids`. Formats +follow Linux to the character: `0x8086` in `vendor`, `%04x` in `idVendor`, +`PCI_CLASS` in upper case. + +Like `/proc`, nothing is stored: a listing comes from the live PCI list, the +USB core's table of attached devices, the ACPI root table and the SMBIOS table +at the moment it is read, so a stick shows up in `/sys/bus/usb/devices` as soon +as it is attached. Text files report size 0, as `/proc`'s do; binary ones (the +tables, `config`) report their real length, since tools copy them by it. + +What is missing, compared with Linux: `/sys/devices` and its symlinks (the +`bus/*/devices` entries are directories rather than links into it), +`/sys/class` beyond `dmi`, `/sys/block`, and anything writable. PCI `config` is +256 bytes, because config space is read through port `0xCF8`, which reaches +no further. + +The machine's identity comes from SMBIOS (`kernel/dev/dmi.c`): the UEFI loader +hands over the entry point from the firmware's configuration table, and the +kernel falls back to scanning `0xF0000`-`0xFFFFF`. The boot log carries Linux's +line for it, `DMI: /, BIOS `. + ## Errno, and why `return -1` is a bug A syscall that fails returns a negative errno. `-1` is therefore **`-EPERM`**, diff --git a/docs/guides/hardware-report.md b/docs/guides/hardware-report.md new file mode 100644 index 0000000..1366c45 --- /dev/null +++ b/docs/guides/hardware-report.md @@ -0,0 +1,59 @@ +# Reporting a machine + +When SchOS half-works on a machine (no network, a dead touchpad, a disk it +cannot see), the fastest fix starts with a description of that machine. +`hwreport` writes one onto a USB stick that any other computer can read. + +``` +hwreport # onto a mounted FAT volume, or the first USB stick that mounts +hwreport /mnt # into a directory of your choosing +``` + +1. Plug in a FAT32 USB stick. Nearly every stick comes formatted that way. +2. Run `hwreport`. It mounts the stick itself if nothing is mounted, writes a + folder named `hwreport-YYYYMMDD-HHMMSS`, syncs, and unmounts again. +3. Pull the stick out once it says so, and send the folder from another + computer. Zip it first if your mail or chat client prefers one file. + +It also prints `summary.txt` on the screen. On a machine where even the USB +port is not working, a photo of that screen is the next best thing. + +## What is in the folder + +| file | what it is | +|------|------------| +| `summary.txt` | machine, firmware, CPU, memory, and every PCI and USB device with the driver that claimed it, or `-- no driver` | +| `dmesg.txt` | the kernel log from this boot (and earlier ones, if a crash left them in memory) | +| `lspci.txt` | each PCI device the way `lspci -nnk -xxx` shows it: IDs, subsystem, driver, IRQ, the first 256 bytes of config space | +| `lsusb.txt` | each USB device and its `uevent` | +| `dmi.txt` | the SMBIOS identity: vendor, model, board, BIOS version | +| `memmap.txt` | the firmware's memory map | +| `acpi/*.dat` | every ACPI table, byte for byte; `iasl -d DSDT.dat` disassembles one | +| `acpi/tables.txt` | the table list with lengths and OEM IDs | +| `proc/*.txt` | `/proc` files: interrupts, devices, partitions, mounts and more | + +The file names and formats are Linux's, so a report taken here can be compared +with `lspci`, `acpidump` and `dmesg` output taken under Linux on the same +machine. + +## What is left out + +- **Serial numbers and the system UUID.** The kernel does not publish them. +- **The MSDM and SLIC ACPI tables.** They hold the machine's Windows licence + key. `acpi/tables.txt` lists them as left out. + +Nothing else in the report identifies its owner. Read `summary.txt` before +sending it if you want to be sure. + +## Where the information comes from + +All of it comes from `/sys`, laid out the way Linux lays it out (see +[filesystem.md](../architecture/filesystem.md#sysfs)), plus `/proc`. The same +files are there to read by hand: + +``` +cat /sys/class/dmi/id/product_name +ls /sys/firmware/acpi/tables +lspci -nnk +lsusb +``` diff --git a/include/file.h b/include/file.h index 04ece42..79fac57 100644 --- a/include/file.h +++ b/include/file.h @@ -170,6 +170,11 @@ struct inode { // The synthetic /dev. See kernel/fs/devfs.c. #define DEVFS_DEV 81 +// The synthetic /sys. See kernel/fs/sysfs.c. +#define SYSFS_DEV 82 +// Graft it over `target`; 0 or a negative errno. In sysfs.c. +int sysfs_mount(const char *target); + // Mounted FAT volumes, FAT_DEV onward. See include/fat.h. // Linux's limits, for every filesystem behind the VFS. A path component can be diff --git a/include/hwinfo.h b/include/hwinfo.h new file mode 100644 index 0000000..2024376 --- /dev/null +++ b/include/hwinfo.h @@ -0,0 +1,63 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#pragma once + +// What the firmware and the buses said about this machine, kept so /sys can +// say it again. kernel/fs/sysfs.c is the reader; each producer is named +// beside its part. +// +// Read-only views, copied out under whatever lock the producer has, so a +// caller never holds a pointer into a table that may change under it. + +// --- the memory map, as the loader handed it over (kernel/arch/x86_64/e820.c) +struct e820_rec { + uint64 base, len; + uint32 type; // E820: 1 RAM, 2 reserved, 3 ACPI, 4 NVS, 5 bad +}; +int e820_nth(int i, struct e820_rec *out); // 0, or -1 past the end +// The RAM the page allocator was given, in bytes: MemTotal and sysinfo's +// totalram. Not PHYSTOP, which is an address and counts the PCI hole. +uint64 ram_total_bytes(void); + +// --- ACPI tables (kernel/arch/x86_64/acpi.c) +// +// Every table the RSDT or XSDT lists, then the DSDT and FACS, which only the +// FADT points at. `sig` gets four characters, no terminator. 0 for a table, +// 1 for an empty slot (keep counting), -1 past the end. +int acpi_table_nth(int i, uint64 *pa, uint32 *len, char sig[4]); + +// --- SMBIOS (kernel/dev/dmi.c) +void dmi_init(void); +// The entry point structure and the table it describes, both physical. +int dmi_entry(uint64 *pa, uint32 *len); +int dmi_table(uint64 *pa, uint32 *len); +// Bytes of physical memory, through the direct map or ioremap. 0 on failure. +void *phys_view(uint64 pa, uint64 len); + +// The strings Linux publishes in /sys/class/dmi/id, under its names. Serial +// numbers and the UUID are left out on purpose: nothing here needs them, and a +// hardware report is meant to be handed to somebody else. +enum { + DMI_BIOS_VENDOR, DMI_BIOS_VERSION, DMI_BIOS_DATE, DMI_BIOS_RELEASE, + DMI_SYS_VENDOR, DMI_PRODUCT_NAME, DMI_PRODUCT_VERSION, DMI_PRODUCT_SKU, + DMI_PRODUCT_FAMILY, + DMI_BOARD_VENDOR, DMI_BOARD_NAME, DMI_BOARD_VERSION, + DMI_CHASSIS_VENDOR, DMI_CHASSIS_TYPE, DMI_CHASSIS_VERSION, + DMI_NFIELDS +}; +extern const char *const dmi_field_names[DMI_NFIELDS]; +// Length written (no terminator counted), or -1 if the machine does not say. +int dmi_field(int f, char *out, int cap); + +// 1 if the UEFI loader booted this kernel. +int boot_is_efi(void); + +// --- USB devices (kernel/drivers/usb/core/usb.c), one per addressed device +struct usb_info { + int bus, devnum, speed; // speed: USB_SPEED_* + uint16 vid, pid, bcd; + uint8 cls, subcls, proto; // the device descriptor's + uint8 nconf; + char path[24]; // Linux's name: "1-3", "2-1.4" + char driver[16]; // the first interface's, or empty +}; +int usb_info_nth(int i, struct usb_info *out); diff --git a/include/schboot.h b/include/schboot.h index ccac702..ddb16f0 100644 --- a/include/schboot.h +++ b/include/schboot.h @@ -85,16 +85,18 @@ #define SCHBOOT_MEDIA_SEG 0x3000 #define SCHBOOT_MEDIA_OFF 0x101 -// The E820 memory map: a uint32 count, then that many 24-byte entries. Capped -// at 32 so a pathological firmware cannot run past the buffer; the map ends at -// 0x508, which is what leaves 0x600 free below. +// The E820 memory map: a uint32 count at BASE+0x200, and the entries, 24 bytes +// each, in a page-and-a-half of their own from BASE+0x1000. +// +// The entries used to sit between the count and the RSDP field, which left +// room for 40. A laptop's UEFI map is 60 to 150 descriptors, and even after +// the loader merges neighbours of the same type the RAM above 4 GB -- which +// the firmware lists last -- was what fell off the end: a 16 GB machine came +// up with 2 or 3 of it. 256 is more than any map seen merges to, and the area +// it takes, 0x31000-0x32800, is clear of everything else at BASE. #define SCHBOOT_E820_COUNT (SCHBOOT_BASE + 0x200) -#define SCHBOOT_E820_MAP (SCHBOOT_BASE + 0x208) -/* 40, not 32. The map lives at SCHBOOT_E820_MAP (BASE+0x208) and the next - * field is SCHBOOT_RSDP_ADDR at BASE+0x600, so there are 1016 bytes and each - * entry is 24 -- 42 fit. Real UEFI firmware produces far more descriptors than - * QEMU's, and the headroom costs nothing that was being used. */ -#define SCHBOOT_E820_MAX 40 +#define SCHBOOT_E820_MAP (SCHBOOT_BASE + 0x1000) +#define SCHBOOT_E820_MAX 256 // Where the loader leaves the ACPI RSDP, as a little-endian 64-bit physical // address. Zero means "not provided, go and look for it yourself". @@ -164,6 +166,17 @@ // BGRX in memory, so format 0 is the one that needs its channels swapped. #define SCHBOOT_FB_FORMAT (SCHBOOT_BASE + 0x634) /* uint32 */ +// The SMBIOS entry point, from the UEFI configuration table: the 64-bit +// "_SM3_" one when the firmware has it, else the 32-bit "_SM_". Zero means +// none was handed over, and the kernel scans 0xF0000-0xFFFFF the BIOS way. +// It is what names the machine -- vendor, model, firmware version -- in +// /sys/class/dmi/id, and on UEFI the scan alone finds nothing on most laptops. +#define SCHBOOT_SMBIOS_ADDR (SCHBOOT_BASE + 0x638) /* uint64, physical */ +// SCHBOOT_EFI_MAGIC when the UEFI loader booted this kernel, which is what +// /sys/firmware/efi existing means on Linux. +#define SCHBOOT_EFI_FLAG (SCHBOOT_BASE + 0x640) /* uint32 */ +#define SCHBOOT_EFI_MAGIC 0x21494645u /* "EFI!" */ + #ifndef __ASSEMBLER__ struct sch64_header { diff --git a/include/user.h b/include/user.h index 5329eac..882cee0 100644 --- a/include/user.h +++ b/include/user.h @@ -121,6 +121,9 @@ int rt_sigaction(int, void*, void*, uint64); // kernel's internal one. Returns -EINTR always, as sigsuspend must. int rt_sigsuspend(const uint64 *mask); int mount(const char *src, const char *tgt, const char *fstype, uint64 flags, void *data); +int umount2(const char *tgt, int flags); +int sync(void); +int fsync(int fd); // Directory reading. The kernel has had getdents64 since the Linux ABI landed, // but nothing declared it here, so first-party programs had no way to list a diff --git a/kernel/arch/x86_64/acpi.c b/kernel/arch/x86_64/acpi.c index 1025ff4..5a3a273 100644 --- a/kernel/arch/x86_64/acpi.c +++ b/kernel/arch/x86_64/acpi.c @@ -17,6 +17,7 @@ // another kernel; they are all in chapter 5 of the published standard. #include "types.h" +#include "hwinfo.h" #include "defs.h" #include "schboot.h" // SCHBOOT_RSDP_ADDR: the loader/kernel hand-off #include "param.h" @@ -252,6 +253,87 @@ find_table(struct acpi_rsdp *rsdp, const char *sig) return 0; } +// The n-th table, for /sys/firmware/acpi/tables: 0, 1 for a slot with nothing +// readable in it (go on to n + 1), or -1 past the end. The root's entries in order, +// then the DSDT and FACS: no root lists those two, only the FADT does, and +// they are the ones anybody debugging a machine wants first. +int +acpi_table_nth(int n, uint64 *pa, uint32 *len, char sig[4]) +{ + struct acpi_rsdp *rsdp; + struct acpi_sdt *root; + int wide; + + if (rsdp_phys == 0) + return -1; + rsdp = (struct acpi_rsdp *)P2V(rsdp_phys); + 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 -1; + + uint32 cnt = (root->length - sizeof(struct acpi_sdt)) / (wide ? 8 : 4); + uint64 fadt = 0; + for (uint32 i = 0; i < cnt; i++) { + uint64 p; + if (wide) { + uint8 *b = (uint8 *)(root + 1) + i * 8; + p = 0; + for (int k = 7; k >= 0; k--) + p = (p << 8) | b[k]; + } else { + p = ((uint32 *)(root + 1))[i]; + } + if (p == 0 || p >= 0x100000000ULL) { /* outside the direct map */ + if ((int)i == n) + return 1; + continue; + } + struct acpi_sdt *t = (struct acpi_sdt *)P2V(p); + if (memcmp(t->sig, "FACP", 4) == 0) + fadt = p; + if ((int)i == n) { + *pa = p; + *len = t->length; + memmove(sig, t->sig, 4); + return 0; + } + } + if (fadt == 0) + return -1; + + // From the FADT: DSDT at 40 (32-bit) or X_DSDT at 140, FACS at 36 or + // X_FIRMWARE_CTRL at 132. The wide ones win when the table is long enough + // to have them and they are set. + struct acpi_sdt *f = (struct acpi_sdt *)P2V(fadt); + uint8 *fb = (uint8 *)f; + uint64 dsdt = *(uint32 *)(fb + 40), facs = *(uint32 *)(fb + 36); + if (f->length >= 148 && *(uint64 *)(fb + 140)) + dsdt = *(uint64 *)(fb + 140); + if (f->length >= 140 && *(uint64 *)(fb + 132)) + facs = *(uint64 *)(fb + 132); + + uint64 extra[2] = { dsdt, facs }; + for (int k = 0; k < 2; k++) { + if ((int)cnt + k != n) + continue; + uint64 p = extra[k]; + if (p == 0 || p >= 0x100000000ULL) + return 1; + uint8 *t = (uint8 *)P2V(p); + memmove(sig, t, 4); + *pa = p; + *len = *(uint32 *)(t + 4); /* both keep the length at 4 */ + return 0; + } + return -1; +} + // Returns 1 if the processors were enumerated, 0 to fall back to mpinit(). // One processor from the MADT. // diff --git a/kernel/arch/x86_64/e820.c b/kernel/arch/x86_64/e820.c index c22ea10..e51e6f3 100644 --- a/kernel/arch/x86_64/e820.c +++ b/kernel/arch/x86_64/e820.c @@ -17,6 +17,7 @@ #include "memlayout.h" #include "mmu.h" #include "dma.h" // DMA_ARENA_BYTES: the top of PHYSTOP must be able to hold it +#include "hwinfo.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 @@ -60,12 +61,43 @@ struct e820entry { uint32 acpi_ext; } __attribute__((packed)); +// The map exactly as handed over, for /sys/firmware/memmap. The low page it +// came in is not the kernel's to keep. +static struct e820_rec map_copy[E820_MAXENTRIES]; +static int nmap_copy; + +int +e820_nth(int i, struct e820_rec *out) +{ + if (i < 0 || i >= nmap_copy) + return -1; + *out = map_copy[i]; + return 0; +} + +uint64 +ram_total_bytes(void) +{ + uint64 t = 0; + for (int i = 0; i < nmem_usable; i++) + t += mem_usable[i].end - mem_usable[i].base; + return t; +} + void e820_init(void) { uint32 count = *(uint32 *)P2V(SCHBOOT_E820_COUNT); struct e820entry *e = (struct e820entry *)P2V(SCHBOOT_E820_MAP); + if (count <= E820_MAXENTRIES) + for (uint32 i = 0; i < count; i++) { + map_copy[i].base = e[i].base; + map_copy[i].len = e[i].length; + map_copy[i].type = e[i].type; + nmap_copy = (int)i + 1; + } + // 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. @@ -105,6 +137,20 @@ e820_init(void) if (base >= end) continue; + // Full: the smallest region gives way to a larger one. A map that + // fragments RAM into more than MEMREGION_MAX pieces loses its crumbs + // rather than whatever happens to be listed last, which is the RAM above + // 4 GB. + if (nmem_usable == MEMREGION_MAX) { + int s = 0; + for (int k = 1; k < nmem_usable; k++) + if (mem_usable[k].end - mem_usable[k].base < + mem_usable[s].end - mem_usable[s].base) + s = k; + if (mem_usable[s].end - mem_usable[s].base >= end - base) + continue; + mem_usable[s] = mem_usable[--nmem_usable]; + } mem_usable[nmem_usable].base = base; mem_usable[nmem_usable].end = end; nmem_usable++; diff --git a/kernel/core/main.c b/kernel/core/main.c index 1d34006..8691e99 100644 --- a/kernel/core/main.c +++ b/kernel/core/main.c @@ -13,6 +13,7 @@ #include "usb.h" #include "acpi.h" #include "vmap.h" +#include "hwinfo.h" static void *klog_base; // the persistent log, just above the kernel image @@ -306,6 +307,8 @@ kmain(uint32 fb_addr) // The firmware's AML, loaded before the bus is: pci_init() asks it where // each device's interrupt really goes. See kernel/acpi/aml.c. acpi_aml_init(); + // The machine's name, for the log: Linux's "DMI:" line. See kernel/dev/dmi.c. + dmi_init(); pci_init(); if (boot_verbose) cprintf("verbose boot: pci_init done\n"); diff --git a/kernel/dev/dmi.c b/kernel/dev/dmi.c new file mode 100644 index 0000000..757409b --- /dev/null +++ b/kernel/dev/dmi.c @@ -0,0 +1,285 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +// +// SMBIOS: what the firmware says this machine is. +// +// Vendor, model, board and firmware version live nowhere else. ACPI describes +// the hardware's wiring and PCI its parts, but "Lenovo 20XW / ThinkPad X1 +// Carbon Gen 9, BIOS N32ET75W" is only in this table, and it is the first line +// anybody asks for about a machine that will not boot. Linux prints it as the +// "DMI:" line of its log and publishes the fields in /sys/class/dmi/id; this +// does both, under the same names. +// +// Written from the DMTF's SMBIOS specification (DSP0134). The entry point is +// handed over by the UEFI loader from the configuration table, or found by the +// BIOS-era scan of 0xF0000-0xFFFFF when it was not. + +#include "types.h" +#include "defs.h" +#include "memlayout.h" +#include "schboot.h" +#include "hwinfo.h" + +static int tried; +static uint64 ep_pa; static uint32 ep_len; +static uint64 tab_pa; static uint32 tab_len; +static const uint8 *tab; // mapped, or 0 + +const char *const dmi_field_names[DMI_NFIELDS] = { + [DMI_BIOS_VENDOR] = "bios_vendor", + [DMI_BIOS_VERSION] = "bios_version", + [DMI_BIOS_DATE] = "bios_date", + [DMI_BIOS_RELEASE] = "bios_release", + [DMI_SYS_VENDOR] = "sys_vendor", + [DMI_PRODUCT_NAME] = "product_name", + [DMI_PRODUCT_VERSION] = "product_version", + [DMI_PRODUCT_SKU] = "product_sku", + [DMI_PRODUCT_FAMILY] = "product_family", + [DMI_BOARD_VENDOR] = "board_vendor", + [DMI_BOARD_NAME] = "board_name", + [DMI_BOARD_VERSION] = "board_version", + [DMI_CHASSIS_VENDOR] = "chassis_vendor", + [DMI_CHASSIS_TYPE] = "chassis_type", + [DMI_CHASSIS_VERSION] = "chassis_version", +}; + +void * +phys_view(uint64 pa, uint64 len) +{ + if (pa + len <= 4ULL * 1024 * 1024 * 1024) + return P2V(pa); + return ioremap(pa, len); +} + +int +boot_is_efi(void) +{ + return *(uint32 *)P2V(SCHBOOT_EFI_FLAG) == SCHBOOT_EFI_MAGIC; +} + +static int +sum_ok(const uint8 *p, uint32 n) +{ + uint8 s = 0; + while (n--) + s += *p++; + return s == 0; +} + +// One entry point at `pa`, either kind. Sets the table on success. +static int +try_entry(uint64 pa) +{ + const uint8 *e = phys_view(pa, 32); + if (e == 0) + return 0; + + if (memcmp(e, "_SM3_", 5) == 0) { + uint32 n = e[6]; + if (n < 24 || n > 32 || !sum_ok(e, n)) + return 0; + ep_len = n; + tab_len = *(uint32 *)(e + 0x0C); // the most it can be + tab_pa = *(uint64 *)(e + 0x10); + } else if (memcmp(e, "_SM_", 4) == 0) { + uint32 n = e[5]; + if (n < 0x1F || n > 32 || !sum_ok(e, n) || memcmp(e + 0x10, "_DMI_", 5)) + return 0; + ep_len = n; + tab_len = *(uint16 *)(e + 0x16); + tab_pa = *(uint32 *)(e + 0x18); + } else { + return 0; + } + // A table that large is not one; a firmware bug, and walking it would be + // walking whatever memory follows. + if (tab_pa == 0 || tab_len == 0 || tab_len > 1024 * 1024) + return 0; + ep_pa = pa; + return 1; +} + +static void +find(void) +{ + if (tried) + return; + tried = 1; + + uint64 handed = *(uint64 *)P2V(SCHBOOT_SMBIOS_ADDR); + int ok = boot_is_efi() && handed && try_entry(handed); + + // The BIOS convention: 16-byte aligned somewhere in the last 64 KB below + // 1 MB. The 64-bit kind first, as Linux prefers it. + for (int pass = 0; !ok && pass < 2; pass++) + for (uint64 a = 0xF0000; !ok && a < 0x100000; a += 16) { + const char *p = (const char *)P2V(a); + if (memcmp(p, pass == 0 ? "_SM3_" : "_SM_", pass == 0 ? 5 : 4) == 0) + ok = try_entry(a); + } + if (!ok) { + ep_pa = tab_pa = 0; + return; + } + tab = phys_view(tab_pa, tab_len); +} + +int +dmi_entry(uint64 *pa, uint32 *len) +{ + find(); + if (ep_pa == 0) + return -1; + *pa = ep_pa; + *len = ep_len; + return 0; +} + +// The real length: up to and including the end-of-table structure, which for +// SMBIOS 3 is less than the maximum the entry point gave. +int +dmi_table(uint64 *pa, uint32 *len) +{ + find(); + if (tab == 0) + return -1; + uint32 off = 0; + while (off + 4 <= tab_len) { + uint8 type = tab[off], flen = tab[off + 1]; + if (flen < 4) + break; + uint32 next = off + flen; + while (next + 1 < tab_len && (tab[next] || tab[next + 1])) + next++; + next += 2; + off = next > tab_len ? tab_len : next; + if (type == 127) + break; + } + *pa = tab_pa; + *len = off; + return 0; +} + +// The first structure of `type`, and its length. Strings follow it. +static const uint8 * +find_struct(int type, uint8 *flen) +{ + uint32 off = 0; + while (tab && off + 4 <= tab_len) { + const uint8 *s = tab + off; + if (s[1] < 4) + return 0; + if (s[0] == type) { + *flen = s[1]; + return s; + } + if (s[0] == 127) + return 0; + uint32 next = off + s[1]; + while (next + 1 < tab_len && (tab[next] || tab[next + 1])) + next++; + off = next + 2; + } + return 0; +} + +// String number `idx` of a structure, trimmed as Linux trims it. +static int +get_str(const uint8 *s, uint8 flen, int field, char *out, int cap) +{ + if (field >= flen) + return -1; + int idx = s[field]; + if (idx == 0) + return -1; + const char *p = (const char *)s + flen; + const char *end = (const char *)tab + tab_len; + while (--idx > 0 && p < end) { + while (p < end && *p) + p++; + p++; + if (p < end && *p == 0) // the double NUL: there is no such string + return -1; + } + int n = 0; + while (p < end && *p == ' ') + p++; + while (p < end && *p && n < cap - 1) + out[n++] = *p++; + while (n > 0 && out[n - 1] == ' ') + n--; + out[n] = 0; + return n ? n : -1; +} + +static int +put_u(char *out, int cap, uint v) +{ + char t[12]; + int k = 0, n = 0; + do { t[k++] = '0' + v % 10; v /= 10; } while (v); + while (k && n < cap - 1) + out[n++] = t[--k]; + out[n] = 0; + return n; +} + +int +dmi_field(int f, char *out, int cap) +{ + static const struct { uint8 type, off; } at[DMI_NFIELDS] = { + [DMI_BIOS_VENDOR] = { 0, 4 }, [DMI_BIOS_VERSION] = { 0, 5 }, + [DMI_BIOS_DATE] = { 0, 8 }, + [DMI_SYS_VENDOR] = { 1, 4 }, [DMI_PRODUCT_NAME] = { 1, 5 }, + [DMI_PRODUCT_VERSION] = { 1, 6 }, [DMI_PRODUCT_SKU] = { 1, 0x19 }, + [DMI_PRODUCT_FAMILY] = { 1, 0x1A }, + [DMI_BOARD_VENDOR] = { 2, 4 }, [DMI_BOARD_NAME] = { 2, 5 }, + [DMI_BOARD_VERSION] = { 2, 6 }, + [DMI_CHASSIS_VENDOR] = { 3, 4 }, [DMI_CHASSIS_VERSION] = { 3, 6 }, + }; + uint8 flen; + const uint8 *s; + + if (f < 0 || f >= DMI_NFIELDS || cap < 2) + return -1; + find(); + if (tab == 0) + return -1; + + if (f == DMI_BIOS_RELEASE) { + if ((s = find_struct(0, &flen)) == 0 || flen < 0x16 || + s[0x14] == 0xFF || s[0x15] == 0xFF) + return -1; + int n = put_u(out, cap, s[0x14]); + if (n < cap - 1) out[n++] = '.'; + return n + put_u(out + n, cap - n, s[0x15]); + } + if (f == DMI_CHASSIS_TYPE) { + if ((s = find_struct(3, &flen)) == 0 || flen < 6) + return -1; + return put_u(out, cap, s[5] & 0x7F); + } + if ((s = find_struct(at[f].type, &flen)) == 0) + return -1; + return get_str(s, flen, at[f].off, out, cap); +} + +// Linux's boot line, word for word in shape, because it is the line people +// search for: "DMI: LENOVO 20XW/20XW, BIOS N32ET75W (1.51 ) 09/05/2023". +void +dmi_init(void) +{ + char v[64], p[64], b[64], bv[64], bd[32]; + + find(); + if (tab == 0) { + cprintf("DMI: not present or invalid.\n"); + return; + } + if (dmi_field(DMI_SYS_VENDOR, v, sizeof v) < 0) v[0] = 0; + if (dmi_field(DMI_PRODUCT_NAME, p, sizeof p) < 0) p[0] = 0; + if (dmi_field(DMI_BOARD_NAME, b, sizeof b) < 0) b[0] = 0; + if (dmi_field(DMI_BIOS_VERSION, bv, sizeof bv) < 0) bv[0] = 0; + if (dmi_field(DMI_BIOS_DATE, bd, sizeof bd) < 0) bd[0] = 0; + cprintf("DMI: %s %s/%s, BIOS %s %s\n", v, p, b, bv, bd); +} diff --git a/kernel/dev/procfs.c b/kernel/dev/procfs.c index f114789..0745f7c 100644 --- a/kernel/dev/procfs.c +++ b/kernel/dev/procfs.c @@ -25,6 +25,7 @@ #include "netdev.h" #include "param.h" #include "memlayout.h" +#include "hwinfo.h" #include "mmu.h" #include "spinlock.h" #include "sleeplock.h" @@ -49,15 +50,12 @@ // --- 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; + // Usable RAM, not PHYSTOP: that is an address and counts the PCI hole, so a + // 6 GB machine reported 8. + uint64 total = ram_total_bytes() / 1024; uint64 freek = kalloc_free_bytes() / 1024; sb_kv(sb, "MemTotal: ", total, "kB"); sb_kv(sb, "MemFree: ", freek, "kB"); diff --git a/kernel/drivers/usb/core/usb.c b/kernel/drivers/usb/core/usb.c index 4f1cc97..37780e4 100644 --- a/kernel/drivers/usb/core/usb.c +++ b/kernel/drivers/usb/core/usb.c @@ -15,6 +15,7 @@ #include "spinlock.h" #include "dma.h" #include "usb.h" +#include "hwinfo.h" // A staging buffer for control transfers. // @@ -167,6 +168,108 @@ usb_offer(const struct usb_driver *drv, struct usb_dev *dev) } // --------------------------------------------------------------------------- +// What is plugged in, for /sys/bus/usb/devices. +// +// The controller owns each usb_dev; this only remembers which ones are +// attached, from usb_attach until usb_detach, and copies their identity out +// under the lock so a reader never follows a pointer the controller may be +// about to reuse. A bus number is handed to each controller the first time one +// of its devices appears, in that order, as Linux numbers usb1, usb2. + +#define NPRESENT 64 +#define NBUSES 8 + +static struct spinlock present_lock; +static int present_init; +static struct usb_dev *present[NPRESENT]; +static void *buses[NBUSES]; + +static void +present_add(struct usb_dev *dev) +{ + if(!present_init){ + initlock(&present_lock, "usbpresent"); + present_init = 1; + } + acquire(&present_lock); + for(int i = 0; i < NPRESENT; i++) + if(present[i] == 0){ present[i] = dev; break; } + for(int b = 0; b < NBUSES; b++){ + if(buses[b] == dev->hc) break; + if(buses[b] == 0){ buses[b] = dev->hc; break; } + } + release(&present_lock); +} + +static void +present_del(struct usb_dev *dev) +{ + if(!present_init) + return; + acquire(&present_lock); + for(int i = 0; i < NPRESENT; i++) + if(present[i] == dev) present[i] = 0; + release(&present_lock); +} + +static void +put_num(char *out, int *n, int cap, int v) +{ + char t[12]; + int k = 0; + do { t[k++] = '0' + v % 10; v /= 10; } while(v); + while(k && *n < cap - 1) out[(*n)++] = t[--k]; + out[*n] = 0; +} + +int +usb_info_nth(int idx, struct usb_info *o) +{ + if(!present_init) + return -1; + acquire(&present_lock); + int seen = 0; + for(int i = 0; i < NPRESENT; i++){ + struct usb_dev *d = present[i]; + if(d == 0 || seen++ != idx) + continue; + memset(o, 0, sizeof *o); + o->bus = 1; + for(int b = 0; b < NBUSES; b++) + if(buses[b] == d->hc){ o->bus = b + 1; break; } + o->devnum = d->slot; + o->speed = d->speed; + o->vid = d->desc.idVendor; + o->pid = d->desc.idProduct; + o->bcd = d->desc.bcdDevice; + o->cls = d->desc.bDeviceClass; + o->subcls = d->desc.bDeviceSubClass; + o->proto = d->desc.bDeviceProtocol; + o->nconf = d->desc.bNumConfigurations; + + // "-[....]": the route string holds one + // downstream port per tier below the root, a nibble each. + int n = 0, cap = (int)sizeof o->path; + put_num(o->path, &n, cap, o->bus); + o->path[n++] = '-'; + put_num(o->path, &n, cap, d->port); + for(int t = 0; t < d->tier && n < cap - 3; t++){ + o->path[n++] = '.'; + put_num(o->path, &n, cap, (int)((d->route >> (4 * t)) & 0xF)); + } + + const char *drv = d->driver; + for(int k = 0; !drv && k < NIFDEV; k++) + if(ifdevs[k].in_use && ifdevs[k].base == d) + drv = ifdevs[k].driver; + if(drv) + safestrcpy(o->driver, drv, sizeof o->driver); + release(&present_lock); + return 0; + } + release(&present_lock); + return -1; +} void usb_attach(struct usb_dev *dev) @@ -177,6 +280,7 @@ usb_attach(struct usb_dev *dev) if(buf == 0) return; + present_add(dev); // Nine bytes first, because wTotalLength is in them and there is no other // way to know how much to ask for. @@ -260,6 +364,7 @@ usb_attach(struct usb_dev *dev) void usb_detach(struct usb_dev *dev) { + present_del(dev); for(int i = 0; i < NIFDEV; i++){ struct usb_dev *d = &ifdevs[i]; if(!d->in_use || d->base != dev) diff --git a/kernel/fs/fs.c b/kernel/fs/fs.c index b1d3b46..bd20f4c 100644 --- a/kernel/fs/fs.c +++ b/kernel/fs/fs.c @@ -283,6 +283,7 @@ extern const struct fs_ops fat_ops; // Defined in fs/procfs.c -- the synthetic /proc. extern const struct fs_ops procfs_ops; extern const struct fs_ops devfs_ops; +extern const struct fs_ops sysfs_ops; // Is this filesystem read-only? // @@ -305,6 +306,7 @@ ops_for(uint dev) if (SCHFS_IS_ANY_DEV(dev)) return &schfs_ops; if (dev == PROCFS_DEV) return &procfs_ops; if (dev == DEVFS_DEV) return &devfs_ops; + if (dev == SYSFS_DEV) return &sysfs_ops; if (FAT_IS_DEV(dev)) return &fat_ops; return &v1_ops; } diff --git a/kernel/fs/sysfs.c b/kernel/fs/sysfs.c new file mode 100644 index 0000000..1f745c8 --- /dev/null +++ b/kernel/fs/sysfs.c @@ -0,0 +1,712 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +// +// /sys, laid out the way Linux lays it out, for the parts that exist here. +// +// /sys/bus/pci/devices/0000:00:1f.2/ vendor device class revision irq +// subsystem_vendor subsystem_device +// resource config uevent +// /sys/bus/usb/devices/1-3.2/ idVendor idProduct bcdDevice busnum +// devnum speed bDevice* uevent +// /sys/firmware/acpi/tables/DSDT the tables, byte for byte +// /sys/firmware/dmi/tables/DMI the SMBIOS table and entry point +// /sys/firmware/memmap/N/ start end type +// /sys/firmware/efi/ present when UEFI booted the kernel +// /sys/class/dmi/id/product_name ... the machine, as text +// +// Why Linux's layout rather than one of its own: everything that reads +// hardware information already reads it from these paths. toybox's lspci and +// lsusb walk /sys/bus/*/devices and parse each uevent; acpidump-style tools, +// fastfetch and every "what laptop is this" script read the others. Getting +// the names and formats right makes all of them work at once, and makes a +// report taken here read the same as one taken on Linux on the same machine. +// +// Like /proc, nothing is stored. A directory is listed and a file generated +// from the live tables when asked, so a USB stick appears in /sys the moment +// the hot-plug thread attaches it. + +#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 "file.h" +#include "kmem.h" +#include "uaccess.h" +#include "errno.h" +#include "stat.h" +#include "pci.h" +#include "usb.h" +#include "hwinfo.h" + +// --------------------------------------------------------------------------- +// Inode numbers: kind, index, file -- the whole identity, since iget gives +// nothing else back. +// --------------------------------------------------------------------------- + +#define K_DIR 1ULL // a fixed directory; idx is a dirs[] index +#define K_PCI 2ULL // idx a pci_get() index +#define K_USB 3ULL // idx bus << 8 | devnum +#define K_ACPI 4ULL // idx a slot of acpi_table_nth +#define K_MEMMAP 5ULL // idx an entry of e820_nth +#define K_DMIID 6ULL // file a DMI_* field + 1 +#define K_DMITAB 7ULL // file 1 the entry point, 2 the table + +#define INO(k, idx, f) (((uint64)(k) << 40) | ((uint64)(idx) << 8) | (uint64)(f)) +#define I_KIND(i) ((int)((i) >> 40)) +#define I_IDX(i) ((int)(((i) >> 8) & 0xFFFFFFFF)) +#define I_FILE(i) ((int)((i) & 0xFF)) + +enum { + D_ROOT, D_BUS, D_PCI, D_PCIDEV, D_USB, D_USBDEV, D_FW, D_ACPI, D_ACPITAB, + D_DMI, D_DMITAB, D_MEMMAP, D_EFI, D_CLASS, D_CLASSDMI, D_DMIID, D_NDIRS +}; + +static const struct { const char *name; int parent; } dirs[D_NDIRS] = { + [D_ROOT] = { "", D_ROOT }, + [D_BUS] = { "bus", D_ROOT }, + [D_PCI] = { "pci", D_BUS }, + [D_PCIDEV] = { "devices", D_PCI }, + [D_USB] = { "usb", D_BUS }, + [D_USBDEV] = { "devices", D_USB }, + [D_FW] = { "firmware",D_ROOT }, + [D_ACPI] = { "acpi", D_FW }, + [D_ACPITAB] = { "tables", D_ACPI }, + [D_DMI] = { "dmi", D_FW }, + [D_DMITAB] = { "tables", D_DMI }, + [D_MEMMAP] = { "memmap", D_FW }, + [D_EFI] = { "efi", D_FW }, + [D_CLASS] = { "class", D_ROOT }, + [D_CLASSDMI] = { "dmi", D_CLASS }, + [D_DMIID] = { "id", D_CLASSDMI }, +}; + +#define SYSFS_ROOT INO(K_DIR, D_ROOT, 0) + +static const char *const pcifiles[] = { + 0, "vendor", "device", "class", "revision", "subsystem_vendor", + "subsystem_device", "irq", "resource", "config", "uevent", +}; +#define PCI_CONFIG 9 +#define NPCIFILES ((int)(sizeof pcifiles / sizeof pcifiles[0])) + +static const char *const usbfiles[] = { + 0, "idVendor", "idProduct", "bcdDevice", "busnum", "devnum", "speed", + "bDeviceClass", "bDeviceSubClass", "bDeviceProtocol", "bNumConfigurations", + "uevent", +}; +#define NUSBFILES ((int)(sizeof usbfiles / sizeof usbfiles[0])) + +static const char *const memfiles[] = { 0, "start", "end", "type" }; + +// --------------------------------------------------------------------------- +// Text +// --------------------------------------------------------------------------- + +struct out { char *p; int n, cap; }; + +static void putc_(struct out *o, char c) { if (o->n < o->cap) o->p[o->n++] = c; } +static void puts_(struct out *o, const char *s) { while (*s) putc_(o, *s++); } + +static void +putu(struct out *o, uint64 v) +{ + char t[24]; + int k = 0; + do { t[k++] = '0' + v % 10; v /= 10; } while (v); + while (k) putc_(o, t[--k]); +} + +// `digits` 0 means as few as it takes. Lower case unless `upper`. +static void +putx(struct out *o, uint64 v, int digits, int upper) +{ + const char *h = upper ? "0123456789ABCDEF" : "0123456789abcdef"; + char t[16]; + int k = 0; + do { t[k++] = h[v & 0xF]; v >>= 4; } while (v || k < digits); + while (k) putc_(o, t[--k]); +} + +static void +pci_name(struct out *o, const struct pci_dev *d) +{ + puts_(o, "0000:"); + putx(o, d->bus, 2, 0); putc_(o, ':'); + putx(o, d->slot, 2, 0); putc_(o, '.'); + putx(o, d->func, 1, 0); +} + +static void +memmap_type(struct out *o, uint32 t) +{ + // /sys/firmware/memmap's strings, which are e820's as Linux prints them. + switch (t) { + case 1: puts_(o, "System RAM"); break; + case 3: puts_(o, "ACPI Tables"); break; + case 4: puts_(o, "ACPI Non-volatile Storage"); break; + case 5: puts_(o, "Unusable memory"); break; + case 7: puts_(o, "Persistent Memory"); break; + default: puts_(o, "Reserved"); break; + } +} + +// --------------------------------------------------------------------------- +// ACPI names: a signature that occurs more than once is numbered from 1, all +// of its instances, as Linux numbers SSDT1, SSDT2. +// --------------------------------------------------------------------------- + +static int +acpi_name(int slot, char *name) +{ + uint64 pa; uint32 len; char sig[4], s2[4]; + if (acpi_table_nth(slot, &pa, &len, sig) != 0) + return -1; + int rank = 0, total = 0; + for (int i = 0, r; (r = acpi_table_nth(i, &pa, &len, s2)) >= 0; i++) { + if (r != 0 || memcmp(sig, s2, 4) != 0) + continue; + if (i < slot) + rank++; + total++; + } + int n = 0; + for (int k = 0; k < 4; k++) + name[n++] = (sig[k] > ' ' && sig[k] < 127 && sig[k] != '/') ? sig[k] : '_'; + if (total > 1) { + struct out o = { name + n, 0, 8 }; + putu(&o, (uint64)rank + 1); + n += o.n; + } + name[n] = 0; + return 0; +} + +// --------------------------------------------------------------------------- +// The namespace: every child of a directory, in order. `cb` returns nonzero +// to stop, which is how both lookup and getdents use the one walk. +// --------------------------------------------------------------------------- + +typedef int (*child_fn)(void *ctx, uint64 ino, int isdir, const char *name); + +static int +usb_by_idx(int idx, struct usb_info *u) +{ + for (int i = 0; usb_info_nth(i, u) == 0; i++) + if (((u->bus << 8) | u->devnum) == idx) + return 0; + return -1; +} + +static int +children(uint64 dino, child_fn cb, void *ctx) +{ + char name[64]; + int kind = I_KIND(dino), idx = I_IDX(dino); + + if (kind == K_DIR) { + for (int d = 1; d < D_NDIRS; d++) { + if (dirs[d].parent != idx) + continue; + if (d == D_EFI && !boot_is_efi()) + continue; + if (cb(ctx, INO(K_DIR, d, 0), 1, dirs[d].name)) + return 1; + } + switch (idx) { + case D_PCIDEV: + for (int i = 0; i < pci_count(); i++) { + struct out o = { name, 0, sizeof name - 1 }; + pci_name(&o, pci_get(i)); + name[o.n] = 0; + if (cb(ctx, INO(K_PCI, i, 0), 1, name)) + return 1; + } + break; + case D_USBDEV: { + struct usb_info u; + for (int i = 0; usb_info_nth(i, &u) == 0; i++) + if (cb(ctx, INO(K_USB, (u.bus << 8) | u.devnum, 0), 1, u.path)) + return 1; + break; + } + case D_ACPITAB: + for (int i = 0; ; i++) { + uint64 pa; uint32 len; char sig[4]; + int r = acpi_table_nth(i, &pa, &len, sig); + if (r < 0) + break; + if (r == 0 && acpi_name(i, name) == 0 && + cb(ctx, INO(K_ACPI, i, 1), 0, name)) + return 1; + } + break; + case D_DMITAB: { + uint64 pa; uint32 len; + if (dmi_entry(&pa, &len) == 0) { + if (cb(ctx, INO(K_DMITAB, 0, 1), 0, "smbios_entry_point")) return 1; + if (cb(ctx, INO(K_DMITAB, 0, 2), 0, "DMI")) return 1; + } + break; + } + case D_MEMMAP: { + struct e820_rec e; + for (int i = 0; e820_nth(i, &e) == 0; i++) { + struct out o = { name, 0, sizeof name - 1 }; + putu(&o, (uint64)i); + name[o.n] = 0; + if (cb(ctx, INO(K_MEMMAP, i, 0), 1, name)) + return 1; + } + break; + } + case D_DMIID: + for (int f = 0; f < DMI_NFIELDS; f++) + if (dmi_field(f, name, sizeof name) >= 0 && + cb(ctx, INO(K_DMIID, 0, f + 1), 0, dmi_field_names[f])) + return 1; + break; + } + return 0; + } + + if (kind == K_PCI && I_FILE(dino) == 0) { + for (int f = 1; f < NPCIFILES; f++) + if (cb(ctx, INO(K_PCI, idx, f), 0, pcifiles[f])) + return 1; + } else if (kind == K_USB && I_FILE(dino) == 0) { + for (int f = 1; f < NUSBFILES; f++) + if (cb(ctx, INO(K_USB, idx, f), 0, usbfiles[f])) + return 1; + } else if (kind == K_MEMMAP && I_FILE(dino) == 0) { + for (int f = 1; f < 4; f++) + if (cb(ctx, INO(K_MEMMAP, idx, f), 0, memfiles[f])) + return 1; + } + return 0; +} + +static uint64 +parent_of(uint64 dino) +{ + switch (I_KIND(dino)) { + case K_DIR: return INO(K_DIR, dirs[I_IDX(dino)].parent, 0); + case K_PCI: return INO(K_DIR, D_PCIDEV, 0); + case K_USB: return INO(K_DIR, D_USBDEV, 0); + case K_MEMMAP: return INO(K_DIR, D_MEMMAP, 0); + } + return SYSFS_ROOT; +} + +// --------------------------------------------------------------------------- +// Contents +// --------------------------------------------------------------------------- + +// Bytes straight out of memory the firmware owns, or a PCI config space. +// Sets *src for a mapped region; fills `buf` for config space. Returns the +// length, or -1 when this is not a binary file. +static int +binary(uint64 ino, const uchar **src, uchar *buf) +{ + uint64 pa; uint32 len; char sig[4]; + + switch (I_KIND(ino)) { + case K_ACPI: + if (acpi_table_nth(I_IDX(ino), &pa, &len, sig) != 0) + return -1; + break; + case K_DMITAB: + if ((I_FILE(ino) == 1 ? dmi_entry(&pa, &len) : dmi_table(&pa, &len)) != 0) + return -1; + break; + case K_PCI: + if (I_FILE(ino) != PCI_CONFIG || I_IDX(ino) >= pci_count()) + return -1; + if (buf) { + struct pci_dev *d = pci_get(I_IDX(ino)); + for (int off = 0; off < 256; off += 4) { + uint32 v = pci_read32(d->bus, d->slot, d->func, (uint8)off); + memmove(buf + off, &v, 4); + } + *src = buf; + } + return 256; + default: + return -1; + } + if (src) { + *src = phys_view(pa, len); + if (*src == 0) + return -1; + } + return (int)len; +} + +static void +gen_pci(struct out *o, struct pci_dev *d, int f) +{ + uint16 svid = 0, sdid = 0; + if ((d->header_type & 0x7F) == 0) { + uint32 ss = pci_read32(d->bus, d->slot, d->func, 0x2C); + svid = ss & 0xFFFF; + sdid = ss >> 16; + } + uint32 cls = ((uint32)d->class << 16) | ((uint32)d->subclass << 8) | d->prog_if; + + switch (f) { + case 1: puts_(o, "0x"); putx(o, d->vendor, 4, 0); break; + case 2: puts_(o, "0x"); putx(o, d->device, 4, 0); break; + case 3: puts_(o, "0x"); putx(o, cls, 6, 0); break; + case 4: puts_(o, "0x"); putx(o, d->revision, 2, 0); break; + case 5: puts_(o, "0x"); putx(o, svid, 4, 0); break; + case 6: puts_(o, "0x"); putx(o, sdid, 4, 0); break; + case 7: putu(o, d->irq == 0xFF ? 0 : d->irq); break; + case 8: + // start, end, flags per BAR, then the ROM, as Linux prints them. + for (int b = 0; b < 7; b++) { + uint64 s = 0, e = 0, fl = 0; + if (b < 6 && d->bar[b].base) { + s = d->bar[b].base; + e = s + (d->bar[b].size ? d->bar[b].size - 1 : 0); + fl = d->bar[b].is_io ? 0x101 : 0x200; + if (d->bar[b].prefetch) fl |= 0x2000; + if (d->bar[b].is_64) fl |= 0x100000; + } + puts_(o, "0x"); putx(o, s, 16, 0); + puts_(o, " 0x"); putx(o, e, 16, 0); + puts_(o, " 0x"); putx(o, fl, 16, 0); + putc_(o, '\n'); + } + return; + case 10: + if (d->driver) { puts_(o, "DRIVER="); puts_(o, d->driver); putc_(o, '\n'); } + puts_(o, "PCI_CLASS="); putx(o, cls, 4, 1); + puts_(o, "\nPCI_ID="); putx(o, d->vendor, 4, 1); putc_(o, ':'); + putx(o, d->device, 4, 1); + puts_(o, "\nPCI_SUBSYS_ID="); putx(o, svid, 4, 1); putc_(o, ':'); + putx(o, sdid, 4, 1); + puts_(o, "\nPCI_SLOT_NAME="); pci_name(o, d); + puts_(o, "\nMODALIAS=pci:v"); putx(o, d->vendor, 8, 1); + puts_(o, "d"); putx(o, d->device, 8, 1); + puts_(o, "sv"); putx(o, svid, 8, 1); + puts_(o, "sd"); putx(o, sdid, 8, 1); + puts_(o, "bc"); putx(o, d->class, 2, 1); + puts_(o, "sc"); putx(o, d->subclass, 2, 1); + puts_(o, "i"); putx(o, d->prog_if, 2, 1); + break; + } + putc_(o, '\n'); +} + +static void +gen_usb(struct out *o, const struct usb_info *u, int f) +{ + static const char *const speeds[] = { "unknown", "12", "1.5", "480", "5000" }; + switch (f) { + case 1: putx(o, u->vid, 4, 0); break; + case 2: putx(o, u->pid, 4, 0); break; + case 3: putx(o, u->bcd, 4, 0); break; + case 4: putu(o, (uint64)u->bus); break; + case 5: putu(o, (uint64)u->devnum); break; + case 6: puts_(o, u->speed >= 1 && u->speed <= 4 ? speeds[u->speed] : speeds[0]); break; + case 7: putx(o, u->cls, 2, 0); break; + case 8: putx(o, u->subcls, 2, 0); break; + case 9: putx(o, u->proto, 2, 0); break; + case 10: putu(o, u->nconf); break; + case 11: + puts_(o, "DEVTYPE=usb_device\n"); + if (u->driver[0]) { puts_(o, "DRIVER="); puts_(o, u->driver); putc_(o, '\n'); } + puts_(o, "PRODUCT="); putx(o, u->vid, 0, 0); putc_(o, '/'); + putx(o, u->pid, 0, 0); putc_(o, '/'); putx(o, u->bcd, 0, 0); + puts_(o, "\nTYPE="); putu(o, u->cls); putc_(o, '/'); + putu(o, u->subcls); putc_(o, '/'); putu(o, u->proto); + puts_(o, "\nBUSNUM="); + if (u->bus < 100) putc_(o, '0'); + if (u->bus < 10) putc_(o, '0'); + putu(o, (uint64)u->bus); + puts_(o, "\nDEVNUM="); + if (u->devnum < 100) putc_(o, '0'); + if (u->devnum < 10) putc_(o, '0'); + putu(o, (uint64)u->devnum); + break; + } + putc_(o, '\n'); +} + +// A text file, whole. Returns its length, or -1 if there is no such file. +static int +text(uint64 ino, char *buf, int cap) +{ + struct out o = { buf, 0, cap }; + int idx = I_IDX(ino), f = I_FILE(ino); + + switch (I_KIND(ino)) { + case K_PCI: + if (idx >= pci_count() || f < 1 || f >= NPCIFILES || f == PCI_CONFIG) + return -1; + gen_pci(&o, pci_get(idx), f); + return o.n; + case K_USB: { + struct usb_info u; + if (usb_by_idx(idx, &u) < 0 || f < 1 || f >= NUSBFILES) + return -1; + gen_usb(&o, &u, f); + return o.n; + } + case K_MEMMAP: { + struct e820_rec e; + if (e820_nth(idx, &e) < 0 || f < 1 || f > 3) + return -1; + if (f == 1) { puts_(&o, "0x"); putx(&o, e.base, 0, 0); } + if (f == 2) { puts_(&o, "0x"); putx(&o, e.base + e.len - 1, 0, 0); } + if (f == 3) memmap_type(&o, e.type); + putc_(&o, '\n'); + return o.n; + } + case K_DMIID: { + int n = dmi_field(f - 1, buf, cap - 1); + if (n < 0) + return -1; + buf[n++] = '\n'; + return n; + } + } + return -1; +} + +// --------------------------------------------------------------------------- +// fs_ops +// --------------------------------------------------------------------------- + +static int +exists_dir(uint64 ino) +{ + switch (I_KIND(ino)) { + case K_DIR: + return I_IDX(ino) < D_NDIRS && (I_IDX(ino) != D_EFI || boot_is_efi()); + case K_PCI: + return I_IDX(ino) < pci_count(); + case K_USB: { + struct usb_info u; + return usb_by_idx(I_IDX(ino), &u) == 0; + } + case K_MEMMAP: { + struct e820_rec e; + return e820_nth(I_IDX(ino), &e) == 0; + } + } + return 0; +} + +static void +sysfs_iload(struct inode *ip) +{ + uint64 ino = ip->inum; + char *buf; + + ip->valid = 1; + ip->major = ip->minor = 0; + ip->rwmode = 2; // read-only, as procfs marks it + ip->showmode = 1; + ip->atime = ip->mtime = ip->ctime = 0; + ip->size = 0; + ip->nlink = 1; + + if (I_FILE(ino) == 0 && exists_dir(ino)) { + ip->type = T_DIR; + ip->nlink = 2; + return; + } + int n = binary(ino, 0, 0); + if (n >= 0) { + ip->type = T_FILE; + ip->size = (uint64)n; // real: tools copy tables by length + return; + } + // A text file reports 0, as procfs does and for the same reason: its size + // is whatever it says when it is generated. Generating it here only to + // check that it exists is cheap, and a vanished USB device says no. + if ((buf = kmalloc(512)) != 0) { + n = text(ino, buf, 512); + kmfree(buf); + if (n >= 0) { + ip->type = T_FILE; + return; + } + } + ip->type = 0; + ip->nlink = 0; +} + +static void sysfs_istore(struct inode *ip) { (void)ip; } +static void sysfs_ifree(struct inode *ip) { (void)ip; } +static void sysfs_truncate(struct inode *ip, uint64 n) { (void)ip; (void)n; } + +static int +deliver(int user_dst, uint64 dst, const void *src, uint n) +{ + if (user_dst) + return copy_to_user(dst, (void *)src, n) < 0 ? -EFAULT : (int)n; + memmove((void *)dst, src, n); + return (int)n; +} + +static int +sysfs_read(struct inode *ip, int user_dst, uint64 dst, uint64 off, uint n) +{ + const uchar *src; + uchar *buf; + int len, r; + + if (ip->type == T_DIR) + return -EISDIR; + if ((buf = (uchar *)kalloc()) == 0) + return -ENOMEM; + + len = binary(ip->inum, &src, buf); + if (len < 0) { + len = text(ip->inum, (char *)buf, PGSIZE); + src = buf; + } + if (len < 0) { + kfree((char *)buf); + return -ENOENT; + } + if (off >= (uint64)len) { + kfree((char *)buf); + return 0; + } + if (off + n > (uint64)len) + n = (uint)((uint64)len - off); + r = deliver(user_dst, dst, src + off, n); + kfree((char *)buf); + return r; +} + +static int +sysfs_write(struct inode *ip, int u, uint64 s, uint64 o, uint n) +{ (void)ip; (void)u; (void)s; (void)o; (void)n; return -EROFS; } + +struct find { const char *name; uint64 ino; }; + +static int +find_cb(void *ctx, uint64 ino, int isdir, const char *name) +{ + struct find *f = ctx; + (void)isdir; + if (namecmp((char *)name, (char *)f->name) != 0) + return 0; + f->ino = ino; + return 1; +} + +static struct inode * +sysfs_dirlookup(struct inode *dp, char *name, uint *poff) +{ + struct find f = { name, 0 }; + + if (poff) + *poff = 0; + if (namecmp(name, ".") == 0) + return iget(dp->dev, dp->inum); + if (namecmp(name, "..") == 0) + return iget(dp->dev, parent_of(dp->inum)); + if (!children(dp->inum, find_cb, &f)) + return 0; + return iget(dp->dev, f.ino); +} + +static int sysfs_dirlink(struct inode *dp, char *name, uint64 inum) +{ (void)dp; (void)name; (void)inum; return -EROFS; } +static int sysfs_dirunlink(struct inode *dp, char *name, uint off) +{ (void)dp; (void)name; (void)off; return -EROFS; } +static int sysfs_dirempty(struct inode *dp) { (void)dp; return 0; } +static struct inode *sysfs_ialloc(uint dev, short type) +{ (void)dev; (void)type; return 0; } + +struct linux_dirent64 { + uint64 d_ino; + int64 d_off; + ushort d_reclen; + uchar d_type; + char d_name[]; +}; + +struct list { + char *out; + int cap, used, idx, full; + uint64 want, *off; +}; + +static int +emit_cb(void *ctx, uint64 ino, int isdir, const char *name) +{ + struct list *l = ctx; + if (l->idx++ < (int)l->want) + return 0; + + int nlen = (int)strlen(name); + int rec = ((int)sizeof(struct linux_dirent64) + nlen + 1 + 7) & ~7; + if (l->used + rec > l->cap) { + l->full = 1; + return 1; + } + struct linux_dirent64 *de = (struct linux_dirent64 *)(l->out + l->used); + de->d_ino = ino; + de->d_off = l->used + rec; + de->d_reclen = (ushort)rec; + de->d_type = isdir ? 4 : 8; + memmove(de->d_name, name, nlen); + memset(de->d_name + nlen, 0, rec - (int)sizeof(*de) - nlen); + l->used += rec; + (*l->off)++; + return 0; +} + +// *off is an index into the listing, generated fresh each call -- the same +// contract, and the same race with a device vanishing, as /proc. +static int +sysfs_getdents(struct inode *dp, char *out, int cap, uint64 *off) +{ + struct list l = { out, cap, 0, 0, 0, *off, off }; + + if (dp->type != T_DIR) + return -ENOTDIR; + if (emit_cb(&l, dp->inum, 1, ".") || emit_cb(&l, parent_of(dp->inum), 1, "..")) + return l.used; + children(dp->inum, emit_cb, &l); + return l.used; +} + +const struct fs_ops sysfs_ops = { + .name = "sysfs", + .iload = sysfs_iload, + .istore = sysfs_istore, + .ifree = sysfs_ifree, + .read = sysfs_read, + .write = sysfs_write, + .truncate = sysfs_truncate, + .dirlookup = sysfs_dirlookup, + .dirlink = sysfs_dirlink, + .dirunlink = sysfs_dirunlink, + .dirempty = sysfs_dirempty, + .ialloc = sysfs_ialloc, + .getdents = sysfs_getdents, +}; + +// `mount -t sysfs sysfs /sys`. Init does it. +int +sysfs_mount(const char *target) +{ + struct inode *dp = namei((char *)target); + int r; + + if (dp == 0) + return -ENOENT; + r = mount_add(dp->dev, dp->inum, SYSFS_DEV, SYSFS_ROOT, (char *)target, + "sysfs", "sysfs"); + iput(dp); + return r < 0 ? -EBUSY : 0; +} diff --git a/kernel/sys/sys_fs.c b/kernel/sys/sys_fs.c index 92c5d8d..ed4d20f 100644 --- a/kernel/sys/sys_fs.c +++ b/kernel/sys/sys_fs.c @@ -2174,6 +2174,10 @@ addr_t sys_mount(void) return 0; } + // sysfs: the hardware, as Linux's /sys lays it out. kernel/fs/sysfs.c. + if (strncmp(fstype, "sysfs", 6) == 0) + return sysfs_mount(target); + // FAT, under the three names Linux knows it by. vfat is what everything // means today; msdos is the same driver without long names on Linux, and // here simply the same driver. diff --git a/kernel/sys/sys_proc.c b/kernel/sys/sys_proc.c index 592e88e..89868e3 100644 --- a/kernel/sys/sys_proc.c +++ b/kernel/sys/sys_proc.c @@ -2,6 +2,7 @@ #include "defs.h" #include "param.h" #include "memlayout.h" +#include "hwinfo.h" #include "mmu.h" #include "proc.h" #include "x86.h" @@ -505,10 +506,10 @@ addr_t sys_sysinfo(void) uint64 upsec, upusec; monotonic(&upsec, &upusec); si->uptime = (long)upsec; - // 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; + // Real values: the RAM the page allocator was given, and how much of it is + // still on the free list. These used to be the constants 256MB/128MB, and + // then PHYSTOP, an address, which counted the PCI hole as memory. + si->totalram = ram_total_bytes(); si->freeram = kalloc_free_bytes(); si->mem_unit = 1; si->procs = (uint16)proc_num(); diff --git a/lib/usys.S b/lib/usys.S index b96b616..22898a2 100644 --- a/lib/usys.S +++ b/lib/usys.S @@ -122,6 +122,9 @@ SYSCALL(getsockopt) SYSCALL(sendmsg) SYSCALL(recvmsg) SYSCALL(mount) +SYSCALL(umount2) +SYSCALL(sync) +SYSCALL(fsync) SYSCALL(getdents64) // Attributes and the index over them (SchFS phase 5). setxattr/getxattr/