efi/
console.rs

1use ffi::{
2    console::{
3        EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL, 
4        EFI_SIMPLE_TEXT_INPUT_PROTOCOL,
5        EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL, 
6        EFI_KEY_DATA,
7        EFI_INPUT_KEY,
8        EFI_SHIFT_STATE_VALID,
9        EFI_LEFT_CONTROL_PRESSED,
10        EFI_RIGHT_CONTROL_PRESSED,
11        EFI_BLACK,
12        EFI_BLUE,
13        EFI_GREEN,
14        EFI_CYAN,
15        EFI_RED,
16        EFI_MAGENTA,
17        EFI_BROWN,
18        EFI_LIGHTGRAY,
19        EFI_DARKGRAY,
20        EFI_LIGHTBLUE,
21        EFI_LIGHTGREEN,
22        EFI_LIGHTCYAN,
23        EFI_LIGHTRED,
24        EFI_LIGHTMAGENTA,
25        EFI_YELLOW,
26        EFI_WHITE,
27        EFI_BACKGROUND_BLACK,
28        EFI_BACKGROUND_BLUE,
29        EFI_BACKGROUND_GREEN,
30        EFI_BACKGROUND_CYAN,
31        EFI_BACKGROUND_RED,
32        EFI_BACKGROUND_MAGENTA,
33        EFI_BACKGROUND_BROWN,
34        EFI_BACKGROUND_LIGHTGRAY,
35    }, 
36    IsSuccess, 
37    UINTN,
38    TRUE,
39    FALSE,
40};
41use core::{cmp, mem::transmute};
42use crate::{SystemTable, io::{self, Write, Cursor, BufRead, BufReader, LineWriter}};
43use crate::Result;
44use crate::system_table;
45use crate::TextInputProcolPtr;
46use alloc::{vec::Vec, string::String, str, fmt};
47
48// TODO: This whole module has gotten ugly. Needs cleanup.
49// TODO: Should we replace Console with two structs, StdIn and StdOut, corresponding to input and output? This is more in line with Rust stdlib.
50
51#[derive(Debug, Copy, Clone)]
52#[repr(usize)]
53pub enum ForeColor {
54    Black = EFI_BLACK,
55    Blue = EFI_BLUE,
56    Green = EFI_GREEN,
57    Cyan = EFI_CYAN,
58    Red = EFI_RED,
59    Magenta = EFI_MAGENTA,
60    Brown = EFI_BROWN,
61    LightGray = EFI_LIGHTGRAY,
62    DarkGray = EFI_DARKGRAY,
63    LightBlue = EFI_LIGHTBLUE,
64    LightGreen = EFI_LIGHTGREEN,
65    LightCyan = EFI_LIGHTCYAN,
66    LightRed = EFI_LIGHTRED,
67    LightMagenta = EFI_LIGHTMAGENTA,
68    Yellow = EFI_YELLOW,
69    White = EFI_WHITE,
70}
71
72impl From<UINTN> for ForeColor {
73    fn from(color_num: UINTN) -> Self {
74        match color_num {
75            EFI_BLACK..=EFI_WHITE => unsafe { transmute(color_num) },
76            _ => panic!("Attempt to convert an out-of-range number to ForeColor")
77        }
78    }
79}
80
81impl From<ForeColor> for UINTN {
82    fn from(fore_color: ForeColor) -> UINTN {
83        fore_color as UINTN
84    }
85}
86
87#[derive(Debug, Copy, Clone)]
88#[repr(usize)]
89pub enum BackColor {
90    Black = EFI_BACKGROUND_BLACK,
91    Blue = EFI_BACKGROUND_BLUE,
92    Green = EFI_BACKGROUND_GREEN,
93    Cyan = EFI_BACKGROUND_CYAN,
94    Red = EFI_BACKGROUND_RED,
95    Magenta = EFI_BACKGROUND_MAGENTA,
96    Brown = EFI_BACKGROUND_BROWN,
97    LightGray = EFI_BACKGROUND_LIGHTGRAY,
98}
99
100impl From<UINTN> for BackColor {
101    fn from(color_num: UINTN) -> Self {
102        match color_num {
103            EFI_BACKGROUND_BLACK..=EFI_BACKGROUND_LIGHTGRAY => unsafe { transmute(color_num) },
104            _ => panic!("Attempt to convert an out-of-range number to BackColor")
105        }
106    }
107}
108
109impl From<BackColor> for UINTN {
110    fn from(back_color: BackColor) -> UINTN {
111        back_color as UINTN
112    }
113}
114
115pub struct Console {
116    pub input: TextInputProcolPtr,
117    pub output: *const EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL,
118    utf8_buf: io::Cursor<Vec<u8>>
119}
120
121const LF: u16 = 10;
122const CR: u16 = 13;
123const BS: u16 = 8;
124
125impl Console {
126    pub fn new(input: TextInputProcolPtr, output: *const EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL) -> Self {
127        Self { input, output, utf8_buf: Cursor::new(Vec::new()) }
128    }
129
130    pub fn cursor_pos(&self) -> Position {
131        let mode = unsafe { (*(*self).output).Mode };
132        Position { row: unsafe { (*mode).CursorRow } as u32, col: unsafe { (*mode).CursorColumn } as u32 } // To convert from i32 to u32 since screen coords can't be negative
133    }
134
135    pub fn set_cursor_pos(&self, pos: Position) -> Result<()> {
136        unsafe {
137            ret_on_err!(((*(*self).output).SetCursorPosition)(self.output, pos.col as usize, pos.row as usize));
138        }
139
140        Ok(())
141    }
142
143    pub fn enable_cursor(&mut self) -> Result<()> {
144        unsafe {
145            ret_on_err!(((*(*self).output).EnableCursor)(self.output, TRUE));
146        }
147
148        Ok(())
149    }
150
151    pub fn disable_cursor(&mut self) -> Result<()> {
152        unsafe {
153            ret_on_err!(((*(*self).output).EnableCursor)(self.output, FALSE));
154        }
155
156        Ok(())
157    }
158
159    pub fn clear_screen(&mut self) -> Result<()> {
160        unsafe {
161            ret_on_err!(((*(*self).output).ClearScreen)(self.output));
162        }
163
164        Ok(())
165    }
166
167    // TODO: instead of u32 use a strong type like 'ConsoleMode'
168    // It may simply be a newtype around u32 and have methods
169    // 'resolution()' which return something like 80x25, 80x50, Custom etc.
170    pub fn max_supported_mode(&mut self) -> u32 {
171        unsafe { (*(*(*self).output).Mode).MaxMode  as u32 } // Cast from i32 to u32 to is safe
172    }
173
174    pub fn set_mode(&mut self, mode_number: u32) -> Result<()> {
175        unsafe {
176            ret_on_err!(((*(*self).output).SetMode)(self.output, mode_number as usize)); // TODO: Cast should be safe on patforms with 32 and 64 ptr widths. Do we need to worry about other platforms?
177        }
178
179        Ok(())
180    }
181
182    pub fn fore_color(&mut self) -> ForeColor {
183        let attribute = unsafe { (*(*(*self).output).Mode).Attribute } as UINTN; // TODO: Cast should be safe on patforms with 32 and 64 ptr widths. Do we need to worry about other platforms?
184        let fore_color_num = attribute & 0b1111; // Bits 0..3 are fore color, 4..6 are back color
185
186        fore_color_num.into()
187    }
188
189    pub fn set_fore_color(&mut self, fore_color: ForeColor) -> Result<()> {
190        let curr_attribute = unsafe { (*(*(*self).output).Mode).Attribute } as UINTN; // TODO: Cast should be safe on patforms with 32 and 64 ptr widths. Do we need to worry about other platforms?
191        let curr_back_color = curr_attribute & 0b1111_0000; // Bits 0..3 are fore color, 4..6 are back color
192        let new_attribute = usize::from(fore_color) | curr_back_color;
193
194        unsafe {
195            ret_on_err!(((*(*self).output).SetAttribute)(self.output, new_attribute));
196        }
197
198        Ok(())
199    }
200
201    pub fn back_color(&mut self) -> BackColor {
202        let attribute = unsafe { (*(*(*self).output).Mode).Attribute } as UINTN; // TODO: Cast should be safe on patforms with 32 and 64 ptr widths. Do we need to worry about other platforms?
203        let back_color_num = attribute & 0b1111_0000; // Bits 0..3 are fore color, 4..6 are back color
204
205        back_color_num.into()
206    }
207
208    pub fn set_back_color(&mut self, back_color: BackColor) -> Result<()> {
209        let curr_attribute = unsafe { (*(*(*self).output).Mode).Attribute } as UINTN; // TODO: Cast should be safe on patforms with 32 and 64 ptr widths. Do we need to worry about other platforms?
210        let curr_fore_color = curr_attribute & 0b1111; // Bits 0..3 are fore color, 4..6 are back color
211        let new_attribute = curr_fore_color | usize::from(back_color);
212
213        unsafe {
214            ret_on_err!(((*(*self).output).SetAttribute)(self.output, new_attribute));
215        }
216
217        Ok(())
218    }
219
220    pub fn reset(&mut self, extended_verification: bool) -> Result<()> {
221        unsafe {
222            ret_on_err!(((*(*self).output).Reset)(self.output, if extended_verification { TRUE } else { FALSE }));
223        }
224
225        Ok(())
226    }
227
228    fn write_to_efi(&self, buf: &[u16]) -> Result<()> {
229        unsafe {
230            let (ptr, _) = to_ptr(buf);
231            ret_on_err!(((*(*self).output).OutputString)(self.output, ptr));
232            Ok(())
233        }
234    }
235
236    fn read_from_efi(&self, buf: &mut [u16]) -> Result<usize> {
237        match self.input {
238            TextInputProcolPtr::Input(input) => self.read_from_efi_input(buf, input),
239            TextInputProcolPtr::InputEx(input_ex) => self.read_from_efi_input_ex(buf, input_ex),
240        }
241    }
242
243    // TODO: code in this function is super ugly and prone to bugs. Clean it up.
244    fn read_from_efi_input_ex(&self, buf: &mut [u16], input_ex: *mut EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL) -> Result<usize> {
245        let mut bytes_read = 0;
246
247        let mut evt_index: UINTN = 0;
248        let mut key_data = EFI_KEY_DATA::default();
249        let mut evt_list = unsafe { [(*input_ex).WaitForKeyEx; 1] };
250
251        while bytes_read < buf.len() {
252            // TODO: For some reason we can't use ret_on_err here. Why?
253            let status = unsafe { ((*system_table().BootServices).WaitForEvent)(evt_list.len(), evt_list.as_mut_ptr(), &mut evt_index) };
254            if !IsSuccess(status) {
255                return Err(status.into()); // TODO: Can we send some error text too with such errors
256            }
257
258            // TODO: For some reason we can't use ret_on_err here. Why?
259            let status = unsafe { ((*input_ex).ReadKeyStrokeEx)(input_ex, &mut key_data) };
260            if !IsSuccess(status) {
261                return Err(status.into()); // TODO: Can we send some error text too with such errors
262            }
263
264            fn is_ctr_z(key_data: &EFI_KEY_DATA) -> bool {
265                (key_data.Key.UnicodeChar == 'z' as u16 || key_data.Key.UnicodeChar == 'Z' as u16) && 
266                (key_data.KeyState.KeyShiftState & EFI_SHIFT_STATE_VALID) != 0  &&
267                ((key_data.KeyState.KeyShiftState & EFI_LEFT_CONTROL_PRESSED) != 0 || (key_data.KeyState.KeyShiftState & EFI_RIGHT_CONTROL_PRESSED) != 0) 
268            }
269
270            if key_data.Key.UnicodeChar != 0 { // != 0 means it's a printable unicode char
271                if key_data.Key.UnicodeChar == CR { // Safe to check for CR only without waiting for LF because in my experience UEFI only ever inserts CR when you press the Enter key
272                    key_data.Key.UnicodeChar = LF; // Always translate CR's to LF's to normalize line endings.
273                }
274
275                match key_data.Key.UnicodeChar {
276                    BS => {
277                        if bytes_read > 0 {
278                            bytes_read -= 1;
279                            self.write_to_efi(&[BS, 0])?; // 0 is for null termination
280                        }
281                    },
282                    c => {
283                        if is_ctr_z(&key_data) {
284                            break;
285                        } else {
286                            buf[bytes_read] = c;
287                            bytes_read += 1;
288
289                            if c == LF {
290                                self.write_to_efi(&[CR, LF, 0])?; // Must echo both CR and LF because other wise it fucks up the cursor position.
291                                break;
292                            } else {
293                                self.write_to_efi(&[c, 0])?;
294                            }
295                        }
296                    }
297                };
298            } else {
299                // TODO: handle scan codes here.
300            }
301
302            // TODO: should also support ctrl+z as a terminating sequence?
303        }
304
305        Ok(bytes_read)
306    }
307
308    fn read_from_efi_input(&self, buf: &mut [u16], input: *mut EFI_SIMPLE_TEXT_INPUT_PROTOCOL) -> Result<usize> {
309        let mut bytes_read = 0;
310
311        let mut evt_index: UINTN = 0;
312        let mut key_data = EFI_INPUT_KEY::default();
313        let mut evt_list = unsafe { [(*input).WaitForKey; 1] };
314
315        while bytes_read < buf.len() {
316            // TODO: For some reason we can't use ret_on_err here. Why?
317            let status = unsafe { ((*system_table().BootServices).WaitForEvent)(evt_list.len(), evt_list.as_mut_ptr(), &mut evt_index) };
318            if !IsSuccess(status) {
319                return Err(status.into()); // TODO: Can we send some error text too with such errors
320            }
321
322            // TODO: For some reason we can't use ret_on_err here. Why?
323            let status = unsafe { ((*input).ReadKeyStroke)(input, &mut key_data) };
324            if !IsSuccess(status) {
325                return Err(status.into()); // TODO: Can we send some error text too with such errors
326            }
327
328            if key_data.UnicodeChar != 0 { // != 0 means it's a printable unicode char
329                if key_data.UnicodeChar == CR { // Safe to check for CR only without waiting for LF because in my experience UEFI only ever inserts CR when you press the Enter key
330                    key_data.UnicodeChar = LF; // Always translate CR's to LF's to normalize line endings.
331                }
332
333                match key_data.UnicodeChar {
334                    BS => {
335                        if bytes_read > 0 {
336                            bytes_read -= 1;
337                            self.write_to_efi(&[BS, 0])?; // 0 is for null termination
338                        }
339                    },
340                    c => {
341                        buf[bytes_read] = c;
342                        bytes_read += 1;
343
344                        if c == LF {
345                            self.write_to_efi(&[CR, LF, 0])?; // Must echo both CR and LF because other wise it fucks up the cursor position.
346                            break;
347                        } else {
348                            self.write_to_efi(&[c, 0])?;
349                        }
350                    }
351                };
352            } else {
353                // TODO: handle scan codes here.
354            }
355
356            // TODO: should also support ctrl+z as a terminating sequence?
357        }
358
359        Ok(bytes_read)
360    }
361
362}
363
364// TODO: write! works but writeln! doesn't. It doesn't result in an carriage returns at the end even though we're normalizing line endings in write() below. Fix this.
365impl io::Write for Console {
366    /// Writes given UTF8 buffer to the console.
367    /// UEFI console natively only supports UCS-2.
368    /// Therefore any code-points above the BMP will show up garbled.
369    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
370        const WRITE_BUFSIZE: usize = 8192;
371        let bytes_to_write = cmp::min(buf.len(), WRITE_BUFSIZE);
372        let utf8_buf = match str::from_utf8(&buf[..bytes_to_write]) {
373            Ok(str_) => str_,
374            Err(ref e) if e.valid_up_to() == 0 => return Err(invalid_encoding()),
375            Err(e) => str::from_utf8(&buf[..e.valid_up_to()]).unwrap(), // At least write those that are valid
376        };
377
378        // Convert to UTF16, normalizing all LF's to CRLF's (if present)
379        // because UEFI console doesn't automatically perform carriage upon seeing LF's
380        let utf16_iter = utf8_buf.encode_utf16();
381        let mut expected_utf16_buf_size = utf16_iter.size_hint().1.unwrap_or(utf8_buf.len()); // Guessing the capacity of utf16 buffer.
382        let five_percent = (expected_utf16_buf_size as f32 * 0.05) as usize;
383        let extra_size_for_line_endings = cmp::max(5, five_percent); // Least of 5 chars worth of space will come into play for very small writes. Without min limit extra could come out to be zero.
384        expected_utf16_buf_size += extra_size_for_line_endings; // Extra added in case we have to normalize line endings
385        let mut utf16_buf = Vec::with_capacity(expected_utf16_buf_size);
386
387        let mut last_c = 0_u16;
388        for (i, c) in utf16_iter.enumerate() {
389            if c == LF && (i == 0 || last_c != CR) { // Normalizing LF's
390                utf16_buf.push(CR);
391            }
392            utf16_buf.push(c);
393            last_c = c;
394        }
395
396        utf16_buf.push(0); // Appending the null terminator
397
398        self.write_to_efi(&utf16_buf)
399            .map_err(|_| io::Error::new(io::ErrorKind::Other, "Failed to write to EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL"))?; // TODO: Don't swallaow EFI status like this. Error handling in this whole crate needs fixing
400
401        Ok(utf8_buf.len())
402    }
403
404    fn flush(&mut self) -> io::Result<()> {
405        Ok(()) // Do nothing. UEFI SIMPLE_TEXT_OUTPUT protocol does not support flushing
406    } 
407}
408
409
410impl io::Read for Console {
411    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
412        // Read more if the buffer is empty
413        if self.utf8_buf.position() as usize == self.utf8_buf.get_ref().len() {
414            let mut utf16_buf = vec![0u16; 0x1000];
415            let bytes_read = self.read_from_efi(&mut utf16_buf).map_err(|_| io::Error::new(io::ErrorKind::Other, "Failed to read from EFI_SIMPLE_TEXT_INPUT_PROTOCOL"))?; // TODO: again swallowing incoming efi status
416            utf16_buf.truncate(bytes_read as usize);
417            // FIXME: what to do about this data that has already been read?
418            let data = match String::from_utf16(&utf16_buf) {
419                Ok(utf8_buf) => utf8_buf.into_bytes(),
420                Err(..) => return Err(invalid_encoding()),
421            };
422
423            self.utf8_buf = Cursor::new(data);
424        }
425
426        // MemReader shouldn't error here since we just filled it
427        self.utf8_buf.read(buf)
428    }
429}
430
431// TODO: Implement StdErr
432pub struct StdIn(BufReader<Console>);
433
434impl StdIn {
435    fn new(c: Console) -> Self {
436        StdIn(BufReader::new(c))
437    }
438}
439
440impl io::Read for StdIn {
441    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
442        self.0.read(buf)
443    }
444}
445
446impl BufRead for StdIn {
447    fn fill_buf(&mut self) -> io::Result<&[u8]> { self.0.fill_buf() }
448    fn consume(&mut self, n: usize) { self.0.consume(n) }
449}
450
451pub struct StdOut(LineWriter<Console>);
452
453impl StdOut {
454    fn new(c: Console) -> Self {
455        StdOut(LineWriter::new(c))
456    }
457}
458
459impl io::Write for StdOut {
460    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
461        self.0.write(buf)
462    }
463
464    fn flush(&mut self) -> io::Result<()> {
465        self.0.flush()
466    }
467}
468
469#[derive(Debug, Copy, Clone)]
470pub struct Position {
471    pub row: u32,
472    pub col: u32
473}
474
475pub fn console() -> Console {
476    SystemTable::new(system_table())
477        .expect("failed to create system table").console()
478}
479
480// TODO: Remove this uncessary SystemTable::new() business
481// Do we need this SystemTable type?
482pub fn stdin() -> StdIn {
483    StdIn::new(console())
484}
485
486pub fn stdout() -> StdOut {
487    StdOut::new(console())
488}
489
490#[macro_export]
491macro_rules! println {
492    ($fmt:expr) => (print!(concat!($fmt, "\n")));
493    ($fmt:expr, $($arg:tt)*) => (print!(concat!($fmt, "\n"), $($arg)*));
494}
495
496#[macro_export]
497macro_rules! print {
498    ($($arg:tt)*) => ($crate::console::print_args(format_args!($($arg)*)));
499}
500
501// TODO: Call to stdout() creates a new StdOut obj everytime. Remove this extravagance.
502pub fn print_args(args: fmt::Arguments) {
503    return stdout().write_fmt(args).expect("Failed to write to stdout")
504}
505
506
507fn invalid_encoding() -> io::Error {
508    io::Error::new(io::ErrorKind::InvalidData, "text was not valid unicode")
509}
510
511fn to_ptr<T>(slice: &[T]) -> (*const T, usize) {
512    unsafe {
513        transmute(slice)
514    }
515}