Expand hwutils, udev-shim, and redbear-sessiond system recipes
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
@@ -9,6 +9,7 @@ template = "cargo"
|
|||||||
"/usr/bin/lsusb" = "lsusb"
|
"/usr/bin/lsusb" = "lsusb"
|
||||||
"/usr/bin/redbear-usb-check" = "redbear-usb-check"
|
"/usr/bin/redbear-usb-check" = "redbear-usb-check"
|
||||||
"/usr/bin/redbear-bluetooth-battery-check" = "redbear-bluetooth-battery-check"
|
"/usr/bin/redbear-bluetooth-battery-check" = "redbear-bluetooth-battery-check"
|
||||||
|
"/usr/bin/redbear-drm-display-check" = "redbear-drm-display-check"
|
||||||
"/usr/bin/redbear-phase4-wayland-check" = "redbear-phase4-wayland-check"
|
"/usr/bin/redbear-phase4-wayland-check" = "redbear-phase4-wayland-check"
|
||||||
"/usr/bin/redbear-phase5-network-check" = "redbear-phase5-network-check"
|
"/usr/bin/redbear-phase5-network-check" = "redbear-phase5-network-check"
|
||||||
"/usr/bin/redbear-phase5-wifi-check" = "redbear-phase5-wifi-check"
|
"/usr/bin/redbear-phase5-wifi-check" = "redbear-phase5-wifi-check"
|
||||||
|
|||||||
@@ -59,6 +59,10 @@ path = "src/bin/redbear-phase5-wifi-link-check.rs"
|
|||||||
name = "redbear-phase6-kde-check"
|
name = "redbear-phase6-kde-check"
|
||||||
path = "src/bin/redbear-phase6-kde-check.rs"
|
path = "src/bin/redbear-phase6-kde-check.rs"
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "redbear-drm-display-check"
|
||||||
|
path = "src/bin/redbear-drm-display-check.rs"
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "redbear-phase-iommu-check"
|
name = "redbear-phase-iommu-check"
|
||||||
path = "src/bin/redbear-phase-iommu-check.rs"
|
path = "src/bin/redbear-phase-iommu-check.rs"
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
use std::fs;
|
use std::fs;
|
||||||
use std::process;
|
use std::process;
|
||||||
|
|
||||||
use redbear_hwutils::{parse_args, parse_pci_location, PciLocation};
|
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::PciDeviceInfo;
|
||||||
use redox_driver_sys::quirks::{lookup_pci_quirks, PciQuirkFlags};
|
use redox_driver_sys::quirks::{lookup_pci_quirks, PciQuirkFlags};
|
||||||
|
|
||||||
@@ -153,6 +155,11 @@ fn run() -> Result<(), String> {
|
|||||||
device.device_id,
|
device.device_id,
|
||||||
device.revision,
|
device.revision,
|
||||||
);
|
);
|
||||||
|
if let Some(device_name) = lookup_pci_device_name(device.vendor_id, device.device_id) {
|
||||||
|
print!(" ({device_name})");
|
||||||
|
} else if let Some(vendor_name) = lookup_pci_vendor_name(device.vendor_id) {
|
||||||
|
print!(" ({vendor_name})");
|
||||||
|
}
|
||||||
if !device.quirk_flags.is_empty() {
|
if !device.quirk_flags.is_empty() {
|
||||||
print!(" quirks: {}", format_quirk_flags(device.quirk_flags));
|
print!(" quirks: {}", format_quirk_flags(device.quirk_flags));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,631 @@
|
|||||||
|
use std::fs::{File, OpenOptions};
|
||||||
|
use std::io::{Read, Write};
|
||||||
|
use std::mem::{size_of, MaybeUninit};
|
||||||
|
use std::path::Path;
|
||||||
|
use std::process::{self};
|
||||||
|
|
||||||
|
const PROGRAM: &str = "redbear-drm-display-check";
|
||||||
|
const USAGE: &str = "Usage: redbear-drm-display-check --vendor amd|intel [--card /scheme/drm/card0] [--modeset CONNECTOR:MODE]\n\nBounded DRM/KMS display validation checker. This proves only display-path evidence, not render proof.";
|
||||||
|
|
||||||
|
const DRM_IOCTL_BASE: usize = 0x00A0;
|
||||||
|
const DRM_IOCTL_MODE_GETRESOURCES: usize = DRM_IOCTL_BASE;
|
||||||
|
const DRM_IOCTL_MODE_SETCRTC: usize = DRM_IOCTL_BASE + 2;
|
||||||
|
const DRM_IOCTL_MODE_GETCRTC: usize = DRM_IOCTL_BASE + 3;
|
||||||
|
const DRM_IOCTL_MODE_GETENCODER: usize = DRM_IOCTL_BASE + 6;
|
||||||
|
const DRM_IOCTL_MODE_GETCONNECTOR: usize = DRM_IOCTL_BASE + 7;
|
||||||
|
const DRM_IOCTL_MODE_GETMODES: usize = DRM_IOCTL_BASE + 8;
|
||||||
|
const DRM_IOCTL_MODE_CREATE_DUMB: usize = DRM_IOCTL_BASE + 18;
|
||||||
|
const DRM_IOCTL_MODE_DESTROY_DUMB: usize = DRM_IOCTL_BASE + 20;
|
||||||
|
const DRM_IOCTL_MODE_ADDFB: usize = DRM_IOCTL_BASE + 21;
|
||||||
|
const DRM_IOCTL_MODE_RMFB: usize = DRM_IOCTL_BASE + 22;
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Clone, Copy, Debug, Default)]
|
||||||
|
struct DrmResourcesWire {
|
||||||
|
connector_count: u32,
|
||||||
|
crtc_count: u32,
|
||||||
|
encoder_count: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
struct ResourcesSummary {
|
||||||
|
connector_count: u32,
|
||||||
|
connector_ids: Vec<u32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Clone, Copy, Debug, Default)]
|
||||||
|
struct DrmConnectorWire {
|
||||||
|
connector_id: u32,
|
||||||
|
connection: u32,
|
||||||
|
connector_type: u32,
|
||||||
|
mm_width: u32,
|
||||||
|
mm_height: u32,
|
||||||
|
encoder_id: u32,
|
||||||
|
mode_count: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Clone, Copy, Debug, Default)]
|
||||||
|
struct DrmModeWire {
|
||||||
|
clock: u32,
|
||||||
|
hdisplay: u16,
|
||||||
|
hsync_start: u16,
|
||||||
|
hsync_end: u16,
|
||||||
|
htotal: u16,
|
||||||
|
hskew: u16,
|
||||||
|
vdisplay: u16,
|
||||||
|
vsync_start: u16,
|
||||||
|
vsync_end: u16,
|
||||||
|
vtotal: u16,
|
||||||
|
vscan: u16,
|
||||||
|
vrefresh: u32,
|
||||||
|
flags: u32,
|
||||||
|
type_: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Clone, Copy, Debug, Default)]
|
||||||
|
struct DrmSetCrtcWire {
|
||||||
|
crtc_id: u32,
|
||||||
|
fb_handle: u32,
|
||||||
|
connector_count: u32,
|
||||||
|
connectors: [u32; 8],
|
||||||
|
mode: DrmModeWire,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Clone, Copy, Debug, Default)]
|
||||||
|
struct DrmCreateDumbWire {
|
||||||
|
width: u32,
|
||||||
|
height: u32,
|
||||||
|
bpp: u32,
|
||||||
|
flags: u32,
|
||||||
|
pitch: u32,
|
||||||
|
size: u64,
|
||||||
|
handle: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Clone, Copy, Debug, Default)]
|
||||||
|
struct DrmDestroyDumbWire {
|
||||||
|
handle: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Clone, Copy, Debug, Default)]
|
||||||
|
struct DrmGetEncoderWire {
|
||||||
|
encoder_id: u32,
|
||||||
|
encoder_type: u32,
|
||||||
|
crtc_id: u32,
|
||||||
|
possible_crtcs: u32,
|
||||||
|
possible_clones: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Clone, Copy, Debug, Default)]
|
||||||
|
struct DrmAddFbWire {
|
||||||
|
width: u32,
|
||||||
|
height: u32,
|
||||||
|
pitch: u32,
|
||||||
|
bpp: u32,
|
||||||
|
depth: u32,
|
||||||
|
handle: u32,
|
||||||
|
fb_id: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Clone, Copy, Debug, Default)]
|
||||||
|
struct DrmRmFbWire {
|
||||||
|
fb_id: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Clone, Copy, Debug, Default)]
|
||||||
|
struct DrmGetCrtcWire {
|
||||||
|
crtc_id: u32,
|
||||||
|
fb_id: u32,
|
||||||
|
x: u32,
|
||||||
|
y: u32,
|
||||||
|
mode_valid: u32,
|
||||||
|
mode: DrmModeWire,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
struct ModeSummary {
|
||||||
|
wire: DrmModeWire,
|
||||||
|
name: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
struct ConnectorSummary {
|
||||||
|
id: u32,
|
||||||
|
mode_count: u32,
|
||||||
|
encoder_id: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn require_path(path: &str, label: &str) -> Result<(), String> {
|
||||||
|
if Path::new(path).exists() {
|
||||||
|
println!("{label}=ok");
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(format!("{label}=missing"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_args() -> Result<(String, String, Option<String>), String> {
|
||||||
|
let mut vendor = None;
|
||||||
|
let mut card = "/scheme/drm/card0".to_string();
|
||||||
|
let mut modeset = None;
|
||||||
|
|
||||||
|
let mut args = std::env::args().skip(1);
|
||||||
|
while let Some(arg) = args.next() {
|
||||||
|
match arg.as_str() {
|
||||||
|
"--vendor" => vendor = args.next(),
|
||||||
|
"--card" => card = args.next().ok_or_else(|| "missing value for --card".to_string())?,
|
||||||
|
"--modeset" => {
|
||||||
|
modeset = Some(args.next().ok_or_else(|| "missing value for --modeset".to_string())?)
|
||||||
|
}
|
||||||
|
"-h" | "--help" => {
|
||||||
|
println!("{USAGE}");
|
||||||
|
process::exit(0);
|
||||||
|
}
|
||||||
|
_ => return Err(format!("unsupported argument: {arg}")),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let vendor = vendor.ok_or_else(|| "missing --vendor amd|intel".to_string())?;
|
||||||
|
if vendor != "amd" && vendor != "intel" {
|
||||||
|
return Err(format!("unsupported vendor '{vendor}'"));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok((vendor, card, modeset))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn decode_wire<T: Copy>(bytes: &[u8]) -> Result<T, String> {
|
||||||
|
if bytes.len() < size_of::<T>() {
|
||||||
|
return Err(format!(
|
||||||
|
"short DRM response: expected {} bytes, got {}",
|
||||||
|
size_of::<T>(),
|
||||||
|
bytes.len()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
let mut out = MaybeUninit::<T>::uninit();
|
||||||
|
unsafe {
|
||||||
|
std::ptr::copy_nonoverlapping(bytes.as_ptr(), out.as_mut_ptr().cast::<u8>(), size_of::<T>());
|
||||||
|
Ok(out.assume_init())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bytes_of<T>(value: &T) -> &[u8] {
|
||||||
|
unsafe { std::slice::from_raw_parts((value as *const T).cast::<u8>(), size_of::<T>()) }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn open_drm_card(card_path: &str) -> Result<File, String> {
|
||||||
|
OpenOptions::new()
|
||||||
|
.read(true)
|
||||||
|
.write(true)
|
||||||
|
.open(card_path)
|
||||||
|
.map_err(|err| format!("failed to open {card_path}: {err}"))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn drm_query(file: &mut File, request: usize, payload: &[u8]) -> Result<Vec<u8>, String> {
|
||||||
|
let mut request_buf = request.to_le_bytes().to_vec();
|
||||||
|
request_buf.extend_from_slice(payload);
|
||||||
|
file.write_all(&request_buf)
|
||||||
|
.map_err(|err| format!("failed to send DRM ioctl {request:#x}: {err}"))?;
|
||||||
|
|
||||||
|
let mut response = vec![0u8; 8192];
|
||||||
|
let len = file
|
||||||
|
.read(&mut response)
|
||||||
|
.map_err(|err| format!("failed to read DRM ioctl {request:#x} response: {err}"))?;
|
||||||
|
response.truncate(len);
|
||||||
|
Ok(response)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn query_empty(file: &mut File, request: usize, payload: &[u8]) -> Result<(), String> {
|
||||||
|
let response = drm_query(file, request, payload)?;
|
||||||
|
if response == [0] || response.is_empty() {
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(format!("unexpected non-empty response for ioctl {request:#x}"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn query_resources(file: &mut File) -> Result<ResourcesSummary, String> {
|
||||||
|
let response = drm_query(file, DRM_IOCTL_MODE_GETRESOURCES, &[])?;
|
||||||
|
let header = decode_wire::<DrmResourcesWire>(&response)?;
|
||||||
|
let mut connector_ids = Vec::new();
|
||||||
|
let mut offset = size_of::<DrmResourcesWire>();
|
||||||
|
for _ in 0..header.connector_count {
|
||||||
|
if response.len() < offset + size_of::<u32>() {
|
||||||
|
return Err("resources response missing connector id payload".to_string());
|
||||||
|
}
|
||||||
|
connector_ids.push(decode_wire::<u32>(&response[offset..offset + size_of::<u32>()])?);
|
||||||
|
offset += size_of::<u32>();
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(ResourcesSummary {
|
||||||
|
connector_count: header.connector_count,
|
||||||
|
connector_ids,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn query_connector(file: &mut File, connector_id: u32) -> Result<DrmConnectorWire, String> {
|
||||||
|
let response = drm_query(file, DRM_IOCTL_MODE_GETCONNECTOR, &connector_id.to_le_bytes())?;
|
||||||
|
decode_wire(&response)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn query_modes(file: &mut File, connector_id: u32) -> Result<Vec<ModeSummary>, String> {
|
||||||
|
let response = drm_query(file, DRM_IOCTL_MODE_GETMODES, &connector_id.to_le_bytes())?;
|
||||||
|
if response == [0] {
|
||||||
|
return Ok(Vec::new());
|
||||||
|
}
|
||||||
|
|
||||||
|
let mode_size = size_of::<DrmModeWire>();
|
||||||
|
let mut modes = Vec::new();
|
||||||
|
let mut offset = 0usize;
|
||||||
|
|
||||||
|
while offset < response.len() {
|
||||||
|
if response.len() - offset < mode_size {
|
||||||
|
return Err(format!(
|
||||||
|
"truncated mode response: {} trailing bytes left",
|
||||||
|
response.len() - offset
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let mode = decode_wire::<DrmModeWire>(&response[offset..offset + mode_size])?;
|
||||||
|
offset += mode_size;
|
||||||
|
|
||||||
|
let name_bytes = &response[offset..];
|
||||||
|
let Some(name_len) = name_bytes.iter().position(|byte| *byte == 0) else {
|
||||||
|
return Err("mode response missing trailing NUL after mode name".to_string());
|
||||||
|
};
|
||||||
|
let name = String::from_utf8_lossy(&name_bytes[..name_len]).to_string();
|
||||||
|
offset += name_len + 1;
|
||||||
|
modes.push(ModeSummary { wire: mode, name });
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(modes)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn enumerate_connectors(file: &mut File) -> Result<Vec<ConnectorSummary>, String> {
|
||||||
|
let resources = query_resources(file)?;
|
||||||
|
let mut found = Vec::new();
|
||||||
|
|
||||||
|
for connector_id in resources.connector_ids {
|
||||||
|
let Ok(connector) = query_connector(file, connector_id) else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
found.push(ConnectorSummary {
|
||||||
|
id: connector.connector_id,
|
||||||
|
mode_count: connector.mode_count,
|
||||||
|
encoder_id: connector.encoder_id,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if found.is_empty() && resources.connector_count != 0 {
|
||||||
|
return Err("DRM_CONNECTOR_ENUM=missing".to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(found)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn query_encoder(file: &mut File, encoder_id: u32) -> Result<DrmGetEncoderWire, String> {
|
||||||
|
let response = drm_query(file, DRM_IOCTL_MODE_GETENCODER, &encoder_id.to_le_bytes())?;
|
||||||
|
decode_wire(&response)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn query_addfb(file: &mut File, request: &DrmAddFbWire) -> Result<DrmAddFbWire, String> {
|
||||||
|
let response = drm_query(file, DRM_IOCTL_MODE_ADDFB, bytes_of(request))?;
|
||||||
|
decode_wire(&response)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn query_create_dumb(file: &mut File, request: &DrmCreateDumbWire) -> Result<DrmCreateDumbWire, String> {
|
||||||
|
let response = drm_query(file, DRM_IOCTL_MODE_CREATE_DUMB, bytes_of(request))?;
|
||||||
|
decode_wire(&response)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn query_get_crtc(file: &mut File, request: &DrmGetCrtcWire) -> Result<DrmGetCrtcWire, String> {
|
||||||
|
let response = drm_query(file, DRM_IOCTL_MODE_GETCRTC, bytes_of(request))?;
|
||||||
|
decode_wire(&response)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn find_mode<'a>(modes: &'a [ModeSummary], name: &str) -> Option<&'a ModeSummary> {
|
||||||
|
modes.iter().find(|mode| mode.name == name)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_modeset_spec(spec: &str) -> Result<(u32, &str), String> {
|
||||||
|
let (connector_text, mode_name) = spec
|
||||||
|
.split_once(':')
|
||||||
|
.ok_or_else(|| "--modeset must be CONNECTOR:MODE".to_string())?;
|
||||||
|
let connector_id = connector_text
|
||||||
|
.parse::<u32>()
|
||||||
|
.map_err(|err| format!("invalid connector id '{connector_text}': {err}"))?;
|
||||||
|
Ok((connector_id, mode_name))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn disable_crtc_request(crtc_id: u32) -> DrmSetCrtcWire {
|
||||||
|
DrmSetCrtcWire {
|
||||||
|
crtc_id,
|
||||||
|
fb_handle: 0,
|
||||||
|
connector_count: 0,
|
||||||
|
connectors: [0; 8],
|
||||||
|
mode: DrmModeWire::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn setcrtc_request(crtc_id: u32, connector_id: u32, fb_id: u32, mode: DrmModeWire) -> DrmSetCrtcWire {
|
||||||
|
let mut request = DrmSetCrtcWire {
|
||||||
|
crtc_id,
|
||||||
|
fb_handle: fb_id,
|
||||||
|
connector_count: 1,
|
||||||
|
connectors: [0; 8],
|
||||||
|
mode,
|
||||||
|
};
|
||||||
|
request.connectors[0] = connector_id;
|
||||||
|
request
|
||||||
|
}
|
||||||
|
|
||||||
|
fn proof_teardown_requests(
|
||||||
|
crtc_id: u32,
|
||||||
|
fb_id: u32,
|
||||||
|
gem_handle: u32,
|
||||||
|
) -> (DrmSetCrtcWire, DrmRmFbWire, DrmDestroyDumbWire) {
|
||||||
|
(
|
||||||
|
disable_crtc_request(crtc_id),
|
||||||
|
DrmRmFbWire { fb_id },
|
||||||
|
DrmDestroyDumbWire { handle: gem_handle },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bounded_modeset_proof(
|
||||||
|
file: &mut File,
|
||||||
|
connectors: &[ConnectorSummary],
|
||||||
|
spec: &str,
|
||||||
|
) -> Result<(), String> {
|
||||||
|
let (connector_id, mode_name) = parse_modeset_spec(spec)?;
|
||||||
|
|
||||||
|
let connector = connectors
|
||||||
|
.iter()
|
||||||
|
.find(|connector| connector.id == connector_id)
|
||||||
|
.ok_or_else(|| format!("connector {connector_id} not found in enumeration results"))?;
|
||||||
|
|
||||||
|
let modes = query_modes(file, connector_id)?;
|
||||||
|
let mode = find_mode(&modes, mode_name)
|
||||||
|
.ok_or_else(|| format!("mode '{mode_name}' not found on connector {connector_id}"))?;
|
||||||
|
|
||||||
|
let encoder = query_encoder(file, connector.encoder_id)?;
|
||||||
|
let crtc_id = encoder.crtc_id;
|
||||||
|
if crtc_id == 0 {
|
||||||
|
return Err(format!("connector {connector_id} encoder did not report a usable CRTC"));
|
||||||
|
}
|
||||||
|
|
||||||
|
let create = query_create_dumb(
|
||||||
|
file,
|
||||||
|
&DrmCreateDumbWire {
|
||||||
|
width: mode.wire.hdisplay as u32,
|
||||||
|
height: mode.wire.vdisplay as u32,
|
||||||
|
bpp: 32,
|
||||||
|
..DrmCreateDumbWire::default()
|
||||||
|
},
|
||||||
|
)?;
|
||||||
|
let addfb = query_addfb(
|
||||||
|
file,
|
||||||
|
&DrmAddFbWire {
|
||||||
|
width: mode.wire.hdisplay as u32,
|
||||||
|
height: mode.wire.vdisplay as u32,
|
||||||
|
pitch: create.pitch,
|
||||||
|
bpp: 32,
|
||||||
|
depth: 24,
|
||||||
|
handle: create.handle,
|
||||||
|
..DrmAddFbWire::default()
|
||||||
|
},
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let setcrtc = setcrtc_request(crtc_id, connector_id, addfb.fb_id, mode.wire);
|
||||||
|
query_empty(file, DRM_IOCTL_MODE_SETCRTC, bytes_of(&setcrtc))?;
|
||||||
|
|
||||||
|
let getcrtc = query_get_crtc(
|
||||||
|
file,
|
||||||
|
&DrmGetCrtcWire {
|
||||||
|
crtc_id,
|
||||||
|
..DrmGetCrtcWire::default()
|
||||||
|
},
|
||||||
|
)?;
|
||||||
|
if getcrtc.fb_id != addfb.fb_id || getcrtc.mode_valid == 0 {
|
||||||
|
return Err("GETCRTC did not confirm the programmed framebuffer/mode".to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
let (disable, rmfb, destroy) = proof_teardown_requests(crtc_id, addfb.fb_id, create.handle);
|
||||||
|
query_empty(file, DRM_IOCTL_MODE_SETCRTC, bytes_of(&disable))?;
|
||||||
|
query_empty(file, DRM_IOCTL_MODE_RMFB, bytes_of(&rmfb))?;
|
||||||
|
query_empty(file, DRM_IOCTL_MODE_DESTROY_DUMB, bytes_of(&destroy))?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
fn has_connector_section(text: &str) -> bool {
|
||||||
|
text.contains("Connectors:")
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
fn has_mode_lines(text: &str) -> bool {
|
||||||
|
text.lines().any(|line| {
|
||||||
|
let trimmed = line.trim_start();
|
||||||
|
let mut parts = trimmed.split_whitespace();
|
||||||
|
matches!(
|
||||||
|
(parts.next(), parts.next()),
|
||||||
|
(Some(id), Some(mode)) if id.chars().all(|c| c.is_ascii_digit()) && mode.contains('x')
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run() -> Result<(), String> {
|
||||||
|
let (vendor, card_path, modeset) = parse_args()?;
|
||||||
|
|
||||||
|
println!("=== Red Bear DRM Display Runtime Check ===");
|
||||||
|
println!("DRM_VENDOR={vendor}");
|
||||||
|
println!("DRM_CARD={card_path}");
|
||||||
|
|
||||||
|
require_path("/scheme/drm", "DRM_SCHEME")?;
|
||||||
|
require_path(&card_path, "DRM_CARD_NODE")?;
|
||||||
|
|
||||||
|
let mut drm = open_drm_card(&card_path)?;
|
||||||
|
let connectors = enumerate_connectors(&mut drm)?;
|
||||||
|
println!("DRM_CONNECTOR_ENUM=ok");
|
||||||
|
|
||||||
|
let mut mode_lines_found = false;
|
||||||
|
for connector in &connectors {
|
||||||
|
let modes = query_modes(&mut drm, connector.id)?;
|
||||||
|
if !modes.is_empty() && modes.len() as u32 == connector.mode_count {
|
||||||
|
mode_lines_found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !mode_lines_found {
|
||||||
|
return Err("DRM_MODE_ENUM=missing".to_string());
|
||||||
|
}
|
||||||
|
println!("DRM_MODE_ENUM=ok");
|
||||||
|
println!("DRM_ENUMERATION=ok");
|
||||||
|
|
||||||
|
if let Some(spec) = modeset {
|
||||||
|
if let Err(err) = bounded_modeset_proof(&mut drm, &connectors, &spec) {
|
||||||
|
println!("DRM_MODESET_PROOF=failed");
|
||||||
|
println!("DRM_MODESET_SPEC={spec}");
|
||||||
|
return Err(err);
|
||||||
|
}
|
||||||
|
println!("DRM_MODESET_PROOF=ok");
|
||||||
|
println!("DRM_MODESET_SPEC={spec}");
|
||||||
|
} else {
|
||||||
|
println!("DRM_MODESET_PROOF=skipped_no_spec");
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("DRM_RENDER_PROOF=not_attempted");
|
||||||
|
println!("DRM_TRANCHE_SUMMARY=display_validation_only");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
if let Err(err) = run() {
|
||||||
|
eprintln!("{PROGRAM}: {err}");
|
||||||
|
process::exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::{bytes_of, decode_wire, disable_crtc_request, find_mode, has_connector_section, has_mode_lines, parse_modeset_spec, proof_teardown_requests, setcrtc_request, DrmModeWire, DrmResourcesWire, ModeSummary};
|
||||||
|
|
||||||
|
fn owned_bytes_of<T>(value: &T) -> Vec<u8> {
|
||||||
|
unsafe {
|
||||||
|
std::slice::from_raw_parts((value as *const T).cast::<u8>(), size_of::<T>()).to_vec()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn connector_section_detected() {
|
||||||
|
assert!(has_connector_section("foo\nConnectors:\nbar"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn mode_lines_detected() {
|
||||||
|
assert!(has_mode_lines(" 42 1920x1080 60.00"));
|
||||||
|
assert!(!has_mode_lines("Connectors:\nnone"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn query_modes_accepts_empty_sentinel() {
|
||||||
|
let parsed = if vec![0] == [0] { Vec::<DrmModeWire>::new() } else { unreachable!() };
|
||||||
|
assert!(parsed.is_empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn resources_header_plus_connector_ids_round_trip() {
|
||||||
|
let header = DrmResourcesWire {
|
||||||
|
connector_count: 2,
|
||||||
|
crtc_count: 1,
|
||||||
|
encoder_count: 2,
|
||||||
|
};
|
||||||
|
let mut payload = owned_bytes_of(&header);
|
||||||
|
payload.extend_from_slice(&1u32.to_ne_bytes());
|
||||||
|
payload.extend_from_slice(&7u32.to_ne_bytes());
|
||||||
|
|
||||||
|
let decoded = decode_wire::<DrmResourcesWire>(&payload).unwrap();
|
||||||
|
assert_eq!(decoded.connector_count, 2);
|
||||||
|
let first = decode_wire::<u32>(&payload[size_of::<DrmResourcesWire>()..]).unwrap();
|
||||||
|
let second = decode_wire::<u32>(&payload[size_of::<DrmResourcesWire>() + 4..]).unwrap();
|
||||||
|
assert_eq!(first, 1);
|
||||||
|
assert_eq!(second, 7);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn mode_wire_decode_round_trip_works() {
|
||||||
|
let mode = DrmModeWire {
|
||||||
|
hdisplay: 1920,
|
||||||
|
vdisplay: 1080,
|
||||||
|
vrefresh: 60,
|
||||||
|
..DrmModeWire::default()
|
||||||
|
};
|
||||||
|
let decoded = decode_wire::<DrmModeWire>(bytes_of(&mode)).unwrap();
|
||||||
|
assert_eq!(decoded.hdisplay, 1920);
|
||||||
|
assert_eq!(decoded.vdisplay, 1080);
|
||||||
|
assert_eq!(decoded.vrefresh, 60);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn find_mode_matches_by_name() {
|
||||||
|
let modes = vec![ModeSummary {
|
||||||
|
wire: DrmModeWire::default(),
|
||||||
|
name: "1920x1080@60".to_string(),
|
||||||
|
}];
|
||||||
|
|
||||||
|
assert!(find_mode(&modes, "1920x1080@60").is_some());
|
||||||
|
assert!(find_mode(&modes, "1280x720@60").is_none());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_modeset_spec_accepts_connector_and_mode() {
|
||||||
|
let (connector, mode) = parse_modeset_spec("7:1920x1080@60").unwrap();
|
||||||
|
|
||||||
|
assert_eq!(connector, 7);
|
||||||
|
assert_eq!(mode, "1920x1080@60");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_modeset_spec_rejects_bad_shape() {
|
||||||
|
assert!(parse_modeset_spec("broken-spec").is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn disable_crtc_request_zeroes_active_state() {
|
||||||
|
let request = disable_crtc_request(3);
|
||||||
|
|
||||||
|
assert_eq!(request.crtc_id, 3);
|
||||||
|
assert_eq!(request.fb_handle, 0);
|
||||||
|
assert_eq!(request.connector_count, 0);
|
||||||
|
assert!(request.connectors.iter().all(|&value| value == 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn setcrtc_request_targets_single_connector_and_fb() {
|
||||||
|
let request = setcrtc_request(3, 7, 11, DrmModeWire::default());
|
||||||
|
|
||||||
|
assert_eq!(request.crtc_id, 3);
|
||||||
|
assert_eq!(request.fb_handle, 11);
|
||||||
|
assert_eq!(request.connector_count, 1);
|
||||||
|
assert_eq!(request.connectors[0], 7);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn proof_teardown_requests_disable_then_release_resources() {
|
||||||
|
let (disable, rmfb, destroy) = proof_teardown_requests(3, 11, 22);
|
||||||
|
|
||||||
|
assert_eq!(disable.crtc_id, 3);
|
||||||
|
assert_eq!(disable.fb_handle, 0);
|
||||||
|
assert_eq!(disable.connector_count, 0);
|
||||||
|
assert_eq!(rmfb.fb_id, 11);
|
||||||
|
assert_eq!(destroy.handle, 22);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -60,9 +60,8 @@ fn run() -> Result<(), String> {
|
|||||||
})?;
|
})?;
|
||||||
|
|
||||||
println!("=== Red Bear OS Phase 4 Wayland Runtime Check ===");
|
println!("=== Red Bear OS Phase 4 Wayland Runtime Check ===");
|
||||||
require_path("/usr/bin/orbital-wayland")?;
|
require_path("/usr/bin/redbear-validation-session")?;
|
||||||
require_path("/usr/bin/wayland-session")?;
|
require_path("/usr/bin/wayland-session")?;
|
||||||
require_path("/usr/bin/smallvil")?;
|
|
||||||
require_path("/usr/bin/qt6-bootstrap-check")?;
|
require_path("/usr/bin/qt6-bootstrap-check")?;
|
||||||
require_path("/usr/bin/qt6-plugin-check")?;
|
require_path("/usr/bin/qt6-plugin-check")?;
|
||||||
require_path("/usr/bin/qt6-wayland-smoke")?;
|
require_path("/usr/bin/qt6-wayland-smoke")?;
|
||||||
|
|||||||
@@ -204,7 +204,7 @@ fn run() -> Result<(), String> {
|
|||||||
})?;
|
})?;
|
||||||
|
|
||||||
println!("=== Red Bear OS Phase 6 KDE Runtime Check ===");
|
println!("=== Red Bear OS Phase 6 KDE Runtime Check ===");
|
||||||
require_path("/usr/bin/orbital-kde")?;
|
require_path("/usr/bin/redbear-kde-session")?;
|
||||||
require_path("/usr/bin/kwin_wayland")?;
|
require_path("/usr/bin/kwin_wayland")?;
|
||||||
require_path("/usr/bin/dbus-daemon")?;
|
require_path("/usr/bin/dbus-daemon")?;
|
||||||
require_path("/usr/bin/seatd")?;
|
require_path("/usr/bin/seatd")?;
|
||||||
|
|||||||
@@ -1,4 +1,17 @@
|
|||||||
|
use std::collections::HashMap;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
use std::fs;
|
||||||
|
use std::sync::OnceLock;
|
||||||
|
|
||||||
|
const PCI_IDS_PATH: &str = "/usr/share/misc/pci.ids";
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
struct PciIdDatabase {
|
||||||
|
vendor_names: HashMap<u16, String>,
|
||||||
|
device_names: HashMap<(u16, u16), String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
static PCI_ID_DATABASE: OnceLock<Option<PciIdDatabase>> = OnceLock::new();
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
|
#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
|
||||||
pub struct PciLocation {
|
pub struct PciLocation {
|
||||||
@@ -78,3 +91,110 @@ pub fn describe_usb_device(manufacturer: Option<&str>, product: Option<&str>) ->
|
|||||||
parts.join(" ")
|
parts.join(" ")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn load_pci_id_database() -> Option<PciIdDatabase> {
|
||||||
|
let text = fs::read_to_string(PCI_IDS_PATH).ok()?;
|
||||||
|
Some(parse_pci_id_database(&text))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_pci_id_database(text: &str) -> PciIdDatabase {
|
||||||
|
let mut database = PciIdDatabase::default();
|
||||||
|
let mut current_vendor = None;
|
||||||
|
|
||||||
|
for line in text.lines() {
|
||||||
|
if line.is_empty() || line.starts_with('#') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(rest) = line.strip_prefix("\t\t") {
|
||||||
|
let _ = rest;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(rest) = line.strip_prefix('\t') {
|
||||||
|
let Some(vendor_id) = current_vendor else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
let mut parts = rest.splitn(2, char::is_whitespace).filter(|part| !part.is_empty());
|
||||||
|
let Some(device_hex) = parts.next() else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
let Some(name) = parts.next() else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
let Ok(device_id) = u16::from_str_radix(device_hex, 16) else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
database
|
||||||
|
.device_names
|
||||||
|
.insert((vendor_id, device_id), name.trim().to_string());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut parts = line.splitn(2, char::is_whitespace).filter(|part| !part.is_empty());
|
||||||
|
let Some(vendor_hex) = parts.next() else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
let Some(name) = parts.next() else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
let Ok(vendor_id) = u16::from_str_radix(vendor_hex, 16) else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
current_vendor = Some(vendor_id);
|
||||||
|
database.vendor_names.insert(vendor_id, name.trim().to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
database
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pci_id_database() -> Option<&'static PciIdDatabase> {
|
||||||
|
PCI_ID_DATABASE.get_or_init(load_pci_id_database).as_ref()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn lookup_pci_vendor_name(vendor_id: u16) -> Option<String> {
|
||||||
|
pci_id_database()?.vendor_names.get(&vendor_id).cloned()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn lookup_pci_device_name(vendor_id: u16, device_id: u16) -> Option<String> {
|
||||||
|
pci_id_database()?
|
||||||
|
.device_names
|
||||||
|
.get(&(vendor_id, device_id))
|
||||||
|
.cloned()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::parse_pci_id_database;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parses_vendor_and_device_entries_from_pci_ids() {
|
||||||
|
let db = parse_pci_id_database(
|
||||||
|
"8086 Intel Corporation\n\t46A6 Alder Lake-P Integrated Graphics Controller\n1002 Advanced Micro Devices, Inc. [AMD/ATI]\n\t7480 Navi 32 [Radeon RX 7800 XT / 7700 XT]\n",
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
db.vendor_names.get(&0x8086).map(String::as_str),
|
||||||
|
Some("Intel Corporation")
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
db.device_names.get(&(0x8086, 0x46A6)).map(String::as_str),
|
||||||
|
Some("Alder Lake-P Integrated Graphics Controller")
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
db.device_names.get(&(0x1002, 0x7480)).map(String::as_str),
|
||||||
|
Some("Navi 32 [Radeon RX 7800 XT / 7700 XT]")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn ignores_subsystem_lines_and_comments() {
|
||||||
|
let db = parse_pci_id_database(
|
||||||
|
"# comment\n8086 Intel Corporation\n\t46A6 Alder Lake-P Integrated Graphics Controller\n\t\t17AA 3C6A Lenovo variant\n",
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(db.vendor_names.len(), 1);
|
||||||
|
assert_eq!(db.device_names.len(), 1);
|
||||||
|
assert!(db.device_names.get(&(0x17AA, 0x3C6A)).is_none());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,67 +1,42 @@
|
|||||||
use std::sync::atomic::{AtomicBool, Ordering};
|
|
||||||
use std::time::Duration;
|
|
||||||
use zbus::Connection;
|
use zbus::Connection;
|
||||||
|
|
||||||
static SLEEP_ACTIVE: AtomicBool = AtomicBool::new(false);
|
#[cfg(target_os = "redox")]
|
||||||
static SHUTDOWN_FIRED: AtomicBool = AtomicBool::new(false);
|
const KSTOP_PATH: &str = "/scheme/kernel.acpi/kstop";
|
||||||
|
|
||||||
const ACPI_SLEEP_PATH: &str = "/scheme/acpi/sleep";
|
#[cfg(target_os = "redox")]
|
||||||
const ACPI_SHUTDOWN_PATH: &str = "/scheme/acpi/shutdown";
|
fn wait_for_shutdown_edge() -> std::io::Result<()> {
|
||||||
const POLL_INTERVAL: Duration = Duration::from_secs(5);
|
use std::io::Read;
|
||||||
|
|
||||||
fn read_acpi_flag(path: &str) -> bool {
|
let mut file = std::fs::File::open(KSTOP_PATH)?;
|
||||||
match std::fs::read_to_string(path) {
|
let mut byte = [0_u8; 1];
|
||||||
Ok(content) => {
|
let _ = file.read(&mut byte)?;
|
||||||
let trimmed = content.trim().to_lowercase();
|
Ok(())
|
||||||
!trimmed.is_empty() && trimmed != "0"
|
|
||||||
}
|
|
||||||
Err(_) => false,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn watch_and_emit(connection: Connection) {
|
pub async fn watch_and_emit(connection: Connection) {
|
||||||
loop {
|
#[cfg(target_os = "redox")]
|
||||||
tokio::time::sleep(POLL_INTERVAL).await;
|
match tokio::task::spawn_blocking(wait_for_shutdown_edge).await {
|
||||||
|
Ok(Ok(())) => {
|
||||||
let sleep_now = tokio::task::spawn_blocking(|| read_acpi_flag(ACPI_SLEEP_PATH))
|
let _ = connection
|
||||||
.await
|
.emit_signal(
|
||||||
.unwrap_or(false);
|
None::<&str>,
|
||||||
|
"/org/freedesktop/login1",
|
||||||
let was_sleeping = SLEEP_ACTIVE.load(Ordering::Relaxed);
|
"org.freedesktop.login1.Manager",
|
||||||
|
"PrepareForShutdown",
|
||||||
if sleep_now && !was_sleeping {
|
&true,
|
||||||
SLEEP_ACTIVE.store(true, Ordering::Relaxed);
|
)
|
||||||
let _ = connection.emit_signal(
|
.await;
|
||||||
None::<&str>,
|
|
||||||
"/org/freedesktop/login1",
|
|
||||||
"org.freedesktop.login1.Manager",
|
|
||||||
"PrepareForSleep",
|
|
||||||
&true,
|
|
||||||
).await;
|
|
||||||
} else if !sleep_now && was_sleeping {
|
|
||||||
SLEEP_ACTIVE.store(false, Ordering::Relaxed);
|
|
||||||
let _ = connection.emit_signal(
|
|
||||||
None::<&str>,
|
|
||||||
"/org/freedesktop/login1",
|
|
||||||
"org.freedesktop.login1.Manager",
|
|
||||||
"PrepareForSleep",
|
|
||||||
&false,
|
|
||||||
).await;
|
|
||||||
}
|
}
|
||||||
|
Ok(Err(err)) => {
|
||||||
let shutdown_now = tokio::task::spawn_blocking(|| read_acpi_flag(ACPI_SHUTDOWN_PATH))
|
eprintln!("redbear-sessiond: ACPI shutdown watcher failed: {err}");
|
||||||
.await
|
}
|
||||||
.unwrap_or(false);
|
Err(err) => {
|
||||||
|
eprintln!("redbear-sessiond: ACPI shutdown watcher task failed: {err}");
|
||||||
if shutdown_now && !SHUTDOWN_FIRED.load(Ordering::Relaxed) {
|
|
||||||
SHUTDOWN_FIRED.store(true, Ordering::Relaxed);
|
|
||||||
let _ = connection.emit_signal(
|
|
||||||
None::<&str>,
|
|
||||||
"/org/freedesktop/login1",
|
|
||||||
"org.freedesktop.login1.Manager",
|
|
||||||
"PrepareForShutdown",
|
|
||||||
&true,
|
|
||||||
).await;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(not(target_os = "redox"))]
|
||||||
|
{
|
||||||
|
let _ = connection;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,3 +10,4 @@ redox-scheme = "0.11"
|
|||||||
syscall = { package = "redox_syscall", version = "0.7", features = ["std"] }
|
syscall = { package = "redox_syscall", version = "0.7", features = ["std"] }
|
||||||
log = { version = "0.4", features = ["std"] }
|
log = { version = "0.4", features = ["std"] }
|
||||||
thiserror = "2"
|
thiserror = "2"
|
||||||
|
redbear-hwutils = { path = "../../redbear-hwutils/source" }
|
||||||
|
|||||||
@@ -105,9 +105,14 @@ impl DeviceInfo {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn classify_pci_device(bus: u8, dev: u8, func: u8) -> DeviceInfo {
|
pub fn classify_pci_device(bus: u8, dev: u8, func: u8) -> DeviceInfo {
|
||||||
let devpath = format!("/devices/pci/{:04x}:{:02x}:{:02x}.{}", bus, 0, dev, func);
|
let location = PciLocation {
|
||||||
|
segment: 0,
|
||||||
let config_path = format!("/scheme/pci/{}.{}.{}", bus, dev, func);
|
bus,
|
||||||
|
device: dev,
|
||||||
|
function: func,
|
||||||
|
};
|
||||||
|
let devpath = format!("/devices/pci/{}", location);
|
||||||
|
let config_path = format!("{}/config", location.scheme_path());
|
||||||
let (vendor_id, device_id, class_code, subclass) = read_pci_config(&config_path);
|
let (vendor_id, device_id, class_code, subclass) = read_pci_config(&config_path);
|
||||||
let input_kind = detect_input_kind(class_code, subclass);
|
let input_kind = detect_input_kind(class_code, subclass);
|
||||||
|
|
||||||
@@ -174,10 +179,8 @@ fn format_device_name(
|
|||||||
subclass: u8,
|
subclass: u8,
|
||||||
input_kind: Option<InputKind>,
|
input_kind: Option<InputKind>,
|
||||||
) -> String {
|
) -> String {
|
||||||
if class_code == 0x03 {
|
if let Some(name) = lookup_pci_device_name(vendor_id, device_id) {
|
||||||
if let Some(name) = gpu_device_name(vendor_id, device_id) {
|
return format!("{name} [{vendor_id:04x}:{device_id:04x}]");
|
||||||
return format!("{name} [{vendor_id:04x}:{device_id:04x}]");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if class_code == 0x09 {
|
if class_code == 0x09 {
|
||||||
@@ -189,15 +192,7 @@ fn format_device_name(
|
|||||||
return format!("{name} [{vendor_id:04x}:{device_id:04x}]");
|
return format!("{name} [{vendor_id:04x}:{device_id:04x}]");
|
||||||
}
|
}
|
||||||
|
|
||||||
let vendor_name = match vendor_id {
|
let vendor_name = lookup_pci_vendor_name(vendor_id).unwrap_or_else(|| "Unknown".to_string());
|
||||||
0x8086 => "Intel",
|
|
||||||
0x1002 => "AMD",
|
|
||||||
0x10DE => "NVIDIA",
|
|
||||||
0x10EC => "Realtek",
|
|
||||||
0x8087 => "Intel",
|
|
||||||
0x14E4 => "Broadcom",
|
|
||||||
_ => "Unknown",
|
|
||||||
};
|
|
||||||
|
|
||||||
let class_name = match class_code {
|
let class_name = match class_code {
|
||||||
0x03 => "GPU",
|
0x03 => "GPU",
|
||||||
@@ -215,29 +210,22 @@ fn format_device_name(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn gpu_device_name(vendor_id: u16, device_id: u16) -> Option<&'static str> {
|
#[cfg(test)]
|
||||||
match vendor_id {
|
mod tests {
|
||||||
0x1002 => match device_id {
|
use super::classify_pci_device;
|
||||||
0x73A3 => Some("AMD Radeon RX 6600 XT / 6650 XT (RDNA2)"),
|
|
||||||
0x73BF => Some("AMD Radeon RX 6800 XT / 6900 XT (RDNA2)"),
|
#[test]
|
||||||
0x73DF => Some("AMD Radeon RX 6700 XT / 6750 XT (RDNA2)"),
|
fn classify_pci_device_uses_shared_location_format() {
|
||||||
0x73EF => Some("AMD Radeon RX 6800 / 6850M XT (RDNA2)"),
|
let device = classify_pci_device(0x02, 0x00, 0x0);
|
||||||
0x7422 => Some("AMD Radeon 780M (RDNA3)"),
|
|
||||||
0x7448 => Some("AMD Radeon RX 7900 XT (RDNA3)"),
|
assert_eq!(device.devpath, "/devices/pci/0000:02:00.0");
|
||||||
0x744C => Some("AMD Radeon RX 7900 XTX (RDNA3)"),
|
}
|
||||||
0x7480 => Some("AMD Radeon RX 7800 XT / 7700 XT (RDNA3)"),
|
|
||||||
_ => Some("AMD Radeon GPU"),
|
#[test]
|
||||||
},
|
fn id_path_tracks_shared_pci_devpath_shape() {
|
||||||
0x8086 => match device_id {
|
let device = classify_pci_device(0x02, 0x00, 0x0);
|
||||||
0x3E92 => Some("Intel UHD Graphics 630"),
|
|
||||||
0x5912 => Some("Intel HD Graphics 630"),
|
assert_eq!(device.id_path(), "pci-0000:02:00.0");
|
||||||
0x9A49 => Some("Intel Iris Xe Graphics (Tiger Lake)"),
|
|
||||||
0x46A6 => Some("Intel Iris Xe Graphics (Alder Lake-P)"),
|
|
||||||
0x56A0 => Some("Intel Arc Graphics (DG2)"),
|
|
||||||
0x56A1 => Some("Intel Arc A380 (DG2)"),
|
|
||||||
_ => Some("Intel Graphics"),
|
|
||||||
},
|
|
||||||
_ => None,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -305,3 +293,4 @@ pub fn format_uevent_info(dev: &DeviceInfo) -> String {
|
|||||||
}
|
}
|
||||||
info
|
info
|
||||||
}
|
}
|
||||||
|
use redbear_hwutils::{lookup_pci_device_name, lookup_pci_vendor_name, PciLocation};
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ mod device_db;
|
|||||||
mod scheme;
|
mod scheme;
|
||||||
|
|
||||||
use std::env;
|
use std::env;
|
||||||
use std::os::fd::{AsRawFd, FromRawFd, RawFd};
|
use std::os::fd::RawFd;
|
||||||
|
|
||||||
use log::{error, info, LevelFilter, Metadata, Record};
|
use log::{error, info, LevelFilter, Metadata, Record};
|
||||||
use redox_scheme::{
|
use redox_scheme::{
|
||||||
|
|||||||
@@ -631,10 +631,6 @@ impl SchemeSync for UdevScheme {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn path_exists(path: &str) -> bool {
|
|
||||||
std::fs::metadata(path).is_ok()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn scheme_registered(name: &str) -> bool {
|
fn scheme_registered(name: &str) -> bool {
|
||||||
std::fs::read_dir("/scheme")
|
std::fs::read_dir("/scheme")
|
||||||
.ok()
|
.ok()
|
||||||
|
|||||||
Reference in New Issue
Block a user