intel: Phase 1 — DP/HDMI protocol completeness

- dp_aux: add LinkStatus check (DPCD 0x202-0x207), sink_count read (0x200)
- hdmi: expand compute_cea_vic to 27 CEA modes, add VSIF (HDMI 1.4+),
  add is_hdmi_sink() EDID CEA-861 extension block detection
- vbt: support modern 38-byte child device config format (BDB block 33/34),
  parse_child_device_table handles both legacy 2-byte and v2 entries
This commit is contained in:
2026-06-01 20:58:17 +03:00
parent b1e83ae89a
commit ea36397590
3 changed files with 288 additions and 12 deletions
@@ -207,6 +207,101 @@ impl DpAux {
debug!("redox-drm-intel: read {} byte EDID via DP AUX port {}", edid.len(), self.port);
Ok(edid)
}
pub fn check_link_status(&self) -> Result<LinkStatus> {
let lane01_status = self.read_dpcd(0x0202, 4)?;
let adjusted_req = self.read_dpcd(0x0206, 2)?;
if lane01_status.len() < 4 || adjusted_req.len() < 2 {
return Err(DriverError::Initialization("short link status read".into()));
}
Ok(LinkStatus {
lane0_cr_done: (lane01_status[0] & 0x01) != 0,
lane1_cr_done: (lane01_status[0] & 0x10) != 0,
lane2_cr_done: (lane01_status[1] & 0x01) != 0,
lane3_cr_done: (lane01_status[1] & 0x10) != 0,
lane0_channel_eq_done: (lane01_status[0] & 0x02) != 0,
lane1_channel_eq_done: (lane01_status[0] & 0x20) != 0,
lane2_channel_eq_done: (lane01_status[1] & 0x02) != 0,
lane3_channel_eq_done: (lane01_status[1] & 0x20) != 0,
lane0_symbol_locked: (lane01_status[0] & 0x04) != 0,
lane1_symbol_locked: (lane01_status[0] & 0x40) != 0,
lane2_symbol_locked: (lane01_status[1] & 0x04) != 0,
lane3_symbol_locked: (lane01_status[1] & 0x40) != 0,
interlane_aligned: (lane01_status[2] & 0x01) != 0,
adjusted_req_lane0: adjusted_req[0] & 0x0F,
adjusted_req_lane1: (adjusted_req[0] >> 4) & 0x0F,
adjusted_req_lane2: adjusted_req[1] & 0x0F,
adjusted_req_lane3: (adjusted_req[1] >> 4) & 0x0F,
})
}
pub fn read_sink_count(&self) -> Result<u8> {
let data = self.read_dpcd(0x0200, 1)?;
if data.is_empty() {
return Err(DriverError::Initialization("empty sink count read".into()));
}
Ok(data[0] & 0x3F)
}
pub fn sink_count_changed(&self, previous_count: u8) -> Result<bool> {
let current = self.read_sink_count()?;
if current != previous_count {
debug!(
"redox-drm-intel: DP sink count changed: {} → {}",
previous_count, current
);
return Ok(true);
}
Ok(false)
}
}
#[derive(Debug, Clone)]
pub struct LinkStatus {
pub lane0_cr_done: bool,
pub lane1_cr_done: bool,
pub lane2_cr_done: bool,
pub lane3_cr_done: bool,
pub lane0_channel_eq_done: bool,
pub lane1_channel_eq_done: bool,
pub lane2_channel_eq_done: bool,
pub lane3_channel_eq_done: bool,
pub lane0_symbol_locked: bool,
pub lane1_symbol_locked: bool,
pub lane2_symbol_locked: bool,
pub lane3_symbol_locked: bool,
pub interlane_aligned: bool,
pub adjusted_req_lane0: u8,
pub adjusted_req_lane1: u8,
pub adjusted_req_lane2: u8,
pub adjusted_req_lane3: u8,
}
impl LinkStatus {
pub fn all_lanes_cr_done(&self, lane_count: u8) -> bool {
match lane_count {
1 => self.lane0_cr_done,
2 => self.lane0_cr_done && self.lane1_cr_done,
3 => self.lane0_cr_done && self.lane1_cr_done && self.lane2_cr_done,
_ => self.lane0_cr_done && self.lane1_cr_done && self.lane2_cr_done && self.lane3_cr_done,
}
}
pub fn all_lanes_eq_done(&self, lane_count: u8) -> bool {
match lane_count {
1 => self.lane0_channel_eq_done,
2 => self.lane0_channel_eq_done && self.lane1_channel_eq_done,
3 => self.lane0_channel_eq_done && self.lane1_channel_eq_done && self.lane2_channel_eq_done,
_ => self.lane0_channel_eq_done && self.lane1_channel_eq_done
&& self.lane2_channel_eq_done && self.lane3_channel_eq_done,
}
}
pub fn link_good(&self, lane_count: u8) -> bool {
self.interlane_aligned && self.all_lanes_cr_done(lane_count) && self.all_lanes_eq_done(lane_count)
}
}
pub struct DpcdCaps {
@@ -30,6 +30,15 @@ const AUDIO_SAMPLE_RATE_48KHZ: u8 = 0b011;
const AUDIO_CHANNEL_COUNT_2CH: u8 = 1;
const AUDIO_SPEAKER_FL_FR: u8 = 0x01;
const HDMI_IEEE_OUI: [u8; 3] = [0x03, 0x0C, 0x00];
const VSIF_HEADER: u8 = 0x81;
const VSIF_VERSION: u8 = 0x01;
const VSIF_LENGTH: u8 = 5;
const HDMI_VSIF_VIDEO_FORMAT_MASK: u8 = 0x07;
const HDMI_VSIF_VIDEO_FORMAT_HDMI: u8 = 0x02;
const HDMI_VSIF_VIDEO_FORMAT_DVI: u8 = 0x00;
pub struct HdmiInfoframes {
mmio: Arc<MmioRegion>,
}
@@ -156,6 +165,53 @@ impl HdmiInfoframes {
(256u32.wrapping_sub(sum % 256) % 256) as u8
}
pub fn program_vsif(&self, pipe: u8, _port: u8) -> Result<()> {
let dip_ctl = HSW_TVIDEO_DIP_CTL_BASE + (pipe as usize) * PIPE_STRIDE;
let dip_data = HSW_TVIDEO_DIP_AVI_DATA_BASE + (pipe as usize) * PIPE_STRIDE;
let mut packet = [0u8; 9];
packet[0] = VSIF_HEADER;
packet[1] = VSIF_VERSION;
packet[2] = VSIF_LENGTH;
packet[3] = HDMI_IEEE_OUI[0];
packet[4] = HDMI_IEEE_OUI[1];
packet[5] = HDMI_IEEE_OUI[2];
let video_format = HDMI_VSIF_VIDEO_FORMAT_HDMI;
let hdmi_vic = 0u8;
packet[6] = video_format | (hdmi_vic << 5);
let _3d_structure = 0u8;
let _3d_ext_data = 0u8;
packet[7] = _3d_structure;
packet[8] = _3d_ext_data;
let checksum = Self::checksum(&packet[..8]);
packet[8] = checksum;
self.mmio.write32(dip_ctl, 0);
for i in 0..3 {
let mut word: u32 = 0;
for j in 0..4 {
let idx = i * 4 + j;
if idx < 9 {
word |= (packet[idx] as u32) << (j * 8);
}
}
self.mmio.write32(dip_data + i * 4, word);
}
let ctl_val = VIDEO_DIP_ENABLE
| VIDEO_DIP_PORT_SELECT_HDMI
| (1u32 << 24)
| VIDEO_DIP_FREQ_VSYNC;
self.mmio.write32(dip_ctl, ctl_val);
debug!("redox-drm-intel: HDMI VSIF programmed for pipe {}", pipe);
Ok(())
}
pub fn disable(&self, pipe: u8) -> Result<()> {
let dip_ctl = HSW_TVIDEO_DIP_CTL_BASE + (pipe as usize) * PIPE_STRIDE;
self.mmio.write32(dip_ctl, 0);
@@ -164,16 +220,33 @@ impl HdmiInfoframes {
}
fn compute_cea_vic(hdisplay: u16, vdisplay: u16, vrefresh: u32) -> u8 {
match (hdisplay, vdisplay, vrefresh as u16) {
match (hdisplay, vdisplay, vrefresh) {
(640, 480, 60) => 1,
(720, 480, 60) => 2,
(720, 480, _) if vrefresh >= 59 && vrefresh <= 61 => 3,
(1280, 720, 60) => 4,
(1920, 1080, _) if vrefresh >= 50 && vrefresh <= 51 => 5,
(720, 576, 50) => 17,
(720, 576, _) if vrefresh >= 49 && vrefresh <= 51 => 18,
(1280, 720, 50) => 19,
(1920, 1080, 50) => 20,
(1920, 1080, 60) => 16,
(1920, 1080, 50) => 31,
(1920, 1080, 24) => 32,
(1920, 1080, 25) => 33,
(1920, 1080, 30) => 34,
(2880, 480, 60) => 35,
(2880, 576, 50) => 37,
(1920, 1080, _) if vrefresh >= 119 && vrefresh <= 121 => 63,
(3840, 2160, 60) => 97,
(3840, 2160, 50) => 96,
(3840, 2160, 30) => 95,
(3840, 2160, 25) => 94,
(3840, 2160, 24) => 93,
(4096, 2160, 60) => 102,
(4096, 2160, 50) => 101,
(4096, 2160, 30) => 100,
(4096, 2160, 25) => 99,
(4096, 2160, 24) => 98,
(2560, 1440, _) => 0,
_ => 0,
}
@@ -185,3 +258,44 @@ fn compute_aspect_ratio(mode: &ModeInfo) -> u8 {
else if ratio > 1.6 { 2 }
else { 1 }
}
pub fn is_hdmi_sink(edid: &[u8]) -> bool {
if edid.len() < 256 {
return false;
}
let digital = edid.len() > 20 && (edid[20] & 0x80) != 0;
if !digital {
return false;
}
let mut offset = 128;
while offset + 3 <= edid.len() && offset < 256 {
let tag = edid[offset] >> 5;
let length = (edid[offset] & 0x1F) as usize;
if tag == 0x02 && length >= 3 && offset + 3 + length <= edid.len() {
let data = &edid[offset + 1..offset + 1 + length];
let mut dtd_start = 0usize;
while dtd_start < data.len() {
let dtd_tag = data[dtd_start] >> 5;
let dtd_len = (data[dtd_start] & 0x1F) as usize;
if dtd_len < 3 {
dtd_start += dtd_len + 1;
continue;
}
if dtd_start + 3 <= data.len()
&& data[dtd_start + 1] == HDMI_IEEE_OUI[2]
&& data[dtd_start + 2] == HDMI_IEEE_OUI[1]
&& data[dtd_start + 3] == HDMI_IEEE_OUI[0]
{
return true;
}
dtd_start += dtd_len + 1;
}
break;
}
offset += length + 1;
}
false
}
@@ -7,6 +7,15 @@ const BDB_HEADER_SIZE: usize = 3;
const BDB_GENERAL_DEFINITIONS: u8 = 2;
const BDB_LVDS_OPTIONS: u8 = 40;
const BDB_MIPI_SEQUENCE: u8 = 53;
const BDB_CHILD_DEVICE_TABLE: u8 = 33;
const BDB_CHILD_DEVICE_TABLE_OLD: u8 = 34;
const CHILD_DEVICE_LEGACY_SIZE: usize = 2;
const CHILD_DEVICE_V2_MIN_SIZE: usize = 38;
const DEVICE_TYPE_HDMI: u16 = 0x2000;
const DEVICE_TYPE_DP: u16 = 0x4000;
const DEVICE_TYPE_EDP: u16 = 0x8000;
#[derive(Debug, Clone)]
pub struct VbtInfo {
@@ -98,15 +107,8 @@ impl VbtInfo {
self.has_mipi = false;
}
}
33 => {
let count = block_size / 2;
for i in 0..count {
let device = u16::from_le_bytes([
block_data[i * 2], block_data[i * 2 + 1],
]);
let config = self.parse_child_device(device);
self.child_devices.push(config);
}
BDB_CHILD_DEVICE_TABLE | BDB_CHILD_DEVICE_TABLE_OLD => {
self.parse_child_device_table(block_data);
}
_ => {}
}
@@ -115,7 +117,71 @@ impl VbtInfo {
}
}
fn parse_child_device(&self, device: u16) -> ChildDeviceConfig {
fn parse_child_device_table(&mut self, data: &[u8]) {
if data.is_empty() {
return;
}
let entry_size = if data.len() % CHILD_DEVICE_V2_MIN_SIZE == 0 {
CHILD_DEVICE_V2_MIN_SIZE
} else if data.len() % CHILD_DEVICE_LEGACY_SIZE == 0 {
CHILD_DEVICE_LEGACY_SIZE
} else {
CHILD_DEVICE_LEGACY_SIZE
};
let count = data.len() / entry_size;
debug!(
"redox-drm-intel: VBT child device table: {} entries × {} bytes each",
count, entry_size
);
for i in 0..count {
let offset = i * entry_size;
if offset + entry_size > data.len() {
break;
}
let entry = &data[offset..offset + entry_size];
let config = if entry_size >= CHILD_DEVICE_V2_MIN_SIZE {
self.parse_child_device_v2(entry)
} else {
let device = u16::from_le_bytes([entry[0], entry[1]]);
self.parse_child_device_legacy(device)
};
self.child_devices.push(config);
}
}
fn parse_child_device_v2(&self, entry: &[u8]) -> ChildDeviceConfig {
let device_type = u16::from_le_bytes([entry[2], entry[3]]);
let dvo_port = entry.get(16).copied().unwrap_or(0);
let ddc_pin = entry.get(17).copied().unwrap_or(0);
let aux_channel_raw = entry.get(18).copied().unwrap_or(0);
let hdmi = (device_type & DEVICE_TYPE_HDMI) != 0
|| entry.get(26).map_or(false, |&b| (b & 0x08) != 0);
let dp = (device_type & DEVICE_TYPE_DP) != 0;
let edp = (device_type & DEVICE_TYPE_EDP) != 0;
let aux_channel = if dp || edp {
Some(if aux_channel_raw > 0 { aux_channel_raw } else { ddc_pin })
} else {
None
};
ChildDeviceConfig {
device_type,
dvo_port,
ddc_pin,
aux_channel,
hdmi_support: hdmi,
dp_support: dp,
edp_support: edp,
device_id: device_type,
}
}
fn parse_child_device_legacy(&self, device: u16) -> ChildDeviceConfig {
let dvo_port = ((device >> 0) & 0x0F) as u8;
let ddc_pin = ((device >> 4) & 0x0F) as u8;
let hdmi = (device >> 8) & 0x01 != 0;
@@ -165,6 +231,7 @@ pub fn connector_type_from_vbt(vbt: Option<&VbtInfo>, port: u8, pp_status: u32)
}
}
}
connector_type_heuristic(port, pp_status)
}