hwreport: describe a machine onto a USB stick, from a Linux-style /sys
hwreport writes one folder to the first read-write FAT mount, or mounts the first USB stick that takes vfat itself and unmounts it afterwards: summary.txt (machine, firmware, CPU, memory, every PCI and USB device with its driver or "-- no driver"), dmesg.txt, lspci.txt in the shape of lspci -nnk -xxx, lsusb.txt, dmi.txt, memmap.txt, the ACPI tables as raw .dat files iasl -d reads, and copies of /proc. Serials, the UUID and the MSDM/SLIC tables (a Windows licence key) are left out. It reads /sys, which is new: sysfs at Linux's paths and in Linux's formats -- /sys/bus/pci/devices with uevent, config and resource, /sys/bus/usb/devices, /sys/firmware/acpi/tables, dmi/tables, memmap and efi, /sys/class/dmi/id. toybox's lspci and lsusb work unmodified against it. The USB core now keeps a table of attached devices for it. The machine's name comes from SMBIOS (kernel/dev/dmi.c). The UEFI loader hands over the entry point from the configuration table, the kernel falls back to the 0xF0000 scan, and the log gets Linux's "DMI:" line. Two bugs found on the way: - The loader handed over at most 40 memory-map entries, in firmware order. A laptop's UEFI map is 60-150 descriptors with RAM above 4 GB listed last, so that is what was dropped. The entries now have their own area at 0x31000 with room for 256, are sorted before merging, and the kernel's 32 usable regions keep the largest when there are more. - MemTotal and sysinfo's totalram were PHYSTOP, an address, which counts the PCI hole: a 6 GB machine reported 8. Now the usable RAM, summed. usb.ids moves to /usr/share/hwdata, where lsusb looks for it, and pci.ids joins it (`make pci-ids`, BSD-3-Clause like usb.ids). Checked under QEMU q35: the report written to a FAT32 stick, fsck.fat clean, DSDT.dat disassembled by iasl on the host, lspci -k and lsusb showing names, 6 GB of RAM seen as 6137 MB. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S1giNvrHTrPMPw7V6x1kQ4
This commit is contained in:
parent
edc4e0537d
commit
eab13241a0
25 changed files with 2520 additions and 65 deletions
|
|
@ -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.
|
||||
//
|
||||
|
|
|
|||
|
|
@ -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++;
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
||||
|
|
|
|||
285
kernel/dev/dmi.c
Normal file
285
kernel/dev/dmi.c
Normal file
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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");
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
// "<bus>-<root port>[.<hub port>...]": 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)
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
712
kernel/fs/sysfs.c
Normal file
712
kernel/fs/sysfs.c
Normal file
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
Loading…
Reference in a new issue