capsules_extra/
text_screen.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 2022.
4
5//! Provides userspace with access to the text screen.
6//!
7//! Usage:
8//! -----
9//!
10//! You need a screen that provides the `hil::text_screen::TextScreen`
11//! trait.
12//!
13//! ```rust,ignore
14//! let text_screen = components::text_screen::TextScreenComponent::new(board_kernel, lcd)
15//!         .finalize(components::screen_buffer_size!(64));
16//! ```
17
18use core::cmp;
19
20use kernel::grant::{AllowRoCount, AllowRwCount, Grant, UpcallCount};
21use kernel::hil;
22use kernel::processbuffer::ReadableProcessBuffer;
23use kernel::syscall::{CommandReturn, SyscallDriver};
24use kernel::utilities::cells::{OptionalCell, TakeCell};
25use kernel::{ErrorCode, ProcessId};
26
27/// Syscall driver number.
28use capsules_core::driver;
29pub const DRIVER_NUM: usize = driver::NUM::TextScreen as usize;
30
31/// Ids for read-only allow buffers
32mod ro_allow {
33    pub const SHARED: usize = 0;
34    /// The number of allow buffers the kernel stores for this grant
35    pub const COUNT: u8 = 1;
36}
37
38#[derive(Clone, Copy, PartialEq)]
39enum TextScreenCommand {
40    Idle,
41    GetResolution,
42    Display,
43    NoDisplay,
44    Blink,
45    NoBlink,
46    SetCursor,
47    NoCursor,
48    ShowCursor,
49    Write,
50    Clear,
51    Home,
52}
53
54pub struct App {
55    pending_command: bool,
56    write_len: usize,
57    command: TextScreenCommand,
58    data1: usize,
59    data2: usize,
60}
61
62impl Default for App {
63    fn default() -> App {
64        App {
65            pending_command: false,
66            write_len: 0,
67            command: TextScreenCommand::Idle,
68            data1: 1,
69            data2: 0,
70        }
71    }
72}
73
74pub struct TextScreen<'a> {
75    text_screen: &'a dyn hil::text_screen::TextScreen<'static>,
76    apps: Grant<App, UpcallCount<1>, AllowRoCount<{ ro_allow::COUNT }>, AllowRwCount<0>>,
77    current_app: OptionalCell<ProcessId>,
78    buffer: TakeCell<'static, [u8]>,
79}
80
81impl<'a> TextScreen<'a> {
82    pub fn new(
83        text_screen: &'static dyn hil::text_screen::TextScreen,
84        buffer: &'static mut [u8],
85        grant: Grant<App, UpcallCount<1>, AllowRoCount<{ ro_allow::COUNT }>, AllowRwCount<0>>,
86    ) -> TextScreen<'a> {
87        TextScreen {
88            text_screen,
89            apps: grant,
90            current_app: OptionalCell::empty(),
91            buffer: TakeCell::new(buffer),
92        }
93    }
94
95    fn enqueue_command(
96        &self,
97        command: TextScreenCommand,
98        data1: usize,
99        data2: usize,
100        processid: ProcessId,
101    ) -> CommandReturn {
102        let res = self
103            .apps
104            .enter(processid, |app, _| {
105                if self.current_app.is_none() {
106                    self.current_app.set(processid);
107                    app.data1 = data1;
108                    app.data2 = data2;
109                    app.command = command;
110                    Ok(true)
111                } else {
112                    if app.pending_command {
113                        Err(ErrorCode::BUSY)
114                    } else {
115                        app.pending_command = true;
116                        app.command = command;
117                        app.data1 = data1;
118                        app.data2 = data2;
119                        Ok(false)
120                    }
121                }
122            })
123            .map_err(ErrorCode::from);
124        let res = match res {
125            Ok(value) => value,
126            Err(err) => Err(err),
127        };
128        match res {
129            Ok(execute_now) => {
130                if execute_now {
131                    match self.do_command() {
132                        Ok(()) => CommandReturn::success(),
133                        Err(err) => {
134                            self.current_app.clear();
135                            CommandReturn::failure(err)
136                        }
137                    }
138                } else {
139                    CommandReturn::success()
140                }
141            }
142            Err(err) => CommandReturn::failure(err),
143        }
144    }
145
146    fn do_command(&self) -> Result<(), ErrorCode> {
147        let mut run_next = false;
148        let res = self.current_app.map_or(Err(ErrorCode::FAIL), |app| {
149            self.apps
150                .enter(app, |app, kernel_data| match app.command {
151                    TextScreenCommand::GetResolution => {
152                        let (x, y) = self.text_screen.get_size();
153                        app.pending_command = false;
154                        let _ = kernel_data
155                            .schedule_upcall(0, (kernel::errorcode::into_statuscode(Ok(())), x, y));
156                        run_next = true;
157                        Ok(())
158                    }
159                    TextScreenCommand::Display => self.text_screen.display_on(),
160                    TextScreenCommand::NoDisplay => self.text_screen.display_off(),
161                    TextScreenCommand::Blink => self.text_screen.blink_cursor_on(),
162                    TextScreenCommand::NoBlink => self.text_screen.blink_cursor_off(),
163                    TextScreenCommand::SetCursor => {
164                        self.text_screen.set_cursor(app.data1, app.data2)
165                    }
166                    TextScreenCommand::NoCursor => self.text_screen.hide_cursor(),
167                    TextScreenCommand::Write => {
168                        if app.data1 > 0 {
169                            app.write_len = app.data1;
170                            let res = kernel_data
171                                .get_readonly_processbuffer(ro_allow::SHARED)
172                                .and_then(|shared| {
173                                    shared.enter(|to_write_buffer| {
174                                        self.buffer.take().map_or(Err(ErrorCode::BUSY), |buffer| {
175                                            let len = cmp::min(app.write_len, buffer.len());
176                                            for n in 0..len {
177                                                buffer[n] = to_write_buffer[n].get();
178                                            }
179                                            match self.text_screen.print(buffer, len) {
180                                                Ok(()) => Ok(()),
181                                                Err((ecode, buffer)) => {
182                                                    self.buffer.replace(buffer);
183                                                    Err(ecode)
184                                                }
185                                            }
186                                        })
187                                    })
188                                });
189                            match res {
190                                Ok(Ok(())) => Ok(()),
191                                Ok(Err(err)) => Err(err),
192                                Err(err) => err.into(),
193                            }
194                        } else {
195                            Err(ErrorCode::NOMEM)
196                        }
197                    }
198                    TextScreenCommand::Clear => self.text_screen.clear(),
199                    TextScreenCommand::Home => self.text_screen.clear(),
200                    TextScreenCommand::ShowCursor => self.text_screen.show_cursor(),
201                    _ => Err(ErrorCode::NOSUPPORT),
202                })
203                .map_err(ErrorCode::from)
204        });
205        if run_next {
206            self.run_next_command();
207        }
208        match res {
209            Ok(value) => value,
210            Err(err) => Err(err),
211        }
212    }
213
214    fn run_next_command(&self) {
215        // Check for pending events.
216        for app in self.apps.iter() {
217            let processid = app.processid();
218            let current_command = app.enter(|app, _| {
219                if app.pending_command {
220                    app.pending_command = false;
221                    self.current_app.set(processid);
222                    true
223                } else {
224                    false
225                }
226            });
227            if current_command {
228                if self.do_command() != Ok(()) {
229                    self.current_app.clear();
230                } else {
231                    break;
232                }
233            }
234        }
235    }
236
237    fn schedule_callback(&self, data1: usize, data2: usize, data3: usize) {
238        self.current_app.take().map(|processid| {
239            let _ = self.apps.enter(processid, |app, kernel_data| {
240                app.pending_command = false;
241                kernel_data.schedule_upcall(0, (data1, data2, data3)).ok();
242            });
243        });
244    }
245}
246
247impl SyscallDriver for TextScreen<'_> {
248    fn command(
249        &self,
250        command_num: usize,
251        data1: usize,
252        data2: usize,
253        processid: ProcessId,
254    ) -> CommandReturn {
255        match command_num {
256            // This driver exists.
257            0 => CommandReturn::success(),
258            // Get Resolution
259            1 => self.enqueue_command(TextScreenCommand::GetResolution, data1, data2, processid),
260            // Display
261            2 => self.enqueue_command(TextScreenCommand::Display, data1, data2, processid),
262            // No Display
263            3 => self.enqueue_command(TextScreenCommand::NoDisplay, data1, data2, processid),
264            // Blink
265            4 => self.enqueue_command(TextScreenCommand::Blink, data1, data2, processid),
266            // No Blink
267            5 => self.enqueue_command(TextScreenCommand::NoBlink, data1, data2, processid),
268            // Show Cursor
269            6 => self.enqueue_command(TextScreenCommand::ShowCursor, data1, data2, processid),
270            // No Cursor
271            7 => self.enqueue_command(TextScreenCommand::NoCursor, data1, data2, processid),
272            // Write
273            8 => self.enqueue_command(TextScreenCommand::Write, data1, data2, processid),
274            // Clear
275            9 => self.enqueue_command(TextScreenCommand::Clear, data1, data2, processid),
276            // Home
277            10 => self.enqueue_command(TextScreenCommand::Home, data1, data2, processid),
278            //Set Curosr
279            11 => self.enqueue_command(TextScreenCommand::SetCursor, data1, data2, processid),
280            // NOSUPPORT
281            _ => CommandReturn::failure(ErrorCode::NOSUPPORT),
282        }
283    }
284
285    fn allocate_grant(&self, processid: ProcessId) -> Result<(), kernel::process::Error> {
286        self.apps.enter(processid, |_, _| {})
287    }
288}
289
290impl hil::text_screen::TextScreenClient for TextScreen<'_> {
291    fn command_complete(&self, r: Result<(), ErrorCode>) {
292        self.schedule_callback(kernel::errorcode::into_statuscode(r), 0, 0);
293        self.run_next_command();
294    }
295
296    fn write_complete(&self, buffer: &'static mut [u8], len: usize, r: Result<(), ErrorCode>) {
297        self.buffer.replace(buffer);
298        self.schedule_callback(kernel::errorcode::into_statuscode(r), len, 0);
299        self.run_next_command();
300    }
301}