Skip to main content

ferroui_linuxframebuffer/output/
fb_device.rs

1//! What the framebuffer output asks of its device.
2//!
3//! An addition (docs/porting/linux-framebuffer-platform.md, section 5):
4//! the reference calls the kernel directly. With [`LinuxFbDevice`] the
5//! calls are the reference's; [`FileFbDevice`] is a plain file that stands
6//! in for a device in tests and on machines without one.
7
8use crate::native_unsafe_methods::{fb_fix_screeninfo, fb_var_screeninfo};
9use std::fs::{File, OpenOptions};
10use std::io;
11use std::os::fd::AsRawFd;
12
13/// The memory of a device, mapped into the process. It is unmapped when
14/// the last owner lets go of it.
15pub struct FbMapping {
16    address: *mut u8,
17    length: usize,
18}
19
20// SAFETY: the mapping is memory shared with a device (or a file); nothing
21// in it is tied to a thread. The output hands it to one renderer at a
22// time, and a write that races another only mixes the pixels of two
23// frames in memory that has no validity invariant.
24unsafe impl Send for FbMapping {}
25// SAFETY: as above.
26unsafe impl Sync for FbMapping {}
27
28impl FbMapping {
29    /// `mmap(null, length, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0)`.
30    fn map(fd: i32, length: usize) -> io::Result<Self> {
31        // SAFETY: asks the kernel for a new shared mapping of `length`
32        // bytes of the descriptor; no existing memory is affected.
33        let address = unsafe {
34            libc::mmap(std::ptr::null_mut(), length, libc::PROT_READ | libc::PROT_WRITE, libc::MAP_SHARED, fd, 0)
35        };
36        if address == libc::MAP_FAILED {
37            let error = io::Error::last_os_error();
38            return Err(io::Error::new(error.kind(), format!("Unable to mmap {length} bytes, error {error}")));
39        }
40        Ok(Self { address: address.cast(), length })
41    }
42
43    pub fn address(&self) -> *mut u8 {
44        self.address
45    }
46
47    pub fn len(&self) -> usize {
48        self.length
49    }
50
51    pub fn is_empty(&self) -> bool {
52        self.length == 0
53    }
54
55    /// Copies `source` to the start of the mapping, as much as fits.
56    pub fn copy_from(&self, source: &[u8]) {
57        let count = source.len().min(self.length);
58        // SAFETY: both ranges are valid for `count` bytes and do not
59        // overlap (`source` is memory of the process, not of the mapping).
60        unsafe {
61            std::ptr::copy_nonoverlapping(source.as_ptr(), self.address, count);
62        }
63    }
64
65    /// The content of the mapping.
66    pub fn to_vec(&self) -> Vec<u8> {
67        let mut rv = vec![0u8; self.length];
68        // SAFETY: the mapping is valid for `length` bytes and `rv` is new
69        // memory of that length.
70        unsafe {
71            std::ptr::copy_nonoverlapping(self.address, rv.as_mut_ptr(), self.length);
72        }
73        rv
74    }
75}
76
77impl Drop for FbMapping {
78    fn drop(&mut self) {
79        // SAFETY: the range is the one `mmap` returned, unmapped once.
80        unsafe {
81            libc::munmap(self.address.cast(), self.length);
82        }
83    }
84}
85
86/// A framebuffer device.
87pub trait IFbDevice: Send {
88    /// `FBIOGET_VSCREENINFO`.
89    fn get_var_screen_info(&self) -> io::Result<fb_var_screeninfo>;
90
91    /// `FBIOPUT_VSCREENINFO`.
92    fn put_var_screen_info(&mut self, info: &fb_var_screeninfo) -> io::Result<()>;
93
94    /// `FBIOGET_FSCREENINFO`.
95    fn get_fix_screen_info(&self) -> io::Result<fb_fix_screeninfo>;
96
97    /// Maps `length` bytes of the device's memory for reading and writing.
98    fn map(&self, length: usize) -> io::Result<FbMapping>;
99
100    /// `FBIO_WAITFORVSYNC`; a device without it returns at once.
101    fn wait_for_vsync(&self);
102}
103
104/// A framebuffer device of the kernel.
105#[cfg(target_os = "linux")]
106pub struct LinuxFbDevice {
107    fd: std::os::fd::OwnedFd,
108}
109
110#[cfg(target_os = "linux")]
111impl LinuxFbDevice {
112    /// Opens the device for reading and writing.
113    pub fn open(file_name: &str) -> io::Result<Self> {
114        use std::os::fd::FromRawFd;
115        let fd = crate::native_unsafe_methods::sys::open(file_name, libc::O_RDWR)
116            .map_err(|error| io::Error::new(error.kind(), format!("Error: {error}")))?;
117        // SAFETY: `fd` was just opened and is owned by nothing else.
118        Ok(Self { fd: unsafe { std::os::fd::OwnedFd::from_raw_fd(fd) } })
119    }
120}
121
122#[cfg(target_os = "linux")]
123impl IFbDevice for LinuxFbDevice {
124    fn get_var_screen_info(&self) -> io::Result<fb_var_screeninfo> {
125        use crate::native_unsafe_methods::{fb_io_ctl, sys};
126        let mut info = fb_var_screeninfo::default();
127        sys::ioctl_struct(self.fd.as_raw_fd(), fb_io_ctl::FBIOGET_VSCREENINFO, &mut info)
128            .map_err(|error| io::Error::new(error.kind(), format!("FBIOGET_VSCREENINFO error: {error}")))?;
129        Ok(info)
130    }
131
132    fn put_var_screen_info(&mut self, info: &fb_var_screeninfo) -> io::Result<()> {
133        use crate::native_unsafe_methods::{fb_io_ctl, sys};
134        let mut info = *info;
135        sys::ioctl_struct(self.fd.as_raw_fd(), fb_io_ctl::FBIOPUT_VSCREENINFO, &mut info).map(|_| ())
136    }
137
138    fn get_fix_screen_info(&self) -> io::Result<fb_fix_screeninfo> {
139        use crate::native_unsafe_methods::{fb_io_ctl, sys};
140        let mut info = fb_fix_screeninfo::default();
141        sys::ioctl_struct(self.fd.as_raw_fd(), fb_io_ctl::FBIOGET_FSCREENINFO, &mut info)
142            .map_err(|error| io::Error::new(error.kind(), format!("FBIOGET_FSCREENINFO error: {error}")))?;
143        Ok(info)
144    }
145
146    fn map(&self, length: usize) -> io::Result<FbMapping> {
147        FbMapping::map(self.fd.as_raw_fd(), length)
148    }
149
150    fn wait_for_vsync(&self) {
151        use crate::native_unsafe_methods::{fb_io_ctl, sys};
152        sys::ioctl_null(self.fd.as_raw_fd(), fb_io_ctl::FBIO_WAITFORVSYNC);
153    }
154}
155
156/// A plain file as a framebuffer: `line_length * yres` bytes, mapped as a
157/// device's memory is.
158pub struct FileFbDevice {
159    file: File,
160    var_info: fb_var_screeninfo,
161    id: [u8; 16],
162}
163
164impl FileFbDevice {
165    /// Creates (or truncates) the file for a framebuffer of the given
166    /// variable information; the lines have no padding.
167    pub fn create(path: &str, var_info: fb_var_screeninfo) -> io::Result<Self> {
168        let file = OpenOptions::new().read(true).write(true).create(true).truncate(true).open(path)?;
169        let mut id = [0u8; 16];
170        id[..4].copy_from_slice(b"file");
171        let this = Self { file, var_info, id };
172        this.resize()?;
173        Ok(this)
174    }
175
176    fn line_length(&self) -> u32 {
177        self.var_info.xres * self.var_info.bits_per_pixel.div_ceil(8)
178    }
179
180    fn resize(&self) -> io::Result<()> {
181        self.file.set_len(self.line_length() as u64 * self.var_info.yres as u64)
182    }
183}
184
185impl IFbDevice for FileFbDevice {
186    fn get_var_screen_info(&self) -> io::Result<fb_var_screeninfo> {
187        Ok(self.var_info)
188    }
189
190    fn put_var_screen_info(&mut self, info: &fb_var_screeninfo) -> io::Result<()> {
191        if info.bits_per_pixel != 16 && info.bits_per_pixel != 32 {
192            return Err(io::Error::from(io::ErrorKind::InvalidInput));
193        }
194        self.var_info = *info;
195        self.resize()
196    }
197
198    fn get_fix_screen_info(&self) -> io::Result<fb_fix_screeninfo> {
199        let line_length = self.line_length();
200        Ok(fb_fix_screeninfo {
201            id: self.id,
202            smem_len: line_length * self.var_info.yres,
203            line_length,
204            ..Default::default()
205        })
206    }
207
208    fn map(&self, length: usize) -> io::Result<FbMapping> {
209        FbMapping::map(self.file.as_raw_fd(), length)
210    }
211
212    fn wait_for_vsync(&self) {}
213}