memf_linux/
framebuffer.rs1use memf_core::framebuffer::{encode_png, FramebufferResult, PixelFormat};
12use memf_core::object_reader::ObjectReader;
13use memf_format::PhysicalMemoryProvider;
14
15use crate::Result;
16
17const MAX_FB_BYTES: u64 = 32 * 1024 * 1024; const LFB_BASE_OFF: u64 = 0x10;
20const LFB_WIDTH_OFF: u64 = 0x14;
21const LFB_HEIGHT_OFF: u64 = 0x16;
22const LFB_DEPTH_OFF: u64 = 0x18;
23const LFB_STRIDE_OFF: u64 = 0x1A;
24
25pub fn walk_framebuffer_linux<P: PhysicalMemoryProvider + Clone>(
28 reader: &ObjectReader<P>,
29) -> Result<FramebufferResult> {
30 let boot_params_va =
31 reader
32 .required_symbol("boot_params")
33 .map_err(|_| crate::Error::WalkFailed {
34 walker: "framebuffer",
35 reason: "boot_params symbol not found".into(),
36 })?;
37
38 let lfb_base = {
39 let b = reader
40 .read_bytes(boot_params_va + LFB_BASE_OFF, 4)
41 .map_err(|e| crate::Error::WalkFailed {
42 walker: "framebuffer",
43 reason: format!("read lfb_base: {e}"),
44 })?;
45 u64::from(b.try_into().map_or(0, u32::from_le_bytes))
46 };
47
48 let width = {
49 let b = reader
50 .read_bytes(boot_params_va + LFB_WIDTH_OFF, 2)
51 .map_err(|e| crate::Error::WalkFailed {
52 walker: "framebuffer",
53 reason: format!("read lfb_width: {e}"),
54 })?;
55 u32::from(b.try_into().map_or(0, u16::from_le_bytes))
56 };
57
58 let height = {
59 let b = reader
60 .read_bytes(boot_params_va + LFB_HEIGHT_OFF, 2)
61 .map_err(|e| crate::Error::WalkFailed {
62 walker: "framebuffer",
63 reason: format!("read lfb_height: {e}"),
64 })?;
65 u32::from(b.try_into().map_or(0, u16::from_le_bytes))
66 };
67
68 let depth = {
69 let b = reader
70 .read_bytes(boot_params_va + LFB_DEPTH_OFF, 2)
71 .map_err(|e| crate::Error::WalkFailed {
72 walker: "framebuffer",
73 reason: format!("read lfb_depth: {e}"),
74 })?;
75 b.try_into().map_or(0, u16::from_le_bytes)
76 };
77
78 let stride = {
79 let b = reader
80 .read_bytes(boot_params_va + LFB_STRIDE_OFF, 4)
81 .map_err(|e| crate::Error::WalkFailed {
82 walker: "framebuffer",
83 reason: format!("read lfb_linelength: {e}"),
84 })?;
85 b.try_into().map_or(0, u32::from_le_bytes)
86 };
87
88 if width == 0 || height == 0 || lfb_base == 0 {
89 return Err(crate::Error::WalkFailed {
90 walker: "framebuffer",
91 reason: "screen_info has zero dimensions or base address".into(),
92 });
93 }
94
95 let pixel_format = match depth {
96 32 => PixelFormat::Xbgr8888,
97 24 => PixelFormat::Bgr24,
98 16 => PixelFormat::Rgb565,
99 d => PixelFormat::Unknown(d as u8),
100 };
101
102 let fb_size = u64::from(stride) * u64::from(height);
103 if fb_size > MAX_FB_BYTES {
104 return Err(crate::Error::WalkFailed {
105 walker: "framebuffer",
106 reason: format!("framebuffer size {fb_size} exceeds 32 MiB"),
107 });
108 }
109
110 let mut fb_bytes = vec![0u8; fb_size as usize];
111 reader
112 .vas()
113 .physical()
114 .read_phys(lfb_base, &mut fb_bytes)
115 .map_err(|_| crate::Error::WalkFailed {
116 walker: "framebuffer",
117 reason: format!("could not read {fb_size} bytes from PA {lfb_base:#x}"),
118 })?;
119
120 let png_bytes = encode_png(&fb_bytes, width, height, pixel_format).map_err(|e| {
121 crate::Error::WalkFailed {
122 walker: "framebuffer",
123 reason: format!("PNG encode: {e}"),
124 }
125 })?;
126
127 Ok(FramebufferResult {
128 width,
129 height,
130 stride,
131 pixel_format: format!("{pixel_format:?}"),
132 phys_base: lfb_base,
133 source: "boot_params.screen_info".into(),
134 png_bytes,
135 })
136}
137
138#[cfg(test)]
139mod tests {
140 use memf_core::object_reader::ObjectReader;
141 use memf_core::test_builders::{flags as ptf, PageTableBuilder};
142 use memf_core::vas::{TranslationMode, VirtualAddressSpace};
143 use memf_symbols::isf::IsfResolver;
144 use memf_symbols::test_builders::IsfBuilder;
145
146 use super::walk_framebuffer_linux;
147
148 const BOOT_PARAMS_VA: u64 = 0xFFFF_8800_00A0_0000;
164 const BOOT_PARAMS_PA: u64 = 0x00A0_0000; const FB_PA: u64 = 0x00B0_0000; fn build_reader() -> ObjectReader<memf_core::test_builders::SyntheticPhysMem> {
168 let mut page = [0u8; 4096];
169
170 page[0x10..0x14].copy_from_slice(&(FB_PA as u32).to_le_bytes());
172 page[0x14..0x16].copy_from_slice(&4u16.to_le_bytes());
174 page[0x16..0x18].copy_from_slice(&4u16.to_le_bytes());
176 page[0x18..0x1a].copy_from_slice(&32u16.to_le_bytes());
178 page[0x1a..0x1e].copy_from_slice(&16u32.to_le_bytes());
180
181 let fb_data: Vec<u8> = [0x10u8, 0x20, 0x30, 0xFF]
183 .iter()
184 .copied()
185 .cycle()
186 .take(64)
187 .collect();
188
189 let isf = IsfBuilder::new()
190 .add_symbol("boot_params", BOOT_PARAMS_VA)
191 .build_json();
192 let resolver = IsfResolver::from_value(&isf).unwrap();
193
194 let (cr3, mem) = PageTableBuilder::new()
195 .map_4k(BOOT_PARAMS_VA, BOOT_PARAMS_PA, ptf::WRITABLE)
196 .write_phys(BOOT_PARAMS_PA, &page)
197 .write_phys(FB_PA, &fb_data)
198 .build();
199
200 let vas = VirtualAddressSpace::new(mem, cr3, TranslationMode::X86_64FourLevel);
201 ObjectReader::new(vas, Box::new(resolver))
202 }
203
204 fn build_reader_no_sym() -> ObjectReader<memf_core::test_builders::SyntheticPhysMem> {
205 let isf = IsfBuilder::new().build_json();
206 let resolver = IsfResolver::from_value(&isf).unwrap();
207 let (cr3, mem) = PageTableBuilder::new().build();
208 let vas = VirtualAddressSpace::new(mem, cr3, TranslationMode::X86_64FourLevel);
209 ObjectReader::new(vas, Box::new(resolver))
210 }
211
212 #[test]
213 fn linux_framebuffer_extracts_4x4_from_boot_params() {
214 let reader = build_reader();
215 let result = walk_framebuffer_linux(&reader).expect("should find framebuffer");
216 assert_eq!(result.width, 4);
217 assert_eq!(result.height, 4);
218 assert_eq!(result.source, "boot_params.screen_info");
219 assert_eq!(result.phys_base, FB_PA);
220 }
221
222 #[test]
223 fn linux_framebuffer_png_output_has_png_magic() {
224 let reader = build_reader();
225 let result = walk_framebuffer_linux(&reader).expect("should succeed");
226 assert!(
227 result.png_bytes.starts_with(b"\x89PNG\r\n\x1a\n"),
228 "PNG magic not found; first bytes: {:?}",
229 &result.png_bytes[..result.png_bytes.len().min(16)]
230 );
231 }
232
233 #[test]
234 fn linux_framebuffer_missing_symbol_returns_err() {
235 let reader = build_reader_no_sym();
236 let result = walk_framebuffer_linux(&reader);
237 assert!(
238 result.is_err(),
239 "expected Err when boot_params symbol absent"
240 );
241 }
242}