Advance firmware and IOMMU support
Red Bear OS Team
This commit is contained in:
@@ -6,6 +6,7 @@ use std::sync::{Arc, Mutex};
|
|||||||
use log::{info, warn};
|
use log::{info, warn};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
#[derive(Error, Debug)]
|
#[derive(Error, Debug)]
|
||||||
pub enum BlobError {
|
pub enum BlobError {
|
||||||
#[error("firmware directory not found: {0}")]
|
#[error("firmware directory not found: {0}")]
|
||||||
@@ -22,12 +23,14 @@ pub enum BlobError {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
pub struct FirmwareBlob {
|
pub struct FirmwareBlob {
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub path: PathBuf,
|
pub path: PathBuf,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
pub struct FirmwareRegistry {
|
pub struct FirmwareRegistry {
|
||||||
base_dir: PathBuf,
|
base_dir: PathBuf,
|
||||||
blobs: HashMap<String, FirmwareBlob>,
|
blobs: HashMap<String, FirmwareBlob>,
|
||||||
@@ -67,10 +70,12 @@ impl FirmwareRegistry {
|
|||||||
&self.base_dir
|
&self.base_dir
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
pub fn contains(&self, key: &str) -> bool {
|
pub fn contains(&self, key: &str) -> bool {
|
||||||
self.blobs.contains_key(key)
|
self.blobs.contains_key(key)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
pub fn load(&self, key: &str) -> Result<Arc<Vec<u8>>, BlobError> {
|
pub fn load(&self, key: &str) -> Result<Arc<Vec<u8>>, BlobError> {
|
||||||
{
|
{
|
||||||
let cache = self.cache.lock().map_err(|e| BlobError::ReadError {
|
let cache = self.cache.lock().map_err(|e| BlobError::ReadError {
|
||||||
@@ -115,10 +120,6 @@ impl FirmwareRegistry {
|
|||||||
self.blobs.len()
|
self.blobs.len()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_empty(&self) -> bool {
|
|
||||||
self.blobs.is_empty()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub fn list_keys(&self) -> Vec<&str> {
|
pub fn list_keys(&self) -> Vec<&str> {
|
||||||
self.blobs.keys().map(|s| s.as_str()).collect()
|
self.blobs.keys().map(|s| s.as_str()).collect()
|
||||||
@@ -162,12 +163,15 @@ fn discover_firmware(base_dir: &Path) -> Result<HashMap<String, FirmwareBlob>, B
|
|||||||
format!("{}/{}", prefix, file_name)
|
format!("{}/{}", prefix, file_name)
|
||||||
};
|
};
|
||||||
stack.push((path, new_prefix));
|
stack.push((path, new_prefix));
|
||||||
} else if metadata.is_file() && file_name.ends_with(".bin") {
|
} else if metadata.is_file() {
|
||||||
let stem = file_name.trim_end_matches(".bin");
|
if is_metadata_file(&file_name) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
let key = if prefix.is_empty() {
|
let key = if prefix.is_empty() {
|
||||||
stem.to_string()
|
file_name.to_string()
|
||||||
} else {
|
} else {
|
||||||
format!("{}/{}", prefix, stem)
|
format!("{}/{}", prefix, file_name)
|
||||||
};
|
};
|
||||||
|
|
||||||
blobs.insert(key.clone(), FirmwareBlob { name: key, path });
|
blobs.insert(key.clone(), FirmwareBlob { name: key, path });
|
||||||
@@ -177,3 +181,46 @@ fn discover_firmware(base_dir: &Path) -> Result<HashMap<String, FirmwareBlob>, B
|
|||||||
|
|
||||||
Ok(blobs)
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,14 +2,17 @@ mod blob;
|
|||||||
mod scheme;
|
mod scheme;
|
||||||
|
|
||||||
use std::env;
|
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::path::PathBuf;
|
||||||
use std::process;
|
use std::process;
|
||||||
|
|
||||||
use log::{error, info, LevelFilter, Metadata, Record};
|
use log::{error, info, LevelFilter, Metadata, Record};
|
||||||
|
#[cfg(target_os = "redox")]
|
||||||
use redox_scheme::{scheme::SchemeSync, SignalBehavior, Socket};
|
use redox_scheme::{scheme::SchemeSync, SignalBehavior, Socket};
|
||||||
|
|
||||||
use blob::FirmwareRegistry;
|
use blob::FirmwareRegistry;
|
||||||
|
#[cfg(target_os = "redox")]
|
||||||
use scheme::FirmwareScheme;
|
use scheme::FirmwareScheme;
|
||||||
|
|
||||||
struct StderrLogger {
|
struct StderrLogger {
|
||||||
@@ -38,9 +41,10 @@ fn init_logging(level: LevelFilter) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn default_firmware_dir() -> PathBuf {
|
fn default_firmware_dir() -> PathBuf {
|
||||||
PathBuf::from("/usr/firmware/")
|
PathBuf::from("/lib/firmware/")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "redox")]
|
||||||
unsafe fn get_init_notify_fd() -> RawFd {
|
unsafe fn get_init_notify_fd() -> RawFd {
|
||||||
let fd: RawFd = env::var("INIT_NOTIFY")
|
let fd: RawFd = env::var("INIT_NOTIFY")
|
||||||
.expect("firmware-loader: INIT_NOTIFY not set")
|
.expect("firmware-loader: INIT_NOTIFY not set")
|
||||||
@@ -50,6 +54,7 @@ unsafe fn get_init_notify_fd() -> RawFd {
|
|||||||
fd
|
fd
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "redox")]
|
||||||
fn notify_scheme_ready(notify_fd: RawFd, socket: &Socket, scheme: &mut FirmwareScheme) {
|
fn notify_scheme_ready(notify_fd: RawFd, socket: &Socket, scheme: &mut FirmwareScheme) {
|
||||||
let cap_id = scheme
|
let cap_id = scheme
|
||||||
.scheme_root()
|
.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");
|
.expect("firmware-loader: failed to notify init that scheme is ready");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "redox")]
|
||||||
fn run_daemon(notify_fd: RawFd, registry: FirmwareRegistry) -> ! {
|
fn run_daemon(notify_fd: RawFd, registry: FirmwareRegistry) -> ! {
|
||||||
let socket = Socket::create().expect("firmware-loader: failed to create scheme socket");
|
let socket = Socket::create().expect("firmware-loader: failed to create scheme socket");
|
||||||
let mut scheme = FirmwareScheme::new(registry);
|
let mut scheme = FirmwareScheme::new(registry);
|
||||||
@@ -137,6 +143,25 @@ fn main() {
|
|||||||
firmware_dir.display()
|
firmware_dir.display()
|
||||||
);
|
);
|
||||||
|
|
||||||
let notify_fd = unsafe { get_init_notify_fd() };
|
if env::args().nth(1).as_deref() == Some("--probe") {
|
||||||
run_daemon(notify_fd, registry);
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,8 +10,10 @@ use syscall::{EventFlags, Stat, MODE_FILE};
|
|||||||
|
|
||||||
use crate::blob::FirmwareRegistry;
|
use crate::blob::FirmwareRegistry;
|
||||||
|
|
||||||
|
#[cfg_attr(not(target_os = "redox"), allow(dead_code))]
|
||||||
const SCHEME_ROOT_ID: usize = 1;
|
const SCHEME_ROOT_ID: usize = 1;
|
||||||
|
|
||||||
|
#[cfg_attr(not(target_os = "redox"), allow(dead_code))]
|
||||||
struct Handle {
|
struct Handle {
|
||||||
blob_key: String,
|
blob_key: String,
|
||||||
data: Arc<Vec<u8>>,
|
data: Arc<Vec<u8>>,
|
||||||
@@ -19,12 +21,14 @@ struct Handle {
|
|||||||
closed: bool,
|
closed: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg_attr(not(target_os = "redox"), allow(dead_code))]
|
||||||
pub struct FirmwareScheme {
|
pub struct FirmwareScheme {
|
||||||
registry: FirmwareRegistry,
|
registry: FirmwareRegistry,
|
||||||
next_id: usize,
|
next_id: usize,
|
||||||
handles: BTreeMap<usize, Handle>,
|
handles: BTreeMap<usize, Handle>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg_attr(not(target_os = "redox"), allow(dead_code))]
|
||||||
impl FirmwareScheme {
|
impl FirmwareScheme {
|
||||||
pub fn new(registry: FirmwareRegistry) -> Self {
|
pub fn new(registry: FirmwareRegistry) -> Self {
|
||||||
FirmwareScheme {
|
FirmwareScheme {
|
||||||
@@ -56,15 +60,11 @@ fn resolve_key(path: &str) -> Option<String> {
|
|||||||
);
|
);
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
let key = if cleaned.ends_with(".bin") {
|
let key = cleaned.to_string();
|
||||||
cleaned.trim_end_matches(".bin").to_string()
|
// Final sanity: key must be purely alphanumeric with /, -, _, .
|
||||||
} else {
|
|
||||||
cleaned.to_string()
|
|
||||||
};
|
|
||||||
// Final sanity: key must be purely alphanumeric with /, -, _
|
|
||||||
if !key
|
if !key
|
||||||
.chars()
|
.chars()
|
||||||
.all(|c| c.is_alphanumeric() || c == '/' || c == '-' || c == '_')
|
.all(|c| c.is_alphanumeric() || c == '/' || c == '-' || c == '_' || c == '.')
|
||||||
{
|
{
|
||||||
log::warn!(
|
log::warn!(
|
||||||
"firmware-loader: rejecting invalid characters in key: {:?}",
|
"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<usize> {
|
fn fpath(&mut self, id: usize, buf: &mut [u8], _ctx: &CallerCtx) -> Result<usize> {
|
||||||
let handle = self.handle(id)?;
|
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 bytes = path.as_bytes();
|
||||||
let len = bytes.len().min(buf.len());
|
let len = bytes.len().min(buf.len());
|
||||||
buf[..len].copy_from_slice(&bytes[..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")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -92,15 +92,6 @@ impl AmdViUnit {
|
|||||||
})?;
|
})?;
|
||||||
self.mmio = Some(MmioMapping { region });
|
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()?;
|
self.disable_unit()?;
|
||||||
|
|
||||||
let device_table = DeviceTable::new().map_err(|err| err.to_string())?;
|
let device_table = DeviceTable::new().map_err(|err| err.to_string())?;
|
||||||
@@ -126,12 +117,6 @@ impl AmdViUnit {
|
|||||||
if ext & ext_feature::NX_SUP != 0 {
|
if ext & ext_feature::NX_SUP != 0 {
|
||||||
control_value |= control::NX_EN;
|
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)?;
|
||||||
|
|
||||||
self.mmio_write32(offsets::CONTROL, control_value | control::IOMMU_ENABLE)?;
|
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> {
|
fn wait_for_running(&self, expected: bool) -> Result<(), String> {
|
||||||
|
let expected_mask = status::CMDBUF_RUN | status::EVT_RUN;
|
||||||
for _ in 0..100_000 {
|
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 {
|
if running == expected {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
@@ -227,8 +214,10 @@ impl AmdViUnit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Err(format!(
|
Err(format!(
|
||||||
"timed out waiting for AMD-Vi unit {} running={expected}",
|
"timed out waiting for AMD-Vi unit {} running={expected} (status={:#x}, expected_mask={:#x})",
|
||||||
self.info.unit_id()
|
self.info.unit_id(),
|
||||||
|
self.mmio_read32(offsets::STATUS).unwrap_or_default(),
|
||||||
|
expected_mask,
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ use redox_driver_sys::dma::DmaBuffer;
|
|||||||
pub const DEVICE_TABLE_ENTRIES: usize = 65_536;
|
pub const DEVICE_TABLE_ENTRIES: usize = 65_536;
|
||||||
pub const DTE_SIZE: usize = 32;
|
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_VALID_BIT: u64 = 1 << 0;
|
||||||
const DTE_TRANSLATION_VALID_BIT: u64 = 1 << 1;
|
const DTE_TRANSLATION_VALID_BIT: u64 = 1 << 1;
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ pub mod control {
|
|||||||
pub const EVENT_LOG_EN: u32 = 1 << 2;
|
pub const EVENT_LOG_EN: u32 = 1 << 2;
|
||||||
pub const EVENT_INT_EN: u32 = 1 << 3;
|
pub const EVENT_INT_EN: u32 = 1 << 3;
|
||||||
pub const COM_WAIT_INT_EN: u32 = 1 << 4;
|
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_LOG_EN: u32 = 1 << 6;
|
||||||
pub const PPR_INT_EN: u32 = 1 << 7;
|
pub const PPR_INT_EN: u32 = 1 << 7;
|
||||||
pub const PPR_EN: u32 = 1 << 8;
|
pub const PPR_EN: u32 = 1 << 8;
|
||||||
@@ -46,12 +46,14 @@ pub mod control {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub mod status {
|
pub mod status {
|
||||||
pub const IOMMU_RUNNING: u32 = 1 << 0;
|
pub const EVENT_OVERFLOW: u32 = 1 << 0;
|
||||||
pub const EVENT_OVERFLOW: u32 = 1 << 1;
|
pub const EVENT_LOG_INT: u32 = 1 << 1;
|
||||||
pub const EVENT_LOG_INT: u32 = 1 << 2;
|
pub const COM_WAIT_INT: u32 = 1 << 2;
|
||||||
pub const COM_WAIT_INT: u32 = 1 << 3;
|
pub const EVT_RUN: u32 = 1 << 3;
|
||||||
pub const PPR_OVERFLOW: u32 = 1 << 4;
|
pub const CMDBUF_RUN: u32 = 1 << 4;
|
||||||
pub const PPR_INT: u32 = 1 << 5;
|
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 {
|
pub mod ext_feature {
|
||||||
|
|||||||
Reference in New Issue
Block a user