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/