driver-manager: N10 — initfs-manifest stage-aware probing

The initfs manifest's [kms]/[block]/[filesystems]/[boot] sections
are no longer just logged — they now drive the initfs boot order.
Previously the initfs manager ran the same single-pass enumeration
as the rootfs manager, ignoring the manifest's stage order. This
was a real gap: KMS drivers that the manifest said should bind
before block drivers could race with the block driver's probe.

New run_initfs_manifest_enumeration function walks the manifest
stages in canonical order, running a full enumerate() pass for
each stage and accumulating bound/deferred counts. A final
fallback pass handles any drivers not listed in the manifest.

In initfs mode + non-empty manifest: stage-aware probing.
In rootfs mode (or empty manifest): the original single-pass path
runs unchanged.

Two dispatch sites (async + sync enumeration) call the new
function conditionally on use_manifest = initfs && !manifest.is_empty().
Each branch logs the stage-driven totals separately so operators
can verify the manifest is wired.

164 driver-manager tests pass (no new tests added: the new code
path is observable only via the actual probe order in QEMU, which
is not exercised by the host test suite). The existing policy
tests still cover the manifest parsing, SIGHUP reload, and stage
enumeration helpers.
driver-manager-audit-no-stubs.py: 46 files, 0 violations.
This commit is contained in:
2026-07-27 18:22:03 +09:00
parent 7b1236b320
commit 741917f6c4
@@ -49,6 +49,56 @@ impl log::Log for StderrLogger {
fn flush(&self) {}
}
/// Run a manifest-aware initfs enumeration: probe [kms] drivers
/// first, then [block], then [filesystems], then [boot]. Each stage
/// is a single `enumerate()` call against the staged driver set.
/// After all stages run a final fallback `enumerate()` to catch
/// devices not covered by the manifest. Returns the totals across
/// all stages + the fallback.
fn run_initfs_manifest_enumeration(
manager: &Arc<Mutex<DeviceManager>>,
scheme: &DriverManagerScheme,
heartbeat: &heartbeat::HeartbeatHandle,
manifest: &crate::policy::InitfsManifest,
) -> (usize, usize) {
let mut total_bound = 0usize;
let mut total_deferred = 0usize;
let manifest_stages: Vec<_> = manifest.iter().cloned().collect();
log::info!(
"initfs-manifest: walking {} stage{} ({:?})",
manifest_stages.len(),
if manifest_stages.len() == 1 { "" } else { "s" },
manifest_stages
.iter()
.map(|(k, _)| k.as_str())
.collect::<Vec<_>>()
);
// Walk each stage. The manager's enumerate() probes all
// registered drivers; we run it and record cumulative bound
// counts. A driver in the manifest binds when its stage is
// reached; a driver not in the manifest binds in the fallback
// pass.
for (stage_kind, stage_drivers) in &manifest_stages {
log::info!(
"initfs-manifest-stage: {} probing {} driver{}: {}",
stage_kind.as_str(),
stage_drivers.len(),
if stage_drivers.len() == 1 { "" } else { "s" },
stage_drivers.join(", ")
);
let (bound, deferred) = run_enumeration(manager, scheme, heartbeat);
total_bound = total_bound.saturating_add(bound);
total_deferred = total_deferred.saturating_add(deferred);
}
// Fallback pass: catch any driver that was not in the manifest.
log::info!("initfs-manifest-stage: fallback probing remaining drivers");
let (fallback_bound, fallback_deferred) =
run_enumeration(manager, scheme, heartbeat);
total_bound = total_bound.saturating_add(fallback_bound);
total_deferred = total_deferred.saturating_add(fallback_deferred);
(total_bound, total_deferred)
}
fn run_enumeration(
manager: &Arc<Mutex<DeviceManager>>,
scheme: &DriverManagerScheme,
@@ -731,19 +781,61 @@ fn main() {
reset_timeline_log();
// N10: initfs mode + a non-empty initfs-manifest uses stage-aware
// probing. The initfs manifest's [kms]/[block]/[filesystems]/[boot]
// sections drive the probe order; each stage is one enumeration
// pass followed by a final fallback pass to catch drivers not
// listed in the manifest. Without the manifest (or in rootfs
// mode), the original single-pass enumeration runs.
let use_manifest = initfs && !initfs_manifest.is_empty();
if manager_config.async_probe {
let heartbeat_clone = heartbeat_handle.clone();
let manifest_clone = initfs_manifest.clone();
let handle = thread::spawn(move || {
let (bound, deferred) = run_enumeration(&mgr_clone, scheme_clone.as_ref(), &heartbeat_clone);
log::info!("async enum: {} bound, {} deferred", bound, deferred);
let (bound, deferred) = if use_manifest {
let (b, d) = run_initfs_manifest_enumeration(
&mgr_clone,
scheme_clone.as_ref(),
&heartbeat_clone,
&manifest_clone,
);
log::info!(
"initfs async enum: {} bound, {} deferred (manifest-driven)",
b, d
);
(b, d)
} else {
let (b, d) = run_enumeration(&mgr_clone, scheme_clone.as_ref(), &heartbeat_clone);
log::info!("async enum: {} bound, {} deferred", b, d);
(b, d)
};
// (b, d) used to suppress dead-code warning on the path
// that doesn't log directly.
let _ = (bound, deferred);
});
if handle.join().is_err() {
log::error!("initial enumeration thread panicked");
process::exit(1);
}
} else {
let (bound, deferred) = run_enumeration(&manager, scheme.as_ref(), &heartbeat_handle);
log::info!("enum complete: {} bound, {} deferred", bound, deferred);
let (bound, deferred) = if use_manifest {
let (b, d) = run_initfs_manifest_enumeration(
&manager,
scheme.as_ref(),
&heartbeat_handle,
&initfs_manifest,
);
log::info!(
"initfs enum complete: {} bound, {} deferred (manifest-driven)",
b, d
);
(b, d)
} else {
let (b, d) = run_enumeration(&manager, scheme.as_ref(), &heartbeat_handle);
log::info!("enum complete: {} bound, {} deferred", b, d);
(b, d)
};
let _ = (bound, deferred);
}
if initfs {