use std::fmt;
use std::fmt::Debug;
use super::protocol::GpuResponse::*;
use super::protocol::VirtioGpuResult;
use crate::virtio::gpu::GpuDisplayParameters;
const EDID_DATA_LENGTH: usize = 128;
const DEFAULT_HORIZONTAL_BLANKING: u16 = 560;
const DEFAULT_VERTICAL_BLANKING: u16 = 50;
const DEFAULT_HORIZONTAL_FRONT_PORCH: u16 = 64;
const DEFAULT_VERTICAL_FRONT_PORCH: u16 = 1;
const DEFAULT_HORIZONTAL_SYNC_PULSE: u16 = 192;
const DEFAULT_VERTICAL_SYNC_PULSE: u16 = 3;
const MILLIMETERS_PER_INCH: f32 = 25.4;
const DATA_BLOCK_TYPE_1_DETAILED_TIMING: u8 = 0x3;
const DATA_BLOCK_TYPE_1_DETAILED_TIMING_SIZE: u8 = 20;
const DATA_BLOCK_TYPE_1_DETAILED_TIMING_VERSION: u8 = 0x13;
const DISPLAYID_EXT: u8 = 0x70;
#[repr(C)]
pub struct EdidBytes {
bytes: Vec<u8>,
}
impl EdidBytes {
pub fn new(info: &DisplayInfo) -> VirtioGpuResult {
let mut edid = vec![0u8; EDID_DATA_LENGTH * 2];
populate_header(&mut edid);
populate_edid_version(&mut edid);
populate_size(&mut edid, info);
populate_standard_timings(&mut edid)?;
let display_name_block = &mut edid[54..72];
populate_display_name(display_name_block);
edid[126] = 1;
calculate_checksum(&mut edid, 127);
let display_id_extension = &mut edid[EDID_DATA_LENGTH..EDID_DATA_LENGTH * 2];
display_id_extension[0] = DISPLAYID_EXT; populate_displayid_detailed_timings(display_id_extension, 1, info);
calculate_checksum(display_id_extension, 127);
Ok(OkEdid(Box::new(Self { bytes: edid })))
}
pub fn len(&self) -> usize {
self.bytes.len()
}
pub fn as_bytes(&self) -> &[u8] {
&self.bytes
}
}
impl Debug for EdidBytes {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.bytes[..].fmt(f)
}
}
impl PartialEq for EdidBytes {
fn eq(&self, other: &EdidBytes) -> bool {
self.bytes[..] == other.bytes[..]
}
}
#[derive(Copy, Clone)]
pub struct Resolution {
width: u32,
height: u32,
}
impl Resolution {
fn new(width: u32, height: u32) -> Resolution {
Resolution { width, height }
}
fn get_aspect_ratio(&self) -> (u32, u32) {
let divisor = gcd(self.width, self.height);
(self.width / divisor, self.height / divisor)
}
}
fn gcd(x: u32, y: u32) -> u32 {
match y {
0 => x,
_ => gcd(y, x % y),
}
}
#[derive(Copy, Clone)]
pub struct DisplayInfo {
resolution: Resolution,
refresh_rate: u32,
horizontal_blanking: u16,
vertical_blanking: u16,
horizontal_front: u16,
vertical_front: u16,
horizontal_sync: u16,
vertical_sync: u16,
width_millimeters: u16,
height_millimeters: u16,
}
impl DisplayInfo {
pub fn new(params: &GpuDisplayParameters) -> Self {
let (width, height) = params.get_virtual_display_size();
let width_millimeters = if params.horizontal_dpi() != 0 {
((width as f32 / params.horizontal_dpi() as f32) * MILLIMETERS_PER_INCH) as u16
} else {
0
};
let height_millimeters = if params.vertical_dpi() != 0 {
((height as f32 / params.vertical_dpi() as f32) * MILLIMETERS_PER_INCH) as u16
} else {
0
};
Self {
resolution: Resolution::new(width, height),
refresh_rate: params.refresh_rate,
horizontal_blanking: DEFAULT_HORIZONTAL_BLANKING,
vertical_blanking: DEFAULT_VERTICAL_BLANKING,
horizontal_front: DEFAULT_HORIZONTAL_FRONT_PORCH,
vertical_front: DEFAULT_VERTICAL_FRONT_PORCH,
horizontal_sync: DEFAULT_HORIZONTAL_SYNC_PULSE,
vertical_sync: DEFAULT_VERTICAL_SYNC_PULSE,
width_millimeters,
height_millimeters,
}
}
pub fn width(&self) -> u32 {
self.resolution.width
}
pub fn height(&self) -> u32 {
self.resolution.height
}
pub fn width_centimeters(&self) -> u8 {
(self.width_millimeters / 10) as u8
}
pub fn height_centimeters(&self) -> u8 {
(self.height_millimeters / 10) as u8
}
}
fn populate_display_name(edid_block: &mut [u8]) {
edid_block[0..5].clone_from_slice(&[0x00, 0x00, 0x00, 0xFC, 0x00]);
edid_block[5..].clone_from_slice("CrosvmDisplay".as_bytes());
}
fn populate_displayid_detailed_timings(block: &mut [u8], start_index: usize, info: &DisplayInfo) {
let block = &mut block[start_index..start_index + 28];
block[0] = DATA_BLOCK_TYPE_1_DETAILED_TIMING_VERSION; block[1] = DATA_BLOCK_TYPE_1_DETAILED_TIMING_SIZE + 3; block[2] = DATA_BLOCK_TYPE_1_DETAILED_TIMING; block[3] = 0; block[4] = DATA_BLOCK_TYPE_1_DETAILED_TIMING; block[5] = 0x00; block[6] = DATA_BLOCK_TYPE_1_DETAILED_TIMING_SIZE; let htotal = info.width() + (info.horizontal_blanking as u32);
let vtotal = info.height() + (info.vertical_blanking as u32);
let clock = info
.refresh_rate
.checked_mul(htotal)
.and_then(|x| x.checked_mul(vtotal))
.map(|x| x / 10000)
.unwrap_or_else(|| {
panic!(
concat!(
"attempt to multiply with overflow: info.refresh_rate = {}, info.width = {}, ",
"info.horizontal_blanking = {}, info.height() = {}, info.vertical_blanking = {}"
),
info.refresh_rate,
info.width(),
info.horizontal_blanking,
info.height(),
info.vertical_blanking
)
});
block[7] = (clock & 0xff) as u8;
block[8] = ((clock & 0xff00) >> 8) as u8;
block[9] = ((clock & 0xff0000) >> 16) as u8;
block[10] = 0x88;
let hblanking = info.horizontal_blanking.saturating_sub(1);
let horizontal_blanking_lsb: u8 = (hblanking & 0xFF) as u8;
let horizontal_blanking_msb: u8 = ((hblanking >> 8) & 0x0F) as u8;
let vblanking = info.vertical_blanking.saturating_sub(1);
let vertical_blanking_lsb: u8 = (vblanking & 0xFF) as u8;
let vertical_blanking_msb: u8 = ((vblanking >> 8) & 0x0F) as u8;
let horizontal_active = info.width().saturating_sub(1);
let horizontal_active_lsb: u8 = (horizontal_active & 0xFF) as u8;
let horizontal_active_msb: u8 = ((horizontal_active >> 8) & 0xFF) as u8;
let vertical_active: u32 = info.height().saturating_sub(1);
let vertical_active_lsb: u8 = (vertical_active & 0xFF) as u8;
let vertical_active_msb: u8 = ((vertical_active >> 8) & 0xFF) as u8;
let hfront = info.horizontal_front.saturating_sub(1);
let horizontal_front_lsb: u8 = (hfront & 0xFF) as u8; let horizontal_front_msb: u8 = ((hfront >> 8) & 0x03) as u8; let hsync = info.horizontal_sync.saturating_sub(1);
let horizontal_sync_lsb: u8 = (hsync & 0xFF) as u8; let horizontal_sync_msb: u8 = ((hsync >> 8) & 0x03) as u8; let vfront = info.vertical_front.saturating_sub(1);
let vertical_front_lsb: u8 = (vfront & 0x0F) as u8; let vertical_front_msb: u8 = ((vfront >> 8) & 0x0F) as u8; let vsync = info.vertical_sync.saturating_sub(1);
let vertical_sync_lsb: u8 = (vsync & 0xFF) as u8; let vertical_sync_msb: u8 = ((vsync >> 8) & 0x0F) as u8; block[11] = horizontal_active_lsb;
block[12] = horizontal_active_msb;
block[13] = horizontal_blanking_lsb;
block[14] = horizontal_blanking_msb;
block[15] = horizontal_front_lsb;
block[16] = horizontal_front_msb;
block[17] = horizontal_sync_lsb;
block[18] = horizontal_sync_msb;
block[19] = vertical_active_lsb;
block[20] = vertical_active_msb;
block[21] = vertical_blanking_lsb;
block[22] = vertical_blanking_msb;
block[23] = vertical_front_lsb;
block[24] = vertical_front_msb;
block[25] = vertical_sync_lsb;
block[26] = vertical_sync_msb;
calculate_checksum(block, 27);
}
fn populate_header(edid: &mut [u8]) {
edid[0] = 0x00;
edid[1] = 0xFF;
edid[2] = 0xFF;
edid[3] = 0xFF;
edid[4] = 0xFF;
edid[5] = 0xFF;
edid[6] = 0xFF;
edid[7] = 0x00;
let manufacturer_name: [char; 3] = ['G', 'G', 'L'];
let manufacturer_id: u16 = manufacturer_name
.iter()
.map(|c| (*c as u8 - b'A' + 1) & 0x1F)
.fold(0u16, |res, lsb| (res << 5) | (lsb as u16));
edid[8..10].copy_from_slice(&manufacturer_id.to_be_bytes());
let manufacture_product_id: u16 = 1;
edid[10..12].copy_from_slice(&manufacture_product_id.to_le_bytes());
let serial_id: u32 = 1;
edid[12..16].copy_from_slice(&serial_id.to_le_bytes());
let manufacture_week: u8 = 8;
edid[16] = manufacture_week;
let manufacture_year: u32 = 2022;
edid[17] = (manufacture_year - 1990u32) as u8;
}
fn populate_standard_timings(edid: &mut [u8]) -> VirtioGpuResult {
let resolutions = [
Resolution::new(1440, 900),
Resolution::new(1600, 900),
Resolution::new(800, 600),
Resolution::new(1680, 1050),
Resolution::new(1856, 1392),
Resolution::new(1280, 1024),
Resolution::new(1400, 1050),
Resolution::new(1920, 1200),
];
for (index, r) in resolutions.iter().enumerate() {
edid[0x26 + (index * 2)] = (r.width / 8 - 31) as u8;
let ar_bits = match r.get_aspect_ratio() {
(8, 5) => 0x0,
(4, 3) => 0x1,
(5, 4) => 0x2,
(16, 9) => 0x3,
(x, y) => return Err(ErrEdid(format!("Unsupported aspect ratio: {} {}", x, y))),
};
edid[0x27 + (index * 2)] = ar_bits;
}
Ok(OkNoData)
}
fn populate_edid_version(edid: &mut [u8]) {
edid[18] = 1;
edid[19] = 4;
}
fn populate_size(edid: &mut [u8], info: &DisplayInfo) {
edid[21] = info.width_centimeters();
edid[22] = info.height_centimeters();
}
fn calculate_checksum(block: &mut [u8], length: usize) {
let mut checksum: u8 = 0;
for byte in block.iter().take(length) {
checksum = checksum.wrapping_add(*byte);
}
if checksum != 0 {
checksum = 255 - checksum + 1;
}
block[length] = checksum;
}