1use std::fmt;
9use std::fmt::Debug;
10
11use super::protocol::GpuResponse::*;
12use super::protocol::VirtioGpuResult;
13use crate::virtio::gpu::GpuDisplayParameters;
14
15const EDID_DATA_LENGTH: usize = 128;
16const DEFAULT_HORIZONTAL_BLANKING: u16 = 560;
17const DEFAULT_VERTICAL_BLANKING: u16 = 50;
18const DEFAULT_HORIZONTAL_FRONT_PORCH: u16 = 64;
19const DEFAULT_VERTICAL_FRONT_PORCH: u16 = 1;
20const DEFAULT_HORIZONTAL_SYNC_PULSE: u16 = 192;
21const DEFAULT_VERTICAL_SYNC_PULSE: u16 = 3;
22const MILLIMETERS_PER_INCH: f32 = 25.4;
23
24const DATA_BLOCK_TYPE_1_DETAILED_TIMING: u8 = 0x3;
25const DATA_BLOCK_TYPE_1_DETAILED_TIMING_SIZE: u8 = 20;
26const DATA_BLOCK_TYPE_1_DETAILED_TIMING_VERSION: u8 = 0x13;
27const DISPLAYID_EXT: u8 = 0x70;
28
29#[repr(C)]
40pub struct EdidBytes {
41 bytes: Vec<u8>,
42}
43
44impl EdidBytes {
45 pub fn new(info: &DisplayInfo) -> VirtioGpuResult {
47 let mut edid = vec![0u8; EDID_DATA_LENGTH * 2];
48
49 populate_header(&mut edid);
50 populate_edid_version(&mut edid);
51 populate_size(&mut edid, info);
52 populate_standard_timings(&mut edid)?;
53
54 let descriptor1 = &mut edid[54..72];
59 populate_detailed_timing_descriptor(descriptor1, info);
60 let descriptor2 = &mut edid[72..90];
61 populate_display_name(descriptor2);
62
63 edid[126] = 1;
65 calculate_checksum(&mut edid, 127);
66
67 let display_id_extension = &mut edid[EDID_DATA_LENGTH..EDID_DATA_LENGTH * 2];
68 display_id_extension[0] = DISPLAYID_EXT; populate_displayid_detailed_timings(display_id_extension, 1, info);
72
73 calculate_checksum(display_id_extension, 127);
74
75 Ok(OkEdid(Box::new(Self { bytes: edid })))
76 }
77
78 pub fn len(&self) -> usize {
79 self.bytes.len()
80 }
81
82 pub fn as_bytes(&self) -> &[u8] {
83 &self.bytes
84 }
85}
86
87impl Debug for EdidBytes {
88 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
89 self.bytes[..].fmt(f)
90 }
91}
92
93impl PartialEq for EdidBytes {
94 fn eq(&self, other: &EdidBytes) -> bool {
95 self.bytes[..] == other.bytes[..]
96 }
97}
98
99#[derive(Copy, Clone)]
100pub struct Resolution {
101 width: u32,
102 height: u32,
103}
104
105impl Resolution {
106 fn new(width: u32, height: u32) -> Resolution {
107 Resolution { width, height }
108 }
109
110 fn get_aspect_ratio(&self) -> (u32, u32) {
111 let divisor = gcd(self.width, self.height);
112 (self.width / divisor, self.height / divisor)
113 }
114}
115
116fn gcd(x: u32, y: u32) -> u32 {
117 match y {
118 0 => x,
119 _ => gcd(y, x % y),
120 }
121}
122
123#[derive(Copy, Clone)]
124pub struct DisplayInfo {
125 resolution: Resolution,
126 refresh_rate: u32,
127 horizontal_blanking: u16,
128 vertical_blanking: u16,
129 horizontal_front: u16,
130 vertical_front: u16,
131 horizontal_sync: u16,
132 vertical_sync: u16,
133 width_millimeters: u16,
134 height_millimeters: u16,
135}
136
137impl DisplayInfo {
138 pub fn new(params: &GpuDisplayParameters) -> Self {
142 let (width, height) = params.get_virtual_display_size();
143
144 let width_millimeters = if params.horizontal_dpi() != 0 {
145 ((width as f32 / params.horizontal_dpi() as f32) * MILLIMETERS_PER_INCH) as u16
146 } else {
147 0
148 };
149 let height_millimeters = if params.vertical_dpi() != 0 {
150 ((height as f32 / params.vertical_dpi() as f32) * MILLIMETERS_PER_INCH) as u16
151 } else {
152 0
153 };
154
155 Self {
156 resolution: Resolution::new(width, height),
157 refresh_rate: params.refresh_rate,
158 horizontal_blanking: DEFAULT_HORIZONTAL_BLANKING,
159 vertical_blanking: DEFAULT_VERTICAL_BLANKING,
160 horizontal_front: DEFAULT_HORIZONTAL_FRONT_PORCH,
161 vertical_front: DEFAULT_VERTICAL_FRONT_PORCH,
162 horizontal_sync: DEFAULT_HORIZONTAL_SYNC_PULSE,
163 vertical_sync: DEFAULT_VERTICAL_SYNC_PULSE,
164 width_millimeters,
165 height_millimeters,
166 }
167 }
168
169 pub fn width(&self) -> u32 {
170 self.resolution.width
171 }
172
173 pub fn height(&self) -> u32 {
174 self.resolution.height
175 }
176
177 pub fn width_centimeters(&self) -> u8 {
178 (self.width_millimeters / 10) as u8
179 }
180
181 pub fn height_centimeters(&self) -> u8 {
182 (self.height_millimeters / 10) as u8
183 }
184}
185
186fn populate_display_name(edid_block: &mut [u8]) {
187 edid_block[0..5].clone_from_slice(&[0x00, 0x00, 0x00, 0xFC, 0x00]);
189 edid_block[5..].clone_from_slice("CrosvmDisplay".as_bytes());
190}
191
192fn populate_detailed_timing_descriptor(edid_block: &mut [u8], info: &DisplayInfo) {
193 assert_eq!(edid_block.len(), 18);
194
195 let htotal = info.width() + (info.horizontal_blanking as u32);
220 let vtotal = info.height() + (info.vertical_blanking as u32);
221 let mut clock: u16 = ((info.refresh_rate * htotal * vtotal) / 10000) as u16;
222 clock = ((clock + 5) / 10) * 10;
224 edid_block[0..2].copy_from_slice(&clock.to_le_bytes());
225
226 let width_lsb: u8 = (info.width() & 0b11111111) as u8; let width_msb: u8 = ((info.width() >> 8) & 0b00001111) as u8; let horizontal_blanking_lsb: u8 = (info.horizontal_blanking & 0b11111111) as u8; let horizontal_blanking_msb: u8 = ((info.horizontal_blanking >> 8) & 0b00001111) as u8; let vertical_blanking_lsb: u8 = (info.vertical_blanking & 0b11111111) as u8; let vertical_blanking_msb: u8 = ((info.vertical_blanking >> 8) & 0b00001111) as u8; edid_block[2] = width_lsb;
237 edid_block[3] = horizontal_blanking_lsb;
239 edid_block[4] = horizontal_blanking_msb | (width_msb << 4);
241
242 let vertical_active: u32 = info.height();
243 let vertical_active_lsb: u8 = (vertical_active & 0xFF) as u8;
244 let vertical_active_msb: u8 = ((vertical_active >> 8) & 0x0F) as u8;
245
246 edid_block[5] = vertical_active_lsb;
248 edid_block[6] = vertical_blanking_lsb;
250 edid_block[7] = vertical_blanking_msb | (vertical_active_msb << 4);
252
253 let horizontal_front_lsb: u8 = (info.horizontal_front & 0b11111111) as u8; let horizontal_front_msb: u8 = ((info.horizontal_front >> 8) & 0b00000011) as u8; let horizontal_sync_lsb: u8 = (info.horizontal_sync & 0b11111111) as u8; let horizontal_sync_msb: u8 = ((info.horizontal_sync >> 8) & 0b00000011) as u8; let vertical_front_lsb: u8 = (info.vertical_front & 0b00001111) as u8; let vertical_front_msb: u8 = ((info.vertical_front >> 4) & 0b00000011) as u8; let vertical_sync_lsb: u8 = (info.vertical_sync & 0b00001111) as u8; let vertical_sync_msb: u8 = ((info.vertical_sync >> 4) & 0b00000011) as u8; edid_block[8] = horizontal_front_lsb;
265 edid_block[9] = horizontal_sync_lsb;
267 edid_block[10] = vertical_sync_lsb | (vertical_front_lsb << 4);
269 edid_block[11] = vertical_sync_msb
271 | (vertical_front_msb << 2)
272 | (horizontal_sync_msb << 4)
273 | (horizontal_front_msb << 6);
274
275 let width_millimeters_lsb: u8 = (info.width_millimeters & 0b11111111) as u8; let width_millimeters_msb: u8 = ((info.width_millimeters >> 8) & 0b00001111) as u8; let height_millimeters_lsb: u8 = (info.height_millimeters & 0b11111111) as u8; let height_millimeters_msb: u8 = ((info.height_millimeters >> 8) & 0b00001111) as u8; edid_block[12] = width_millimeters_lsb;
282 edid_block[13] = height_millimeters_lsb;
283 edid_block[14] = height_millimeters_msb | (width_millimeters_msb << 4);
284}
285
286fn populate_displayid_detailed_timings(block: &mut [u8], start_index: usize, info: &DisplayInfo) {
287 let block = &mut block[start_index..start_index + 28];
293 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);
328 let vtotal = info.height() + (info.vertical_blanking as u32);
329 let clock = info
330 .refresh_rate
331 .checked_mul(htotal)
332 .and_then(|x| x.checked_mul(vtotal))
333 .map(|x| x / 10000)
334 .unwrap_or_else(|| {
335 panic!(
336 concat!(
337 "attempt to multiply with overflow: info.refresh_rate = {}, info.width = {}, ",
338 "info.horizontal_blanking = {}, info.height() = {}, info.vertical_blanking = {}"
339 ),
340 info.refresh_rate,
341 info.width(),
342 info.horizontal_blanking,
343 info.height(),
344 info.vertical_blanking
345 )
346 });
347
348 block[7] = (clock & 0xff) as u8;
350 block[8] = ((clock & 0xff00) >> 8) as u8;
351 block[9] = ((clock & 0xff0000) >> 16) as u8;
352
353 block[10] = 0x88;
355
356 let hblanking = info.horizontal_blanking.saturating_sub(1);
359 let horizontal_blanking_lsb: u8 = (hblanking & 0xFF) as u8;
360 let horizontal_blanking_msb: u8 = ((hblanking >> 8) & 0x0F) as u8;
361
362 let vblanking = info.vertical_blanking.saturating_sub(1);
363 let vertical_blanking_lsb: u8 = (vblanking & 0xFF) as u8;
364 let vertical_blanking_msb: u8 = ((vblanking >> 8) & 0x0F) as u8;
365
366 let horizontal_active = info.width().saturating_sub(1);
367 let horizontal_active_lsb: u8 = (horizontal_active & 0xFF) as u8;
368 let horizontal_active_msb: u8 = ((horizontal_active >> 8) & 0xFF) as u8;
369
370 let vertical_active: u32 = info.height().saturating_sub(1);
371 let vertical_active_lsb: u8 = (vertical_active & 0xFF) as u8;
372 let vertical_active_msb: u8 = ((vertical_active >> 8) & 0xFF) as u8;
373
374 let hfront = info.horizontal_front.saturating_sub(1);
375 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);
379 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);
383 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);
387 let vertical_sync_lsb: u8 = (vsync & 0xFF) as u8; let vertical_sync_msb: u8 = ((vsync >> 8) & 0x0F) as u8; block[11] = horizontal_active_lsb;
391 block[12] = horizontal_active_msb;
392 block[13] = horizontal_blanking_lsb;
393 block[14] = horizontal_blanking_msb;
394 block[15] = horizontal_front_lsb;
395 block[16] = horizontal_front_msb;
396 block[17] = horizontal_sync_lsb;
397 block[18] = horizontal_sync_msb;
398 block[19] = vertical_active_lsb;
399 block[20] = vertical_active_msb;
400 block[21] = vertical_blanking_lsb;
401 block[22] = vertical_blanking_msb;
402 block[23] = vertical_front_lsb;
403 block[24] = vertical_front_msb;
404 block[25] = vertical_sync_lsb;
405 block[26] = vertical_sync_msb;
406
407 calculate_checksum(block, 27);
408}
409
410fn populate_header(edid: &mut [u8]) {
412 edid[0] = 0x00;
413 edid[1] = 0xFF;
414 edid[2] = 0xFF;
415 edid[3] = 0xFF;
416 edid[4] = 0xFF;
417 edid[5] = 0xFF;
418 edid[6] = 0xFF;
419 edid[7] = 0x00;
420
421 let manufacturer_name: [char; 3] = ['G', 'G', 'L'];
422 let manufacturer_id: u16 = manufacturer_name
424 .iter()
425 .map(|c| (*c as u8 - b'A' + 1) & 0x1F)
426 .fold(0u16, |res, lsb| (res << 5) | (lsb as u16));
427 edid[8..10].copy_from_slice(&manufacturer_id.to_be_bytes());
428
429 let manufacture_product_id: u16 = 1;
430 edid[10..12].copy_from_slice(&manufacture_product_id.to_le_bytes());
431
432 let serial_id: u32 = 1;
433 edid[12..16].copy_from_slice(&serial_id.to_le_bytes());
434
435 let manufacture_week: u8 = 8;
436 edid[16] = manufacture_week;
437
438 let manufacture_year: u32 = 2022;
439 edid[17] = (manufacture_year - 1990u32) as u8;
440}
441
442fn populate_standard_timings(edid: &mut [u8]) -> VirtioGpuResult {
445 let resolutions = [
446 Resolution::new(1440, 900),
447 Resolution::new(1600, 900),
448 Resolution::new(800, 600),
449 Resolution::new(1680, 1050),
450 Resolution::new(1856, 1392),
451 Resolution::new(1280, 1024),
452 Resolution::new(1400, 1050),
453 Resolution::new(1920, 1200),
454 ];
455
456 for (index, r) in resolutions.iter().enumerate() {
460 edid[0x26 + (index * 2)] = (r.width / 8 - 31) as u8;
461 let ar_bits = match r.get_aspect_ratio() {
462 (8, 5) => 0x0,
463 (4, 3) => 0x1,
464 (5, 4) => 0x2,
465 (16, 9) => 0x3,
466 (x, y) => return Err(ErrEdid(format!("Unsupported aspect ratio: {x} {y}"))),
467 };
468 edid[0x27 + (index * 2)] = ar_bits;
469 }
470 Ok(OkNoData)
471}
472
473fn populate_edid_version(edid: &mut [u8]) {
475 edid[18] = 1;
476 edid[19] = 4;
477}
478
479fn populate_size(edid: &mut [u8], info: &DisplayInfo) {
480 edid[21] = info.width_centimeters();
481 edid[22] = info.height_centimeters();
482}
483
484fn calculate_checksum(block: &mut [u8], length: usize) {
485 let mut checksum: u8 = 0;
486 for byte in block.iter().take(length) {
487 checksum = checksum.wrapping_add(*byte);
488 }
489
490 if checksum != 0 {
491 checksum = 255 - checksum + 1;
492 }
493
494 block[length] = checksum;
495}