1use crate::{Error, Result};
9use alloc::vec::Vec;
10
11const NUM_STANDARD_TIMINGS: usize = 8;
13
14const DTD1_OFFSET: usize = 0x36;
16
17const DTD_LEN: usize = 18;
19
20const STANDARD_TIMINGS_OFFSET: usize = 38;
22
23const STANDARD_TIMING_LEN: usize = 2;
25
26#[derive(Debug, Clone, Copy, PartialEq, Eq)]
28enum AspectRatio {
29 Ratio16x10,
31 Ratio4x3,
33 Ratio5x4,
35 Ratio16x9,
37}
38
39impl AspectRatio {
40 fn from_bits(bits: u8) -> Self {
41 match bits {
42 0 => Self::Ratio16x10,
43 1 => Self::Ratio4x3,
44 2 => Self::Ratio5x4,
45 3 => Self::Ratio16x9,
46 _ => unreachable!(),
47 }
48 }
49
50 fn v_pixels(self, h_pixels: u32) -> u32 {
52 match self {
53 Self::Ratio16x10 => h_pixels * 10 / 16,
54 Self::Ratio4x3 => h_pixels * 3 / 4,
55 Self::Ratio5x4 => h_pixels * 4 / 5,
56 Self::Ratio16x9 => h_pixels * 9 / 16,
57 }
58 }
59}
60
61struct StandardTiming {
65 h_pixels: u32,
67 v_pixels: u32,
69}
70
71impl StandardTiming {
72 const UNUSED: [u8; 2] = [0x01, 0x01];
74
75 fn parse(bytes: &[u8; 2]) -> Option<Self> {
79 if *bytes == Self::UNUSED {
80 return None;
81 }
82 let h_pixels = (bytes[0] as u32 + 31) * 8;
83 let aspect = AspectRatio::from_bits((bytes[1] >> 6) & 0x03);
84 let v_pixels = aspect.v_pixels(h_pixels);
85 Some(Self { h_pixels, v_pixels })
86 }
87}
88
89struct DetailedTiming {
93 h_active: u32,
95 v_active: u32,
97}
98
99impl DetailedTiming {
100 fn parse(bytes: &[u8; DTD_LEN]) -> Option<Self> {
104 let h_active = bytes[2] as u32 | ((bytes[4] as u32 & 0xF0) << 4);
106 let v_active = bytes[5] as u32 | ((bytes[7] as u32 & 0xF0) << 4);
108 if h_active == 0 || v_active == 0 {
109 return None;
110 }
111 Some(Self { h_active, v_active })
112 }
113}
114
115pub struct Edid {
120 pub(super) data: [u8; 1024],
121 pub(super) size: u32,
122}
123
124impl Edid {
125 fn has_base_block(&self) -> bool {
127 self.size >= 128
128 }
129
130 fn first_detailed_timing(&self) -> Option<DetailedTiming> {
132 if !self.has_base_block() {
133 return None;
134 }
135 let bytes = &self.data[DTD1_OFFSET..][..DTD_LEN].try_into().unwrap();
136 DetailedTiming::parse(bytes)
137 }
138
139 fn standard_timing(&self, index: usize) -> Option<StandardTiming> {
141 let offset = STANDARD_TIMINGS_OFFSET + index * STANDARD_TIMING_LEN;
142 let bytes = &self.data[offset..][..STANDARD_TIMING_LEN]
143 .try_into()
144 .unwrap();
145 StandardTiming::parse(bytes)
146 }
147
148 pub fn preferred_resolution(&self) -> Result<(u32, u32)> {
153 let dtd = self.first_detailed_timing().ok_or(Error::IoError)?;
154 Ok((dtd.h_active, dtd.v_active))
155 }
156
157 pub fn standard_timings(&self) -> Vec<(u32, u32)> {
162 if !self.has_base_block() {
163 return Vec::new();
164 }
165 let mut resolutions: Vec<(u32, u32)> = (0..NUM_STANDARD_TIMINGS)
166 .filter_map(|i| self.standard_timing(i))
167 .map(|st| (st.h_pixels, st.v_pixels))
168 .collect();
169 resolutions.sort_by(|a, b| (b.0 as u64 * b.1 as u64).cmp(&(a.0 as u64 * a.1 as u64)));
170 resolutions
171 }
172}
173
174#[cfg(test)]
175mod tests {
176 use super::*;
177
178 fn qemu_edid() -> [u8; 1024] {
187 let mut edid = [0u8; 1024];
188 let data: [u8; 256] = [
189 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x49, 0x14, 0x34, 0x12, 0x00, 0x00,
191 0x00, 0x00, 0x2a, 0x18, 0x01, 0x04, 0xa5, 0x30, 0x1b, 0x78, 0x06, 0xee, 0x91, 0xa3,
192 0x54, 0x4c, 0x99, 0x26, 0x0f, 0x50, 0x54, 0x21, 0x08, 0x00, 0xe1, 0xc0, 0xd1, 0xc0,
193 0xd1, 0x00, 0xa9, 0x40, 0xb3, 0x00, 0x95, 0x00, 0x81, 0x80, 0x81, 0x40, 0xd2, 0x54,
194 0x80, 0xa0, 0x72, 0x38, 0x25, 0x40, 0xe0, 0x39, 0x55, 0x40, 0xe7, 0x12, 0x11, 0x00,
195 0x00, 0x18, 0x00, 0x00, 0x00, 0xf7, 0x00, 0x0a, 0x00, 0x40, 0x82, 0x00, 0x28, 0x20,
196 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfd, 0x00, 0x32, 0x7d, 0x1e,
197 0xa0, 0xff, 0x01, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x00, 0x00, 0x00, 0xfc,
198 0x00, 0x51, 0x45, 0x4d, 0x55, 0x20, 0x4d, 0x6f, 0x6e, 0x69, 0x74, 0x6f, 0x72, 0x0a,
199 0x01, 0xb0, 0x02, 0x03, 0x0b, 0x00, 0x46, 0x7d, 0x65, 0x60, 0x59, 0x1f, 0x61, 0x00, 0x00, 0x00,
201 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
202 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
203 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00,
204 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00,
205 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
206 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
207 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
208 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
209 0x00, 0x2f,
210 ];
211 edid[..256].copy_from_slice(&data);
212 edid
213 }
214
215 fn make_edid(data: [u8; 1024], size: u32) -> Edid {
216 Edid { data, size }
217 }
218
219 fn qemu_dtd1_bytes() -> [u8; DTD_LEN] {
221 let data = qemu_edid();
222 data[DTD1_OFFSET..DTD1_OFFSET + DTD_LEN].try_into().unwrap()
223 }
224
225 #[test]
228 fn aspect_ratio_from_bits() {
229 assert_eq!(AspectRatio::from_bits(0), AspectRatio::Ratio16x10);
230 assert_eq!(AspectRatio::from_bits(1), AspectRatio::Ratio4x3);
231 assert_eq!(AspectRatio::from_bits(2), AspectRatio::Ratio5x4);
232 assert_eq!(AspectRatio::from_bits(3), AspectRatio::Ratio16x9);
233 }
234
235 #[test]
236 fn aspect_ratio_v_pixels() {
237 assert_eq!(AspectRatio::Ratio16x10.v_pixels(1920), 1200);
238 assert_eq!(AspectRatio::Ratio4x3.v_pixels(1600), 1200);
239 assert_eq!(AspectRatio::Ratio5x4.v_pixels(1280), 1024);
240 assert_eq!(AspectRatio::Ratio16x9.v_pixels(1920), 1080);
241 }
242
243 #[test]
246 fn standard_timing_unused_entry() {
247 assert!(StandardTiming::parse(&[0x01, 0x01]).is_none());
248 }
249
250 #[test]
251 fn standard_timing_known_entry() {
252 let st = StandardTiming::parse(&[0xe1, 0xc0]).unwrap();
257 assert_eq!(st.h_pixels, 2048);
258 assert_eq!(st.v_pixels, 1152);
259 }
260
261 #[test]
262 fn standard_timing_each_aspect_ratio() {
263 let st = StandardTiming::parse(&[0xd1, 0x00]).unwrap();
265 assert_eq!((st.h_pixels, st.v_pixels), (1920, 1200));
266
267 let st = StandardTiming::parse(&[0xa9, 0x40]).unwrap();
269 assert_eq!((st.h_pixels, st.v_pixels), (1600, 1200));
270
271 let st = StandardTiming::parse(&[0x81, 0x80]).unwrap();
273 assert_eq!((st.h_pixels, st.v_pixels), (1280, 1024));
274
275 let st = StandardTiming::parse(&[0xd1, 0xc0]).unwrap();
277 assert_eq!((st.h_pixels, st.v_pixels), (1920, 1080));
278 }
279
280 #[test]
283 fn detailed_timing_zeroed() {
284 assert!(DetailedTiming::parse(&[0u8; DTD_LEN]).is_none());
285 }
286
287 #[test]
288 fn detailed_timing_known_1920x1080() {
289 let dtd = DetailedTiming::parse(&qemu_dtd1_bytes()).unwrap();
290 assert_eq!(dtd.h_active, 1920);
291 assert_eq!(dtd.v_active, 1080);
292 }
293
294 #[test]
295 fn detailed_timing_h_active_uses_upper_nibble() {
296 let mut bytes = [0u8; DTD_LEN];
300 bytes[0] = 0x01; bytes[2] = 0x00; bytes[4] = 0x50; bytes[5] = 0x00; bytes[7] = 0x40; let dtd = DetailedTiming::parse(&bytes).unwrap();
306 assert_eq!(dtd.h_active, 1280);
307 assert_eq!(dtd.v_active, 1024);
308 }
309
310 #[test]
313 fn edid_size_exactly_128() {
314 let edid = make_edid(qemu_edid(), 128);
315 assert!(edid.preferred_resolution().is_ok());
316 assert!(!edid.standard_timings().is_empty());
317 }
318
319 #[test]
320 fn edid_size_127_fails() {
321 let edid = make_edid(qemu_edid(), 127);
322 assert!(edid.preferred_resolution().is_err());
323 assert!(edid.standard_timings().is_empty());
324 }
325
326 #[test]
327 fn qemu_edid_preferred_resolution() {
328 let edid = make_edid(qemu_edid(), 256);
329 let (w, h) = edid.preferred_resolution().unwrap();
330 assert_eq!((w, h), (1920, 1080));
331 }
332
333 #[test]
334 fn qemu_edid_standard_timings() {
335 let edid = make_edid(qemu_edid(), 256);
336 let res = edid.standard_timings();
337 assert_eq!(
341 res,
342 vec![
343 (2048, 1152), (1920, 1200), (1920, 1080), (1600, 1200), (1680, 1050), (1280, 1024), (1440, 900), (1280, 960), ]
352 );
353 }
354
355 #[test]
356 fn qemu_edid_highest_resolution_is_2048x1152() {
357 let edid = make_edid(qemu_edid(), 256);
358 let res = edid.standard_timings();
359 assert_eq!(res.first(), Some(&(2048, 1152)));
360 }
361
362 #[test]
363 fn preferred_resolution_too_short() {
364 let edid = make_edid([0u8; 1024], 64);
365 assert!(edid.preferred_resolution().is_err());
366 }
367
368 #[test]
369 fn preferred_resolution_zeroed_active_pixels() {
370 let mut data = qemu_edid();
372 data[0x38] = 0x00; data[0x3A] &= 0x0F; data[0x3B] = 0x00; data[0x3D] &= 0x0F; let edid = make_edid(data, 256);
377 assert!(edid.preferred_resolution().is_err());
378 }
379
380 #[test]
381 fn standard_timings_too_short() {
382 let edid = make_edid([0u8; 1024], 32);
383 let res = edid.standard_timings();
384 assert!(res.is_empty());
385 }
386
387 #[test]
388 fn standard_timings_all_unused() {
389 let mut data = qemu_edid();
391 for i in 0..8 {
392 data[38 + i * 2] = 0x01;
393 data[38 + i * 2 + 1] = 0x01;
394 }
395 let edid = make_edid(data, 256);
396 let res = edid.standard_timings();
397 assert!(res.is_empty());
398 }
399
400 #[test]
401 fn standard_timings_partial_entries() {
402 let mut data = qemu_edid();
404 for i in 2..8 {
405 data[38 + i * 2] = 0x01;
406 data[38 + i * 2 + 1] = 0x01;
407 }
408 let edid = make_edid(data, 256);
409 let res = edid.standard_timings();
410 assert_eq!(res, vec![(2048, 1152), (1920, 1080)]);
412 }
413}