capsules_extra/
screen_shared.rs

1// Licensed under the Apache License, Version 2.0 or the MIT License.
2// SPDX-License-Identifier: Apache-2.0 OR MIT
3// Copyright Tock Contributors 2024.
4
5//! Shares a screen among multiple userspace processes.
6//!
7//! The screen can be split into multiple regions, and regions are assigned to
8//! processes by AppID.
9//!
10//! Boards should create an array of `AppScreenRegion` objects that assign apps
11//! to specific regions (frames) within the screen.
12//!
13//! ```rust,ignore
14//! AppScreenRegion {
15//!     app_id: kernel::process:ShortId::new(id),
16//!     frame: Frame {
17//!         x: 0,
18//!         y: 0,
19//!         width: 8,
20//!         height: 16,
21//!     }
22//! }
23//! ```
24//!
25//! This driver uses a subset of the API from `Screen`. It does not support any
26//! screen config settings (brightness, invert) as those operations affect the
27//! entire screen.
28
29use kernel::grant::{AllowRoCount, AllowRwCount, Grant, UpcallCount};
30use kernel::hil;
31use kernel::processbuffer::ReadableProcessBuffer;
32use kernel::syscall::{CommandReturn, SyscallDriver};
33use kernel::utilities::cells::{OptionalCell, TakeCell};
34use kernel::utilities::leasable_buffer::SubSliceMut;
35use kernel::{ErrorCode, ProcessId};
36
37/// Syscall driver number.
38use capsules_core::driver;
39pub const DRIVER_NUM: usize = driver::NUM::Screen as usize;
40
41/// Ids for read-only allow buffers
42mod ro_allow {
43    pub const SHARED: usize = 0;
44    /// The number of allow buffers the kernel stores for this grant
45    pub const COUNT: u8 = 1;
46}
47
48#[derive(Clone, Copy, PartialEq)]
49enum ScreenCommand {
50    WriteSetFrame,
51    WriteBuffer,
52}
53
54fn pixels_in_bytes(pixels: usize, bits_per_pixel: usize) -> usize {
55    let bytes = pixels * bits_per_pixel / 8;
56    if pixels * bits_per_pixel % 8 != 0 {
57        bytes + 1
58    } else {
59        bytes
60    }
61}
62
63/// Rectangular region of a screen.
64#[derive(Default, Clone, Copy, PartialEq)]
65pub struct Frame {
66    /// X coordinate of the upper left corner of the frame.
67    x: usize,
68    /// Y coordinate of the upper left corner of the frame.
69    y: usize,
70    /// Width of the frame.
71    width: usize,
72    /// Height of the frame.
73    height: usize,
74}
75
76pub struct AppScreenRegion {
77    app_id: kernel::process::ShortId,
78    frame: Frame,
79}
80
81impl AppScreenRegion {
82    pub fn new(
83        app_id: kernel::process::ShortId,
84        x: usize,
85        y: usize,
86        width: usize,
87        height: usize,
88    ) -> Self {
89        Self {
90            app_id,
91            frame: Frame {
92                x,
93                y,
94                width,
95                height,
96            },
97        }
98    }
99}
100
101#[derive(Default)]
102pub struct App {
103    /// The app has requested some screen operation, or `None()` if idle.
104    command: Option<ScreenCommand>,
105    /// The current frame the app is using.
106    frame: Frame,
107}
108
109/// A userspace driver that allows multiple apps to use the same screen.
110///
111/// Each app is given a pre-set rectangular region of the screen to use.
112pub struct ScreenShared<'a, S: hil::screen::Screen<'a>> {
113    /// Underlying screen driver to use.
114    screen: &'a S,
115
116    /// Grant region for apps using the screen.
117    apps: Grant<App, UpcallCount<1>, AllowRoCount<{ ro_allow::COUNT }>, AllowRwCount<0>>,
118
119    /// Static allocations of screen regions for each app.
120    apps_regions: &'a [AppScreenRegion],
121
122    /// The process currently executing a command on the screen.
123    current_process: OptionalCell<ProcessId>,
124
125    /// Internal buffer for write commands.
126    buffer: TakeCell<'static, [u8]>,
127}
128
129impl<'a, S: hil::screen::Screen<'a>> ScreenShared<'a, S> {
130    pub fn new(
131        screen: &'a S,
132        grant: Grant<App, UpcallCount<1>, AllowRoCount<{ ro_allow::COUNT }>, AllowRwCount<0>>,
133        buffer: &'static mut [u8],
134        apps_regions: &'a [AppScreenRegion],
135    ) -> ScreenShared<'a, S> {
136        ScreenShared {
137            screen,
138            apps: grant,
139            current_process: OptionalCell::empty(),
140            buffer: TakeCell::new(buffer),
141            apps_regions,
142        }
143    }
144
145    // Enqueue a command for the given app.
146    fn enqueue_command(&self, command: ScreenCommand, process_id: ProcessId) -> CommandReturn {
147        let ret = self
148            .apps
149            .enter(process_id, |app, _| {
150                if app.command.is_some() {
151                    Err(ErrorCode::BUSY)
152                } else {
153                    app.command = Some(command);
154                    Ok(())
155                }
156            })
157            .map_err(ErrorCode::from)
158            .and_then(|r| r)
159            .into();
160
161        if self.current_process.is_none() {
162            self.run_next_command();
163        }
164
165        ret
166    }
167
168    /// Calculate the frame within the entire screen that the app is currently
169    /// trying to use. This is the `app_frame` within the app's allocated
170    /// `app_screen_region`.
171    fn calculate_absolute_frame(&self, app_screen_region_frame: Frame, app_frame: Frame) -> Frame {
172        // x and y are sums
173        let mut absolute_x = app_screen_region_frame.x + app_frame.x;
174        let mut absolute_y = app_screen_region_frame.y + app_frame.y;
175        // width and height are simply the app_frame width and height.
176        let mut absolute_w = app_frame.width;
177        let mut absolute_h = app_frame.height;
178
179        // Make sure that the calculate frame is within the allocated region.
180        absolute_x = core::cmp::min(
181            app_screen_region_frame.x + app_screen_region_frame.width,
182            absolute_x,
183        );
184        absolute_y = core::cmp::min(
185            app_screen_region_frame.y + app_screen_region_frame.height,
186            absolute_y,
187        );
188        absolute_w = core::cmp::min(
189            app_screen_region_frame.x + app_screen_region_frame.width - absolute_x,
190            absolute_w,
191        );
192        absolute_h = core::cmp::min(
193            app_screen_region_frame.y + app_screen_region_frame.height - absolute_y,
194            absolute_h,
195        );
196
197        Frame {
198            x: absolute_x,
199            y: absolute_y,
200            width: absolute_w,
201            height: absolute_h,
202        }
203    }
204
205    fn call_screen(
206        &self,
207        process_id: ProcessId,
208        app_screen_region_frame: Frame,
209    ) -> Result<(), ErrorCode> {
210        self.apps
211            .enter(process_id, |app, kernel_data| {
212                match app.command {
213                    Some(ScreenCommand::WriteSetFrame) => {
214                        let absolute_frame =
215                            self.calculate_absolute_frame(app_screen_region_frame, app.frame);
216
217                        app.command = Some(ScreenCommand::WriteBuffer);
218                        self.screen
219                            .set_write_frame(
220                                absolute_frame.x,
221                                absolute_frame.y,
222                                absolute_frame.width,
223                                absolute_frame.height,
224                            )
225                            .inspect_err(|_| {
226                                app.command = None;
227                            })
228                    }
229                    Some(ScreenCommand::WriteBuffer) => {
230                        app.command = None;
231                        kernel_data
232                            .get_readonly_processbuffer(ro_allow::SHARED)
233                            .map(|allow_buf| {
234                                let len = allow_buf.len();
235
236                                if len == 0 {
237                                    Err(ErrorCode::NOMEM)
238                                } else if !self.is_len_multiple_color_depth(len) {
239                                    Err(ErrorCode::INVAL)
240                                } else {
241                                    // All good, copy buffer.
242
243                                    self.buffer.take().map_or(Err(ErrorCode::FAIL), |buffer| {
244                                        let copy_len =
245                                            core::cmp::min(buffer.len(), allow_buf.len());
246                                        allow_buf.enter(|ab| {
247                                            // buffer[..copy_len].copy_from_slice(ab[..copy_len]);
248                                            ab[..copy_len].copy_to_slice(&mut buffer[..copy_len])
249                                        })?;
250
251                                        // Send to screen.
252                                        let mut data = SubSliceMut::new(buffer);
253                                        data.slice(..copy_len);
254                                        self.screen.write(data, false)
255                                    })
256                                }
257                            })
258                            .map_err(ErrorCode::from)
259                            .and_then(|r| r)
260                    }
261                    _ => Err(ErrorCode::NOSUPPORT),
262                }
263            })
264            .map_err(ErrorCode::from)
265            .and_then(|r| r)
266    }
267
268    fn schedule_callback(&self, process_id: ProcessId, data1: usize, data2: usize, data3: usize) {
269        let _ = self.apps.enter(process_id, |_app, kernel_data| {
270            kernel_data.schedule_upcall(0, (data1, data2, data3)).ok();
271        });
272    }
273
274    fn get_app_screen_region_frame(&self, process_id: ProcessId) -> Option<Frame> {
275        let short_id = process_id.short_app_id();
276
277        for app_screen_region in self.apps_regions {
278            if short_id == app_screen_region.app_id {
279                return Some(app_screen_region.frame);
280            }
281        }
282        None
283    }
284
285    fn run_next_command(&self) {
286        let ran_cmd = self.current_process.map_or(false, |process_id| {
287            let app_region_frame = self.get_app_screen_region_frame(process_id);
288
289            app_region_frame.is_some_and(|frame| {
290                let r = self.call_screen(process_id, frame);
291                if r.is_err() {
292                    // We were unable to run the screen operation meaning we
293                    // will not get a callback and we need to report the error.
294                    self.current_process.take().map(|process_id| {
295                        self.schedule_callback(
296                            process_id,
297                            kernel::errorcode::into_statuscode(r),
298                            0,
299                            0,
300                        );
301                    });
302                    false
303                } else {
304                    true
305                }
306            })
307        });
308
309        if !ran_cmd {
310            // Check if there are any pending events.
311            for app in self.apps.iter() {
312                let process_id = app.processid();
313
314                // Check if this process has both a pending command and is
315                // allocated a region on the screen.
316                let frame_maybe = app.enter(|app, _| {
317                    if app.command.is_some() {
318                        self.get_app_screen_region_frame(process_id)
319                    } else {
320                        None
321                    }
322                });
323
324                // If we have a candidate, try to execute the screen operation.
325                if frame_maybe.is_some() {
326                    match frame_maybe {
327                        Some(frame) => {
328                            // Reserve the screen for this process and execute
329                            // the operation.
330                            self.current_process.set(process_id);
331                            match self.call_screen(process_id, frame) {
332                                Ok(()) => {
333                                    // Everything is good, stop looking for apps
334                                    // to execute.
335                                    break;
336                                }
337                                Err(err) => {
338                                    // Could not run the screen command.
339                                    // Un-reserve the screen and do an upcall
340                                    // with the bad news.
341                                    self.current_process.clear();
342                                    self.schedule_callback(
343                                        process_id,
344                                        kernel::errorcode::into_statuscode(Err(err)),
345                                        0,
346                                        0,
347                                    );
348                                }
349                            }
350                        }
351                        None => {}
352                    }
353                }
354            }
355        }
356    }
357
358    fn is_len_multiple_color_depth(&self, len: usize) -> bool {
359        let depth = pixels_in_bytes(1, self.screen.get_pixel_format().get_bits_per_pixel());
360        (len % depth) == 0
361    }
362}
363
364impl<'a, S: hil::screen::Screen<'a>> hil::screen::ScreenClient for ScreenShared<'a, S> {
365    fn command_complete(&self, r: Result<(), ErrorCode>) {
366        if r.is_err() {
367            self.current_process.take().map(|process_id| {
368                self.schedule_callback(process_id, kernel::errorcode::into_statuscode(r), 0, 0);
369            });
370        }
371
372        self.run_next_command();
373    }
374
375    fn write_complete(&self, data: SubSliceMut<'static, u8>, r: Result<(), ErrorCode>) {
376        self.buffer.replace(data.take());
377
378        // Notify that the write is finished.
379        self.current_process.take().map(|process_id| {
380            self.schedule_callback(process_id, kernel::errorcode::into_statuscode(r), 0, 0);
381        });
382
383        self.run_next_command();
384    }
385
386    fn screen_is_ready(&self) {
387        self.run_next_command();
388    }
389}
390
391impl<'a, S: hil::screen::Screen<'a>> SyscallDriver for ScreenShared<'a, S> {
392    fn command(
393        &self,
394        command_num: usize,
395        data1: usize,
396        data2: usize,
397        process_id: ProcessId,
398    ) -> CommandReturn {
399        match command_num {
400            // Driver existence check
401            0 => CommandReturn::success(),
402
403            // Get Rotation
404            21 => CommandReturn::success_u32(self.screen.get_rotation() as u32),
405
406            // Get Resolution
407            23 => match self.get_app_screen_region_frame(process_id) {
408                Some(frame) => {
409                    CommandReturn::success_u32_u32(frame.width as u32, frame.height as u32)
410                }
411                None => CommandReturn::failure(ErrorCode::NOSUPPORT),
412            },
413
414            // Get pixel format
415            25 => CommandReturn::success_u32(self.screen.get_pixel_format() as u32),
416
417            // Set Write Frame
418            100 => {
419                let frame = Frame {
420                    x: (data1 >> 16) & 0xFFFF,
421                    y: data1 & 0xFFFF,
422                    width: (data2 >> 16) & 0xFFFF,
423                    height: data2 & 0xFFFF,
424                };
425
426                self.apps
427                    .enter(process_id, |app, kernel_data| {
428                        app.frame = frame;
429
430                        // Just issue upcall.
431                        let _ = kernel_data
432                            .schedule_upcall(0, (kernel::errorcode::into_statuscode(Ok(())), 0, 0));
433                    })
434                    .map_err(ErrorCode::from)
435                    .into()
436            }
437
438            // Write
439            200 => {
440                // First check if this app has any screen real estate allocated.
441                // If not, return error.
442                if self.get_app_screen_region_frame(process_id).is_none() {
443                    CommandReturn::failure(ErrorCode::NOSUPPORT)
444                } else {
445                    self.enqueue_command(ScreenCommand::WriteSetFrame, process_id)
446                }
447            }
448
449            _ => CommandReturn::failure(ErrorCode::NOSUPPORT),
450        }
451    }
452
453    fn allocate_grant(&self, processid: ProcessId) -> Result<(), kernel::process::Error> {
454        self.apps.enter(processid, |_, _| {})
455    }
456}