sysutils: add serial console program
This commit is contained in:
@@ -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 => {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,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
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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"),
|
||||
|
||||
Reference in New Issue
Block a user