From c5f65bed225895dbd799c550321d4a3e08f30cbb Mon Sep 17 00:00:00 2001 From: alice Date: Mon, 7 Sep 2026 18:23:11 +0000 Subject: [PATCH] vms-ddc: probe non-virtio (SCSI/NVMe) disks so SHOW DEVICE is faithful on bare metal vms_devtab_probe_disks() enumerated only /dev/vd* (virtio-blk). On bare metal / KVM -- OVMX's Rule-9 target -- the system disk is SATA/SCSI or NVMe, so no disk unit was entered and `SHOW DEVICE VDA0:` returned %SYSTEM-W-NOSUCHDEV (the operator's direct-test finding, vms-ddc / vms-47d). Fix, in three layers: - executive: vms_devtab_probe_disks() now also probes /dev/sd* -> SDAn: and /dev/nvmeNn1 -> NVMEn:, entered via the existing vms_devtab_add_disk path. INV-6 preserved exactly as the virtio loop: a unit is entered ONLY when exec_blockdev_lookup() actually resolves the node. - kernel: distro/kernel/ovmx-x86_64.config enables virtio-scsi built-in (CONFIG_SCSI/BLK_DEV_SD/SCSI_LOWLEVEL/SCSI_VIRTIO, =y -- the initramfs loads no modules). A prior attempt used NVMe, but the minimal from-source config lacks the MSI-X plumbing NVMe needs to bring up /dev/nvme0n1 in the guest; virtio-scsi reuses the already-enabled virtio transport and presents disks as /dev/sd*, exercising the same non-virtio probe path reliably in a minimal kernel. - test: run_tests.sh attaches a REAL virtio-scsi disk (/dev/sda); test_kmod_disk.c asserts the executive enters SDA0: backing /dev/sda and resolving to the SAME major:minor userspace stat()s independently -- the same "no way to fake it" proof the VDA0:..VDA400: checks use -- plus a negctl that SDA100: (a second, absent unit) reports SS$_NOSUCHDEV. Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_018q4jvj6GCkdbYjRvpy7Vwu --- distro/kernel/ovmx-x86_64.config | 16 +++++++++ src/kernel-core/vms_devtab.c | 57 ++++++++++++++++++++++++++++++++ src/kernel/vms_internal.h | 8 +++-- tests/qemu/run_tests.sh | 15 +++++++-- tests/qemu/test_kmod_disk.c | 38 +++++++++++++++++++-- 5 files changed, 126 insertions(+), 8 deletions(-) diff --git a/distro/kernel/ovmx-x86_64.config b/distro/kernel/ovmx-x86_64.config index 7776fee97..b75259263 100644 --- a/distro/kernel/ovmx-x86_64.config +++ b/distro/kernel/ovmx-x86_64.config @@ -31,6 +31,22 @@ CONFIG_VIRTIO_PCI=y CONFIG_VIRTIO_BLK=y CONFIG_VIRTIO_NET=y +# --- virtio-scsi (non-virtio-blk disks: bare-metal SATA/SCSI, vms-ddc / -- +# vms-47d) ------------------------------------------------------------- +# OVMX targets bare metal, where the system disk is SATA/SCSI/NVMe, not +# virtio-blk. A prior attempt (reverted) tried NVMe here, but this minimal +# from-source config lacks the MSI-X plumbing NVMe needs to bring up +# /dev/nvme0n1 in the guest. virtio-scsi instead reuses the already-enabled +# virtio transport above and presents disks as /dev/sd*, exercising the +# SAME non-virtio probe path (vms_devtab_probe_disks()'s SATA/SCSI loop) +# reliably in a minimal kernel. Built-in (=y, never =m -- the initramfs +# loads no modules). test_kmod_disk.c attaches one real virtio-scsi disk +# and proves the executive enters SDA0: for it. +CONFIG_SCSI=y +CONFIG_BLK_DEV_SD=y +CONFIG_SCSI_LOWLEVEL=y +CONFIG_SCSI_VIRTIO=y + # --- Block layer (the virtio-blk system disk) ----------------------------- CONFIG_BLOCK=y diff --git a/src/kernel-core/vms_devtab.c b/src/kernel-core/vms_devtab.c index 0413c810b..b4c930460 100644 --- a/src/kernel-core/vms_devtab.c +++ b/src/kernel-core/vms_devtab.c @@ -343,6 +343,63 @@ static void vms_devtab_probe_disks(void) pr_info("vms: disk unit %s -> %s (%u:%u)\n", devnam, backing, exec_blockdev_major(dev), exec_blockdev_minor(dev)); } + + /* + * Beyond virtio-blk: real and bare-metal boots (the operator's Rule-9 + * target) rarely have /dev/vd* -- their system disk is SATA/SCSI (/dev/sd*) + * or NVMe (/dev/nvmeNn1). Probing ONLY virtio left the device table with no + * disk row on those substrates, so `SHOW DEVICE :` returned NOSUCHDEV + * and a bare `SHOW DEVICE` showed only the console -- the "hollow" surface a + * user hits on non-virtio hardware (vms-ddc / vms-47d). These two families + * complete the documented device-native naming scheme. INV-6 is preserved + * exactly as the virtio loop above: exec_blockdev_lookup() must actually + * resolve the node before a unit is entered -- never a speculative row. + */ + for (i = 0; i < VMS_DISK_UNITS; i++) { + char path[16]; + char devnam[VMS_DEVNAM_SIZE]; + char backing[VMS_BACKING_SIZE]; + exec_dev_t dev; + int n; + + n = snprintf(path, sizeof(path), "/dev/sd%c", 'a' + i); + if (n < 0 || n >= (int)sizeof(path)) + continue; + if (exec_blockdev_lookup(path, &dev) != 0) + continue; + + snprintf(devnam, sizeof(devnam), "SDA%u:", (unsigned)i * 100u); + snprintf(backing, sizeof(backing), "sd%c", 'a' + i); + vms_devtab_add_disk(devnam, backing, + exec_blockdev_major(dev), exec_blockdev_minor(dev)); + } + + /* + * NVMe names a block device n (nvme0n1, nvme1n1...). + * We probe namespace 1 of each controller -- the boot/system-disk case that + * covers essentially every single-namespace machine. Additional namespaces + * (nvme0n2...) are a documented, deliberate not-yet: they are entered by + * whatever probe actually resolves them, never by a speculative branch here + * (same rule the SATA/SCSI comment above states). + */ + for (i = 0; i < VMS_DISK_UNITS; i++) { + char path[16]; + char devnam[VMS_DEVNAM_SIZE]; + char backing[VMS_BACKING_SIZE]; + exec_dev_t dev; + int n; + + n = snprintf(path, sizeof(path), "/dev/nvme%dn1", i); + if (n < 0 || n >= (int)sizeof(path)) + continue; + if (exec_blockdev_lookup(path, &dev) != 0) + continue; + + snprintf(devnam, sizeof(devnam), "NVME%u:", (unsigned)i * 100u); + snprintf(backing, sizeof(backing), "nvme%dn1", i); + vms_devtab_add_disk(devnam, backing, + exec_blockdev_major(dev), exec_blockdev_minor(dev)); + } } /* diff --git a/src/kernel/vms_internal.h b/src/kernel/vms_internal.h index 2f203e5ac..fa732dc42 100644 --- a/src/kernel/vms_internal.h +++ b/src/kernel/vms_internal.h @@ -1526,9 +1526,11 @@ int vms_devtab_init(void); void vms_devtab_cleanup(void); /* * Enter ONE disk unit the SUBSTRATE enumerated, for a substrate whose disks the - * shared /dev/vd* probe cannot name (rd vms-618 -- NetBSD/vax MSCP units). Not - * called on Linux, where vms_devtab_probe_disks() does the enumeration; declared - * here so the shared facility source keeps one prototype on every substrate. + * shared /dev/vd* probe cannot name (rd vms-618 -- NetBSD/vax MSCP units). On + * Linux, vms_devtab_probe_disks() calls this too for the non-virtio-blk + * families it enumerates itself (SATA/SCSI /dev/sd*, NVMe /dev/nvmeNn1 -- + * vms-ddc / vms-47d, bare-metal device-native naming); declared here so the + * shared facility source keeps one prototype on every substrate. */ int vms_devtab_add_disk(const char *devnam, const char *backing, uint32_t backing_major, uint32_t backing_minor); diff --git a/tests/qemu/run_tests.sh b/tests/qemu/run_tests.sh index 60dd5b235..5bc7e91b2 100644 --- a/tests/qemu/run_tests.sh +++ b/tests/qemu/run_tests.sh @@ -191,7 +191,15 @@ else echo " the IMGACT-over-ACP test (vms-3e8e) needs an ODS-2 image volume on VDA400: (vde)" >&2 exit 2 fi -trap 'rm -f "$ASSERT_TRANSCRIPT" "$OVMX_DISK0" "$OVMX_DISK1" "$OVMX_DISK2" "$OVMX_DISK3" "$OVMX_DISK4"' EXIT +# A scratch SCSI disk (blank), attached as a REAL virtio-scsi disk below. Its +# only purpose is to prove the executive's non-virtio disk probe: with a real +# /dev/sda present in the guest, vms_devtab_probe_disks() must enter SDA0: +# resolving to that node's real major:minor (test_kmod_disk.c asserts it against +# an independent stat()). Blank is fine -- the probe resolves the node, it does +# not read the contents. vms-ddc / vms-47d (device-native naming beyond virtio). +OVMX_SCSI0=$(mktemp) || { echo "run_tests.sh: mktemp failed" >&2; exit 2; } +truncate -s 16M "$OVMX_SCSI0" +trap 'rm -f "$ASSERT_TRANSCRIPT" "$OVMX_DISK0" "$OVMX_DISK1" "$OVMX_DISK2" "$OVMX_DISK3" "$OVMX_DISK4" "$OVMX_SCSI0"' EXIT # One virtio-net NIC (vms-9d2). Exactly as the two virtio disks above give the # executive real block devices to enumerate into DK units, this gives it a real @@ -273,6 +281,9 @@ OUTPUT=$(timeout "$TIMEOUT" $QEMU \ -device virtio-blk-pci,drive=ovmxdisk3 \ -drive if=none,id=ovmxdisk4,file="$OVMX_DISK4",format=raw \ -device virtio-blk-pci,drive=ovmxdisk4 \ + -device virtio-scsi-pci,id=scsi0 \ + -drive if=none,id=ovmxscsi0,file="$OVMX_SCSI0",format=raw \ + -device scsi-hd,drive=ovmxscsi0,bus=scsi0.0 \ 2>&1) || QEMU_RC=$? # Splice the assertion transcript (ttyS1, if this arch has one) back into @@ -316,7 +327,7 @@ OUTPUT_FILE=$(mktemp) || { echo "run_tests.sh: mktemp failed" >&2; exit 2; } # ONE trap, both temp files: a second `trap ... EXIT` REPLACES the first # rather than stacking with it, so registering ASSERT_TRANSCRIPT's cleanup # separately above would have silently dropped it the moment this line ran. -trap 'rm -f "$OUTPUT_FILE" "$ASSERT_TRANSCRIPT" "$OVMX_DISK0" "$OVMX_DISK1" "$OVMX_DISK2" "$OVMX_DISK3" "$OVMX_DISK4"' EXIT +trap 'rm -f "$OUTPUT_FILE" "$ASSERT_TRANSCRIPT" "$OVMX_DISK0" "$OVMX_DISK1" "$OVMX_DISK2" "$OVMX_DISK3" "$OVMX_DISK4" "$OVMX_SCSI0"' EXIT printf '%s\n' "$OUTPUT" > "$OUTPUT_FILE" if harness_verdict_zero_failures "$OUTPUT_FILE"; then diff --git a/tests/qemu/test_kmod_disk.c b/tests/qemu/test_kmod_disk.c index 1ca4cf17a..9efa733cf 100644 --- a/tests/qemu/test_kmod_disk.c +++ b/tests/qemu/test_kmod_disk.c @@ -68,7 +68,8 @@ int main(void) uint32_t vda_maj = 0, vda_min = 0, vdb_maj = 0, vdb_min = 0; uint32_t vdc_maj = 0, vdc_min = 0, vdd_maj = 0, vdd_min = 0; uint32_t vde_maj = 0, vde_min = 0; - int have_vda, have_vdb, have_vdc, have_vdd, have_vde; + uint32_t sda_maj = 0, sda_min = 0; + int have_vda, have_vdb, have_vdc, have_vdd, have_vde, have_sda; printf("=== test_kmod_disk: the executive names the machine's disks ===\n"); @@ -97,6 +98,14 @@ int main(void) CHECK(have_vdc, "/dev/vdc is present (third virtio disk attached to the guest)"); CHECK(have_vdd, "/dev/vdd is present (fourth virtio disk attached to the guest)"); CHECK(have_vde, "/dev/vde is present (fifth virtio disk attached to the guest)"); + /* run_tests.sh also attaches ONE real virtio-scsi disk (vms-ddc / vms-47d): + * this is the non-virtio-blk disk the executive's probe must ALSO enumerate, + * so that SHOW DEVICE is not hollow on bare-metal boots (where the system + * disk is SATA/SCSI/NVMe, not virtio-blk -- the operator's SHOW DEVICE + * NOSUCHDEV). virtio-scsi presents as /dev/sda -- a real, non-virtio-blk + * kernel node -- exercising the same SATA/SCSI probe path bare metal uses. */ + have_sda = (stat_devt("/dev/sda", &sda_maj, &sda_min) == 0); + CHECK(have_sda, "/dev/sda is present (a real virtio-scsi disk attached to the guest)"); /* -------------------------------------------------------------- * 1. VDA0: exists in the executive's table -- nothing in this @@ -170,14 +179,37 @@ int main(void) "VDA400: backing dev_t matches /dev/vde as userspace stat()s it"); /* -------------------------------------------------------------- - * 6. Negative controls -- a resolver that always succeeded would be + * 6. SDA0: is the non-virtio-blk disk -- the real proof that the probe + * reaches beyond /dev/vd*. It must exist in the executive's table, + * back onto /dev/sda, and resolve to the SAME major:minor userspace + * stat()s for that node. A probe that only knew virtio-blk would + * leave SDA0: absent (SS$_NOSUCHDEV) -- which is exactly the hollow + * SHOW DEVICE the operator hit on non-virtio hardware. + * -------------------------------------------------------------- */ + memset(backing, 0, sizeof(backing)); + maj = min = 0; + status = vms_kif_disk_resolve("SDA0:", backing, sizeof(backing), &maj, &min); + CHECK(status == SS_NORMAL, + "SDA0: exists in the executive's table (a non-virtio-blk SCSI disk was enumerated)"); + CHECK(strcmp(backing, "sda") == 0, + "SDA0: backing device is sda (the executive's enumeration)"); + CHECK(have_sda && maj == sda_maj && min == sda_min, + "SDA0: backing dev_t matches /dev/sda as userspace stat()s it"); + + /* -------------------------------------------------------------- + * 7. Negative controls -- a resolver that always succeeded would be * indistinguishable from one that works. * -------------------------------------------------------------- */ - /* Five disks are attached (vda..vde), so there is no sixth unit. */ + /* Five virtio disks are attached (vda..vde), so there is no sixth unit. */ memset(backing, 0, sizeof(backing)); status = vms_kif_disk_resolve("VDA500:", backing, sizeof(backing), &maj, &min); CHECK(status == SS_NOSUCHDEV, "a disk unit that does not exist reports SS$_NOSUCHDEV (no sixth disk attached)"); + /* Only ONE virtio-scsi disk is attached, so SDA100: (a second) must miss. */ + memset(backing, 0, sizeof(backing)); + status = vms_kif_disk_resolve("SDA100:", backing, sizeof(backing), &maj, &min); + CHECK(status == SS_NOSUCHDEV, + "a second SCSI unit that does not exist reports SS$_NOSUCHDEV (only one attached)"); /* OPA0: exists, but it is a TERMINAL -- it has no backing block device. */ memset(backing, 0, sizeof(backing));