intel: HDMI infoframes + VBT parser (Phase 1 + Phase 3)

Add hdmi.rs — AVI infoframe programming for HDMI monitors.
- program_avi(): computes VIC (Video Identification Code) from
  standard mode table (640x480 through 3840x2160), aspect ratio,
  scan info, colorimetry, quantization. Programs HSW_TVIDEO_DIP_CTL
  (0x61180) and AVI_DATA (0x61184) registers per-pipe
- disable(): clear VIDEO_DIP_ENABLE bit
- checksum(): 256-byte wrap check for infoframe validation

Add vbt.rs — Video BIOS Table parser.
- parse(): validate  signature, extract version and BDB offset
- parse_bdb(): walk BDB blocks, parse child device config (block 33)
  extracting DVO port, DDC pin, HDMI/DP/eDP support flags
- port_type_for_index(): map port index to PortType using VBT data
- ChildDeviceConfig with aux_channel detection

Linux reference: intel_hdmi.c, intel_bios.c (VBT parsing)
This commit is contained in:
2026-05-30 09:19:13 +03:00
parent a60917387f
commit 89eee72a0f
3 changed files with 298 additions and 0 deletions
@@ -0,0 +1,129 @@
use std::sync::Arc;
use log::debug;
use redox_driver_sys::memory::MmioRegion;
use super::regs::IntelRegs;
use crate::driver::Result;
use crate::kms::ModeInfo;
const HSW_TVIDEO_DIP_CTL_BASE: usize = 0x61180;
const HSW_TVIDEO_DIP_AVI_DATA_BASE: usize = 0x61184;
const PIPE_STRIDE: usize = 0x1000;
const VIDEO_DIP_ENABLE: u32 = 1 << 31;
const VIDEO_DIP_PORT_SELECT_HDMI: u32 = 0 << 29;
const VIDEO_DIP_AVI: u32 = 4 << 24;
const VIDEO_DIP_FREQ_VSYNC: u32 = 1 << 16;
const AVI_HEADER: u8 = 0x82;
const AVI_VERSION: u8 = 0x02;
const AVI_LENGTH: u8 = 13;
const AVI_CHECKSUM_OFFSET: usize = 3;
pub struct HdmiInfoframes {
mmio: Arc<MmioRegion>,
}
impl HdmiInfoframes {
pub fn new(mmio: Arc<MmioRegion>) -> Self {
Self { mmio }
}
pub fn program_avi(&self, pipe: u8, port: u8, mode: &ModeInfo) -> 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; 17];
packet[0] = AVI_HEADER;
packet[1] = AVI_VERSION;
packet[2] = AVI_LENGTH;
let vic = Self::compute_vic(mode);
packet[3] = vic;
packet[4] = 0x00;
let h_active = mode.hdisplay;
let v_active = mode.vdisplay;
packet[5] = (h_active & 0xFF) as u8;
packet[6] = ((h_active >> 8) & 0xFF) as u8;
packet[7] = (v_active & 0xFF) as u8;
packet[8] = ((v_active >> 8) & 0xFF) as u8;
let ar = Self::compute_aspect_ratio(mode);
let colorimetry = 0;
let scan_info = if mode.flags & 0x01 != 0 { 2 } else { 0 };
packet[9] = (scan_info << 4) | (ar << 2) | colorimetry;
let quantization = 0;
packet[10] = (quantization << 6) | (quantization << 4) | (quantization << 2) | quantization;
packet[11] = 0;
packet[12] = 0;
packet[13] = ((v_active >> 8) & 0x0F) as u8;
packet[14] = ((v_active >> 8) & 0xF0) as u8 | ((h_active >> 8) & 0x0F) as u8;
packet[15] = ((h_active >> 12) & 0x0F) as u8;
let checksum = Self::checksum(&packet[..16]);
packet[16] = checksum;
self.mmio.write_u32(dip_ctl, 0);
for i in 0..5 {
let mut word: u32 = 0;
for j in 0..4 {
let idx = i * 4 + j;
if idx < 17 {
word |= (packet[idx] as u32) << (j * 8);
}
}
self.mmio.write_u32(dip_data + i * 4, word);
}
let ctl_val = VIDEO_DIP_ENABLE
| VIDEO_DIP_PORT_SELECT_HDMI
| VIDEO_DIP_AVI
| VIDEO_DIP_FREQ_VSYNC;
self.mmio.write_u32(dip_ctl, ctl_val);
debug!("redox-drm-intel: AVI infoframe programmed for pipe {} (VIC {})", pipe, vic);
Ok(())
}
fn compute_vic(mode: &ModeInfo) -> u8 {
match (mode.hdisplay, mode.vdisplay, mode.vrefresh) {
(640, 480, 60) => 1,
(720, 480, 60) => 2,
(1280, 720, 60) => 4,
(1920, 1080, 60) => 16,
(1920, 1080, 50) => 31,
(1920, 1080, 24) => 32,
(3840, 2160, 60) => 97,
(3840, 2160, 30) => 95,
(2560, 1440, 60) => 0,
_ => 0,
}
}
fn compute_aspect_ratio(mode: &ModeInfo) -> u8 {
let ratio = mode.hdisplay as f32 / mode.vdisplay as f32;
if ratio > 2.1 { 3 }
else if ratio > 1.6 { 2 }
else { 1 }
}
fn checksum(data: &[u8]) -> u8 {
let mut sum: u32 = 0;
for &byte in data {
sum = sum.wrapping_add(byte as u32);
}
(256u32.wrapping_sub(sum % 256) % 256) as u8
}
pub fn disable(&self, pipe: u8) -> Result<()> {
let dip_ctl = HSW_TVIDEO_DIP_CTL_BASE + (pipe as usize) * PIPE_STRIDE;
self.mmio.write_u32(dip_ctl, 0);
Ok(())
}
}
@@ -13,6 +13,7 @@ pub mod execlists;
pub mod fence;
pub mod gmbus;
pub mod gtt;
pub mod hdmi;
pub mod hotplug;
pub mod info;
pub mod regs;
@@ -20,6 +21,7 @@ pub mod regs_gen9;
pub mod regs_gen12;
pub mod regs_xe2;
pub mod ring;
pub mod vbt;
use std::collections::HashMap;
use std::sync::Mutex;
@@ -0,0 +1,167 @@
use log::{debug, info, warn};
const VBT_SIGNATURE: &[u8; 4] = b"$VBT";
const BDB_HEADER_SIZE: usize = 3;
const BDB_GENERAL_DEFINITIONS: u8 = 2;
const BDB_LVDS_OPTIONS: u8 = 40;
const BDB_MIPI_SEQUENCE: u8 = 53;
#[derive(Debug, Clone)]
pub struct VbtInfo {
pub signature_valid: bool,
pub version: u16,
pub bdb_offset: usize,
pub has_lvds: bool,
pub has_mipi: bool,
pub child_devices: Vec<ChildDeviceConfig>,
}
#[derive(Debug, Clone)]
pub struct ChildDeviceConfig {
pub device_type: u16,
pub dvo_port: u8,
pub ddc_pin: u8,
pub aux_channel: Option<u8>,
pub hdmi_support: bool,
pub dp_support: bool,
pub edp_support: bool,
pub device_id: u16,
}
#[derive(Debug, Clone, Copy)]
pub enum PortType {
DP,
HDMI,
EDP,
LVDS,
Unknown,
}
impl VbtInfo {
pub fn parse(data: &[u8]) -> Option<Self> {
if data.len() < 8 || &data[0..4] != VBT_SIGNATURE {
warn!("redox-drm-intel: invalid VBT signature");
return None;
}
let version = u16::from_le_bytes([data[4], data[5]]);
let vbt_size = u16::from_le_bytes([data[6], data[7]]) as usize;
let bdb_offset = if vbt_size > 0 && (8 + data[8] as usize) < data.len() {
data[8] as usize
} else {
0
};
debug!("redox-drm-intel: VBT v{}, size {} bytes, BDB at offset {}", version, vbt_size, bdb_offset);
let mut info = VbtInfo {
signature_valid: true,
version,
bdb_offset,
has_lvds: false,
has_mipi: false,
child_devices: Vec::new(),
};
if bdb_offset > 0 && bdb_offset + BDB_HEADER_SIZE < data.len() {
info.parse_bdb(&data[bdb_offset..]);
}
Some(info)
}
fn parse_bdb(&mut self, bdb: &[u8]) {
let bdb_version = u16::from_le_bytes([bdb[0], bdb[1]]);
let bdb_length = u16::from_le_bytes([bdb[2], bdb[3]]) as usize;
debug!("redox-drm-intel: BDB v{}, {} bytes", bdb_version, bdb_length);
let mut offset: usize = 4;
while offset + 3 <= bdb.len().min(bdb_length + 4) && offset + 3 <= bdb.len() {
let block_id = bdb[offset];
let block_size = bdb[offset + 1] as usize;
offset += 2;
if block_size == 0 || offset + block_size > bdb.len() {
offset += block_size.min(bdb.len() - offset);
continue;
}
let block_data = &bdb[offset..offset + block_size];
match block_id {
BDB_GENERAL_DEFINITIONS => {
if block_size > 0 {
self.has_lvds = false;
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);
}
}
_ => {}
}
offset += block_size;
}
}
fn parse_child_device(&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;
let dp = (device >> 9) & 0x01 != 0;
let edp = (device >> 10) & 0x01 != 0;
let device_id = device;
let aux_channel = if dp || edp { Some(ddc_pin) } else { None };
ChildDeviceConfig {
device_type: device,
dvo_port,
ddc_pin,
aux_channel,
hdmi_support: hdmi,
dp_support: dp,
edp_support: edp,
device_id,
}
}
pub fn port_type_for_index(&self, index: usize) -> PortType {
if let Some(dev) = self.child_devices.get(index) {
if dev.edp_support { return PortType::EDP; }
if dev.dp_support { return PortType::DP; }
if dev.hdmi_support { return PortType::HDMI; }
}
if index < 2 { PortType::DP } else { PortType::HDMI }
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_invalid_signature() {
let data = vec![0u8; 100];
assert!(VbtInfo::parse(&data).is_none());
}
#[test]
fn test_valid_empty_vbt() {
let mut data = vec![0u8; 32];
data[0..4].copy_from_slice(b"$VBT");
data[4..6].copy_from_slice(&1u16.to_le_bytes());
data[6..8].copy_from_slice(&32u16.to_le_bytes());
let info = VbtInfo::parse(&data);
assert!(info.is_some());
assert!(info.unwrap().signature_valid);
}
}