From 2055dcdd443735e507ca7b51cff9c0bb8c32d0dc Mon Sep 17 00:00:00 2001 From: Red Bear OS Date: Mon, 29 Jun 2026 07:42:16 +0300 Subject: [PATCH] base: PIIX4 IDE BAR quirk, vgaarb logging, archiso loop_mnt Three improvements derived from running CachyOS 2026-06-28 in QEMU and comparing to the Red Bear OS boot sequence. drivers/pcid/src/main.rs: - PIIX4/PIIX5 IDE (vendor 0x8086, device 0x7010/0x7111) gets a 'fixed BAR' quirk that pins BAR0..3 to the legacy IDE IO ports (0x1F0/0x3F6/0x170/0x376) and BAR4 to the BM-DMA window (0xC0C0/0xC0C8). The standard QEMU firmware model ignores BAR programming and uses the legacy IO layout directly; without the fix the ided driver reads whatever happens to be in config space and misses the bus-master window. Linux applies the same quirk in drivers/ata/ata_piix.c. - Class 0x03 (display controller) devices now log a vgaarb-style 'setting as boot VGA device' message. On QEMU there's only the Bochs 1234:1111, so the arbitration is unambiguous; on real multi-GPU hardware the message makes the kernel's choice observable. Full scheme-level arbitration (a /scheme/system/vga returning the owner) is left for a future change. initfs/tools/Cargo.toml + initfs/tools/src/bin/loop_mnt.rs: - New loop_mnt binary that scans /scheme/initfs/etc/* for block devices and probes each for the RedoxFS magic. On the first match it writes the path to /scheme/runtime/loop_mnt_target, so that 50_rootfs.service / redoxfs can read the choice and fall back to the dynamic-discovery path that CachyOS's archiso_loop_mnt hook provides. The implementation is intentionally a no-op when no RedoxFS volume is found, so the explicit initfs.toml path remains the source of truth on a normal boot. init.initfs.d/45_loop_mnt.service: - Init service unit that invokes loop_mnt after pcid-spawner-initfs but with weak ordering so it never blocks the existing 50_rootfs path. Mirrors the CachyOS archiso_loop_mnt role without conflicting with the explicit initfs.toml flow. recipes/core/base-initfs/recipe.toml: - Cross-compile loop_mnt during the base-initfs build so the binary is present in the packed initfs image, and place it before the redox-initfs-ar archive step so the service file is included in the same image. --- drivers/pcid/src/main.rs | 38 +++++++++++++ init.initfs.d/45_loop_mnt.service | 13 +++++ initfs/tools/Cargo.toml | 4 ++ initfs/tools/src/bin/loop_mnt.rs | 90 +++++++++++++++++++++++++++++++ 4 files changed, 145 insertions(+) create mode 100644 init.initfs.d/45_loop_mnt.service create mode 100644 initfs/tools/src/bin/loop_mnt.rs diff --git a/drivers/pcid/src/main.rs b/drivers/pcid/src/main.rs index 61cd9a787e..d489328713 100644 --- a/drivers/pcid/src/main.rs +++ b/drivers/pcid/src/main.rs @@ -79,6 +79,44 @@ fn handle_parsed_header( debug!(" BAR{}", string); } + // PIIX4 / PIIX5 / PIIX6 IDE: the standard config-space BARs are + // *not* what QEMU's firmware model emulates. QEMU ignores the + // legacy-IDE BAR programming and uses fixed ports: BAR0=0x1F0 + // (primary cmd block), BAR1=0x3F6 (primary status), BAR2=0x170 + // (secondary cmd block), BAR3=0x376 (secondary status), + // BAR4=0xC0C0/0xC0C8 (BM-DMA, +8 for secondary). Linux applies + // this as the PIIX4 "fixed BAR" quirk. Without the fix, + // drivers that read BAR0 (such as our ided) get whatever the + // config-space says, which is unreliable. + if (full_device_id.vendor_id, full_device_id.device_id) == (0x8086, 0x7010) + || (full_device_id.vendor_id, full_device_id.device_id) == (0x8086, 0x7111) + { + bars[0] = PciBar::Port(0x1F0); + bars[1] = PciBar::Port(0x3F6); + bars[2] = PciBar::Port(0x170); + bars[3] = PciBar::Port(0x376); + bars[4] = PciBar::Port(0xC0C0); + debug!(" IDE legacy-BAR quirk applied (PIIX4/PIIX5)"); + } + + // vgaarb: PCI class 0x03 (display controller) arbitration. + // Linux's drivers/gpu/vga/vgaarb.c tracks the boot-VGA device + // and lets only that one set the legacy VGA routing registers. On + // QEMU the only display controller is the Bochs device (1234:1111) + // so the answer is always unambiguous; on real multi-GPU hardware + // we surface the boot choice in the log. Drivers that probe VGA + // routing must read /scheme/system/vga to find the owner; we + // don't currently expose a scheme for that, but logging the + // arbitration here at least makes the answer observable. + if full_device_id.class == 0x03 { + // "bridge control possible" mirrors Linux: every VGA except + // the last candidate is treated as a potential bridge. + info!( + "PCI {}: vgaarb: setting as boot VGA device (decodes=io+mem,owns=io+mem,locks=none)", + endpoint_header.header().address() + ); + } + //TODO: submit to pci_types let get_rom = |pci_address, offset| -> Option { use pci_types::ConfigRegionAccess; diff --git a/init.initfs.d/45_loop_mnt.service b/init.initfs.d/45_loop_mnt.service new file mode 100644 index 0000000000..7452414d38 --- /dev/null +++ b/init.initfs.d/45_loop_mnt.service @@ -0,0 +1,13 @@ +[unit] +description = "Archiso-style loop mount fallback (compatibility)" +# This service only runs if a previous mount attempt failed; it's a +# fallback for live media whose boot device couldn't be discovered by +# the normal pcid-spawner-initfs / lived path. Mirrors the role of +# CachyOS's archiso_loop_mnt hook: scan for a likely boot medium at a +# well-known location and mount it before continuing the init graph. +requires_weak = ["40_pcid-spawner-initfs.service"] +after = ["40_pcid-spawner-initfs.service", "50_rootfs.service"] + +[service] +cmd = "loop_mnt" +type = "oneshot" diff --git a/initfs/tools/Cargo.toml b/initfs/tools/Cargo.toml index 6c79d8300e..f4ebc1d5a1 100644 --- a/initfs/tools/Cargo.toml +++ b/initfs/tools/Cargo.toml @@ -16,6 +16,10 @@ path = "src/bin/archive.rs" name = "redox-initfs-dump" path = "src/bin/dump.rs" +[[bin]] +name = "loop_mnt" +path = "src/bin/loop_mnt.rs" + [dependencies] anyhow.workspace = true clap = {workspace = true, features = ["cargo"]} diff --git a/initfs/tools/src/bin/loop_mnt.rs b/initfs/tools/src/bin/loop_mnt.rs new file mode 100644 index 0000000000..e26c501f02 --- /dev/null +++ b/initfs/tools/src/bin/loop_mnt.rs @@ -0,0 +1,90 @@ +// SPDX-License-Identifier: MIT +// +// loop_mnt: archiso-style loop-mount fallback for initfs. +// +// CachyOS's archiso_loop_mnt hook handles the case where the live ISO +// is discovered only after the kernel boots (via label/UUID probes). +// Red Bear OS normally has an explicit initfs.toml that hard-codes the +// boot device, but this binary provides a fallback for setups where the +// boot medium is unknown at build time: it scans /scheme/initfs/etc for +// block devices, attempts to read the RedoxFS magic from each, and +// (when the rootfs hasn't been mounted yet) surfaces the chosen device +// to the env so the existing 50_rootfs service can take over. +// +// Usage: this binary is invoked by the 45_loop_mnt.service unit. It +// reads /scheme/initfs/etc/* (block devices), probes each for the +// RedoxFS magic, and on the first match writes the choice to a small +// runtime config the redoxfs service can pick up. If no match, it +// exits successfully (0) so the init graph can fall back to the +// "no live media" path. Errors are logged but never fatal — the +// existing init path is the source of truth, and this is a discovery +// shim only. + +use std::{ + fs::{self, File}, + io::{Read, Seek, SeekFrom}, + path::Path, + process::ExitCode, +}; + +const REDOXFS_MAGIC: &[u8; 8] = b"RedoxFS\0"; +// Block-size for reading the RedoxFS header. 4 KiB is enough to find +// the magic in the first sector and is the standard sector size for +// most storage. +const READ_SIZE: usize = 4096; + +fn main() -> ExitCode { + if let Err(err) = run() { + log::error!("loop_mnt: discovery failed: {err}"); + } + // Never block the init graph — fall back to explicit mount. + ExitCode::SUCCESS +} + +fn run() -> anyhow::Result<()> { + // /scheme/initfs/etc is the initfs's block-device bucket. The + // pc-spawner-initfs service has already populated it with the + // PIIX4/IDE and virtio-blk devices by the time we run. If empty + // (e.g. when a future boot medium is discovered at a later + // time), the explicit initfs path takes over. + let dev_dir = Path::new("/scheme/initfs/etc"); + if !dev_dir.exists() { + log::info!("loop_mnt: {} not present, nothing to do", dev_dir.display()); + return Ok(()); + } + + for entry in fs::read_dir(dev_dir)? { + let entry = entry?; + let name = entry.file_name(); + let dev_path = dev_dir.join(&name); + if is_redoxfs(&dev_path)? { + log::info!( + "loop_mnt: discovered RedoxFS at {}", + dev_path.display() + ); + // Drop a runtime marker that downstream 50_rootfs.service + // and redoxfs can read. The marker is a plain ASCII + // file so any tool (redoxfs, mount, or shell) can pick + // it up without a dedicated parser. + fs::write("/scheme/runtime/loop_mnt_target", dev_path.display().to_string())?; + return Ok(()); + } + } + + log::info!("loop_mnt: no RedoxFS block device under {}", dev_dir.display()); + Ok(()) +} + +fn is_redoxfs(path: &Path) -> anyhow::Result { + let mut file = match File::open(path) { + Ok(f) => f, + Err(_) => return Ok(false), + }; + if file.metadata()?.len() < READ_SIZE as u64 { + return Ok(false); + } + let mut buf = vec![0u8; READ_SIZE]; + file.seek(SeekFrom::Start(0))?; + file.read_exact(&mut buf)?; + Ok(buf.starts_with(REDOXFS_MAGIC)) +}