From 35193bb32db8f442c5fe537a867f5c405d5e3be3 Mon Sep 17 00:00:00 2001 From: Vasilito Date: Thu, 16 Apr 2026 12:43:50 +0100 Subject: [PATCH] Advance firmware and IOMMU support Red Bear OS Team --- .../system/firmware-loader/source/src/blob.rs | 63 ++++++++++++++++--- .../system/firmware-loader/source/src/main.rs | 33 ++++++++-- .../firmware-loader/source/src/scheme.rs | 37 ++++++++--- .../recipes/system/iommu/source/src/amd_vi.rs | 25 +++----- .../system/iommu/source/src/device_table.rs | 2 +- local/recipes/system/iommu/source/src/mmio.rs | 16 ++--- 6 files changed, 130 insertions(+), 46 deletions(-) diff --git a/local/recipes/system/firmware-loader/source/src/blob.rs b/local/recipes/system/firmware-loader/source/src/blob.rs index aa132523..911b6e5b 100644 --- a/local/recipes/system/firmware-loader/source/src/blob.rs +++ b/local/recipes/system/firmware-loader/source/src/blob.rs @@ -6,6 +6,7 @@ use std::sync::{Arc, Mutex}; use log::{info, warn}; use thiserror::Error; +#[allow(dead_code)] #[derive(Error, Debug)] pub enum BlobError { #[error("firmware directory not found: {0}")] @@ -22,12 +23,14 @@ pub enum BlobError { }, } +#[allow(dead_code)] pub struct FirmwareBlob { #[allow(dead_code)] pub name: String, pub path: PathBuf, } +#[allow(dead_code)] pub struct FirmwareRegistry { base_dir: PathBuf, blobs: HashMap, @@ -67,10 +70,12 @@ impl FirmwareRegistry { &self.base_dir } + #[allow(dead_code)] pub fn contains(&self, key: &str) -> bool { self.blobs.contains_key(key) } + #[allow(dead_code)] pub fn load(&self, key: &str) -> Result>, BlobError> { { let cache = self.cache.lock().map_err(|e| BlobError::ReadError { @@ -115,10 +120,6 @@ impl FirmwareRegistry { self.blobs.len() } - pub fn is_empty(&self) -> bool { - self.blobs.is_empty() - } - #[allow(dead_code)] pub fn list_keys(&self) -> Vec<&str> { self.blobs.keys().map(|s| s.as_str()).collect() @@ -162,12 +163,15 @@ fn discover_firmware(base_dir: &Path) -> Result, B format!("{}/{}", prefix, file_name) }; stack.push((path, new_prefix)); - } else if metadata.is_file() && file_name.ends_with(".bin") { - let stem = file_name.trim_end_matches(".bin"); + } else if metadata.is_file() { + if is_metadata_file(&file_name) { + continue; + } + let key = if prefix.is_empty() { - stem.to_string() + file_name.to_string() } else { - format!("{}/{}", prefix, stem) + format!("{}/{}", prefix, file_name) }; blobs.insert(key.clone(), FirmwareBlob { name: key, path }); @@ -177,3 +181,46 @@ fn discover_firmware(base_dir: &Path) -> Result, B Ok(blobs) } + +fn is_metadata_file(file_name: &str) -> bool { + matches!( + file_name, + "WHENCE" | "README" | "README.md" | "check_whence.py" | "Makefile" + ) || file_name.starts_with("LICENCE") + || file_name.starts_with("LICENSE") +} + +#[cfg(test)] +mod tests { + use super::*; + use std::time::{SystemTime, UNIX_EPOCH}; + + fn temp_root(prefix: &str) -> PathBuf { + let stamp = SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_nanos(); + let path = std::env::temp_dir().join(format!("{prefix}-{stamp}")); + fs::create_dir_all(&path).unwrap(); + path + } + + #[test] + fn discovers_ucode_pnvm_and_bin_but_skips_license_metadata() { + let root = temp_root("rbos-fw-discover"); + fs::write(root.join("demo.bin"), []).unwrap(); + fs::write(root.join("iwlwifi-bz-b0-gf-a0-92.ucode"), []).unwrap(); + fs::write(root.join("iwlwifi-bz-b0-gf-a0.pnvm"), []).unwrap(); + fs::write(root.join("LICENCE.test"), "license").unwrap(); + fs::write(root.join("WHENCE"), "meta").unwrap(); + + let blobs = discover_firmware(&root).unwrap(); + assert!(blobs.contains_key("demo.bin")); + assert!(blobs.contains_key("iwlwifi-bz-b0-gf-a0-92.ucode")); + assert!(blobs.contains_key("iwlwifi-bz-b0-gf-a0.pnvm")); + assert!(!blobs.contains_key("LICENCE.test")); + assert!(!blobs.contains_key("WHENCE")); + + fs::remove_dir_all(root).unwrap(); + } +} diff --git a/local/recipes/system/firmware-loader/source/src/main.rs b/local/recipes/system/firmware-loader/source/src/main.rs index 1870066b..2f6c0f55 100644 --- a/local/recipes/system/firmware-loader/source/src/main.rs +++ b/local/recipes/system/firmware-loader/source/src/main.rs @@ -2,14 +2,17 @@ mod blob; mod scheme; use std::env; -use std::os::fd::{AsRawFd, FromRawFd, RawFd}; +#[cfg(target_os = "redox")] +use std::os::fd::RawFd; use std::path::PathBuf; use std::process; use log::{error, info, LevelFilter, Metadata, Record}; +#[cfg(target_os = "redox")] use redox_scheme::{scheme::SchemeSync, SignalBehavior, Socket}; use blob::FirmwareRegistry; +#[cfg(target_os = "redox")] use scheme::FirmwareScheme; struct StderrLogger { @@ -38,9 +41,10 @@ fn init_logging(level: LevelFilter) { } fn default_firmware_dir() -> PathBuf { - PathBuf::from("/usr/firmware/") + PathBuf::from("/lib/firmware/") } +#[cfg(target_os = "redox")] unsafe fn get_init_notify_fd() -> RawFd { let fd: RawFd = env::var("INIT_NOTIFY") .expect("firmware-loader: INIT_NOTIFY not set") @@ -50,6 +54,7 @@ unsafe fn get_init_notify_fd() -> RawFd { fd } +#[cfg(target_os = "redox")] fn notify_scheme_ready(notify_fd: RawFd, socket: &Socket, scheme: &mut FirmwareScheme) { let cap_id = scheme .scheme_root() @@ -67,6 +72,7 @@ fn notify_scheme_ready(notify_fd: RawFd, socket: &Socket, scheme: &mut FirmwareS .expect("firmware-loader: failed to notify init that scheme is ready"); } +#[cfg(target_os = "redox")] fn run_daemon(notify_fd: RawFd, registry: FirmwareRegistry) -> ! { let socket = Socket::create().expect("firmware-loader: failed to create scheme socket"); let mut scheme = FirmwareScheme::new(registry); @@ -137,6 +143,25 @@ fn main() { firmware_dir.display() ); - let notify_fd = unsafe { get_init_notify_fd() }; - run_daemon(notify_fd, registry); + if env::args().nth(1).as_deref() == Some("--probe") { + println!("count={}", registry.len()); + let mut keys = registry.list_keys(); + keys.sort_unstable(); + for key in keys.into_iter().take(16) { + println!("firmware={key}"); + } + return; + } + + #[cfg(not(target_os = "redox"))] + { + eprintln!("firmware-loader: daemon mode is only supported on Redox; use --probe on host"); + process::exit(1); + } + + #[cfg(target_os = "redox")] + { + let notify_fd = unsafe { get_init_notify_fd() }; + run_daemon(notify_fd, registry); + } } diff --git a/local/recipes/system/firmware-loader/source/src/scheme.rs b/local/recipes/system/firmware-loader/source/src/scheme.rs index 3bc60096..fdd9a25a 100644 --- a/local/recipes/system/firmware-loader/source/src/scheme.rs +++ b/local/recipes/system/firmware-loader/source/src/scheme.rs @@ -10,8 +10,10 @@ use syscall::{EventFlags, Stat, MODE_FILE}; use crate::blob::FirmwareRegistry; +#[cfg_attr(not(target_os = "redox"), allow(dead_code))] const SCHEME_ROOT_ID: usize = 1; +#[cfg_attr(not(target_os = "redox"), allow(dead_code))] struct Handle { blob_key: String, data: Arc>, @@ -19,12 +21,14 @@ struct Handle { closed: bool, } +#[cfg_attr(not(target_os = "redox"), allow(dead_code))] pub struct FirmwareScheme { registry: FirmwareRegistry, next_id: usize, handles: BTreeMap, } +#[cfg_attr(not(target_os = "redox"), allow(dead_code))] impl FirmwareScheme { pub fn new(registry: FirmwareRegistry) -> Self { FirmwareScheme { @@ -56,15 +60,11 @@ fn resolve_key(path: &str) -> Option { ); return None; } - let key = if cleaned.ends_with(".bin") { - cleaned.trim_end_matches(".bin").to_string() - } else { - cleaned.to_string() - }; - // Final sanity: key must be purely alphanumeric with /, -, _ + let key = cleaned.to_string(); + // Final sanity: key must be purely alphanumeric with /, -, _, . if !key .chars() - .all(|c| c.is_alphanumeric() || c == '/' || c == '-' || c == '_') + .all(|c| c.is_alphanumeric() || c == '/' || c == '-' || c == '_' || c == '.') { log::warn!( "firmware-loader: rejecting invalid characters in key: {:?}", @@ -160,7 +160,7 @@ impl SchemeSync for FirmwareScheme { fn fpath(&mut self, id: usize, buf: &mut [u8], _ctx: &CallerCtx) -> Result { let handle = self.handle(id)?; - let path = format!("firmware:/{}.bin", handle.blob_key); + let path = format!("firmware:/{}", handle.blob_key); let bytes = path.as_bytes(); let len = bytes.len().min(buf.len()); buf[..len].copy_from_slice(&bytes[..len]); @@ -259,3 +259,24 @@ impl SchemeSync for FirmwareScheme { } } } + +#[cfg(test)] +mod tests { + use super::resolve_key; + + #[test] + fn accepts_real_firmware_extensions() { + assert_eq!( + resolve_key("iwlwifi-bz-b0-gf-a0-92.ucode").as_deref(), + Some("iwlwifi-bz-b0-gf-a0-92.ucode") + ); + assert_eq!( + resolve_key("iwlwifi-bz-b0-gf-a0.pnvm").as_deref(), + Some("iwlwifi-bz-b0-gf-a0.pnvm") + ); + assert_eq!( + resolve_key("amdgpu/psp_13_0_0_sos.bin").as_deref(), + Some("amdgpu/psp_13_0_0_sos.bin") + ); + } +} diff --git a/local/recipes/system/iommu/source/src/amd_vi.rs b/local/recipes/system/iommu/source/src/amd_vi.rs index 86cc2833..63328bd2 100644 --- a/local/recipes/system/iommu/source/src/amd_vi.rs +++ b/local/recipes/system/iommu/source/src/amd_vi.rs @@ -92,15 +92,6 @@ impl AmdViUnit { })?; self.mmio = Some(MmioMapping { region }); - let control_initial = self.mmio_read32(offsets::CONTROL)?; - let status_initial = self.mmio_read32(offsets::STATUS)?; - info!( - "amd-vi: unit {} initial control={:#x} status={:#x}", - self.info.unit_id(), - control_initial, - status_initial - ); - self.disable_unit()?; let device_table = DeviceTable::new().map_err(|err| err.to_string())?; @@ -126,12 +117,6 @@ impl AmdViUnit { if ext & ext_feature::NX_SUP != 0 { control_value |= control::NX_EN; } - let control_before = self.mmio_read32(offsets::CONTROL)?; - info!( - "amd-vi: unit {} control register before enable write = {:#x}", - self.info.unit_id(), - control_before - ); self.mmio_write32(offsets::CONTROL, control_value)?; self.mmio_write32(offsets::CONTROL, control_value | control::IOMMU_ENABLE)?; @@ -218,8 +203,10 @@ impl AmdViUnit { } fn wait_for_running(&self, expected: bool) -> Result<(), String> { + let expected_mask = status::CMDBUF_RUN | status::EVT_RUN; for _ in 0..100_000 { - let running = self.mmio_read32(offsets::STATUS)? & status::IOMMU_RUNNING != 0; + let status = self.mmio_read32(offsets::STATUS)?; + let running = (status & expected_mask) == expected_mask; if running == expected { return Ok(()); } @@ -227,8 +214,10 @@ impl AmdViUnit { } Err(format!( - "timed out waiting for AMD-Vi unit {} running={expected}", - self.info.unit_id() + "timed out waiting for AMD-Vi unit {} running={expected} (status={:#x}, expected_mask={:#x})", + self.info.unit_id(), + self.mmio_read32(offsets::STATUS).unwrap_or_default(), + expected_mask, )) } diff --git a/local/recipes/system/iommu/source/src/device_table.rs b/local/recipes/system/iommu/source/src/device_table.rs index 9e1d0db0..2df922b4 100644 --- a/local/recipes/system/iommu/source/src/device_table.rs +++ b/local/recipes/system/iommu/source/src/device_table.rs @@ -7,7 +7,7 @@ use redox_driver_sys::dma::DmaBuffer; pub const DEVICE_TABLE_ENTRIES: usize = 65_536; pub const DTE_SIZE: usize = 32; -const DEVICE_TABLE_BYTES: usize = DEVICE_TABLE_ENTRIES * DTE_SIZE; +pub const DEVICE_TABLE_BYTES: usize = DEVICE_TABLE_ENTRIES * DTE_SIZE; const DTE_VALID_BIT: u64 = 1 << 0; const DTE_TRANSLATION_VALID_BIT: u64 = 1 << 1; diff --git a/local/recipes/system/iommu/source/src/mmio.rs b/local/recipes/system/iommu/source/src/mmio.rs index 01e91a17..4ee629df 100644 --- a/local/recipes/system/iommu/source/src/mmio.rs +++ b/local/recipes/system/iommu/source/src/mmio.rs @@ -27,7 +27,7 @@ pub mod control { pub const EVENT_LOG_EN: u32 = 1 << 2; pub const EVENT_INT_EN: u32 = 1 << 3; pub const COM_WAIT_INT_EN: u32 = 1 << 4; - pub const CMD_BUF_EN: u32 = 1 << 5; + pub const CMD_BUF_EN: u32 = 1 << 12; pub const PPR_LOG_EN: u32 = 1 << 6; pub const PPR_INT_EN: u32 = 1 << 7; pub const PPR_EN: u32 = 1 << 8; @@ -46,12 +46,14 @@ pub mod control { } pub mod status { - pub const IOMMU_RUNNING: u32 = 1 << 0; - pub const EVENT_OVERFLOW: u32 = 1 << 1; - pub const EVENT_LOG_INT: u32 = 1 << 2; - pub const COM_WAIT_INT: u32 = 1 << 3; - pub const PPR_OVERFLOW: u32 = 1 << 4; - pub const PPR_INT: u32 = 1 << 5; + pub const EVENT_OVERFLOW: u32 = 1 << 0; + pub const EVENT_LOG_INT: u32 = 1 << 1; + pub const COM_WAIT_INT: u32 = 1 << 2; + pub const EVT_RUN: u32 = 1 << 3; + pub const CMDBUF_RUN: u32 = 1 << 4; + pub const PPR_LOG_OVERFLOW: u32 = 1 << 5; + pub const PPR_LOG_INT: u32 = 1 << 6; + pub const PPR_RUN: u32 = 1 << 7; } pub mod ext_feature {