sysutils: add serial console program

This commit is contained in:
2025-10-10 09:21:20 +03:00
parent 33474c10d3
commit 312458b8f0
14 changed files with 266 additions and 12 deletions
+25 -2
View File
@@ -64,6 +64,20 @@ pub trait TerminalOutput: Sync + Send {
}
Ok(written)
}
fn baud_rate(&self) -> u32 {
115200
}
fn set_baud_rate(&self, baud: u32) -> Result<(), Error> {
if baud == 115200 {
Ok(())
} else {
log::warn!(
"[this] TerminalOutput impl does not support baud rate changes (want {baud})"
);
Err(Error::NotImplemented)
}
}
}
struct InputBuffer {
@@ -72,7 +86,8 @@ struct InputBuffer {
}
impl<O: TerminalOutput> Terminal<O> {
pub fn from_parts(config: TerminalOptions, input: TerminalInput, output: O) -> Self {
pub fn from_parts(mut config: TerminalOptions, input: TerminalInput, output: O) -> Self {
config.baud_rate = output.baud_rate();
Self {
input,
output,
@@ -212,7 +227,15 @@ impl<O: TerminalOutput> Terminal<O> {
TerminalRequestVariant::SetTerminalOptions => {
let options = device::SetTerminalOptions::load_request(input)?;
self.input.flush();
*self.config.write() = options;
let need_baud_change = {
let mut cfg = self.config.write();
let need_baud_change = cfg.baud_rate != options.baud_rate;
*cfg = options;
need_baud_change
};
if need_baud_change {
self.output.set_baud_rate(options.baud_rate)?;
}
device::SetTerminalOptions::store_response(&(), buffer)
}
TerminalRequestVariant::GetTerminalOptions => {
+32
View File
@@ -17,8 +17,11 @@ use libk_util::sync::IrqSafeSpinlock;
struct Regs {
dr: IoPort<u8>,
lsr: IoPort<u8>,
lcr: IoPort<u8>,
ier: IoPort<u8>,
isr: IoPort<u8>,
baud_rate: u32,
}
struct PortInner {
@@ -78,6 +81,30 @@ impl TerminalOutput for PortInner {
}
Ok(bytes.len())
}
fn baud_rate(&self) -> u32 {
self.regs.lock().baud_rate
}
fn set_baud_rate(&self, baud: u32) -> Result<(), Error> {
if baud > 115200 {
log::warn!("Tried to set baud rate for COM port beyond 115200: {baud}");
return Err(Error::InvalidArgument);
}
let div = 115200 / baud;
let mut regs = self.regs.lock();
regs.lcr.write(regs.lcr.read() | (1 << 7));
regs.dr.write(div as u8);
regs.ier.write((div >> 8) as u8);
regs.lcr.write(regs.lcr.read() & !(1 << 7));
regs.baud_rate = baud;
Ok(())
}
}
impl DebugSink for Port {
@@ -114,6 +141,8 @@ impl Device for Port {
}
unsafe fn init(self: Arc<Self>, _cx: DeviceInitContext) -> Result<(), Error> {
self.terminal.output().set_baud_rate(115200)?;
DEVICE_REGISTRY
.serial_terminal
.register(self.terminal.clone(), Some(self.clone()))
@@ -149,6 +178,9 @@ impl Port {
lsr: IoPort::new(base + 5),
ier: IoPort::new(base + 1),
isr: IoPort::new(base + 2),
lcr: IoPort::new(base + 3),
baud_rate: 0,
}),
};
let terminal = Terminal::from_parts(TerminalOptions::const_default(), input, output);
+2
View File
@@ -191,6 +191,8 @@ struct TerminalOptions {
pub line: TerminalLineOptions,
/// Specifies control characters of the terminal
pub chars: TerminalControlCharacters,
/// For hardware terminals, controls baud rate used for the communication line
pub baud_rate: u32
}
/// Describes terminal size in rows and columns
+2 -1
View File
@@ -31,6 +31,7 @@ impl TerminalOptions {
input: TerminalInputOptions::const_default(),
line: TerminalLineOptions::const_default(),
chars: TerminalControlCharacters::const_default(),
baud_rate: 115200,
}
}
@@ -70,7 +71,7 @@ abi_serde::impl_struct_serde!(TerminalControlCharacters: [
eof, kill, erase, werase, interrupt
]);
abi_serde::impl_struct_serde!(TerminalOptions: [
line, input, output, chars
line, input, output, chars, baud_rate
]);
abi_serde::impl_struct_serde!(TerminalSize: [
rows, columns
+1 -1
View File
@@ -1,4 +1,4 @@
init:1:wait:/sbin/rc default
logd:1:once:/sbin/logd
user:1:once:/sbin/login /dev/ttyS0
# user:1:once:/sbin/login /dev/ttyS0
+9 -6
View File
@@ -308,14 +308,17 @@ impl Terminal {
pty_master.write_all(&[termios.erase_char()]).ok();
need_redraw = s.scroll_end();
}
(KeyModifiers::CTRL, Key::Char(b'c')) => {
pty_master.write_all(&[termios.interrupt_char()]).unwrap();
need_redraw = s.scroll_end();
}
(KeyModifiers::CTRL, Key::Char(b'd')) => {
pty_master.write_all(&[termios.eof_char()]).unwrap();
(KeyModifiers::CTRL, Key::Char(ch)) if ch.is_ascii_lowercase() => {
let byte = ch - 0x60;
pty_master.write_all(&[byte]).unwrap();
need_redraw = s.scroll_end();
// pty_master.write_all(&[termios.interrupt_char()]).unwrap();
// need_redraw = s.scroll_end();
}
// (KeyModifiers::CTRL, Key::Char(b'd')) => {
// pty_master.write_all(&[termios.eof_char()]).unwrap();
// need_redraw = s.scroll_end();
// }
// other sequences
(m, k) if let Some(data) = escape::map(m, k) => {
pty_master.write_all(data).unwrap();
+45 -1
View File
@@ -1,13 +1,14 @@
use std::{
io::{self, Read, Write},
os::fd::{AsRawFd, IntoRawFd, RawFd},
path::Path,
process::Stdio,
time::Duration,
};
use crate::sys::{
self, PidFd as SysPidFd, Pipe as SysPipe, Poll as SysPoll, PtyMaster as SysPtyMaster,
RawStdin as SysRawStdin, TimerFd as SysTimerFd,
RawStdin as SysRawStdin, SerialPort as SysSerialPort, TimerFd as SysTimerFd,
};
use self::sys::PipeImpl;
@@ -35,6 +36,9 @@ pub struct PtySlave(sys::PtySlaveImpl);
#[repr(transparent)]
pub struct PtyMaster(sys::PtyMasterImpl);
#[repr(transparent)]
pub struct SerialPort(sys::SerialPortImpl);
#[derive(Clone, Copy, PartialEq, Debug)]
pub struct TerminalSize {
pub rows: u16,
@@ -43,6 +47,12 @@ pub struct TerminalSize {
pub y_pixels: u32,
}
#[derive(Clone, Copy, PartialEq, Debug)]
pub struct SerialPortSettings {
pub baud_rate: Option<u32>,
// TODO parity, stop bits, etc
}
impl Poll {
pub fn new() -> io::Result<Self> {
sys::PollImpl::new().map(Self)
@@ -220,6 +230,40 @@ impl IntoRawFd for PtySlave {
}
}
impl SerialPort {
pub fn open<P: AsRef<Path>>(device: P, options: &SerialPortSettings) -> io::Result<Self> {
sys::SerialPortImpl::open(device, options).map(Self)
}
}
impl Read for SerialPort {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
self.0.read(buf)
}
}
impl Write for SerialPort {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.0.write(buf)
}
fn flush(&mut self) -> io::Result<()> {
self.0.flush()
}
}
impl AsRawFd for SerialPort {
fn as_raw_fd(&self) -> RawFd {
self.0.as_raw_fd()
}
}
impl IntoRawFd for SerialPort {
fn into_raw_fd(self) -> RawFd {
self.0.into_raw_fd()
}
}
pub fn open_pty(
options: &TerminalOptionsImpl,
size: TerminalSize,
+5 -1
View File
@@ -18,7 +18,7 @@ use std::{
time::Duration,
};
use crate::io::TerminalSize;
use crate::io::{SerialPortSettings, TerminalSize};
// I/O
@@ -58,6 +58,10 @@ pub(crate) trait PtyMaster: Read + Write + AsRawFd + IntoRawFd {
#[allow(unused)]
pub(crate) trait PtySlave: Read + Write + AsRawFd + IntoRawFd {}
pub(crate) trait SerialPort: Read + Write + AsRawFd + IntoRawFd + Sized {
fn open<P: AsRef<Path>>(device: P, options: &SerialPortSettings) -> io::Result<Self>;
}
pub trait TerminalOptions: Copy {
fn normal() -> Self;
fn raw() -> Self;
@@ -4,6 +4,7 @@ pub mod pid;
pub mod pipe;
pub mod poll;
pub mod pty;
pub mod serial;
pub mod socket;
pub mod term;
pub mod time;
@@ -20,6 +21,7 @@ pub use pid::PidFdImpl;
pub use pipe::PipeImpl;
pub use poll::PollImpl;
pub use pty::{open as open_pty, PtyMasterImpl, PtySlaveImpl, TerminalOptionsImpl};
pub use serial::SerialPortImpl;
pub use socket::{BorrowedAddressImpl, LocalPacketSocketImpl, OwnedAddressImpl};
pub use term::RawStdinImpl;
pub use timer::TimerFdImpl;
@@ -43,6 +43,7 @@ impl TerminalOptions for TerminalOptionsImpl {
kill: 0x15,
werase: 0x17,
},
baud_rate: 115200,
}
}
@@ -0,0 +1,57 @@
use std::{
fs::{File, OpenOptions},
io::{self, Read, Write},
os::fd::{AsRawFd, IntoRawFd, RawFd},
path::Path,
};
use runtime::{abi::io::TerminalOptions, rt::io::terminal::set_terminal_options};
use crate::{io::SerialPortSettings, sys::SerialPort};
pub struct SerialPortImpl(File);
impl SerialPort for SerialPortImpl {
fn open<P: AsRef<Path>>(device: P, options: &SerialPortSettings) -> io::Result<Self> {
let file = OpenOptions::new().read(true).write(true).open(device)?;
if let Some(baud_rate) = options.baud_rate {
let termios = TerminalOptions {
baud_rate,
..TerminalOptions::raw_input()
};
set_terminal_options(file.as_raw_fd(), &termios)?;
}
Ok(Self(file))
}
}
impl Read for SerialPortImpl {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
self.0.read(buf)
}
}
impl Write for SerialPortImpl {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.0.write(buf)
}
fn flush(&mut self) -> io::Result<()> {
self.0.flush()
}
}
impl AsRawFd for SerialPortImpl {
fn as_raw_fd(&self) -> RawFd {
self.0.as_raw_fd()
}
}
impl IntoRawFd for SerialPortImpl {
fn into_raw_fd(self) -> RawFd {
self.0.into_raw_fd()
}
}
+4
View File
@@ -169,6 +169,10 @@ path = "src/ps.rs"
name = "top"
path = "src/top.rs"
[[bin]]
name = "serial"
path = "src/serial.rs"
[[bin]]
name = "tst"
path = "src/tst.rs"
+80
View File
@@ -0,0 +1,80 @@
#![feature(yggdrasil_os)]
use std::{
io::{self, stdout, Read, Write},
os::fd::AsRawFd,
path::PathBuf,
process::ExitCode,
};
use clap::Parser;
use cross::io::{Poll, RawStdin, SerialPort, SerialPortSettings};
#[derive(Parser)]
struct Args {
#[clap(short = 'B', default_value_t = 115200)]
baud_rate: u32,
device: PathBuf,
}
fn run(args: Args) -> io::Result<()> {
let serial_options = SerialPortSettings {
baud_rate: Some(args.baud_rate),
};
let mut poll = Poll::new()?;
let mut serial = SerialPort::open(args.device, &serial_options)?;
let mut stdout = stdout();
let mut stdin = RawStdin::open()?;
let mut buffer = [0; 1];
let mut ctrl_a = false;
poll.add(&serial)?;
poll.add(&stdin)?;
loop {
let fd = poll.wait(None)?.unwrap();
if fd == stdin.as_raw_fd() {
if stdin.read(&mut buffer)? != 1 {
break;
}
if ctrl_a {
match buffer[0] {
b'x' => break,
_ => ctrl_a = false,
}
} else {
if buffer[0] == 0x01 {
// Ctrl+A
ctrl_a = true;
} else {
serial.write_all(&buffer)?;
}
}
} else {
if serial.read(&mut buffer)? != 1 {
break;
}
stdout.write_all(&buffer).ok();
stdout.flush().ok();
}
}
Ok(())
}
fn main() -> ExitCode {
let args = Args::parse();
match run(args) {
Ok(()) => ExitCode::SUCCESS,
Err(error) => {
eprintln!("{error}");
ExitCode::FAILURE
}
}
}
+1
View File
@@ -61,6 +61,7 @@ const PROGRAMS: &[(&str, &str)] = &[
("tree", "bin/tree"),
("tst", "bin/tst"),
("view", "bin/view"),
("serial", "bin/serial"),
// netutils
("netconf", "sbin/netconf"),
("dhcp-client", "sbin/dhcp-client"),