Advance Red Bear runtime services and tools
This commit is contained in:
@@ -83,11 +83,20 @@ path = "src/bin/redbear-phase-timer-check.rs"
|
||||
name = "redbear-phase-dma-check"
|
||||
path = "src/bin/redbear-phase-dma-check.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "redbear-phase-acpi-check"
|
||||
path = "src/bin/redbear-phase-acpi-check.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "redbear-phase-pci-irq-check"
|
||||
path = "src/bin/redbear-phase-pci-irq-check.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "redbear-usb-check"
|
||||
path = "src/bin/redbear-usb-check.rs"
|
||||
|
||||
[dependencies]
|
||||
redbear-login-protocol = { path = "../../redbear-login-protocol/source" }
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_json = "1"
|
||||
orbclient = "0.3"
|
||||
|
||||
@@ -4,12 +4,12 @@ use std::process;
|
||||
use redbear_hwutils::{
|
||||
lookup_pci_device_name, lookup_pci_vendor_name, parse_args, parse_pci_location, PciLocation,
|
||||
};
|
||||
use redox_driver_sys::pci::PciDeviceInfo;
|
||||
use redox_driver_sys::pci::{parse_device_info_from_config_space, InterruptSupport, PciDeviceInfo};
|
||||
use redox_driver_sys::quirks::{lookup_pci_quirks, PciQuirkFlags};
|
||||
|
||||
const USAGE: &str = "Usage: lspci\nList PCI devices exposed by /scheme/pci.";
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
struct PciDeviceSummary {
|
||||
location: PciLocation,
|
||||
vendor_id: u16,
|
||||
@@ -20,6 +20,9 @@ struct PciDeviceSummary {
|
||||
revision: u8,
|
||||
subvendor_id: u16,
|
||||
subdevice_id: u16,
|
||||
irq: Option<u32>,
|
||||
interrupt_support: InterruptSupport,
|
||||
irq_reason: Option<String>,
|
||||
quirk_flags: PciQuirkFlags,
|
||||
}
|
||||
|
||||
@@ -36,71 +39,33 @@ fn main() {
|
||||
|
||||
fn format_quirk_flags(flags: PciQuirkFlags) -> String {
|
||||
let mut names = Vec::new();
|
||||
if flags.contains(PciQuirkFlags::NO_MSI) {
|
||||
names.push("no_msi");
|
||||
}
|
||||
if flags.contains(PciQuirkFlags::NO_MSIX) {
|
||||
names.push("no_msix");
|
||||
}
|
||||
if flags.contains(PciQuirkFlags::FORCE_LEGACY_IRQ) {
|
||||
names.push("force_legacy_irq");
|
||||
}
|
||||
if flags.contains(PciQuirkFlags::NO_PM) {
|
||||
names.push("no_pm");
|
||||
}
|
||||
if flags.contains(PciQuirkFlags::NO_D3COLD) {
|
||||
names.push("no_d3cold");
|
||||
}
|
||||
if flags.contains(PciQuirkFlags::NO_ASPM) {
|
||||
names.push("no_aspm");
|
||||
}
|
||||
if flags.contains(PciQuirkFlags::NEED_IOMMU) {
|
||||
names.push("need_iommu");
|
||||
}
|
||||
if flags.contains(PciQuirkFlags::NO_IOMMU) {
|
||||
names.push("no_iommu");
|
||||
}
|
||||
if flags.contains(PciQuirkFlags::DMA_32BIT_ONLY) {
|
||||
names.push("dma_32bit_only");
|
||||
}
|
||||
if flags.contains(PciQuirkFlags::RESIZE_BAR) {
|
||||
names.push("resize_bar");
|
||||
}
|
||||
if flags.contains(PciQuirkFlags::DISABLE_BAR_SIZING) {
|
||||
names.push("disable_bar_sizing");
|
||||
}
|
||||
if flags.contains(PciQuirkFlags::NEED_FIRMWARE) {
|
||||
names.push("need_firmware");
|
||||
}
|
||||
if flags.contains(PciQuirkFlags::DISABLE_ACCEL) {
|
||||
names.push("disable_accel");
|
||||
}
|
||||
if flags.contains(PciQuirkFlags::FORCE_VRAM_ONLY) {
|
||||
names.push("force_vram_only");
|
||||
}
|
||||
if flags.contains(PciQuirkFlags::NO_USB3) {
|
||||
names.push("no_usb3");
|
||||
}
|
||||
if flags.contains(PciQuirkFlags::RESET_DELAY_MS) {
|
||||
names.push("reset_delay_ms");
|
||||
}
|
||||
if flags.contains(PciQuirkFlags::NO_STRING_FETCH) {
|
||||
names.push("no_string_fetch");
|
||||
}
|
||||
if flags.contains(PciQuirkFlags::BAD_EEPROM) {
|
||||
names.push("bad_eeprom");
|
||||
}
|
||||
if flags.contains(PciQuirkFlags::BUS_MASTER_DELAY) {
|
||||
names.push("bus_master_delay");
|
||||
}
|
||||
if flags.contains(PciQuirkFlags::WRONG_CLASS) {
|
||||
names.push("wrong_class");
|
||||
}
|
||||
if flags.contains(PciQuirkFlags::BROKEN_BRIDGE) {
|
||||
names.push("broken_bridge");
|
||||
}
|
||||
if flags.contains(PciQuirkFlags::NO_RESOURCE_RELOC) {
|
||||
names.push("no_resource_reloc");
|
||||
for (flag, name) in [
|
||||
(PciQuirkFlags::NO_MSI, "no_msi"),
|
||||
(PciQuirkFlags::NO_MSIX, "no_msix"),
|
||||
(PciQuirkFlags::FORCE_LEGACY_IRQ, "force_legacy_irq"),
|
||||
(PciQuirkFlags::NO_PM, "no_pm"),
|
||||
(PciQuirkFlags::NO_D3COLD, "no_d3cold"),
|
||||
(PciQuirkFlags::NO_ASPM, "no_aspm"),
|
||||
(PciQuirkFlags::NEED_IOMMU, "need_iommu"),
|
||||
(PciQuirkFlags::NO_IOMMU, "no_iommu"),
|
||||
(PciQuirkFlags::DMA_32BIT_ONLY, "dma_32bit_only"),
|
||||
(PciQuirkFlags::RESIZE_BAR, "resize_bar"),
|
||||
(PciQuirkFlags::DISABLE_BAR_SIZING, "disable_bar_sizing"),
|
||||
(PciQuirkFlags::NEED_FIRMWARE, "need_firmware"),
|
||||
(PciQuirkFlags::DISABLE_ACCEL, "disable_accel"),
|
||||
(PciQuirkFlags::FORCE_VRAM_ONLY, "force_vram_only"),
|
||||
(PciQuirkFlags::NO_USB3, "no_usb3"),
|
||||
(PciQuirkFlags::RESET_DELAY_MS, "reset_delay_ms"),
|
||||
(PciQuirkFlags::NO_STRING_FETCH, "no_string_fetch"),
|
||||
(PciQuirkFlags::BAD_EEPROM, "bad_eeprom"),
|
||||
(PciQuirkFlags::BUS_MASTER_DELAY, "bus_master_delay"),
|
||||
(PciQuirkFlags::WRONG_CLASS, "wrong_class"),
|
||||
(PciQuirkFlags::BROKEN_BRIDGE, "broken_bridge"),
|
||||
(PciQuirkFlags::NO_RESOURCE_RELOC, "no_resource_reloc"),
|
||||
] {
|
||||
if flags.contains(flag) {
|
||||
names.push(name);
|
||||
}
|
||||
}
|
||||
names.join(",")
|
||||
}
|
||||
@@ -163,6 +128,13 @@ fn run() -> Result<(), String> {
|
||||
if !device.quirk_flags.is_empty() {
|
||||
print!(" quirks: {}", format_quirk_flags(device.quirk_flags));
|
||||
}
|
||||
print!(" irq-support: {}", device.interrupt_support.as_str());
|
||||
if let Some(line) = device.irq {
|
||||
print!(" line={line}");
|
||||
}
|
||||
if let Some(reason) = &device.irq_reason {
|
||||
print!(" reason={reason}");
|
||||
}
|
||||
println!();
|
||||
}
|
||||
|
||||
@@ -195,47 +167,56 @@ fn collect_devices() -> Result<Vec<PciDeviceSummary>, String> {
|
||||
Err(_) => continue,
|
||||
};
|
||||
|
||||
if config.len() < 16 {
|
||||
if config.len() < 64 {
|
||||
continue;
|
||||
}
|
||||
|
||||
let vendor_id = u16::from_le_bytes([config[0x00], config[0x01]]);
|
||||
let device_id = u16::from_le_bytes([config[0x02], config[0x03]]);
|
||||
let revision = config[0x08];
|
||||
let prog_if = config[0x09];
|
||||
let subclass = config[0x0A];
|
||||
let class_code = config[0x0B];
|
||||
|
||||
let (subvendor_id, subdevice_id) = if config.len() >= 0x30 {
|
||||
(
|
||||
u16::from_le_bytes([config[0x2C], config[0x2D]]),
|
||||
u16::from_le_bytes([config[0x2E], config[0x2F]]),
|
||||
)
|
||||
} else {
|
||||
(0xFFFF, 0xFFFF)
|
||||
let info = match parse_device_info_from_config_space(
|
||||
redox_driver_sys::pci::PciLocation {
|
||||
segment: location.segment,
|
||||
bus: location.bus,
|
||||
device: location.device,
|
||||
function: location.function,
|
||||
},
|
||||
&config,
|
||||
) {
|
||||
Some(info) => info,
|
||||
None => continue,
|
||||
};
|
||||
|
||||
let quirk_flags = lookup_quirks(
|
||||
vendor_id,
|
||||
device_id,
|
||||
revision,
|
||||
class_code,
|
||||
subclass,
|
||||
prog_if,
|
||||
subvendor_id,
|
||||
subdevice_id,
|
||||
info.vendor_id,
|
||||
info.device_id,
|
||||
info.revision,
|
||||
info.class_code,
|
||||
info.subclass,
|
||||
info.prog_if,
|
||||
info.subsystem_vendor_id,
|
||||
info.subsystem_device_id,
|
||||
);
|
||||
let irq_reason = if quirk_flags.contains(PciQuirkFlags::FORCE_LEGACY_IRQ) {
|
||||
Some("quirk_force_legacy_irq".to_string())
|
||||
} else if quirk_flags.contains(PciQuirkFlags::NO_MSIX) {
|
||||
Some("quirk_disable_msix".to_string())
|
||||
} else if quirk_flags.contains(PciQuirkFlags::NO_MSI) {
|
||||
Some("quirk_disable_msi".to_string())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
devices.push(PciDeviceSummary {
|
||||
location,
|
||||
vendor_id,
|
||||
device_id,
|
||||
revision,
|
||||
prog_if,
|
||||
subclass,
|
||||
class_code,
|
||||
subvendor_id,
|
||||
subdevice_id,
|
||||
vendor_id: info.vendor_id,
|
||||
device_id: info.device_id,
|
||||
revision: info.revision,
|
||||
prog_if: info.prog_if,
|
||||
subclass: info.subclass,
|
||||
class_code: info.class_code,
|
||||
subvendor_id: info.subsystem_vendor_id,
|
||||
subdevice_id: info.subsystem_device_id,
|
||||
irq: info.irq,
|
||||
interrupt_support: info.interrupt_support(),
|
||||
irq_reason,
|
||||
quirk_flags,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ use std::{
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use redbear_login_protocol::{GreeterRequest as Request, GreeterResponse};
|
||||
|
||||
const PROGRAM: &str = "redbear-greeter-check";
|
||||
const USAGE: &str = "Usage: redbear-greeter-check [--invalid USER PASSWORD | --valid USER PASSWORD]\n\nQuery the installed Red Bear greeter surface inside the guest.";
|
||||
@@ -28,35 +28,6 @@ const DEBUG_CONSOLE_SERVICE: &str = "/usr/lib/init.d/31_debug_console.service";
|
||||
const VALIDATION_REQUEST: &str = "/run/redbear-kde-session.validation-request";
|
||||
const VALIDATION_SUCCESS: &str = "/run/redbear-kde-session.validation-success";
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
#[serde(tag = "type", rename_all = "snake_case")]
|
||||
enum Request<'a> {
|
||||
Hello { version: u32 },
|
||||
SubmitLogin { username: &'a str, password: &'a str },
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(tag = "type", rename_all = "snake_case")]
|
||||
enum Response {
|
||||
HelloOk {
|
||||
background: String,
|
||||
icon: String,
|
||||
session_name: String,
|
||||
state: String,
|
||||
message: String,
|
||||
},
|
||||
LoginResult {
|
||||
ok: bool,
|
||||
state: String,
|
||||
message: String,
|
||||
},
|
||||
Error {
|
||||
message: String,
|
||||
},
|
||||
#[serde(other)]
|
||||
Other,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
enum Mode {
|
||||
Status,
|
||||
@@ -64,6 +35,21 @@ enum Mode {
|
||||
Valid { username: String, password: String },
|
||||
}
|
||||
|
||||
fn parse_credentials(args: &mut impl Iterator<Item = String>, flag: &str) -> Result<(String, String), String> {
|
||||
let username = args
|
||||
.next()
|
||||
.ok_or_else(|| format!("missing username after {flag}"))?;
|
||||
let password = args
|
||||
.next()
|
||||
.ok_or_else(|| format!("missing password after {flag}"))?;
|
||||
if args.next().is_some() {
|
||||
return Err(format!(
|
||||
"unexpected extra arguments after {flag} USER PASSWORD"
|
||||
));
|
||||
}
|
||||
Ok((username, password))
|
||||
}
|
||||
|
||||
fn parse_mode_from_args<I>(args: I) -> Result<Mode, String>
|
||||
where
|
||||
I: IntoIterator<Item = String>,
|
||||
@@ -73,19 +59,11 @@ where
|
||||
None => Ok(Mode::Status),
|
||||
Some(flag) if flag == "--help" || flag == "-h" => Err(String::new()),
|
||||
Some(flag) if flag == "--invalid" => {
|
||||
let username = args.next().ok_or_else(|| String::from("missing username after --invalid"))?;
|
||||
let password = args.next().ok_or_else(|| String::from("missing password after --invalid"))?;
|
||||
if args.next().is_some() {
|
||||
return Err(String::from("unexpected extra arguments after --invalid USER PASSWORD"));
|
||||
}
|
||||
let (username, password) = parse_credentials(&mut args, &flag)?;
|
||||
Ok(Mode::Invalid { username, password })
|
||||
}
|
||||
Some(flag) if flag == "--valid" => {
|
||||
let username = args.next().ok_or_else(|| String::from("missing username after --valid"))?;
|
||||
let password = args.next().ok_or_else(|| String::from("missing password after --valid"))?;
|
||||
if args.next().is_some() {
|
||||
return Err(String::from("unexpected extra arguments after --valid USER PASSWORD"));
|
||||
}
|
||||
let (username, password) = parse_credentials(&mut args, &flag)?;
|
||||
Ok(Mode::Valid { username, password })
|
||||
}
|
||||
Some(other) => Err(format!("unsupported argument '{other}'")),
|
||||
@@ -96,7 +74,7 @@ fn parse_mode() -> Result<Mode, String> {
|
||||
parse_mode_from_args(std::env::args().skip(1))
|
||||
}
|
||||
|
||||
fn send_request(request: &Request<'_>) -> Result<Response, String> {
|
||||
fn send_request(request: &Request) -> Result<GreeterResponse, String> {
|
||||
let mut stream = UnixStream::connect(GREETER_SOCKET)
|
||||
.map_err(|err| format!("failed to connect to {GREETER_SOCKET}: {err}"))?;
|
||||
let payload = serde_json::to_string(request)
|
||||
@@ -139,7 +117,7 @@ fn wait_for_greeter_ready(timeout: Duration) -> Result<(), String> {
|
||||
let start = Instant::now();
|
||||
while start.elapsed() <= timeout {
|
||||
match send_request(&Request::Hello { version: 1 }) {
|
||||
Ok(Response::HelloOk { state, message, .. }) if state == "greeter_ready" => {
|
||||
Ok(GreeterResponse::HelloOk { state, message, .. }) if state == "greeter_ready" => {
|
||||
println!("GREETER_VALID_READY_MESSAGE={message}");
|
||||
return Ok(());
|
||||
}
|
||||
@@ -169,7 +147,7 @@ fn run_status() -> Result<(), String> {
|
||||
require_path(GREETER_SOCKET)?;
|
||||
|
||||
match send_request(&Request::Hello { version: 1 })? {
|
||||
Response::HelloOk {
|
||||
GreeterResponse::HelloOk {
|
||||
background,
|
||||
icon,
|
||||
session_name,
|
||||
@@ -184,15 +162,18 @@ fn run_status() -> Result<(), String> {
|
||||
println!("GREETER_HELLO=ok");
|
||||
Ok(())
|
||||
}
|
||||
Response::Error { message } => Err(format!("greeter hello failed: {message}")),
|
||||
Response::Other => Err(String::from("unexpected greeter hello response")),
|
||||
Response::LoginResult { .. } => Err(String::from("unexpected login result when greeting greeter")),
|
||||
GreeterResponse::Error { message } => Err(format!("greeter hello failed: {message}")),
|
||||
GreeterResponse::ActionResult { .. } => Err(String::from("unexpected power response when greeting greeter")),
|
||||
GreeterResponse::LoginResult { .. } => Err(String::from("unexpected login result when greeting greeter")),
|
||||
}
|
||||
}
|
||||
|
||||
fn run_invalid(username: &str, password: &str) -> Result<(), String> {
|
||||
match send_request(&Request::SubmitLogin { username, password })? {
|
||||
Response::LoginResult { ok, state, message } => {
|
||||
match send_request(&Request::SubmitLogin {
|
||||
username: username.to_string(),
|
||||
password: password.to_string(),
|
||||
})? {
|
||||
GreeterResponse::LoginResult { ok, state, message } => {
|
||||
println!("GREETER_INVALID_STATE={state}");
|
||||
println!("GREETER_INVALID_MESSAGE={message}");
|
||||
if ok {
|
||||
@@ -202,9 +183,9 @@ fn run_invalid(username: &str, password: &str) -> Result<(), String> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
Response::Error { message } => Err(format!("invalid-login request failed: {message}")),
|
||||
Response::Other => Err(String::from("unexpected greeter response for invalid login")),
|
||||
Response::HelloOk { .. } => Err(String::from("unexpected hello response for invalid login")),
|
||||
GreeterResponse::Error { message } => Err(format!("invalid-login request failed: {message}")),
|
||||
GreeterResponse::ActionResult { .. } => Err(String::from("unexpected power response for invalid login")),
|
||||
GreeterResponse::HelloOk { .. } => Err(String::from("unexpected hello response for invalid login")),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -214,8 +195,11 @@ fn run_valid(username: &str, password: &str) -> Result<(), String> {
|
||||
fs::write(VALIDATION_REQUEST, b"bounded-session\n")
|
||||
.map_err(|err| format!("failed to create validation request: {err}"))?;
|
||||
|
||||
match send_request(&Request::SubmitLogin { username, password })? {
|
||||
Response::LoginResult { ok, state, message } => {
|
||||
match send_request(&Request::SubmitLogin {
|
||||
username: username.to_string(),
|
||||
password: password.to_string(),
|
||||
})? {
|
||||
GreeterResponse::LoginResult { ok, state, message } => {
|
||||
println!("GREETER_VALID_STATE={state}");
|
||||
println!("GREETER_VALID_MESSAGE={message}");
|
||||
if !ok {
|
||||
@@ -223,15 +207,15 @@ fn run_valid(username: &str, password: &str) -> Result<(), String> {
|
||||
return Err(String::from("valid login unexpectedly failed"));
|
||||
}
|
||||
}
|
||||
Response::Error { message } => {
|
||||
GreeterResponse::Error { message } => {
|
||||
let _ = fs::remove_file(VALIDATION_REQUEST);
|
||||
return Err(format!("valid-login request failed: {message}"));
|
||||
}
|
||||
Response::Other => {
|
||||
GreeterResponse::ActionResult { .. } => {
|
||||
let _ = fs::remove_file(VALIDATION_REQUEST);
|
||||
return Err(String::from("unexpected greeter response for valid login"));
|
||||
return Err(String::from("unexpected power response for valid login"));
|
||||
}
|
||||
Response::HelloOk { .. } => {
|
||||
GreeterResponse::HelloOk { .. } => {
|
||||
let _ = fs::remove_file(VALIDATION_REQUEST);
|
||||
return Err(String::from("unexpected hello response for valid login"));
|
||||
}
|
||||
|
||||
@@ -0,0 +1,197 @@
|
||||
use std::{
|
||||
fs,
|
||||
path::{Path, PathBuf},
|
||||
process,
|
||||
};
|
||||
|
||||
use redbear_hwutils::parse_args;
|
||||
|
||||
const PROGRAM: &str = "redbear-phase-acpi-check";
|
||||
const USAGE: &str = "Usage: redbear-phase-acpi-check\n\nShow the bounded ACPI runtime surface inside the target runtime.";
|
||||
|
||||
#[derive(Debug, Default, Eq, PartialEq)]
|
||||
struct AcpiSurface {
|
||||
acpi_root_present: bool,
|
||||
kernel_kstop_present: bool,
|
||||
dmi_present: bool,
|
||||
reboot_present: bool,
|
||||
power_present: bool,
|
||||
adapter_count: usize,
|
||||
battery_count: usize,
|
||||
dmi_match_lines: usize,
|
||||
}
|
||||
|
||||
fn root_prefix() -> PathBuf {
|
||||
std::env::var_os("REDBEAR_HWUTILS_ROOT")
|
||||
.map(PathBuf::from)
|
||||
.unwrap_or_else(|| PathBuf::from("/"))
|
||||
}
|
||||
|
||||
fn resolve(root: &Path, absolute: &str) -> PathBuf {
|
||||
root.join(absolute.trim_start_matches('/'))
|
||||
}
|
||||
|
||||
fn read_dir_names(path: &Path) -> Vec<String> {
|
||||
let mut names = match fs::read_dir(path) {
|
||||
Ok(entries) => entries
|
||||
.filter_map(|entry| entry.ok())
|
||||
.filter_map(|entry| entry.file_name().into_string().ok())
|
||||
.collect::<Vec<_>>(),
|
||||
Err(_) => Vec::new(),
|
||||
};
|
||||
names.sort();
|
||||
names
|
||||
}
|
||||
|
||||
fn discover_surface(root: &Path) -> AcpiSurface {
|
||||
let acpi_root = resolve(root, "/scheme/acpi");
|
||||
let kernel_kstop = resolve(root, "/scheme/kernel.acpi/kstop");
|
||||
let dmi = resolve(root, "/scheme/acpi/dmi");
|
||||
let reboot = resolve(root, "/scheme/acpi/reboot");
|
||||
let power = resolve(root, "/scheme/acpi/power");
|
||||
|
||||
let dmi_match_lines = fs::read_to_string(&dmi)
|
||||
.ok()
|
||||
.map(|content| {
|
||||
content
|
||||
.lines()
|
||||
.map(str::trim)
|
||||
.filter(|line| !line.is_empty())
|
||||
.count()
|
||||
})
|
||||
.unwrap_or(0);
|
||||
|
||||
let adapter_count = read_dir_names(&power.join("adapters")).len();
|
||||
let battery_count = read_dir_names(&power.join("batteries")).len();
|
||||
|
||||
AcpiSurface {
|
||||
acpi_root_present: acpi_root.exists(),
|
||||
kernel_kstop_present: kernel_kstop.exists(),
|
||||
dmi_present: dmi.exists(),
|
||||
reboot_present: reboot.exists(),
|
||||
power_present: power.exists(),
|
||||
adapter_count,
|
||||
battery_count,
|
||||
dmi_match_lines,
|
||||
}
|
||||
}
|
||||
|
||||
fn run() -> Result<(), String> {
|
||||
parse_args(PROGRAM, USAGE, std::env::args())?;
|
||||
|
||||
let root = root_prefix();
|
||||
let surface = discover_surface(&root);
|
||||
|
||||
println!(
|
||||
"ACPI_ROOT={}",
|
||||
if surface.acpi_root_present { "present" } else { "missing" }
|
||||
);
|
||||
println!(
|
||||
"KERNEL_KSTOP={}",
|
||||
if surface.kernel_kstop_present {
|
||||
"present"
|
||||
} else {
|
||||
"missing"
|
||||
}
|
||||
);
|
||||
println!("ACPI_DMI={}", if surface.dmi_present { "present" } else { "missing" });
|
||||
println!(
|
||||
"ACPI_REBOOT={}",
|
||||
if surface.reboot_present {
|
||||
"present"
|
||||
} else {
|
||||
"missing"
|
||||
}
|
||||
);
|
||||
println!(
|
||||
"ACPI_POWER={}",
|
||||
if surface.power_present {
|
||||
"present"
|
||||
} else {
|
||||
"unavailable"
|
||||
}
|
||||
);
|
||||
println!("ACPI_POWER_ADAPTERS={}", surface.adapter_count);
|
||||
println!("ACPI_POWER_BATTERIES={}", surface.battery_count);
|
||||
println!("ACPI_DMI_MATCH_LINES={}", surface.dmi_match_lines);
|
||||
|
||||
if !surface.kernel_kstop_present {
|
||||
return Err("missing /scheme/kernel.acpi/kstop".to_string());
|
||||
}
|
||||
if !surface.dmi_present {
|
||||
return Err("missing /scheme/acpi/dmi".to_string());
|
||||
}
|
||||
if !surface.reboot_present {
|
||||
return Err("missing /scheme/acpi/reboot".to_string());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn main() {
|
||||
if let Err(err) = run() {
|
||||
eprintln!("{PROGRAM}: {err}");
|
||||
process::exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
|
||||
fn temp_root() -> PathBuf {
|
||||
let unique = SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_nanos();
|
||||
let path = std::env::temp_dir().join(format!("redbear-phase-acpi-check-{unique}"));
|
||||
fs::create_dir_all(&path).unwrap();
|
||||
path
|
||||
}
|
||||
|
||||
fn create_dir(root: &Path, absolute: &str) {
|
||||
fs::create_dir_all(resolve(root, absolute)).unwrap();
|
||||
}
|
||||
|
||||
fn write_file(root: &Path, absolute: &str, content: &str) {
|
||||
let path = resolve(root, absolute);
|
||||
if let Some(parent) = path.parent() {
|
||||
fs::create_dir_all(parent).unwrap();
|
||||
}
|
||||
fs::write(path, content).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn discover_surface_marks_optional_power_as_unavailable() {
|
||||
let root = temp_root();
|
||||
create_dir(&root, "/scheme/acpi");
|
||||
write_file(&root, "/scheme/kernel.acpi/kstop", "1");
|
||||
write_file(&root, "/scheme/acpi/dmi", "sys_vendor=Framework\n");
|
||||
write_file(&root, "/scheme/acpi/reboot", "");
|
||||
|
||||
let surface = discover_surface(&root);
|
||||
assert!(surface.acpi_root_present);
|
||||
assert!(surface.kernel_kstop_present);
|
||||
assert!(surface.dmi_present);
|
||||
assert!(surface.reboot_present);
|
||||
assert!(!surface.power_present);
|
||||
assert_eq!(surface.dmi_match_lines, 1);
|
||||
|
||||
fs::remove_dir_all(root).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn discover_surface_counts_power_entries_when_present() {
|
||||
let root = temp_root();
|
||||
create_dir(&root, "/scheme/acpi/power/adapters/AC");
|
||||
create_dir(&root, "/scheme/acpi/power/batteries/BAT0");
|
||||
|
||||
let surface = discover_surface(&root);
|
||||
assert!(surface.power_present);
|
||||
assert_eq!(surface.adapter_count, 1);
|
||||
assert_eq!(surface.battery_count, 1);
|
||||
|
||||
fs::remove_dir_all(root).unwrap();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,196 @@
|
||||
use std::{
|
||||
fs,
|
||||
path::{Path, PathBuf},
|
||||
process,
|
||||
};
|
||||
|
||||
use redbear_hwutils::parse_args;
|
||||
|
||||
const PROGRAM: &str = "redbear-phase-pci-irq-check";
|
||||
const USAGE: &str = "Usage: redbear-phase-pci-irq-check\n\nShow bounded live PCI/IRQ runtime reporting from the current target runtime.";
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
struct IrqReport {
|
||||
driver: String,
|
||||
pid: u32,
|
||||
device: String,
|
||||
mode: String,
|
||||
reason: String,
|
||||
}
|
||||
|
||||
fn root_prefix() -> PathBuf {
|
||||
std::env::var_os("REDBEAR_HWUTILS_ROOT")
|
||||
.map(PathBuf::from)
|
||||
.unwrap_or_else(|| PathBuf::from("/"))
|
||||
}
|
||||
|
||||
fn resolve(root: &Path, absolute: &str) -> PathBuf {
|
||||
root.join(absolute.trim_start_matches('/'))
|
||||
}
|
||||
|
||||
fn read_dir_names(path: &Path) -> Vec<String> {
|
||||
let mut names = match fs::read_dir(path) {
|
||||
Ok(entries) => entries
|
||||
.filter_map(|entry| entry.ok())
|
||||
.filter_map(|entry| entry.file_name().into_string().ok())
|
||||
.collect::<Vec<_>>(),
|
||||
Err(_) => Vec::new(),
|
||||
};
|
||||
names.sort();
|
||||
names
|
||||
}
|
||||
|
||||
fn collect_irq_reports(root: &Path) -> Vec<IrqReport> {
|
||||
let mut reports = Vec::new();
|
||||
let mut seen = std::collections::BTreeSet::new();
|
||||
|
||||
for dir in [
|
||||
"/tmp/redbear-irq-report",
|
||||
"/tmp/run/redbear-irq-report",
|
||||
"/run/redbear-irq-report",
|
||||
"/var/run/redbear-irq-report",
|
||||
"/scheme/initfs/tmp/redbear-irq-report",
|
||||
"/scheme/initfs/tmp/run/redbear-irq-report",
|
||||
"/scheme/initfs/run/redbear-irq-report",
|
||||
"/scheme/initfs/var/run/redbear-irq-report",
|
||||
] {
|
||||
for name in read_dir_names(&resolve(root, dir))
|
||||
.into_iter()
|
||||
.filter(|name| name.ends_with(".env"))
|
||||
{
|
||||
let path = resolve(root, &format!("{dir}/{name}"));
|
||||
if !seen.insert(path.clone()) {
|
||||
continue;
|
||||
}
|
||||
let Ok(content) = fs::read_to_string(&path) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
let mut driver = None;
|
||||
let mut pid = None;
|
||||
let mut device = None;
|
||||
let mut mode = None;
|
||||
let mut reason = None;
|
||||
|
||||
for line in content.lines() {
|
||||
let Some((key, value)) = line.split_once('=') else {
|
||||
continue;
|
||||
};
|
||||
match key.trim() {
|
||||
"driver" => driver = Some(value.trim().to_string()),
|
||||
"pid" => pid = value.trim().parse::<u32>().ok(),
|
||||
"device" => device = Some(value.trim().to_string()),
|
||||
"mode" => mode = Some(value.trim().to_string()),
|
||||
"reason" => reason = Some(value.trim().to_string()),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
if let (Some(driver), Some(pid), Some(device), Some(mode), Some(reason)) =
|
||||
(driver, pid, device, mode, reason)
|
||||
{
|
||||
if !resolve(root, &format!("/proc/{pid}")).exists() {
|
||||
continue;
|
||||
}
|
||||
reports.push(IrqReport {
|
||||
driver,
|
||||
pid,
|
||||
device,
|
||||
mode,
|
||||
reason,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
reports.sort_by(|left, right| left.driver.cmp(&right.driver).then(left.device.cmp(&right.device)));
|
||||
reports
|
||||
}
|
||||
|
||||
fn run() -> Result<(), String> {
|
||||
parse_args(PROGRAM, USAGE, std::env::args())?;
|
||||
let root = root_prefix();
|
||||
let reports = collect_irq_reports(&root);
|
||||
|
||||
println!("PCI_IRQ_REPORTS={}", reports.len());
|
||||
for report in &reports {
|
||||
println!(
|
||||
"PCI_IRQ_REPORT={} pid={} device={} mode={} reason={}",
|
||||
report.driver, report.pid, report.device, report.mode, report.reason
|
||||
);
|
||||
}
|
||||
|
||||
if reports.is_empty() {
|
||||
return Err("no live PCI/IRQ runtime reports found".to_string());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn main() {
|
||||
if let Err(err) = run() {
|
||||
eprintln!("{PROGRAM}: {err}");
|
||||
process::exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
|
||||
fn temp_root() -> PathBuf {
|
||||
let unique = SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_nanos();
|
||||
let path = std::env::temp_dir().join(format!("redbear-phase-pci-irq-check-{unique}"));
|
||||
fs::create_dir_all(&path).unwrap();
|
||||
path
|
||||
}
|
||||
|
||||
fn create_dir(root: &Path, absolute: &str) {
|
||||
fs::create_dir_all(resolve(root, absolute)).unwrap();
|
||||
}
|
||||
|
||||
fn write_file(root: &Path, absolute: &str, content: &str) {
|
||||
let path = resolve(root, absolute);
|
||||
if let Some(parent) = path.parent() {
|
||||
fs::create_dir_all(parent).unwrap();
|
||||
}
|
||||
fs::write(path, content).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn collect_irq_reports_uses_live_pid_entries() {
|
||||
let root = temp_root();
|
||||
create_dir(&root, "/proc/42");
|
||||
write_file(
|
||||
&root,
|
||||
"/tmp/redbear-irq-report/xhcid--0000_00_14.0.env",
|
||||
"driver=xhcid\npid=42\ndevice=0000:00:14.0\nmode=msi_or_msix\nreason=driver_selected_interrupt_delivery\n",
|
||||
);
|
||||
|
||||
let reports = collect_irq_reports(&root);
|
||||
assert_eq!(reports.len(), 1);
|
||||
assert_eq!(reports[0].driver, "xhcid");
|
||||
assert_eq!(reports[0].mode, "msi_or_msix");
|
||||
|
||||
fs::remove_dir_all(root).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn collect_irq_reports_ignores_stale_entries() {
|
||||
let root = temp_root();
|
||||
write_file(
|
||||
&root,
|
||||
"/scheme/initfs/tmp/redbear-irq-report/virtio-netd--0000_00_03.0.env",
|
||||
"driver=virtio-netd\npid=99\ndevice=0000:00:03.0\nmode=msix\nreason=virtio_driver_selected_msix\n",
|
||||
);
|
||||
|
||||
let reports = collect_irq_reports(&root);
|
||||
assert!(reports.is_empty());
|
||||
|
||||
fs::remove_dir_all(root).unwrap();
|
||||
}
|
||||
}
|
||||
@@ -19,6 +19,20 @@ fn require_path(path: &str) -> Result<(), String> {
|
||||
}
|
||||
}
|
||||
|
||||
fn monotonic_path() -> Result<String, String> {
|
||||
let numeric = format!("/scheme/time/{}", flag::CLOCK_MONOTONIC);
|
||||
if require_path(&numeric).is_ok() {
|
||||
return Ok(numeric);
|
||||
}
|
||||
|
||||
let symbolic = "/scheme/time/CLOCK_MONOTONIC".to_string();
|
||||
if require_path(&symbolic).is_ok() {
|
||||
return Ok(symbolic);
|
||||
}
|
||||
|
||||
Err(format!("missing {numeric} and {symbolic}"))
|
||||
}
|
||||
|
||||
fn read_timespec(fd: &Fd) -> Result<TimeSpec, String> {
|
||||
let mut time = TimeSpec::default();
|
||||
let bytes = libredox::call::read(fd.raw(), &mut time)
|
||||
@@ -43,8 +57,7 @@ fn run() -> Result<(), String> {
|
||||
|
||||
println!("=== Red Bear OS Timer Runtime Check ===");
|
||||
|
||||
let time_path = format!("/scheme/time/{}", flag::CLOCK_MONOTONIC);
|
||||
require_path(&time_path)?;
|
||||
let time_path = monotonic_path()?;
|
||||
|
||||
let time_fd = Fd::open(&time_path, flag::O_RDWR, 0)
|
||||
.map_err(|err| format!("failed to open {time_path}: {err}"))?;
|
||||
|
||||
@@ -274,6 +274,10 @@ impl PowerRuntime {
|
||||
}
|
||||
}
|
||||
|
||||
fn available(&self) -> bool {
|
||||
Path::new(POWER_ROOT).exists()
|
||||
}
|
||||
|
||||
fn expected_device_paths(&self) -> BTreeSet<String> {
|
||||
let mut paths = BTreeSet::new();
|
||||
for adapter_id in &self.adapter_ids {
|
||||
@@ -289,8 +293,13 @@ impl PowerRuntime {
|
||||
fn validate_upower(list_names_output: &str) -> Result<(), String> {
|
||||
let runtime = PowerRuntime::discover();
|
||||
let expected_device_paths = runtime.expected_device_paths();
|
||||
let power_surface_available = runtime.available();
|
||||
println!("UPOWER_RUNTIME_ADAPTERS={}", runtime.adapter_ids.len());
|
||||
println!("UPOWER_RUNTIME_BATTERIES={}", runtime.battery_ids.len());
|
||||
println!(
|
||||
"UPOWER_POWER_SURFACE={}",
|
||||
if power_surface_available { "available" } else { "unavailable" }
|
||||
);
|
||||
|
||||
let enumerate_output = run_command_with_retry(
|
||||
DBUS_SEND,
|
||||
@@ -314,6 +323,18 @@ fn validate_upower(list_names_output: &str) -> Result<(), String> {
|
||||
);
|
||||
note_bus_name_registered(list_names_output, UPOWER_DESTINATION, "UPOWER_BUS_NAME");
|
||||
|
||||
if !power_surface_available {
|
||||
if !enumerated_device_paths.is_empty() {
|
||||
return Err(format!(
|
||||
"UPower enumerated devices even though /scheme/acpi/power is unavailable: {}",
|
||||
summarize_set(&enumerated_device_paths)
|
||||
));
|
||||
}
|
||||
|
||||
println!("UPOWER_NATIVE_PATHS=skipped (power surface unavailable)");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let missing_device_paths = expected_device_paths
|
||||
.difference(&enumerated_device_paths)
|
||||
.cloned()
|
||||
@@ -736,6 +757,13 @@ mod tests {
|
||||
assert!(disk_paths.contains("/org/freedesktop/UDisks2/block_devices/disk_2e_nvme_0"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn power_runtime_without_surface_is_marked_unavailable() {
|
||||
let runtime = PowerRuntime::default();
|
||||
assert!(!runtime.available());
|
||||
assert!(runtime.expected_device_paths().is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn managed_object_keys_with_prefix_ignores_property_object_paths() {
|
||||
let output = r#"
|
||||
|
||||
Reference in New Issue
Block a user