keos/
teletype.rs

1//! A teletype (TTY) interface for character-based I/O.
2//!
3//! This module provides a trait [`Teletype`] that defines an interface for
4//! reading from and writing to a teletype device, such as a serial port.
5//! The [`Serial`] struct implements this interface for x86_64 systems.
6
7use crate::{KernelError, spinlock::SpinLock, thread::with_current};
8
9/// The `Teletype` trait represents a generic character-based input/output
10/// device.
11///
12/// Implementations of this trait define methods for:
13/// - Writing data to the teletype (`write`)
14/// - Reading data from the teletype (`read`)
15///
16/// This abstraction allows for different kinds of terminal or serial interfaces
17/// to implement their own communication methods.
18pub trait Teletype {
19    /// Writes data to the teletype.
20    ///
21    /// # Arguments
22    /// - `data`: A byte slice containing the data to be written.
23    ///
24    /// # Returns
25    /// - `Ok(usize)`: The number of bytes successfully written.
26    /// - `Err(KernelError)`: If the write operation failed.
27    fn write(&mut self, data: &[u8]) -> Result<usize, KernelError>;
28
29    /// Reads data from the teletype.
30    ///
31    /// # Arguments
32    /// - `data`: A mutable byte slice where the read data will be stored.
33    ///
34    /// # Returns
35    /// - `Ok(usize)`: The number of bytes successfully read.
36    /// - `Err(KernelError)`: If the read operation failed.
37    fn read(&mut self, data: &mut [u8]) -> Result<usize, KernelError>;
38}
39
40/// A serial teletype interface for x86_64 systems.
41///
42/// This struct provides a basic implementation of a serial TTY using the
43/// **COM1** serial port. It implements the [`Teletype`] trait to allow
44/// read and write operations over a serial interface.
45pub struct Serial {
46    _p: (),
47}
48
49impl Serial {
50    /// Creates a new **COM1** serial interface instance.
51    ///
52    /// This function initializes a serial TTY for performing character-based
53    /// I/O operations. The actual hardware interaction is handled via the
54    /// [`Teletype`] trait methods (`write` and `read`).
55    ///
56    /// # Returns
57    /// - A new instance of `Serial`, representing a COM1 serial interface.
58    pub const fn new() -> Self {
59        Self { _p: () }
60    }
61}
62
63impl Default for Serial {
64    fn default() -> Self {
65        Self::new()
66    }
67}
68
69/// A global serial device protected by a spinlock.
70///
71/// This static instance of [`Serial`] ensures safe concurrent access to the
72/// serial port. It is wrapped in a [`SpinLock`] to provide mutual exclusion,
73/// preventing race conditions when multiple threads attempt to write to or read
74/// from the serial device.
75///
76/// The [`Serial`] struct typically represents a UART (Universal Asynchronous
77/// Receiver-Transmitter) device used for debugging, logging, or kernel output.
78///
79/// # Safety
80/// - Accessing this global requires acquiring the spinlock before modifying the
81///   serial state.
82/// - Since [`SpinLock`] is used instead of [`Mutex`], it should **only be used
83///   in environments without preemption**, such as kernel mode, to avoid
84///   deadlocks.
85///
86/// [`Mutex`]: struct.Mutex.html
87static SERIAL: SpinLock<Serial> = SpinLock::new(Serial::new());
88
89/// Returns a reference to the global serial device.
90///
91/// This function provides safe access to the global serial interface wrapped in
92/// a [`SpinLock`]. Users must lock the spinlock before performing any
93/// operations on the [`Serial`] instance.
94///
95/// # Example
96/// ```
97/// use keos::teletype::Teletype;
98///
99/// let serial = serial().lock();
100/// serial.write("Hello, serial output!").expect("Failed to write tty");
101/// ```
102///
103/// # Safety
104/// - Since this returns a reference to a global [`SpinLock`], the caller must
105///   **ensure proper locking** before accessing the [`Serial`] device.
106///
107/// # Returns
108/// A reference to the [`SpinLock`] wrapping the global [`Serial`] instance.
109pub fn serial() -> &'static SpinLock<Serial> {
110    &SERIAL
111}
112
113impl Teletype for Serial {
114    /// Writes data to the serial teletype (COM1).
115    ///
116    /// This function attempts to convert the input byte slice into a UTF-8
117    /// string. If the conversion is successful, it prints the string to
118    /// the console. If the data is aligned to a **16-byte boundary**, it
119    /// is printed as a single string; otherwise, it is printed byte by byte.
120    ///
121    /// # Arguments
122    /// - `data`: The byte slice to be written.
123    ///
124    /// # Returns
125    /// - `Ok(usize)`: The number of bytes written.
126    /// - `Err`: If the input data is not valid UTF-8.
127    fn write(&mut self, data: &[u8]) -> Result<usize, KernelError> {
128        with_current(|th| {
129            let b = if data.as_ptr().is_aligned_to(8) {
130                if let Ok(b) = core::str::from_utf8(data) {
131                    print!("{}", b);
132                    Ok(data.len())
133                } else {
134                    Err(KernelError::InvalidArgument)
135                }
136            } else {
137                for b in data {
138                    print!("{}", b);
139                }
140                Ok(data.len())
141            };
142            let mut tty_hook = th.tty_hook.lock();
143            let val = match tty_hook.as_mut() {
144                Some(ttyhook) => {
145                    let mut guard = ttyhook.lock();
146                    let val = guard.write(data);
147                    guard.unlock();
148                    val
149                }
150                _ => b,
151            };
152            tty_hook.unlock();
153            val
154        })
155    }
156
157    /// Reads data from the serial teletype (COM1).
158    ///
159    /// This function retrieves data from the serial interface and stores it
160    /// in the provided mutable buffer.
161    ///
162    /// # Arguments
163    /// - `data`: A mutable byte slice where the read data will be stored.
164    ///
165    /// # Returns
166    /// - `Ok(usize)`: The number of bytes successfully read.
167    /// - `Err`: If the read operation failed.
168    fn read(&mut self, data: &mut [u8]) -> Result<usize, KernelError> {
169        with_current(|th| {
170            let mut tty_guard = th.tty_hook.lock();
171
172            let val = match tty_guard.as_mut() {
173                Some(ttyhook) => {
174                    let mut guard = ttyhook.lock();
175                    let val = guard.read(data);
176                    guard.unlock();
177                    val
178                }
179                _ => abyss::dev::x86_64::serial::read_bytes_busywait(data)
180                    .ok_or(KernelError::IOError),
181            };
182            tty_guard.unlock();
183            val
184        })
185    }
186}