// 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 boot_vga // enable graphics/fb0/ // /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 "core/types.h" #include "core/defs.h" #include "core/param.h" #include "arch/memlayout.h" #include "arch/mmu.h" #include "core/spinlock.h" #include "core/sleeplock.h" #include "fs/fs.h" #include "fs/file.h" #include "core/kmem.h" #include "core/uaccess.h" #include "uapi/errno.h" #include "uapi/stat.h" #include "dev/pci.h" #include "dev/usb.h" #include "core/hwinfo.h" #include "gfx/gfx.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", "boot_vga", "enable", }; #define PCI_CONFIG 9 #define PCI_ENABLE 12 #define NPCIFILES ((int)(sizeof pcifiles / sizeof pcifiles[0])) // Two directories under the display device only: graphics/ and graphics/fb0/. // Xorg's fbdev driver finds the framebuffer that belongs to a PCI device by // looking for exactly that path, and libpciaccess picks the primary card by // boot_vga. Empty, because the path existing is the whole answer. #define PCI_GRAPHICS 13 #define PCI_FB0 14 // The display device: the one whose memory the framebuffer is in, which is // what boot_vga means on Linux -- the card firmware brought up and drew on. // Failing that, the first display controller. -1 when there is neither. static int boot_vga_idx(void) { uint64 fb = gfx_fb_phys(); int first = -1; for (int i = 0; i < pci_count(); i++) { struct pci_dev *d = pci_get(i); if (d->class != 0x03) continue; if (first < 0) first = i; for (int b = 0; fb && b < 6; b++) if (!d->bar[b].is_io && d->bar[b].base && fb >= d->bar[b].base && fb < d->bar[b].base + d->bar[b].size) return i; } return first; } 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; if (idx == boot_vga_idx() && gfx_fb_phys() && cb(ctx, INO(K_PCI, idx, PCI_GRAPHICS), 1, "graphics")) return 1; } else if (kind == K_PCI && I_FILE(dino) == PCI_GRAPHICS) { if (cb(ctx, INO(K_PCI, idx, PCI_FB0), 1, "fb0")) 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: if (I_FILE(dino) == PCI_GRAPHICS) return INO(K_PCI, I_IDX(dino), 0); if (I_FILE(dino) == PCI_FB0) return INO(K_PCI, I_IDX(dino), PCI_GRAPHICS); 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 11: putc_(o, d == pci_get(boot_vga_idx()) ? '1' : '0'); break; case PCI_ENABLE: { // Whether memory or I/O decoding is on: what "enabled" amounts to. uint32 cmd = pci_read32(d->bus, d->slot, d->func, 0x04); putc_(o, (cmd & 3) ? '1' : '0'); 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: if (I_FILE(ino) == PCI_GRAPHICS || I_FILE(ino) == PCI_FB0) return I_IDX(ino) == boot_vga_idx() && gfx_fb_phys() != 0; 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; int subdir = I_KIND(ino) == K_PCI && (I_FILE(ino) == PCI_GRAPHICS || I_FILE(ino) == PCI_FB0); if ((I_FILE(ino) == 0 || subdir) && 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; if (I_KIND(ino) == K_PCI && I_FILE(ino) == PCI_ENABLE) ip->rwmode = 3; 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; } // Only `enable`, which libpciaccess writes before it touches a device. The // kernel's drivers already turned decoding on for every device they use, and // one nobody drives does not need it -- so the write is accepted and changes // nothing, which is what it would have done on Linux for a device already on. static int sysfs_write(struct inode *ip, int u, uint64 s, uint64 o, uint n) { (void)u; (void)s; (void)o; if (I_KIND(ip->inum) == K_PCI && I_FILE(ip->inum) == PCI_ENABLE) return (int)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; }