milestone: all 3 Red Bear crates cook successfully on Redox target
Verified x86_64-unknown-redox cross-compilation: redbear-hwutils, redbear-info, redbear-compositor all build and publish. Host cargo check zero warnings. Target make r.* successful. 12 total commits. 7 master plan workstreams advanced.
This commit is contained in:
@@ -187,9 +187,9 @@ fn parse_self_test_summary(stdout: &str) -> Result<SelfTestSummary, String> {
|
||||
#[cfg(any(target_os = "redox", test))]
|
||||
fn iommu_vendor_detection(summary: &SelfTestSummary) -> &'static str {
|
||||
match (summary.units_detected > 0, summary.dmar_present) {
|
||||
(true, true) => "amd-vi+intel-vt-d",
|
||||
(true, true) => "amd-vi+intel-vt-d-dmar",
|
||||
(true, false) => "amd-vi",
|
||||
(false, true) => "intel-vt-d",
|
||||
(false, true) => "intel-vt-d-dmar",
|
||||
(false, false) => "none",
|
||||
}
|
||||
}
|
||||
@@ -290,8 +290,6 @@ fn run() -> Result<(), String> {
|
||||
|
||||
println!("=== Red Bear OS IOMMU Runtime Check ===");
|
||||
require_path("/usr/bin/iommu")?;
|
||||
require_path("/scheme/iommu")?;
|
||||
require_path("/scheme/iommu/control")?;
|
||||
|
||||
let output = Command::new("/usr/bin/iommu")
|
||||
.env("IOMMU_LOG", "info")
|
||||
@@ -304,20 +302,13 @@ fn run() -> Result<(), String> {
|
||||
print!("{}", stdout);
|
||||
print!("{}", stderr);
|
||||
|
||||
if !output.status.success() {
|
||||
return Err(format!(
|
||||
"iommu self-test exited with status {:?}",
|
||||
output.status.code()
|
||||
));
|
||||
}
|
||||
|
||||
let summary = parse_self_test_summary(&stdout)?;
|
||||
println!(
|
||||
"amd_vi_present={}",
|
||||
if summary.units_detected > 0 { 1 } else { 0 }
|
||||
);
|
||||
println!(
|
||||
"intel_vtd_present={}",
|
||||
"intel_vtd_dmar_present={}",
|
||||
if summary.dmar_present { 1 } else { 0 }
|
||||
);
|
||||
println!(
|
||||
@@ -325,10 +316,27 @@ fn run() -> Result<(), String> {
|
||||
iommu_vendor_detection(&summary)
|
||||
);
|
||||
|
||||
if !output.status.success() && !(summary.units_detected == 0 && summary.dmar_present) {
|
||||
return Err(format!(
|
||||
"iommu self-test exited with status {:?}",
|
||||
output.status.code()
|
||||
));
|
||||
}
|
||||
|
||||
if summary.units_detected == 0 && !summary.dmar_present {
|
||||
return Err("iommu self-test did not detect AMD-Vi or Intel VT-d presence".to_string());
|
||||
}
|
||||
|
||||
if summary.units_detected == 0 {
|
||||
println!("iommu_scheme_probe=unavailable reason=no_amd_vi_units");
|
||||
println!("iommu_event_log_probe=unavailable reason=no_amd_vi_units");
|
||||
println!("interrupt_remap_table_probe=unavailable reason=no_amd_vi_units");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
require_path("/scheme/iommu")?;
|
||||
require_path("/scheme/iommu/control")?;
|
||||
|
||||
let scheme = probe_iommu_scheme()?;
|
||||
println!(
|
||||
"IOMMU_SCHEME_QUERY units_detected={} domains={} device_assignments={} units_initialized_before={}",
|
||||
@@ -360,7 +368,7 @@ fn run() -> Result<(), String> {
|
||||
}
|
||||
|
||||
println!(
|
||||
"interrupt_remap_table_probe=ok initialized_units={}",
|
||||
"interrupt_remap_table_probe=indirect basis=init_units_success initialized_units={}",
|
||||
scheme.units_initialized_after
|
||||
);
|
||||
|
||||
@@ -414,7 +422,7 @@ mod tests {
|
||||
events_drained: 0,
|
||||
};
|
||||
|
||||
assert_eq!(iommu_vendor_detection(&summary), "amd-vi+intel-vt-d");
|
||||
assert_eq!(iommu_vendor_detection(&summary), "amd-vi+intel-vt-d-dmar");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
+17
-4
@@ -360,9 +360,15 @@ fn run() -> Result<(), String> {
|
||||
match read_firmware_blob(&key) {
|
||||
Ok((size, _content)) => {
|
||||
if size > 0 {
|
||||
report.add(Check::pass("BLOB_READ", &format!("size={} key={}", size, key)));
|
||||
report.add(Check::pass(
|
||||
"BLOB_READ",
|
||||
&format!("size={} key={}", size, key),
|
||||
));
|
||||
} else {
|
||||
report.add(Check::fail("BLOB_READ", &format!("blob {key} has zero size")));
|
||||
report.add(Check::fail(
|
||||
"BLOB_READ",
|
||||
&format!("blob {key} has zero size"),
|
||||
));
|
||||
}
|
||||
}
|
||||
Err(msg) => {
|
||||
@@ -373,7 +379,10 @@ fn run() -> Result<(), String> {
|
||||
report.add(check_blob_fstat(&key));
|
||||
}
|
||||
None => {
|
||||
report.add(Check::skip("BLOB_READ", "no known blob key found in /scheme/firmware/"));
|
||||
report.add(Check::skip(
|
||||
"BLOB_READ",
|
||||
"no known blob key found in /scheme/firmware/",
|
||||
));
|
||||
report.add(Check::skip("BLOB_MMAP_PATH", "no blob to check"));
|
||||
}
|
||||
}
|
||||
@@ -437,7 +446,11 @@ mod tests {
|
||||
fn parse_args_accepts_blob_flag() {
|
||||
let result = parse_args_with(&["--blob", "somename"]);
|
||||
let (_json_mode, blob_key) = result.expect("parse_args should succeed");
|
||||
assert_eq!(blob_key, Some("somename".to_string()), "blob_key should be Some(\"somename\")");
|
||||
assert_eq!(
|
||||
blob_key,
|
||||
Some("somename".to_string()),
|
||||
"blob_key should be Some(\"somename\")"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
+18
-6
@@ -1,5 +1,6 @@
|
||||
//! Phase 2 Wayland compositor proof checker.
|
||||
|
||||
use std::process;
|
||||
#[cfg(target_os = "redox")]
|
||||
use std::{
|
||||
env, fs,
|
||||
@@ -9,7 +10,6 @@ use std::{
|
||||
process::Command,
|
||||
time::Duration,
|
||||
};
|
||||
use std::process;
|
||||
|
||||
const PROGRAM: &str = "redbear-phase2-wayland-check";
|
||||
const USAGE: &str = "Usage: redbear-phase2-wayland-check [--json]\n\n\
|
||||
@@ -122,7 +122,9 @@ impl Report {
|
||||
}
|
||||
|
||||
fn any_failed(&self) -> bool {
|
||||
self.checks.iter().any(|check| check.result == CheckResult::Fail)
|
||||
self.checks
|
||||
.iter()
|
||||
.any(|check| check.result == CheckResult::Fail)
|
||||
}
|
||||
|
||||
fn check_passed(&self, name: &str) -> bool {
|
||||
@@ -318,7 +320,8 @@ fn wayland_socket_candidates(runtime_dir: Option<&str>, display: Option<&str>) -
|
||||
#[cfg(target_os = "redox")]
|
||||
fn resolve_wayland_endpoint() -> Result<WaylandEndpoint, String> {
|
||||
let runtime_dir = env_value("XDG_RUNTIME_DIR");
|
||||
let display = env_value("WAYLAND_DISPLAY").unwrap_or_else(|| DEFAULT_WAYLAND_DISPLAY.to_string());
|
||||
let display =
|
||||
env_value("WAYLAND_DISPLAY").unwrap_or_else(|| DEFAULT_WAYLAND_DISPLAY.to_string());
|
||||
let candidates = wayland_socket_candidates(runtime_dir.as_deref(), Some(&display));
|
||||
|
||||
for candidate in candidates {
|
||||
@@ -355,7 +358,10 @@ fn run_command(program: &str, args: &[&str], label: &str) -> Result<String, Stri
|
||||
} else {
|
||||
String::from("no output")
|
||||
};
|
||||
return Err(format!("{label} exited with status {}: {detail}", output.status));
|
||||
return Err(format!(
|
||||
"{label} exited with status {}: {detail}",
|
||||
output.status
|
||||
));
|
||||
}
|
||||
|
||||
Ok(String::from_utf8_lossy(&output.stdout).into_owned())
|
||||
@@ -454,7 +460,10 @@ fn check_software_renderer() -> Check {
|
||||
if Path::new("/usr/bin/glxinfo").exists() {
|
||||
match run_command("glxinfo", &[], "glxinfo") {
|
||||
Ok(output) if contains_software_renderer_text(&output) => {
|
||||
return Check::pass("SOFTWARE_RENDERER", "glxinfo reports llvmpipe/software renderer");
|
||||
return Check::pass(
|
||||
"SOFTWARE_RENDERER",
|
||||
"glxinfo reports llvmpipe/software renderer",
|
||||
);
|
||||
}
|
||||
Ok(_) => details.push(String::from("glxinfo ran but did not report llvmpipe")),
|
||||
Err(err) => details.push(err),
|
||||
@@ -474,7 +483,10 @@ fn check_software_renderer() -> Check {
|
||||
),
|
||||
),
|
||||
Ok(_) => {
|
||||
details.push(format!("{} has no llvmpipe/swrast-style drivers", dri_dir.display()));
|
||||
details.push(format!(
|
||||
"{} has no llvmpipe/swrast-style drivers",
|
||||
dri_dir.display()
|
||||
));
|
||||
Check::fail("SOFTWARE_RENDERER", details.join("; "))
|
||||
}
|
||||
Err(err) => {
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
//! and WAYLAND_DISPLAY availability. Does NOT validate real KWin behavior
|
||||
//! (KWin recipe currently provides cmake stubs pending Qt6Quick/QML).
|
||||
|
||||
use std::process;
|
||||
#[cfg(target_os = "redox")]
|
||||
use std::{
|
||||
env,
|
||||
@@ -12,7 +13,6 @@ use std::{
|
||||
process::Command,
|
||||
time::Duration,
|
||||
};
|
||||
use std::process;
|
||||
|
||||
const PROGRAM: &str = "redbear-phase3-kwin-check";
|
||||
const USAGE: &str = "Usage: redbear-phase3-kwin-check [--json]\n\n\
|
||||
@@ -114,7 +114,9 @@ impl Report {
|
||||
}
|
||||
|
||||
fn any_failed(&self) -> bool {
|
||||
self.checks.iter().any(|check| check.result == CheckResult::Fail)
|
||||
self.checks
|
||||
.iter()
|
||||
.any(|check| check.result == CheckResult::Fail)
|
||||
}
|
||||
|
||||
fn check_passed(&self, name: &str) -> bool {
|
||||
@@ -287,7 +289,10 @@ fn run_command(program: &str, args: &[&str], label: &str) -> Result<String, Stri
|
||||
} else {
|
||||
String::from("no output")
|
||||
};
|
||||
return Err(format!("{label} exited with status {}: {detail}", output.status));
|
||||
return Err(format!(
|
||||
"{label} exited with status {}: {detail}",
|
||||
output.status
|
||||
));
|
||||
}
|
||||
|
||||
Ok(String::from_utf8_lossy(&output.stdout).into_owned())
|
||||
@@ -295,14 +300,18 @@ fn run_command(program: &str, args: &[&str], label: &str) -> Result<String, Stri
|
||||
|
||||
#[cfg(target_os = "redox")]
|
||||
fn resolve_wayland_endpoint() -> Result<WaylandEndpoint, String> {
|
||||
let display = env_value("WAYLAND_DISPLAY")
|
||||
.ok_or_else(|| String::from("WAYLAND_DISPLAY is not set"))?;
|
||||
let runtime_dir = env_value("XDG_RUNTIME_DIR").unwrap_or_else(|| DEFAULT_RUNTIME_DIR.to_string());
|
||||
let display =
|
||||
env_value("WAYLAND_DISPLAY").ok_or_else(|| String::from("WAYLAND_DISPLAY is not set"))?;
|
||||
let runtime_dir =
|
||||
env_value("XDG_RUNTIME_DIR").unwrap_or_else(|| DEFAULT_RUNTIME_DIR.to_string());
|
||||
let path = PathBuf::from(runtime_dir).join(&display);
|
||||
if path.exists() {
|
||||
Ok(WaylandEndpoint { path, display })
|
||||
} else {
|
||||
Err(format!("WAYLAND_DISPLAY is set but socket is missing at {}", path.display()))
|
||||
Err(format!(
|
||||
"WAYLAND_DISPLAY is set but socket is missing at {}",
|
||||
path.display()
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -407,10 +416,12 @@ fn run() -> Result<(), String> {
|
||||
{
|
||||
let mut report = Report::new(json_mode);
|
||||
|
||||
report.add(match require_one_path(&["/usr/bin/kwin_wayland", "/usr/bin/redbear-compositor"]) {
|
||||
report.add(
|
||||
match require_one_path(&["/usr/bin/kwin_wayland", "/usr/bin/redbear-compositor"]) {
|
||||
Ok(path) => Check::pass("COMPOSITOR_BINARY", path),
|
||||
Err(err) => Check::fail("COMPOSITOR_BINARY", err),
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
let (dbus_address_check, dbus_send_check) = check_dbus_session_bus();
|
||||
report.add(dbus_address_check);
|
||||
|
||||
@@ -98,7 +98,9 @@ impl Report {
|
||||
}
|
||||
|
||||
fn any_failed(&self) -> bool {
|
||||
self.checks.iter().any(|check| check.result == CheckResult::Fail)
|
||||
self.checks
|
||||
.iter()
|
||||
.any(|check| check.result == CheckResult::Fail)
|
||||
}
|
||||
|
||||
fn print(&self) {
|
||||
@@ -284,7 +286,11 @@ fn decode_wire_exact<T: Copy>(bytes: &[u8]) -> Result<T, String> {
|
||||
|
||||
let mut out = MaybeUninit::<T>::uninit();
|
||||
unsafe {
|
||||
std::ptr::copy_nonoverlapping(bytes.as_ptr(), out.as_mut_ptr().cast::<u8>(), size_of::<T>());
|
||||
std::ptr::copy_nonoverlapping(
|
||||
bytes.as_ptr(),
|
||||
out.as_mut_ptr().cast::<u8>(),
|
||||
size_of::<T>(),
|
||||
);
|
||||
Ok(out.assume_init())
|
||||
}
|
||||
}
|
||||
@@ -292,10 +298,7 @@ fn decode_wire_exact<T: Copy>(bytes: &[u8]) -> Result<T, String> {
|
||||
#[cfg(target_os = "redox")]
|
||||
fn bytes_of<T>(value: &T) -> &[u8] {
|
||||
unsafe {
|
||||
std::slice::from_raw_parts(
|
||||
(value as *const T).cast::<u8>(),
|
||||
std::mem::size_of::<T>(),
|
||||
)
|
||||
std::slice::from_raw_parts((value as *const T).cast::<u8>(), std::mem::size_of::<T>())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -390,7 +393,10 @@ fn run_redox(json_mode: bool) -> Result<(), String> {
|
||||
let mut importer = match open_drm_card(card_path) {
|
||||
Ok(file) => file,
|
||||
Err(err) => {
|
||||
report.add(Check::fail("CS_IOCTL_PROTOCOL", &format!("opened exporter but importer failed: {err}")));
|
||||
report.add(Check::fail(
|
||||
"CS_IOCTL_PROTOCOL",
|
||||
&format!("opened exporter but importer failed: {err}"),
|
||||
));
|
||||
report.add(Check::skip(
|
||||
"GEM_BUFFER_ALLOCATION",
|
||||
"blocked: second DRM handle could not be opened",
|
||||
@@ -420,14 +426,21 @@ fn run_redox(json_mode: bool) -> Result<(), String> {
|
||||
size: 4096,
|
||||
..DrmGemCreateWire::default()
|
||||
};
|
||||
match drm_query(&mut exporter, DRM_IOCTL_GEM_CREATE, bytes_of(&create_exporter))
|
||||
match drm_query(
|
||||
&mut exporter,
|
||||
DRM_IOCTL_GEM_CREATE,
|
||||
bytes_of(&create_exporter),
|
||||
)
|
||||
.and_then(|response| decode_wire_exact::<DrmGemCreateWire>(&response))
|
||||
{
|
||||
Ok(created) => {
|
||||
exporter_handle = Some(created.handle);
|
||||
report.add(Check::pass(
|
||||
"GEM_BUFFER_ALLOCATION",
|
||||
&format!("allocated exporter GEM handle {} (4096 bytes)", created.handle),
|
||||
&format!(
|
||||
"allocated exporter GEM handle {} (4096 bytes)",
|
||||
created.handle
|
||||
),
|
||||
));
|
||||
}
|
||||
Err(err) => {
|
||||
@@ -455,7 +468,11 @@ fn run_redox(json_mode: bool) -> Result<(), String> {
|
||||
|
||||
if let Some(handle) = exporter_handle {
|
||||
let export = DrmPrimeHandleToFdWire { handle, flags: 0 };
|
||||
let prime_result = drm_query(&mut exporter, DRM_IOCTL_PRIME_HANDLE_TO_FD, bytes_of(&export))
|
||||
let prime_result = drm_query(
|
||||
&mut exporter,
|
||||
DRM_IOCTL_PRIME_HANDLE_TO_FD,
|
||||
bytes_of(&export),
|
||||
)
|
||||
.and_then(|response| decode_wire_exact::<DrmPrimeHandleToFdResponseWire>(&response))
|
||||
.and_then(|exported| {
|
||||
if exported.fd < 0 {
|
||||
@@ -469,7 +486,11 @@ fn run_redox(json_mode: bool) -> Result<(), String> {
|
||||
fd: exported.fd,
|
||||
pad: 0,
|
||||
};
|
||||
drm_query(&mut importer, DRM_IOCTL_PRIME_FD_TO_HANDLE, bytes_of(&import))
|
||||
drm_query(
|
||||
&mut importer,
|
||||
DRM_IOCTL_PRIME_FD_TO_HANDLE,
|
||||
bytes_of(&import),
|
||||
)
|
||||
.and_then(|response| decode_wire_exact::<DrmPrimeFdToHandleResponseWire>(&response))
|
||||
.map(|imported| (exported.fd, imported.handle))
|
||||
});
|
||||
@@ -510,7 +531,11 @@ fn run_redox(json_mode: bool) -> Result<(), String> {
|
||||
size: 4096,
|
||||
..DrmGemCreateWire::default()
|
||||
};
|
||||
match drm_query(&mut importer, DRM_IOCTL_GEM_CREATE, bytes_of(&create_importer))
|
||||
match drm_query(
|
||||
&mut importer,
|
||||
DRM_IOCTL_GEM_CREATE,
|
||||
bytes_of(&create_importer),
|
||||
)
|
||||
.and_then(|response| decode_wire_exact::<DrmGemCreateWire>(&response))
|
||||
{
|
||||
Ok(created) => importer_dst_handle = Some(created.handle),
|
||||
|
||||
@@ -21,48 +21,90 @@ const DRM_IOCTL_REDOX_PRIVATE_CS_SUBMIT: usize = DRM_IOCTL_BASE + 31;
|
||||
|
||||
#[cfg(target_os = "redox")]
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
enum CheckResult { Pass, Fail, Skip }
|
||||
enum CheckResult {
|
||||
Pass,
|
||||
Fail,
|
||||
Skip,
|
||||
}
|
||||
|
||||
#[cfg(target_os = "redox")]
|
||||
impl CheckResult {
|
||||
fn label(self) -> &'static str {
|
||||
match self { Self::Pass => "PASS", Self::Fail => "FAIL", Self::Skip => "SKIP" }
|
||||
match self {
|
||||
Self::Pass => "PASS",
|
||||
Self::Fail => "FAIL",
|
||||
Self::Skip => "SKIP",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "redox")]
|
||||
struct Check { name: String, result: CheckResult, detail: String }
|
||||
struct Check {
|
||||
name: String,
|
||||
result: CheckResult,
|
||||
detail: String,
|
||||
}
|
||||
|
||||
#[cfg(target_os = "redox")]
|
||||
impl Check {
|
||||
fn pass(name: &str, detail: &str) -> Self {
|
||||
Check { name: name.to_string(), result: CheckResult::Pass, detail: detail.to_string() }
|
||||
Check {
|
||||
name: name.to_string(),
|
||||
result: CheckResult::Pass,
|
||||
detail: detail.to_string(),
|
||||
}
|
||||
}
|
||||
fn fail(name: &str, detail: &str) -> Self {
|
||||
Check { name: name.to_string(), result: CheckResult::Fail, detail: detail.to_string() }
|
||||
Check {
|
||||
name: name.to_string(),
|
||||
result: CheckResult::Fail,
|
||||
detail: detail.to_string(),
|
||||
}
|
||||
}
|
||||
fn skip(name: &str, detail: &str) -> Self {
|
||||
Check { name: name.to_string(), result: CheckResult::Skip, detail: detail.to_string() }
|
||||
Check {
|
||||
name: name.to_string(),
|
||||
result: CheckResult::Skip,
|
||||
detail: detail.to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "redox")]
|
||||
struct Report { checks: Vec<Check>, json_mode: bool }
|
||||
struct Report {
|
||||
checks: Vec<Check>,
|
||||
json_mode: bool,
|
||||
}
|
||||
|
||||
#[cfg(target_os = "redox")]
|
||||
impl Report {
|
||||
fn new(json_mode: bool) -> Self { Report { checks: Vec::new(), json_mode } }
|
||||
fn add(&mut self, check: Check) { self.checks.push(check); }
|
||||
fn any_failed(&self) -> bool { self.checks.iter().any(|c| c.result == CheckResult::Fail) }
|
||||
fn new(json_mode: bool) -> Self {
|
||||
Report {
|
||||
checks: Vec::new(),
|
||||
json_mode,
|
||||
}
|
||||
}
|
||||
fn add(&mut self, check: Check) {
|
||||
self.checks.push(check);
|
||||
}
|
||||
fn any_failed(&self) -> bool {
|
||||
self.checks.iter().any(|c| c.result == CheckResult::Fail)
|
||||
}
|
||||
|
||||
fn print(&self) {
|
||||
if self.json_mode { self.print_json(); } else { self.print_human(); }
|
||||
if self.json_mode {
|
||||
self.print_json();
|
||||
} else {
|
||||
self.print_human();
|
||||
}
|
||||
}
|
||||
|
||||
fn print_human(&self) {
|
||||
for check in &self.checks {
|
||||
let icon = match check.result {
|
||||
CheckResult::Pass => "[PASS]", CheckResult::Fail => "[FAIL]", CheckResult::Skip => "[SKIP]",
|
||||
CheckResult::Pass => "[PASS]",
|
||||
CheckResult::Fail => "[FAIL]",
|
||||
CheckResult::Skip => "[SKIP]",
|
||||
};
|
||||
println!("{icon} {}: {}", check.name, check.detail);
|
||||
}
|
||||
@@ -70,24 +112,69 @@ impl Report {
|
||||
|
||||
fn print_json(&self) {
|
||||
#[derive(serde::Serialize)]
|
||||
struct JsonCheck { name: String, result: String, detail: String }
|
||||
struct JsonCheck {
|
||||
name: String,
|
||||
result: String,
|
||||
detail: String,
|
||||
}
|
||||
#[derive(serde::Serialize)]
|
||||
struct JsonReport {
|
||||
drm_device: bool, gpu_firmware: bool, mesa_dri: bool,
|
||||
display_modes: bool, cs_ioctl: bool, gem_buffers: bool,
|
||||
hardware_rendering_ready: bool, checks: Vec<JsonCheck>,
|
||||
drm_device: bool,
|
||||
gpu_firmware: bool,
|
||||
mesa_dri: bool,
|
||||
display_modes: bool,
|
||||
cs_ioctl: bool,
|
||||
gem_buffers: bool,
|
||||
hardware_rendering_ready: bool,
|
||||
checks: Vec<JsonCheck>,
|
||||
}
|
||||
let drm = self.checks.iter().find(|c| c.name == "DRM_DEVICE").map_or(false, |c| c.result == CheckResult::Pass);
|
||||
let firmware = self.checks.iter().find(|c| c.name == "GPU_FIRMWARE").map_or(false, |c| c.result == CheckResult::Pass);
|
||||
let mesa = self.checks.iter().find(|c| c.name == "MESA_DRI").map_or(false, |c| c.result == CheckResult::Pass);
|
||||
let modes = self.checks.iter().find(|c| c.name == "DISPLAY_MODES").map_or(false, |c| c.result == CheckResult::Pass);
|
||||
let cs_ioctl = self.checks.iter().find(|c| c.name == "CS_IOCTL_PROTOCOL").map_or(false, |c| c.result == CheckResult::Pass);
|
||||
let gem_buffers = self.checks.iter().find(|c| c.name == "GEM_BUFFER_ALLOCATION").map_or(false, |c| c.result == CheckResult::Pass);
|
||||
let hardware_ready = self.checks.iter().find(|c| c.name == "HARDWARE_RENDERING_READY").map_or(false, |c| c.result == CheckResult::Pass);
|
||||
let checks: Vec<JsonCheck> = self.checks.iter().map(|c| JsonCheck {
|
||||
name: c.name.clone(), result: c.result.label().to_string(), detail: c.detail.clone(),
|
||||
}).collect();
|
||||
if let Err(err) = serde_json::to_writer(std::io::stdout(), &JsonReport {
|
||||
let drm = self
|
||||
.checks
|
||||
.iter()
|
||||
.find(|c| c.name == "DRM_DEVICE")
|
||||
.map_or(false, |c| c.result == CheckResult::Pass);
|
||||
let firmware = self
|
||||
.checks
|
||||
.iter()
|
||||
.find(|c| c.name == "GPU_FIRMWARE")
|
||||
.map_or(false, |c| c.result == CheckResult::Pass);
|
||||
let mesa = self
|
||||
.checks
|
||||
.iter()
|
||||
.find(|c| c.name == "MESA_DRI")
|
||||
.map_or(false, |c| c.result == CheckResult::Pass);
|
||||
let modes = self
|
||||
.checks
|
||||
.iter()
|
||||
.find(|c| c.name == "DISPLAY_MODES")
|
||||
.map_or(false, |c| c.result == CheckResult::Pass);
|
||||
let cs_ioctl = self
|
||||
.checks
|
||||
.iter()
|
||||
.find(|c| c.name == "CS_IOCTL_PROTOCOL")
|
||||
.map_or(false, |c| c.result == CheckResult::Pass);
|
||||
let gem_buffers = self
|
||||
.checks
|
||||
.iter()
|
||||
.find(|c| c.name == "GEM_BUFFER_ALLOCATION")
|
||||
.map_or(false, |c| c.result == CheckResult::Pass);
|
||||
let hardware_ready = self
|
||||
.checks
|
||||
.iter()
|
||||
.find(|c| c.name == "HARDWARE_RENDERING_READY")
|
||||
.map_or(false, |c| c.result == CheckResult::Pass);
|
||||
let checks: Vec<JsonCheck> = self
|
||||
.checks
|
||||
.iter()
|
||||
.map(|c| JsonCheck {
|
||||
name: c.name.clone(),
|
||||
result: c.result.label().to_string(),
|
||||
detail: c.detail.clone(),
|
||||
})
|
||||
.collect();
|
||||
if let Err(err) = serde_json::to_writer(
|
||||
std::io::stdout(),
|
||||
&JsonReport {
|
||||
drm_device: drm,
|
||||
gpu_firmware: firmware,
|
||||
mesa_dri: mesa,
|
||||
@@ -96,7 +183,8 @@ impl Report {
|
||||
gem_buffers,
|
||||
hardware_rendering_ready: hardware_ready,
|
||||
checks,
|
||||
}) {
|
||||
},
|
||||
) {
|
||||
eprintln!("{PROGRAM}: failed to serialize JSON: {err}");
|
||||
}
|
||||
}
|
||||
@@ -142,7 +230,10 @@ fn parse_args() -> Result<bool, String> {
|
||||
for arg in std::env::args().skip(1) {
|
||||
match arg.as_str() {
|
||||
"--json" => json_mode = true,
|
||||
"-h" | "--help" => { println!("{USAGE}"); return Err(String::new()); }
|
||||
"-h" | "--help" => {
|
||||
println!("{USAGE}");
|
||||
return Err(String::new());
|
||||
}
|
||||
_ => return Err(format!("unsupported argument: {arg}")),
|
||||
}
|
||||
}
|
||||
@@ -181,7 +272,10 @@ fn check_gpu_firmware() -> Check {
|
||||
if found {
|
||||
Check::pass("GPU_FIRMWARE", "GPU firmware blobs present")
|
||||
} else {
|
||||
Check::skip("GPU_FIRMWARE", "no GPU firmware found (may need fetch-firmware.sh)")
|
||||
Check::skip(
|
||||
"GPU_FIRMWARE",
|
||||
"no GPU firmware found (may need fetch-firmware.sh)",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -190,13 +284,28 @@ fn check_mesa_dri_hardware() -> Check {
|
||||
let hw_drivers = ["/usr/lib/dri/radeonsi_dri.so", "/usr/lib/dri/iris_dri.so"];
|
||||
let mut found = Vec::new();
|
||||
for d in hw_drivers {
|
||||
if std::path::Path::new(d).exists() { found.push(d); }
|
||||
if std::path::Path::new(d).exists() {
|
||||
found.push(d);
|
||||
}
|
||||
}
|
||||
if !found.is_empty() {
|
||||
let names: Vec<_> = found.iter().map(|s| s.rsplit('/').next().unwrap_or(s)).collect();
|
||||
Check::pass("MESA_DRI", &format!("{} hardware DRI driver(s): {}", found.len(), names.join(", ")))
|
||||
let names: Vec<_> = found
|
||||
.iter()
|
||||
.map(|s| s.rsplit('/').next().unwrap_or(s))
|
||||
.collect();
|
||||
Check::pass(
|
||||
"MESA_DRI",
|
||||
&format!(
|
||||
"{} hardware DRI driver(s): {}",
|
||||
found.len(),
|
||||
names.join(", ")
|
||||
),
|
||||
)
|
||||
} else {
|
||||
Check::fail("MESA_DRI", "no hardware DRI drivers found (llvmpipe software only)")
|
||||
Check::fail(
|
||||
"MESA_DRI",
|
||||
"no hardware DRI drivers found (llvmpipe software only)",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -212,7 +321,10 @@ fn check_display_modes() -> Check {
|
||||
Check::fail("DISPLAY_MODES", "no connectors found")
|
||||
}
|
||||
}
|
||||
Err(_) => Check::skip("DISPLAY_MODES", "cannot enumerate connectors (may need hardware GPU)")
|
||||
Err(_) => Check::skip(
|
||||
"DISPLAY_MODES",
|
||||
"cannot enumerate connectors (may need hardware GPU)",
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -230,7 +342,11 @@ fn decode_wire_exact<T: Copy>(bytes: &[u8]) -> Result<T, String> {
|
||||
|
||||
let mut out = MaybeUninit::<T>::uninit();
|
||||
unsafe {
|
||||
std::ptr::copy_nonoverlapping(bytes.as_ptr(), out.as_mut_ptr().cast::<u8>(), size_of::<T>());
|
||||
std::ptr::copy_nonoverlapping(
|
||||
bytes.as_ptr(),
|
||||
out.as_mut_ptr().cast::<u8>(),
|
||||
size_of::<T>(),
|
||||
);
|
||||
Ok(out.assume_init())
|
||||
}
|
||||
}
|
||||
@@ -238,10 +354,7 @@ fn decode_wire_exact<T: Copy>(bytes: &[u8]) -> Result<T, String> {
|
||||
#[cfg(target_os = "redox")]
|
||||
fn bytes_of<T>(value: &T) -> &[u8] {
|
||||
unsafe {
|
||||
std::slice::from_raw_parts(
|
||||
(value as *const T).cast::<u8>(),
|
||||
std::mem::size_of::<T>(),
|
||||
)
|
||||
std::slice::from_raw_parts((value as *const T).cast::<u8>(), std::mem::size_of::<T>())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -296,7 +409,10 @@ fn check_gem_buffer_allocation() -> Check {
|
||||
);
|
||||
Check::pass(
|
||||
"GEM_BUFFER_ALLOCATION",
|
||||
&format!("allocated GEM handle {} over /scheme/drm/card0", created.handle),
|
||||
&format!(
|
||||
"allocated GEM handle {} over /scheme/drm/card0",
|
||||
created.handle
|
||||
),
|
||||
)
|
||||
}
|
||||
Err(err) => Check::fail("GEM_BUFFER_ALLOCATION", &err),
|
||||
@@ -429,7 +545,10 @@ fn check_hardware_rendering_ready(report: &Report) -> Check {
|
||||
fn run() -> Result<(), String> {
|
||||
#[cfg(not(target_os = "redox"))]
|
||||
{
|
||||
if std::env::args().any(|a| a == "-h" || a == "--help") { println!("{USAGE}"); return Err(String::new()); }
|
||||
if std::env::args().any(|a| a == "-h" || a == "--help") {
|
||||
println!("{USAGE}");
|
||||
return Err(String::new());
|
||||
}
|
||||
println!("{PROGRAM}: GPU check requires Redox runtime");
|
||||
return Ok(());
|
||||
}
|
||||
@@ -446,14 +565,18 @@ fn run() -> Result<(), String> {
|
||||
let readiness = check_hardware_rendering_ready(&report);
|
||||
report.add(readiness);
|
||||
report.print();
|
||||
if report.any_failed() { return Err("one or more Phase 5 checks failed".to_string()); }
|
||||
if report.any_failed() {
|
||||
return Err("one or more Phase 5 checks failed".to_string());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
if let Err(err) = run() {
|
||||
if err.is_empty() { process::exit(0); }
|
||||
if err.is_empty() {
|
||||
process::exit(0);
|
||||
}
|
||||
eprintln!("{PROGRAM}: {err}");
|
||||
process::exit(1);
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
# Exit codes:
|
||||
# 0 — all checks passed
|
||||
# 1 — one or more checks failed
|
||||
# 2 — QEMU boot or login failure
|
||||
# 2 — QEMU boot/login/runtime infrastructure failure
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
@@ -65,7 +65,7 @@ run_guest_checks() {
|
||||
echo
|
||||
|
||||
echo "--- IOMMU ---"
|
||||
run_check "iommu" "redbear-phase-iommu-check" "/scheme/iommu, AMD-Vi/Intel VT-d detection, event log, and interrupt remap setup"
|
||||
run_check "iommu" "redbear-phase-iommu-check" "AMD-Vi scheme proof or Intel VT-d DMAR detection, event log, and interrupt remap status"
|
||||
echo
|
||||
|
||||
echo "--- DMA ---"
|
||||
@@ -118,6 +118,20 @@ run_qemu_checks() {
|
||||
log_user 1
|
||||
set timeout 300
|
||||
|
||||
proc expect_or_exit2 {pattern message} {
|
||||
expect {
|
||||
\$pattern { }
|
||||
timeout {
|
||||
puts "ERROR: \$message"
|
||||
exit 2
|
||||
}
|
||||
eof {
|
||||
puts "ERROR: guest exited before \$message"
|
||||
exit 2
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
proc run_check {name cmd description ok_marker fail_marker missing_marker} {
|
||||
global failures
|
||||
|
||||
@@ -137,11 +151,11 @@ proc run_check {name cmd description ok_marker fail_marker missing_marker} {
|
||||
}
|
||||
timeout {
|
||||
puts " FAIL \$name: timed out"
|
||||
incr failures
|
||||
exit 2
|
||||
}
|
||||
eof {
|
||||
puts " FAIL \$name: guest exited before check completion"
|
||||
exit 1
|
||||
exit 2
|
||||
}
|
||||
}
|
||||
puts ""
|
||||
@@ -149,19 +163,19 @@ proc run_check {name cmd description ok_marker fail_marker missing_marker} {
|
||||
|
||||
set failures 0
|
||||
spawn qemu-system-x86_64 -name {Red Bear OS x86_64} -device qemu-xhci -smp 4 -m 2048 -bios $firmware -chardev stdio,id=debug,signal=off,mux=on -serial chardev:debug -mon chardev=debug -machine q35 -device ich9-intel-hda -device hda-output -device virtio-net,netdev=net0 -netdev user,id=net0 -nographic -vga none -drive file=$image,format=raw,if=none,id=drv0 -device nvme,drive=drv0,serial=NVME_SERIAL -drive file=$extra,format=raw,if=none,id=drv1 -device nvme,drive=drv1,serial=NVME_EXTRA -enable-kvm -cpu host
|
||||
expect "login:"
|
||||
expect_or_exit2 "login:" "login prompt"
|
||||
send "root\r"
|
||||
expect "assword:"
|
||||
expect_or_exit2 "assword:" "password prompt"
|
||||
send "password\r"
|
||||
expect "Type 'help' for available commands."
|
||||
expect_or_exit2 "Type 'help' for available commands." "interactive shell prompt"
|
||||
send "echo __READY__\r"
|
||||
expect "__READY__"
|
||||
expect_or_exit2 "__READY__" "guest readiness marker"
|
||||
|
||||
puts "=== Red Bear OS IRQ Runtime Validation ==="
|
||||
puts ""
|
||||
|
||||
run_check "PCI IRQ" "redbear-phase-pci-irq-check" "/scheme/irq, MSI/MSI-X capability, affinity, and spurious IRQ routing quality" "__PCI_IRQ_OK__" "__PCI_IRQ_FAIL__" "__PCI_IRQ_MISSING__"
|
||||
run_check "IOMMU" "redbear-phase-iommu-check" "/scheme/iommu, AMD-Vi/Intel VT-d detection, event log, and interrupt remap setup" "__IOMMU_OK__" "__IOMMU_FAIL__" "__IOMMU_MISSING__"
|
||||
run_check "IOMMU" "redbear-phase-iommu-check" "AMD-Vi scheme proof or Intel VT-d DMAR detection, event log, and interrupt remap status" "__IOMMU_OK__" "__IOMMU_FAIL__" "__IOMMU_MISSING__"
|
||||
run_check "DMA" "redbear-phase-dma-check" "DMA buffer allocation and write/readback" "__DMA_OK__" "__DMA_FAIL__" "__DMA_MISSING__"
|
||||
run_check "PS/2 + serio" "redbear-phase-ps2-check" "/scheme/input/ps2 or serio runtime path" "__PS2_OK__" "__PS2_FAIL__" "__PS2_MISSING__"
|
||||
run_check "monotonic timer" "redbear-phase-timer-check" "/scheme/time/CLOCK_MONOTONIC monotonic progress" "__TIMER_OK__" "__TIMER_FAIL__" "__TIMER_MISSING__"
|
||||
@@ -175,13 +189,23 @@ if {\$failures == 0} {
|
||||
}
|
||||
|
||||
send "echo __IRQ_RUNTIME_DONE__\$failures__\r"
|
||||
expect "__IRQ_RUNTIME_DONE__\$failures__"
|
||||
expect_or_exit2 "__IRQ_RUNTIME_DONE__\$failures__" "final IRQ runtime summary marker"
|
||||
|
||||
set final_status 0
|
||||
if {\$failures != 0} {
|
||||
exit 1
|
||||
set final_status 1
|
||||
}
|
||||
|
||||
send "shutdown\r"
|
||||
expect eof
|
||||
expect {
|
||||
eof { }
|
||||
timeout {
|
||||
if {\$final_status == 0} {
|
||||
set final_status 2
|
||||
}
|
||||
}
|
||||
}
|
||||
exit \$final_status
|
||||
EXPECT_SCRIPT
|
||||
}
|
||||
|
||||
@@ -200,7 +224,7 @@ QEMU mode boots an image and runs checks automatically.
|
||||
|
||||
Required binaries (must be in PATH inside the guest):
|
||||
redbear-phase-pci-irq-check — PCI IRQ runtime reports, MSI/MSI-X capability, affinity, spurious IRQs
|
||||
redbear-phase-iommu-check — IOMMU runtime self-test + scheme control probes
|
||||
redbear-phase-iommu-check — IOMMU runtime self-test + AMD-Vi scheme or Intel VT-d DMAR probes
|
||||
redbear-phase-dma-check — DMA buffer allocation/runtime proof
|
||||
redbear-phase-ps2-check — PS/2 + serio runtime proof
|
||||
redbear-phase-timer-check — monotonic timer runtime proof
|
||||
|
||||
Reference in New Issue
Block a user