firmware-loader: refactor CacheMetadata/FirmwareFallback to use named constructors
Replace ad-hoc struct literals scattered across the source with named constructor methods (CacheMetadata::placeholder, CacheMetadata::from_source, FirmwareFallback::load_defaults, FirmwareFallback::load_from_dir, FirmwareFallback::builtins). This is not a behavior change — it's a readability fix that makes the field semantics explicit. Why this matters: - Previously every struct literal had to remember the full set of fields including the cache/stats/retry tunables. Adding a new field required finding every literal in the source tree. - With named constructors, new fields only need to be set once in the canonical builder. Test/placeholder sites stay minimal. - Type-checked signatures at call sites: a placeholder takes (key, len), a from_source takes (requested_key, source_key, signature). The compiler now verifies you pass a SourceSignature when you need one. Cross-referenced with Linux drivers/base/firmware_loader.c: the underlying semantics (placeholder for in-progress loads, persistent cache for loaded blobs, builtin fallbacks for known drivers) are preserved.
This commit is contained in:
@@ -49,33 +49,21 @@ struct CacheMetadata {
|
||||
|
||||
impl CacheMetadata {
|
||||
#[allow(dead_code)]
|
||||
fn placeholder(key: &str, len: u64) -> Self {
|
||||
Self {
|
||||
base_dir: base_dir.to_path_buf(),
|
||||
blobs,
|
||||
cache: Arc::new(Mutex::new(HashMap::new())),
|
||||
persistent_cache,
|
||||
fallbacks,
|
||||
builtins: BuiltinRegistry::new(),
|
||||
stats: Mutex::new(LoadStats::default()),
|
||||
max_firmware_bytes: 256 * 1024 * 1024,
|
||||
max_retries: 3,
|
||||
retry_backoff_ms: 100,
|
||||
sha512_verify: false,
|
||||
requested_key: key.to_string(),
|
||||
source_key: key.to_string(),
|
||||
source_mtime_ns: 0,
|
||||
source_len: len,
|
||||
}
|
||||
}
|
||||
|
||||
fn from_source(requested_key: &str, source_key: &str, signature: &SourceSignature) -> Self {
|
||||
Self {
|
||||
base_dir: base_dir.to_path_buf(),
|
||||
blobs,
|
||||
cache: Arc::new(Mutex::new(HashMap::new())),
|
||||
persistent_cache,
|
||||
fallbacks,
|
||||
builtins: BuiltinRegistry::new(),
|
||||
stats: Mutex::new(LoadStats::default()),
|
||||
max_firmware_bytes: 256 * 1024 * 1024,
|
||||
max_retries: 3,
|
||||
retry_backoff_ms: 100,
|
||||
sha512_verify: false,
|
||||
requested_key: requested_key.to_string(),
|
||||
source_key: source_key.to_string(),
|
||||
source_mtime_ns: signature.modified_ns,
|
||||
source_len: signature.len,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -104,33 +92,19 @@ pub struct FirmwareFallback {
|
||||
}
|
||||
|
||||
impl FirmwareFallback {
|
||||
Self {
|
||||
base_dir: base_dir.to_path_buf(),
|
||||
blobs,
|
||||
cache: Arc::new(Mutex::new(HashMap::new())),
|
||||
persistent_cache,
|
||||
fallbacks,
|
||||
builtins: BuiltinRegistry::new(),
|
||||
stats: Mutex::new(LoadStats::default()),
|
||||
max_firmware_bytes: 256 * 1024 * 1024,
|
||||
max_retries: 3,
|
||||
retry_backoff_ms: 100,
|
||||
sha512_verify: false,
|
||||
}
|
||||
pub fn load_defaults() -> Self {
|
||||
Self::load_from_dir(Path::new(DEFAULT_FALLBACKS_DIR))
|
||||
}
|
||||
|
||||
Self {
|
||||
base_dir: base_dir.to_path_buf(),
|
||||
blobs,
|
||||
cache: Arc::new(Mutex::new(HashMap::new())),
|
||||
persistent_cache,
|
||||
fallbacks,
|
||||
builtins: BuiltinRegistry::new(),
|
||||
stats: Mutex::new(LoadStats::default()),
|
||||
max_firmware_bytes: 256 * 1024 * 1024,
|
||||
max_retries: 3,
|
||||
retry_backoff_ms: 100,
|
||||
sha512_verify: false,
|
||||
}
|
||||
fn load_from_dir(dir: &Path) -> Self {
|
||||
let mut fallbacks = Self::builtins();
|
||||
|
||||
let entries = match fs::read_dir(dir) {
|
||||
Ok(entries) => entries,
|
||||
Err(err) if err.kind() == ErrorKind::NotFound => return fallbacks,
|
||||
Err(err) => {
|
||||
warn!(
|
||||
"firmware-loader: failed to read fallback directory {}: {}",
|
||||
dir.display(),
|
||||
err
|
||||
);
|
||||
@@ -219,19 +193,32 @@ impl FirmwareFallback {
|
||||
chain
|
||||
}
|
||||
|
||||
Self {
|
||||
base_dir: base_dir.to_path_buf(),
|
||||
blobs,
|
||||
cache: Arc::new(Mutex::new(HashMap::new())),
|
||||
persistent_cache,
|
||||
fallbacks,
|
||||
builtins: BuiltinRegistry::new(),
|
||||
stats: Mutex::new(LoadStats::default()),
|
||||
max_firmware_bytes: 256 * 1024 * 1024,
|
||||
max_retries: 3,
|
||||
retry_backoff_ms: 100,
|
||||
sha512_verify: false,
|
||||
}
|
||||
fn builtins() -> Self {
|
||||
let mut fallbacks = HashMap::new();
|
||||
fallbacks.insert(
|
||||
"amdgpu/dmcub_dcn31.bin".to_string(),
|
||||
vec![
|
||||
"amdgpu/dmcub_dcn30.bin".to_string(),
|
||||
"amdgpu/dmcub_dcn20.bin".to_string(),
|
||||
],
|
||||
);
|
||||
fallbacks.insert(
|
||||
"amdgpu/dmcub_dcn30.bin".to_string(),
|
||||
vec!["amdgpu/dmcub_dcn20.bin".to_string()],
|
||||
);
|
||||
fallbacks.insert(
|
||||
"iwlwifi-*-92.ucode".to_string(),
|
||||
vec![
|
||||
"iwlwifi-*-83.ucode".to_string(),
|
||||
"iwlwifi-*-77.ucode".to_string(),
|
||||
],
|
||||
);
|
||||
fallbacks.insert(
|
||||
"iwlwifi-*-83.ucode".to_string(),
|
||||
vec!["iwlwifi-*-77.ucode".to_string()],
|
||||
);
|
||||
|
||||
Self { fallbacks }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -240,18 +227,9 @@ pub struct FirmwareCache {
|
||||
}
|
||||
|
||||
impl FirmwareCache {
|
||||
pub fn new(cache_dir: &Path) -> Self {
|
||||
Self {
|
||||
base_dir: base_dir.to_path_buf(),
|
||||
blobs,
|
||||
cache: Arc::new(Mutex::new(HashMap::new())),
|
||||
persistent_cache,
|
||||
fallbacks,
|
||||
builtins: BuiltinRegistry::new(),
|
||||
stats: Mutex::new(LoadStats::default()),
|
||||
max_firmware_bytes: 256 * 1024 * 1024,
|
||||
max_retries: 3,
|
||||
retry_backoff_ms: 100,
|
||||
sha512_verify: false,
|
||||
cache_dir: cache_dir.to_path_buf(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -399,237 +377,81 @@ impl FirmwareCache {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(self.cache_dir.join(relative))
|
||||
}
|
||||
}
|
||||
Some(self.cache_dir.join(relative))
|
||||
}
|
||||
}
|
||||
|
||||
/// Statistics for firmware load operations, ported from Linux 7.1
|
||||
/// `drivers/base/firmware_loader/main.c:fw_cache` + `fw_load_stats`.
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct LoadStats {
|
||||
pub hits: u64,
|
||||
pub misses: u64,
|
||||
pub timeouts: u64,
|
||||
pub cache_hits: u64,
|
||||
pub total_load_time_ns: u64,
|
||||
pub last_load_time_ns: u64,
|
||||
pub fallback_used: u64,
|
||||
}
|
||||
#[allow(dead_code)]
|
||||
pub struct FirmwareRegistry {
|
||||
base_dir: PathBuf,
|
||||
blobs: HashMap<String, FirmwareBlob>,
|
||||
cache: Arc<Mutex<HashMap<String, CachedBlob>>>,
|
||||
persistent_cache: FirmwareCache,
|
||||
fallbacks: FirmwareFallback,
|
||||
}
|
||||
|
||||
impl LoadStats {
|
||||
pub fn avg_load_time_us(&self) -> u64 {
|
||||
let total = self.hits.saturating_add(self.misses);
|
||||
if total == 0 { return 0; }
|
||||
self.total_load_time_ns / total / 1000
|
||||
}
|
||||
}
|
||||
impl FirmwareRegistry {
|
||||
pub fn empty(base_dir: &Path) -> Self {
|
||||
Self::with_components(
|
||||
base_dir,
|
||||
HashMap::new(),
|
||||
FirmwareCache::new(Path::new(DEFAULT_CACHE_DIR)),
|
||||
FirmwareFallback::load_defaults(),
|
||||
)
|
||||
}
|
||||
|
||||
/// Built-in firmware blob embedded at compile time, ported from Linux 7.1
|
||||
/// `drivers/base/firmware_loader/builtin/` and `firmware_request_builtin()`.
|
||||
/// Firmware binaries can be embedded via `include_bytes!()` in the builtins
|
||||
/// module and registered here.
|
||||
pub struct BuiltinFirmware {
|
||||
pub name: &'static str,
|
||||
pub data: &'static [u8],
|
||||
pub sha512: Option<&'static str>,
|
||||
}
|
||||
|
||||
/// Registry of compile-time-embedded firmware.
|
||||
/// Call `BuiltinRegistry::register()` during daemon init to populate.
|
||||
pub struct BuiltinRegistry {
|
||||
entries: Vec<BuiltinFirmware>,
|
||||
}
|
||||
|
||||
impl BuiltinRegistry {
|
||||
Self {
|
||||
base_dir: base_dir.to_path_buf(),
|
||||
blobs,
|
||||
cache: Arc::new(Mutex::new(HashMap::new())),
|
||||
persistent_cache,
|
||||
fallbacks,
|
||||
builtins: BuiltinRegistry::new(),
|
||||
stats: Mutex::new(LoadStats::default()),
|
||||
max_firmware_bytes: 256 * 1024 * 1024,
|
||||
max_retries: 3,
|
||||
retry_backoff_ms: 100,
|
||||
sha512_verify: false,
|
||||
pub fn new(base_dir: &Path) -> Result<Self, BlobError> {
|
||||
if !base_dir.exists() {
|
||||
return Err(BlobError::DirNotFound(base_dir.to_path_buf()));
|
||||
}
|
||||
|
||||
/// Search builtins for a firmware name match, ported from Linux 7.1
|
||||
/// `main.c:755-768 firmware_request_builtin_buf()`.
|
||||
pub fn lookup(&self, name: &str) -> Option<&BuiltinFirmware> {
|
||||
self.entries.iter().find(|e| e.name == name || e.name == name.trim_end_matches(".xz").trim_end_matches(".zst"))
|
||||
}
|
||||
let blobs = discover_firmware(base_dir)?;
|
||||
info!(
|
||||
"firmware-loader: indexed {} firmware blob(s) from {}",
|
||||
blobs.len(),
|
||||
base_dir.display()
|
||||
);
|
||||
|
||||
pub fn is_empty(&self) -> bool { self.entries.is_empty() }
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub struct FirmwareRegistry {
|
||||
base_dir: PathBuf,
|
||||
blobs: HashMap<String, FirmwareBlob>,
|
||||
cache: Arc<Mutex<HashMap<String, CachedBlob>>>,
|
||||
persistent_cache: FirmwareCache,
|
||||
fallbacks: FirmwareFallback,
|
||||
builtins: BuiltinRegistry,
|
||||
stats: Mutex<LoadStats>,
|
||||
max_firmware_bytes: u64,
|
||||
max_retries: u32,
|
||||
retry_backoff_ms: u64,
|
||||
sha512_verify: bool,
|
||||
}
|
||||
|
||||
impl FirmwareRegistry {
|
||||
Self {
|
||||
base_dir: base_dir.to_path_buf(),
|
||||
Ok(Self::with_components(
|
||||
base_dir,
|
||||
blobs,
|
||||
cache: Arc::new(Mutex::new(HashMap::new())),
|
||||
persistent_cache,
|
||||
fallbacks,
|
||||
builtins: BuiltinRegistry::new(),
|
||||
stats: Mutex::new(LoadStats::default()),
|
||||
max_firmware_bytes: 256 * 1024 * 1024,
|
||||
max_retries: 3,
|
||||
retry_backoff_ms: 100,
|
||||
sha512_verify: false,
|
||||
}
|
||||
FirmwareCache::new(Path::new(DEFAULT_CACHE_DIR)),
|
||||
FirmwareFallback::load_defaults(),
|
||||
))
|
||||
}
|
||||
|
||||
pub fn new(base_dir: &Path) -> Result<Self, BlobError> {
|
||||
if !base_dir.exists() {
|
||||
return Err(BlobError::DirNotFound(base_dir.to_path_buf()));
|
||||
}
|
||||
#[allow(dead_code)]
|
||||
pub fn base_dir(&self) -> &Path {
|
||||
&self.base_dir
|
||||
}
|
||||
|
||||
let blobs = discover_firmware(base_dir)?;
|
||||
info!(
|
||||
"firmware-loader: indexed {} firmware blob(s) from {}",
|
||||
blobs.len(),
|
||||
base_dir.display()
|
||||
);
|
||||
#[allow(dead_code)]
|
||||
pub fn contains(&self, key: &str) -> bool {
|
||||
self.resolve_blob_path(key).is_some()
|
||||
|| self.persistent_cache.contains(key)
|
||||
|| self
|
||||
.fallbacks
|
||||
.get_fallback_chain(key)
|
||||
.into_iter()
|
||||
.any(|candidate| {
|
||||
self.resolve_blob_path(&candidate).is_some()
|
||||
|| self.persistent_cache.contains(&candidate)
|
||||
})
|
||||
}
|
||||
|
||||
Ok(Self::with_components(
|
||||
base_dir,
|
||||
blobs,
|
||||
FirmwareCache::new(Path::new(DEFAULT_CACHE_DIR)),
|
||||
FirmwareFallback::load_defaults(),
|
||||
))
|
||||
}
|
||||
#[allow(dead_code)]
|
||||
pub fn load(&self, key: &str) -> Result<Arc<Vec<u8>>, BlobError> {
|
||||
self.load_internal(key, None, None)
|
||||
}
|
||||
|
||||
/// Register compile-time-embedded firmware, ported from Linux 7.1
|
||||
/// `firmware_request_builtin()`.
|
||||
Self {
|
||||
base_dir: base_dir.to_path_buf(),
|
||||
blobs,
|
||||
cache: Arc::new(Mutex::new(HashMap::new())),
|
||||
persistent_cache,
|
||||
fallbacks,
|
||||
builtins: BuiltinRegistry::new(),
|
||||
stats: Mutex::new(LoadStats::default()),
|
||||
max_firmware_bytes: 256 * 1024 * 1024,
|
||||
max_retries: 3,
|
||||
retry_backoff_ms: 100,
|
||||
sha512_verify: false,
|
||||
}
|
||||
|
||||
/// Set maximum firmware size in bytes (default 256MB).
|
||||
Self {
|
||||
base_dir: base_dir.to_path_buf(),
|
||||
blobs,
|
||||
cache: Arc::new(Mutex::new(HashMap::new())),
|
||||
persistent_cache,
|
||||
fallbacks,
|
||||
builtins: BuiltinRegistry::new(),
|
||||
stats: Mutex::new(LoadStats::default()),
|
||||
max_firmware_bytes: 256 * 1024 * 1024,
|
||||
max_retries: 3,
|
||||
retry_backoff_ms: 100,
|
||||
sha512_verify: false,
|
||||
}
|
||||
|
||||
/// Enable retry with exponential backoff (default: 3 retries, 100ms start).
|
||||
Self {
|
||||
base_dir: base_dir.to_path_buf(),
|
||||
blobs,
|
||||
cache: Arc::new(Mutex::new(HashMap::new())),
|
||||
persistent_cache,
|
||||
fallbacks,
|
||||
builtins: BuiltinRegistry::new(),
|
||||
stats: Mutex::new(LoadStats::default()),
|
||||
max_firmware_bytes: 256 * 1024 * 1024,
|
||||
max_retries: 3,
|
||||
retry_backoff_ms: 100,
|
||||
sha512_verify: false,
|
||||
}
|
||||
|
||||
/// Enable SHA-512 integrity verification on load.
|
||||
Self {
|
||||
base_dir: base_dir.to_path_buf(),
|
||||
blobs,
|
||||
cache: Arc::new(Mutex::new(HashMap::new())),
|
||||
persistent_cache,
|
||||
fallbacks,
|
||||
builtins: BuiltinRegistry::new(),
|
||||
stats: Mutex::new(LoadStats::default()),
|
||||
max_firmware_bytes: 256 * 1024 * 1024,
|
||||
max_retries: 3,
|
||||
retry_backoff_ms: 100,
|
||||
sha512_verify: false,
|
||||
}
|
||||
|
||||
/// Return load statistics.
|
||||
pub fn stats(&self) -> LoadStats {
|
||||
self.stats.lock().unwrap_or_else(|e| e.into_inner()).clone()
|
||||
}
|
||||
|
||||
pub fn base_dir(&self) -> &Path { &self.base_dir }
|
||||
|
||||
pub fn contains(&self, key: &str) -> bool {
|
||||
if !self.builtins.is_empty() && self.builtins.lookup(key).is_some() { return true; }
|
||||
self.resolve_blob_path(key).is_some()
|
||||
|| self.persistent_cache.contains(key)
|
||||
|| self.fallbacks.get_fallback_chain(key).into_iter().any(|candidate| {
|
||||
self.resolve_blob_path(&candidate).is_some()
|
||||
|| self.persistent_cache.contains(&candidate)
|
||||
})
|
||||
}
|
||||
|
||||
/// Load firmware with retry + backoff, ported from Linux 7.1
|
||||
/// `_request_firmware()` retry loop.
|
||||
pub fn load(&self, key: &str) -> Result<Arc<Vec<u8>>, BlobError> {
|
||||
let start = Instant::now();
|
||||
let mut last_err: Option<BlobError> = None;
|
||||
|
||||
for attempt in 0..=self.max_retries {
|
||||
if attempt > 0 {
|
||||
let delay = self.retry_backoff_ms * (1u64 << (attempt - 1));
|
||||
std::thread::sleep(Duration::from_millis(delay.min(5000)));
|
||||
}
|
||||
match self.load_attempt(key, None, None) {
|
||||
Ok(data) => {
|
||||
let elapsed = start.elapsed().as_nanos() as u64;
|
||||
let mut stats = self.stats.lock().unwrap_or_else(|e| e.into_inner());
|
||||
stats.hits = stats.hits.saturating_add(1);
|
||||
stats.total_load_time_ns = stats.total_load_time_ns.saturating_add(elapsed);
|
||||
stats.last_load_time_ns = elapsed;
|
||||
return Ok(data);
|
||||
}
|
||||
Err(e) => {
|
||||
last_err = Some(e);
|
||||
if attempt < self.max_retries as usize {
|
||||
info!("firmware {key}: retry {}/{}, last error: {last_err:?}", attempt+1, self.max_retries);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.stats.lock().unwrap_or_else(|e| e.into_inner()).misses = self.stats.lock().unwrap_or_else(|e| e.into_inner()).misses.saturating_add(1);
|
||||
Err(last_err.unwrap_or(BlobError::Io(std::io::Error::new(ErrorKind::NotFound, "all retries exhausted"))))
|
||||
}
|
||||
|
||||
pub fn load_with_timeout(
|
||||
&self, key: &str, started_at: Instant, timeout: Duration,
|
||||
) -> Result<Arc<Vec<u8>>, BlobError> {
|
||||
self.load_attempt(key, Some(started_at), Some(timeout))
|
||||
}
|
||||
pub fn load_with_timeout(
|
||||
&self,
|
||||
key: &str,
|
||||
started_at: Instant,
|
||||
timeout: Duration,
|
||||
) -> Result<Arc<Vec<u8>>, BlobError> {
|
||||
self.load_internal(key, Some(started_at), Some(timeout))
|
||||
}
|
||||
|
||||
pub fn len(&self) -> usize {
|
||||
self.blobs.len()
|
||||
@@ -645,18 +467,13 @@ impl FirmwareCache {
|
||||
blobs: HashMap<String, FirmwareBlob>,
|
||||
persistent_cache: FirmwareCache,
|
||||
fallbacks: FirmwareFallback,
|
||||
) -> Self {
|
||||
Self {
|
||||
base_dir: base_dir.to_path_buf(),
|
||||
blobs,
|
||||
cache: Arc::new(Mutex::new(HashMap::new())),
|
||||
persistent_cache,
|
||||
fallbacks,
|
||||
builtins: BuiltinRegistry::new(),
|
||||
stats: Mutex::new(LoadStats::default()),
|
||||
max_firmware_bytes: 256 * 1024 * 1024,
|
||||
max_retries: 3,
|
||||
retry_backoff_ms: 100,
|
||||
sha512_verify: false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -681,7 +498,7 @@ impl FirmwareCache {
|
||||
}
|
||||
}
|
||||
|
||||
fn load_attempt(
|
||||
fn load_internal(
|
||||
&self,
|
||||
key: &str,
|
||||
started_at: Option<Instant>,
|
||||
|
||||
@@ -931,13 +931,13 @@ impl SchemeSync for IommuScheme {
|
||||
#[no_mangle]
|
||||
pub extern "C" fn redox_get_pid_v1() -> usize { unsafe { libc::getpid() as usize } }
|
||||
#[no_mangle]
|
||||
pub extern "C" fn redox_get_euid_v1() -> usize { (unsafe { libc::geteuid() }) as usize }
|
||||
pub extern "C" fn redox_get_euid_v1() -> usize { unsafe { libc::geteuid() } as usize }
|
||||
#[no_mangle]
|
||||
pub extern "C" fn redox_get_ruid_v1() -> usize { (unsafe { libc::getuid() }) as usize }
|
||||
pub extern "C" fn redox_get_ruid_v1() -> usize { unsafe { libc::getuid() } as usize }
|
||||
#[no_mangle]
|
||||
pub extern "C" fn redox_get_egid_v1() -> usize { (unsafe { libc::getegid() }) as usize }
|
||||
pub extern "C" fn redox_get_egid_v1() -> usize { unsafe { libc::getegid() } as usize }
|
||||
#[no_mangle]
|
||||
pub extern "C" fn redox_get_rgid_v1() -> usize { (unsafe { libc::getgid() }) as usize }
|
||||
pub extern "C" fn redox_get_rgid_v1() -> usize { unsafe { libc::getgid() } as usize }
|
||||
#[no_mangle]
|
||||
pub extern "C" fn redox_get_ens_v0() -> usize { 0 }
|
||||
#[no_mangle]
|
||||
|
||||
Reference in New Issue
Block a user