capsules_extra/virtualizers/screen/
virtual_screen_split.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 2025.
4
5//! Virtualize a screen by splitting it into rectangular regions.
6//!
7//! Each screen user is given a rectangular region of the screen. Users can
8//! write that portion of the screen but cannot control the general screen
9//! settings (e.g., brightness).
10
11use core::cell::Cell;
12use kernel::collections::list::{List, ListLink, ListNode};
13use kernel::deferred_call::{DeferredCall, DeferredCallClient};
14use kernel::hil;
15use kernel::utilities::cells::OptionalCell;
16use kernel::utilities::leasable_buffer::SubSliceMut;
17use kernel::ErrorCode;
18
19/// Pending asynchronous screen operation from a user.
20enum ScreenSplitOperation {
21    /// Operation to set the writing frame.
22    WriteSetFrame,
23    /// Operation to write a buffer to the screen. `bool` is the continue_write
24    /// argument.
25    WriteBuffer(SubSliceMut<'static, u8>, bool),
26}
27
28/// Rectangular region of a screen.
29#[derive(Default, Clone, Copy, PartialEq)]
30pub struct Frame {
31    /// X coordinate of the upper left corner of the frame.
32    x: usize,
33    /// Y coordinate of the upper left corner of the frame.
34    y: usize,
35    /// Width of the frame.
36    width: usize,
37    /// Height of the frame.
38    height: usize,
39}
40
41/// What the screen split mux is currently working on.
42enum ScreenSplitState {
43    /// Setting the frame is just recording the write frame, so this just needs
44    /// to simulate a callback.
45    SetFrame,
46    /// Do a write to the screen. First step is setting the frame.
47    WriteSetFrame(SubSliceMut<'static, u8>, bool),
48    /// Do a write to the screen. Second step is actually writing the buffer
49    /// contents.
50    WriteBuffer,
51}
52
53/// An implementation of [`Screen`](kernel::hil::screen::Screen) for a subregion
54/// of the actual screen.
55pub struct ScreenSplitUser<'a, S: hil::screen::Screen<'a>> {
56    /// The shared screen manager that serializes screen operations.
57    mux: &'a ScreenSplitMux<'a, S>,
58    /// The frame within the entire screen this split section has access to.
59    frame: Frame,
60    /// The frame inside of the split that is active. Defaults to the entire
61    /// frame.
62    write_frame: Cell<Frame>,
63    /// What operation this section would like to do.[`ScreenSplitSection`] sets
64    /// its intended operation here and then asks the [`ScreenSplit`] to
65    /// execute it.
66    pending: OptionalCell<ScreenSplitOperation>,
67    /// Screen client.
68    client: OptionalCell<&'a dyn hil::screen::ScreenClient>,
69    /// Track the list of screen split users.
70    next: ListLink<'a, ScreenSplitUser<'a, S>>,
71}
72
73impl<'a, S: hil::screen::Screen<'a>> ScreenSplitUser<'a, S> {
74    pub fn new(
75        mux: &'a ScreenSplitMux<'a, S>,
76        x: usize,
77        y: usize,
78        width: usize,
79        height: usize,
80    ) -> Self {
81        let frame = Frame {
82            x,
83            y,
84            width,
85            height,
86        };
87
88        // Default the write frame to the entire frame provided for this split.
89        Self {
90            mux,
91            frame,
92            write_frame: Cell::new(Frame {
93                x: 0,
94                y: 0,
95                width,
96                height,
97            }),
98            pending: OptionalCell::empty(),
99            client: OptionalCell::empty(),
100            next: ListLink::empty(),
101        }
102    }
103
104    pub fn add_to_mux(&'a self) {
105        self.mux.splits.push_head(self);
106    }
107}
108
109impl<'a, S: hil::screen::Screen<'a>> ListNode<'a, ScreenSplitUser<'a, S>>
110    for ScreenSplitUser<'a, S>
111{
112    fn next(&'a self) -> &'a ListLink<'a, ScreenSplitUser<'a, S>> {
113        &self.next
114    }
115}
116
117impl<'a, S: hil::screen::Screen<'a>> hil::screen::Screen<'a> for ScreenSplitUser<'a, S> {
118    fn set_client(&self, client: &'a dyn hil::screen::ScreenClient) {
119        self.client.set(client);
120    }
121
122    fn get_resolution(&self) -> (usize, usize) {
123        (self.frame.width, self.frame.height)
124    }
125
126    fn get_pixel_format(&self) -> hil::screen::ScreenPixelFormat {
127        self.mux.screen.get_pixel_format()
128    }
129
130    fn get_rotation(&self) -> hil::screen::ScreenRotation {
131        self.mux.screen.get_rotation()
132    }
133
134    fn set_write_frame(
135        &self,
136        x: usize,
137        y: usize,
138        width: usize,
139        height: usize,
140    ) -> Result<(), ErrorCode> {
141        if self.pending.is_some() {
142            Err(ErrorCode::BUSY)
143        } else {
144            let frame = Frame {
145                x,
146                y,
147                width,
148                height,
149            };
150
151            self.write_frame.set(frame);
152
153            // Just mark this operation as intended and then ask the shared
154            // split manager to execute it.
155            self.pending.set(ScreenSplitOperation::WriteSetFrame);
156            self.mux.request_operation()
157        }
158    }
159
160    fn write(
161        &self,
162        buffer: SubSliceMut<'static, u8>,
163        continue_write: bool,
164    ) -> Result<(), ErrorCode> {
165        if self.pending.is_some() {
166            Err(ErrorCode::BUSY)
167        } else {
168            // Just mark this operation as intended and then ask the shared
169            // split manager to execute it.
170            self.pending
171                .set(ScreenSplitOperation::WriteBuffer(buffer, continue_write));
172            self.mux.request_operation()
173        }
174    }
175
176    fn set_brightness(&self, _brightness: u16) -> Result<(), ErrorCode> {
177        Err(ErrorCode::NOSUPPORT)
178    }
179
180    fn set_power(&self, _enabled: bool) -> Result<(), ErrorCode> {
181        Err(ErrorCode::NOSUPPORT)
182    }
183
184    fn set_invert(&self, _enabled: bool) -> Result<(), ErrorCode> {
185        Err(ErrorCode::NOSUPPORT)
186    }
187}
188
189impl<'a, S: hil::screen::Screen<'a>> hil::screen::ScreenClient for ScreenSplitUser<'a, S> {
190    fn command_complete(&self, r: Result<(), ErrorCode>) {
191        self.pending.take();
192
193        self.client.map(|client| {
194            client.command_complete(r);
195        });
196    }
197
198    fn write_complete(&self, data: SubSliceMut<'static, u8>, r: Result<(), ErrorCode>) {
199        self.pending.take();
200
201        self.client.map(|client| {
202            client.write_complete(data, r);
203        });
204    }
205
206    fn screen_is_ready(&self) {
207        self.client.map(|client| {
208            client.screen_is_ready();
209        });
210    }
211}
212
213/// Split-screen manager that multiplexes for multiple splits.
214///
215/// This enables two users (e.g., the kernel and all userspace apps) to share
216/// a single physical screen. Each split screen is assigned a fixed region.
217pub struct ScreenSplitMux<'a, S: hil::screen::Screen<'a>> {
218    /// Underlying screen driver to use.
219    screen: &'a S,
220
221    /// List of all users of the screen with their own splits.
222    splits: List<'a, ScreenSplitUser<'a, S>>,
223
224    /// What is using the split screen and what state this mux is in.
225    current_user: OptionalCell<(&'a ScreenSplitUser<'a, S>, ScreenSplitState)>,
226
227    /// Simulate interrupt callbacks for setting the frame.
228    deferred_call: DeferredCall,
229}
230
231impl<'a, S: hil::screen::Screen<'a>> ScreenSplitMux<'a, S> {
232    pub fn new(screen: &'a S) -> Self {
233        Self {
234            screen,
235            splits: List::new(),
236            current_user: OptionalCell::empty(),
237            deferred_call: DeferredCall::new(),
238        }
239    }
240
241    fn request_operation(&self) -> Result<(), ErrorCode> {
242        // Check if we are busy with an existing operation. If so, just return
243        // OK and we will handle the operation later.
244        if self.current_user.is_some() {
245            return Ok(());
246        }
247
248        // Check if there is a split that has work to do.
249        if let Some(split) = self.splits.iter().find(|split| split.pending.is_some()) {
250            // We have a split that has requested an operation.
251            if let Some(operation) = split.pending.take() {
252                self.call_screen(split, operation)
253            } else {
254                Err(ErrorCode::NOMEM)
255            }
256        } else {
257            Err(ErrorCode::NOMEM)
258        }
259    }
260
261    fn call_screen(
262        &self,
263        split: &'a ScreenSplitUser<'a, S>,
264        operation: ScreenSplitOperation,
265    ) -> Result<(), ErrorCode> {
266        match operation {
267            ScreenSplitOperation::WriteSetFrame => {
268                // Just need to set a deferred call since we only write the
269                // frame if we are going to write the screen.
270                self.current_user.set((split, ScreenSplitState::SetFrame));
271                self.deferred_call.set();
272                Ok(())
273            }
274            ScreenSplitOperation::WriteBuffer(subslice, continue_write) => {
275                // First we need to set the frame.
276                let absolute_frame =
277                    self.calculate_absolute_frame(split.frame, split.write_frame.get());
278
279                self.screen
280                    .set_write_frame(
281                        absolute_frame.x,
282                        absolute_frame.y,
283                        absolute_frame.width,
284                        absolute_frame.height,
285                    )
286                    .inspect(|()| {
287                        self.current_user.set((
288                            split,
289                            ScreenSplitState::WriteSetFrame(subslice, continue_write),
290                        ))
291                    })
292            }
293        }
294    }
295
296    /// Calculate the frame within the entire screen that the split is currently
297    /// trying to use. This is the `active_frame` within the split's allocated
298    /// `split_frame`.
299    fn calculate_absolute_frame(&self, split_frame: Frame, active_frame: Frame) -> Frame {
300        // x and y are sums
301        let mut absolute_x = split_frame.x + active_frame.x;
302        let mut absolute_y = split_frame.y + active_frame.y;
303        // width and height are simply the app_frame width and height.
304        let mut absolute_w = active_frame.width;
305        let mut absolute_h = active_frame.height;
306
307        // Make sure that the calculate frame is within the allocated region.
308        absolute_x = core::cmp::min(split_frame.x + split_frame.width, absolute_x);
309        absolute_y = core::cmp::min(split_frame.y + split_frame.height, absolute_y);
310        absolute_w = core::cmp::min(split_frame.x + split_frame.width - absolute_x, absolute_w);
311        absolute_h = core::cmp::min(split_frame.y + split_frame.height - absolute_y, absolute_h);
312
313        Frame {
314            x: absolute_x,
315            y: absolute_y,
316            width: absolute_w,
317            height: absolute_h,
318        }
319    }
320}
321
322impl<'a, S: hil::screen::Screen<'a>> hil::screen::ScreenClient for ScreenSplitMux<'a, S> {
323    fn command_complete(&self, _r: Result<(), ErrorCode>) {
324        if let Some((current_user, ScreenSplitState::WriteSetFrame(subslice, continue_write))) =
325            self.current_user.take()
326        {
327            let _ = self.screen.write(subslice, continue_write).inspect(|()| {
328                self.current_user
329                    .set((current_user, ScreenSplitState::WriteBuffer))
330            });
331        } else {
332            // No other state will trigger this callback.
333        }
334    }
335
336    fn write_complete(&self, data: SubSliceMut<'static, u8>, r: Result<(), ErrorCode>) {
337        if let Some((current_user, _state)) = self.current_user.take() {
338            current_user.write_complete(data, r);
339        }
340
341        // Check to see if there is another pending operation we can run.
342        let _ = self.request_operation();
343    }
344
345    fn screen_is_ready(&self) {
346        // Notify all screen split users.
347        self.splits.iter().for_each(|split| {
348            split.screen_is_ready();
349        })
350    }
351}
352
353impl<'a, S: hil::screen::Screen<'a>> DeferredCallClient for ScreenSplitMux<'a, S> {
354    fn handle_deferred_call(&self) {
355        // All we have to do is trigger the set frame callback.
356        if let Some((current_user, _state)) = self.current_user.take() {
357            hil::screen::ScreenClient::command_complete(current_user, Ok(()));
358        }
359    }
360
361    fn register(&'static self) {
362        self.deferred_call.register(self);
363    }
364}