1use crate::rendering::eink::{
12 FbFixScreeninfo, FbVarScreeninfo, MxcfbRect, MxcfbUpdateData, FBIOGET_FSCREENINFO,
13 FBIOGET_VSCREENINFO, MXCFB_SEND_UPDATE,
14};
15use log::{info, warn};
16
17const UPDATE_MARKER: u32 = 1;
18const WAIT_FALLBACK_MS: u64 = 400;
19
20#[derive(Debug, Clone, Copy)]
21pub struct UpdateRegion {
22 pub x: usize,
23 pub y: usize,
24 pub w: usize,
25 pub h: usize,
26}
27
28pub fn dump_ppm(path: &str, buf: &[u8], w: usize, h: usize) {
29 let mut out = Vec::with_capacity(15 + w * h * 3);
30 out.extend_from_slice(format!("P6\n{} {}\n255\n", w, h).as_bytes());
31 for i in 0..w * h {
32 let off = i * 2;
33 let v = (buf[off] as u16) | ((buf[off + 1] as u16) << 8);
34 let r = ((v >> 11) & 0x1f) as u8;
35 let g = ((v >> 5) & 0x3f) as u8;
36 let b = (v & 0x1f) as u8;
37 out.push((r << 3) | (r >> 2));
38 out.push((g << 2) | (g >> 4));
39 out.push((b << 3) | (b >> 2));
40 }
41 match std::fs::write(path, &out) {
42 Ok(_) => (),
43 Err(e) => warn!("PPM write err: {}", e),
44 }
45}
46
47pub struct Fb {
48 _file: std::fs::File,
49 pub fd: libc::c_int,
50 pub ptr: *mut u8,
51 pub map_len: usize,
52 pub stride: usize,
53 pub bpp: usize,
54 pub xres: usize,
55 pub yres: usize,
56 r_off: u32,
57 g_off: u32,
58 b_off: u32,
59}
60
61impl Fb {
62 pub fn open() -> Option<Fb> {
63 use std::os::unix::io::AsRawFd;
64 let file = std::fs::OpenOptions::new()
65 .read(true)
66 .write(true)
67 .open("/dev/fb0")
68 .ok()?;
69 let fd = file.as_raw_fd();
70 let mut var = FbVarScreeninfo::default();
71 let mut fix = FbFixScreeninfo::default();
72 unsafe {
77 if libc::ioctl(fd, FBIOGET_VSCREENINFO as _, &mut var) < 0 {
78 return None;
79 }
80 if libc::ioctl(fd, FBIOGET_FSCREENINFO as _, &mut fix) < 0 {
81 return None;
82 }
83 }
84 let map_len = fix.smem_len as usize;
85 let ptr = unsafe {
90 libc::mmap(
91 std::ptr::null_mut(),
92 map_len,
93 libc::PROT_READ | libc::PROT_WRITE,
94 libc::MAP_SHARED,
95 fd,
96 0,
97 )
98 };
99 if ptr == libc::MAP_FAILED {
100 return None;
101 }
102 info!(
103 "fb: {}x{} bpp={} line_length={} smem_len={}",
104 var.xres, var.yres, var.bits_per_pixel, fix.line_length, fix.smem_len
105 );
106 let (r_off, g_off, b_off) = (
107 var.rest.first().copied().unwrap_or(16),
108 var.rest.get(3).copied().unwrap_or(8),
109 var.rest.get(6).copied().unwrap_or(0),
110 );
111 info!("fb: rgb offsets r={} g={} b={}", r_off, g_off, b_off);
112 Some(Fb {
113 _file: file,
114 fd,
115 ptr: ptr as *mut u8,
116 map_len,
117 stride: fix.line_length as usize,
118 bpp: var.bits_per_pixel as usize,
119 xres: var.xres as usize,
120 yres: var.yres as usize,
121 r_off,
122 g_off,
123 b_off,
124 })
125 }
126
127 pub fn present_rect(&self, buf: &[u8], w: usize, h: usize, rect: &UpdateRegion, waveform: u32) {
136 let x0 = rect.x.min(self.xres);
137 let y0 = rect.y.min(self.yres);
138 let x1 = (rect.x + rect.w).min(w).min(self.xres);
139 let y1 = (rect.y + rect.h).min(h).min(self.yres);
140 if x0 >= x1 || y0 >= y1 {
141 return;
142 }
143 let fb = unsafe { std::slice::from_raw_parts_mut(self.ptr, self.map_len) };
146 let bpp = self.bpp;
147 let stride = self.stride;
148 for py in y0..y1 {
149 let row = py * w;
150 let fb_row = py * stride;
151 for px in x0..x1 {
152 let buf_off = (row + px) * 2;
153 let pix = (buf[buf_off] as u16) | ((buf[buf_off + 1] as u16) << 8);
154 let off = fb_row + px * (bpp / 8);
155 match bpp {
156 32 => {
157 let r = (((pix >> 11) & 0x1f) << 3) as u8;
158 let g = (((pix >> 5) & 0x3f) << 2) as u8;
159 let b = ((pix & 0x1f) << 3) as u8;
160 fb[off + (self.r_off / 8) as usize] = r;
161 fb[off + (self.g_off / 8) as usize] = g;
162 fb[off + (self.b_off / 8) as usize] = b;
163 fb[off + 3] = 0xff;
164 }
165 16 => {
166 fb[off] = (pix & 0xff) as u8;
167 fb[off + 1] = (pix >> 8) as u8;
168 }
169 _ => {}
170 }
171 }
172 }
173 let sync_start = y0 * stride;
176 let sync_len = ((y1 - y0) * stride).max(1);
177 unsafe {
180 libc::msync(
181 self.ptr.add(sync_start) as *mut libc::c_void,
182 sync_len,
183 libc::MS_SYNC,
184 );
185 }
186 let upd = MxcfbUpdateData {
187 update_region: MxcfbRect {
188 top: y0 as u32,
189 left: x0 as u32,
190 width: (x1 - x0) as u32,
191 height: (y1 - y0) as u32,
192 },
193 waveform_mode: waveform,
194 update_mode: 0,
195 update_marker: UPDATE_MARKER,
196 temp: 0x1000,
197 flags: 0,
198 };
199 let _ = unsafe { libc::ioctl(self.fd, MXCFB_SEND_UPDATE as _, &upd) };
202 }
203
204 pub fn present(
207 &self,
208 buf: &[u8],
209 w: usize,
210 h: usize,
211 full: bool,
212 top: usize,
213 rh: usize,
214 waveform: u32,
215 ) {
216 let fb = unsafe { std::slice::from_raw_parts_mut(self.ptr, self.map_len) };
221 let bpp = self.bpp;
222 let stride = self.stride;
223 let (y0, y1) = if full && top == 0 && rh == 0 {
224 (0, h.min(self.yres))
225 } else {
226 let end = (top + rh).min(h).min(self.yres);
227 (top.min(end), end)
228 };
229 let x1 = w.min(self.xres);
230 for y in y0..y1 {
231 let row = y * w;
232 let fb_row = y * stride;
233 for x in 0..x1 {
234 let buf_off = (row + x) * 2;
235 let px = (buf[buf_off] as u16) | ((buf[buf_off + 1] as u16) << 8);
236 let off = fb_row + x * (bpp / 8);
237 match bpp {
238 32 => {
239 let r = (((px >> 11) & 0x1f) << 3) as u8;
240 let g = (((px >> 5) & 0x3f) << 2) as u8;
241 let b = ((px & 0x1f) << 3) as u8;
242 fb[off + (self.r_off / 8) as usize] = r;
243 fb[off + (self.g_off / 8) as usize] = g;
244 fb[off + (self.b_off / 8) as usize] = b;
245 fb[off + 3] = 0xff;
246 }
247 16 => {
248 fb[off] = (px & 0xff) as u8;
249 fb[off + 1] = (px >> 8) as u8;
250 }
251 _ => {}
252 }
253 }
254 }
255 let sync_start = y0 * self.stride;
256 let sync_len = ((y1 - y0) * self.stride).max(1);
257 unsafe {
261 libc::msync(
262 self.ptr.add(sync_start) as *mut libc::c_void,
263 sync_len,
264 libc::MS_SYNC,
265 );
266 }
267 let upd = MxcfbUpdateData {
268 update_region: MxcfbRect {
269 top: y0 as u32,
270 left: 0,
271 width: x1 as u32,
272 height: (y1 - y0) as u32,
273 },
274 waveform_mode: waveform,
275 update_mode: if full { 1 } else { 0 },
276 update_marker: UPDATE_MARKER,
277 temp: 0x1000,
278 flags: 0,
279 };
280 let _ = unsafe { libc::ioctl(self.fd, MXCFB_SEND_UPDATE as _, &upd) };
284 }
285
286 pub fn wait_for_update_complete(&self) {
287 let marker: u32 = UPDATE_MARKER;
288 let rc = unsafe {
291 libc::ioctl(
292 self.fd,
293 crate::rendering::eink::MXCFB_WAIT_FOR_UPDATE_COMPLETE as _,
294 &marker as *const u32,
295 )
296 };
297 if rc < 0 {
298 std::thread::sleep(std::time::Duration::from_millis(WAIT_FALLBACK_MS));
299 }
300 }
301}
302
303impl Drop for Fb {
304 fn drop(&mut self) {
305 unsafe {
309 libc::munmap(self.ptr as *mut libc::c_void, self.map_len);
310 }
311 }
312}