29e005e52f
- gettext: use -I${COOKBOOK_HOST_SYSROOT}/share/aclocal instead of
/usr/share/aclocal so autoreconf picks up the Redox-patched libtool 2.5.4
macros instead of the host system's libtool 2.6.1, fixing version mismatch
at build time
- libxau: add ACLOCAL=true AUTOMAKE=true AUTOHEADER=true to make invocations
to prevent automake regeneration when host autotools version differs from
what the source expects
- build-redbear.sh: make pre-cook failures non-fatal (warn only) and run with
COOKBOOK_OFFLINE=false so packages that need source fetching can succeed
- redox-drm: restore source from git history (was deleted in dc6805430);
update Cargo.toml version 0.1.0 -> 0.2.4 and dependency constraints to
match current project version
279 lines
7.9 KiB
Rust
279 lines
7.9 KiB
Rust
pub mod connector;
|
|
pub mod crtc;
|
|
pub mod encoder;
|
|
pub mod plane;
|
|
|
|
#[derive(Clone, Debug)]
|
|
pub struct ModeInfo {
|
|
pub clock: u32,
|
|
pub hdisplay: u16,
|
|
pub hsync_start: u16,
|
|
pub hsync_end: u16,
|
|
pub htotal: u16,
|
|
pub hskew: u16,
|
|
pub vdisplay: u16,
|
|
pub vsync_start: u16,
|
|
pub vsync_end: u16,
|
|
pub vtotal: u16,
|
|
pub vscan: u16,
|
|
pub vrefresh: u32,
|
|
pub flags: u32,
|
|
pub type_: u32,
|
|
pub name: String,
|
|
}
|
|
|
|
impl ModeInfo {
|
|
pub fn default_1080p() -> Self {
|
|
Self {
|
|
clock: 148_500,
|
|
hdisplay: 1920,
|
|
hsync_start: 2008,
|
|
hsync_end: 2052,
|
|
htotal: 2200,
|
|
hskew: 0,
|
|
vdisplay: 1080,
|
|
vsync_start: 1084,
|
|
vsync_end: 1089,
|
|
vtotal: 1125,
|
|
vscan: 0,
|
|
vrefresh: 60,
|
|
flags: 0,
|
|
type_: 0,
|
|
name: "1920x1080@60".to_string(),
|
|
}
|
|
}
|
|
|
|
pub fn from_edid(edid: &[u8]) -> Vec<Self> {
|
|
const EDID_HEADER: [u8; 8] = [0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00];
|
|
|
|
if edid.len() < 128 || edid.get(0..8) != Some(&EDID_HEADER) {
|
|
return Vec::new();
|
|
}
|
|
|
|
let mut modes = Vec::new();
|
|
for descriptor in edid[54..126].chunks_exact(18) {
|
|
let pixel_clock = u16::from_le_bytes([descriptor[0], descriptor[1]]) as u32;
|
|
if pixel_clock == 0 {
|
|
continue;
|
|
}
|
|
|
|
let hdisplay = descriptor[2] as u16 | (((descriptor[4] >> 4) as u16) << 8);
|
|
let hblank = descriptor[3] as u16 | (((descriptor[4] & 0x0f) as u16) << 8);
|
|
let vdisplay = descriptor[5] as u16 | (((descriptor[7] >> 4) as u16) << 8);
|
|
let vblank = descriptor[6] as u16 | (((descriptor[7] & 0x0f) as u16) << 8);
|
|
let hsync_offset =
|
|
descriptor[8] as u16 | ((((descriptor[11] >> 6) & 0x03) as u16) << 8);
|
|
let hsync_width = descriptor[9] as u16 | ((((descriptor[11] >> 4) & 0x03) as u16) << 8);
|
|
let vsync_offset =
|
|
((descriptor[10] >> 4) as u16) | ((((descriptor[11] >> 2) & 0x03) as u16) << 4);
|
|
let vsync_width =
|
|
(descriptor[10] & 0x0f) as u16 | (((descriptor[11] & 0x03) as u16) << 4);
|
|
|
|
if hdisplay == 0 || vdisplay == 0 {
|
|
continue;
|
|
}
|
|
|
|
let htotal = hdisplay.saturating_add(hblank);
|
|
let vtotal = vdisplay.saturating_add(vblank);
|
|
let clock = pixel_clock.saturating_mul(10);
|
|
let vrefresh = if htotal != 0 && vtotal != 0 {
|
|
clock.saturating_mul(1000) / (htotal as u32).saturating_mul(vtotal as u32)
|
|
} else {
|
|
0
|
|
};
|
|
|
|
modes.push(Self {
|
|
clock,
|
|
hdisplay,
|
|
hsync_start: hdisplay.saturating_add(hsync_offset),
|
|
hsync_end: hdisplay
|
|
.saturating_add(hsync_offset)
|
|
.saturating_add(hsync_width),
|
|
htotal,
|
|
hskew: 0,
|
|
vdisplay,
|
|
vsync_start: vdisplay.saturating_add(vsync_offset),
|
|
vsync_end: vdisplay
|
|
.saturating_add(vsync_offset)
|
|
.saturating_add(vsync_width),
|
|
vtotal,
|
|
vscan: 0,
|
|
vrefresh,
|
|
flags: if (descriptor[17] & 0x80) != 0 { 1 } else { 0 },
|
|
type_: 0,
|
|
name: format!("{}x{}@{}", hdisplay, vdisplay, vrefresh),
|
|
});
|
|
}
|
|
|
|
modes
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Debug)]
|
|
pub struct ConnectorInfo {
|
|
pub id: u32,
|
|
pub connector_type: ConnectorType,
|
|
#[allow(dead_code)]
|
|
pub connector_type_id: u32,
|
|
pub connection: ConnectorStatus,
|
|
pub mm_width: u32,
|
|
pub mm_height: u32,
|
|
pub encoder_id: u32,
|
|
pub modes: Vec<ModeInfo>,
|
|
}
|
|
|
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
|
pub enum ConnectorType {
|
|
Unknown,
|
|
VGA,
|
|
DVII,
|
|
DVID,
|
|
DVIA,
|
|
#[allow(dead_code)]
|
|
Composite,
|
|
#[allow(dead_code)]
|
|
SVideo,
|
|
#[allow(dead_code)]
|
|
LVDS,
|
|
#[allow(dead_code)]
|
|
Component,
|
|
#[allow(dead_code)]
|
|
NinePinDIN,
|
|
DisplayPort,
|
|
HDMIA,
|
|
#[allow(dead_code)]
|
|
HDMIB,
|
|
#[allow(dead_code)]
|
|
TV,
|
|
EDP,
|
|
Virtual,
|
|
}
|
|
|
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
|
pub enum ConnectorStatus {
|
|
Connected,
|
|
Disconnected,
|
|
Unknown,
|
|
}
|
|
|
|
#[allow(dead_code)]
|
|
#[derive(Clone, Debug)]
|
|
pub struct CrtcInfo {
|
|
pub id: u32,
|
|
pub fb_id: u32,
|
|
pub x: u32,
|
|
pub y: u32,
|
|
pub gamma_size: u32,
|
|
pub mode: Option<ModeInfo>,
|
|
}
|
|
|
|
#[derive(Clone, Debug)]
|
|
pub struct EncoderInfo {
|
|
#[allow(dead_code)]
|
|
pub id: u32,
|
|
#[allow(dead_code)]
|
|
pub encoder_type: u32,
|
|
#[allow(dead_code)]
|
|
pub crtc_id: u32,
|
|
#[allow(dead_code)]
|
|
pub possible_crtcs: u32,
|
|
#[allow(dead_code)]
|
|
pub possible_clones: u32,
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn default_1080p_has_correct_values() {
|
|
let mode = ModeInfo::default_1080p();
|
|
assert_eq!(mode.hdisplay, 1920);
|
|
assert_eq!(mode.vdisplay, 1080);
|
|
assert_eq!(mode.vrefresh, 60);
|
|
assert_eq!(mode.clock, 148_500);
|
|
assert_eq!(mode.name, "1920x1080@60");
|
|
assert_eq!(mode.htotal, 2200);
|
|
assert_eq!(mode.vtotal, 1125);
|
|
}
|
|
|
|
#[test]
|
|
fn from_edid_synthetic_edid_too_short_returns_empty() {
|
|
let edid = super::connector::synthetic_edid();
|
|
assert!(edid.len() < 128, "synthetic EDID is shorter than 128 bytes");
|
|
let modes = ModeInfo::from_edid(&edid);
|
|
assert!(
|
|
modes.is_empty(),
|
|
"EDID shorter than 128 bytes should produce no modes"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn from_edid_empty_input_returns_empty() {
|
|
let modes = ModeInfo::from_edid(&[]);
|
|
assert!(modes.is_empty());
|
|
}
|
|
|
|
#[test]
|
|
fn from_edid_short_input_returns_empty() {
|
|
let modes = ModeInfo::from_edid(&[0u8; 64]);
|
|
assert!(modes.is_empty());
|
|
}
|
|
|
|
#[test]
|
|
fn from_edid_invalid_header_returns_empty() {
|
|
let mut data = vec![0u8; 128];
|
|
data[0] = 0x01;
|
|
let modes = ModeInfo::from_edid(&data);
|
|
assert!(modes.is_empty());
|
|
}
|
|
|
|
#[test]
|
|
fn from_edid_parsed_modes_have_nonzero_dimensions() {
|
|
let edid = super::connector::synthetic_edid();
|
|
let modes = ModeInfo::from_edid(&edid);
|
|
for mode in &modes {
|
|
assert_ne!(mode.hdisplay, 0, "hdisplay should not be zero");
|
|
assert_ne!(mode.vdisplay, 0, "vdisplay should not be zero");
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn from_edid_parsed_modes_have_correct_name_format() {
|
|
let edid = super::connector::synthetic_edid();
|
|
let modes = ModeInfo::from_edid(&edid);
|
|
for mode in &modes {
|
|
let parts: Vec<&str> = mode.name.split('@').collect();
|
|
assert_eq!(
|
|
parts.len(),
|
|
2,
|
|
"name should contain exactly one '@': {}",
|
|
mode.name
|
|
);
|
|
let dims: Vec<&str> = parts[0].split('x').collect();
|
|
assert_eq!(dims.len(), 2, "name prefix should be WxH: {}", mode.name);
|
|
let w: u16 = dims[0].parse().expect("width should be numeric");
|
|
let h: u16 = dims[1].parse().expect("height should be numeric");
|
|
assert_eq!(w, mode.hdisplay);
|
|
assert_eq!(h, mode.vdisplay);
|
|
let refresh: u32 = parts[1].parse().expect("refresh should be numeric");
|
|
assert_eq!(refresh, mode.vrefresh);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn from_edid_with_valid_header_but_zero_pixel_clock_skips_descriptor() {
|
|
let mut data = vec![0u8; 128];
|
|
data[0] = 0x00;
|
|
data[1] = 0xFF;
|
|
data[2] = 0xFF;
|
|
data[3] = 0xFF;
|
|
data[4] = 0xFF;
|
|
data[5] = 0xFF;
|
|
data[6] = 0xFF;
|
|
data[7] = 0x00;
|
|
let modes = ModeInfo::from_edid(&data);
|
|
assert!(modes.is_empty());
|
|
}
|
|
}
|