This commit is contained in:
2025-10-29 15:13:20 +01:00
parent b8aba85880
commit 9747f66664
3 changed files with 153 additions and 104 deletions

32
Cargo.lock generated
View File

@@ -2,24 +2,6 @@
# It is not intended for manual editing.
version = 4
[[package]]
name = "bitflags"
version = "2.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3"
[[package]]
name = "cfg-if"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
[[package]]
name = "cfg_aliases"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
[[package]]
name = "itoa"
version = "1.0.15"
@@ -38,23 +20,11 @@ version = "2.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273"
[[package]]
name = "nix"
version = "0.30.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6"
dependencies = [
"bitflags",
"cfg-if",
"cfg_aliases",
"libc",
]
[[package]]
name = "oc2r-rust"
version = "0.1.0"
dependencies = [
"nix",
"libc",
"serde",
"serde_json",
]

View File

@@ -12,4 +12,4 @@ codegen-units = 1
[dependencies]
serde = { version = "1", features = ["derive"]}
serde_json = "1"
nix = { version = "0.30", features = ["term", "poll"]}
libc = "0.2"

View File

@@ -1,12 +1,8 @@
use nix::errno::Errno;
use nix::poll::{PollFd, PollFlags, PollTimeout, poll};
use nix::sys::termios;
use nix::sys::termios::SpecialCharacterIndices::{VMIN, VTIME};
use nix::unistd::{read, write};
use serde::{Deserialize, Serialize};
use std::fs::OpenOptions;
use std::os::fd::{AsFd, BorrowedFd};
use std::{io, result};
use std::io;
use std::os::fd::{AsRawFd, RawFd};
use std::result;
// Requests -----------------------------------------------------------
#[derive(Serialize, Debug)]
@@ -76,7 +72,6 @@ type Result<T> = result::Result<T, Error>;
#[derive(Debug)]
enum Error {
Io(io::Error),
Nix(Errno),
Json(serde_json::Error),
Framing,
Protocol(&'static str),
@@ -86,109 +81,193 @@ impl From<io::Error> for Error {
Error::Io(e)
}
}
impl From<Errno> for Error {
fn from(e: Errno) -> Self {
Error::Nix(e)
}
}
impl From<serde_json::Error> for Error {
fn from(e: serde_json::Error) -> Self {
Error::Json(e)
}
}
fn set_raw(fd: BorrowedFd<'_>) -> Result<()> {
let mut term = termios::tcgetattr(fd)?;
term.local_flags.remove(
termios::LocalFlags::ICANON | termios::LocalFlags::ECHO | termios::LocalFlags::ISIG,
);
term.control_chars[VMIN as usize] = 1;
term.control_chars[VTIME as usize] = 0;
termios::tcsetattr(fd, termios::SetArg::TCSANOW, &term)?;
impl std::fmt::Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Error::Io(e) => write!(f, "io error: {e}"),
Error::Json(e) => write!(f, "json error: {e}"),
Error::Framing => f.write_str("invalid message framing"),
Error::Protocol(msg) => write!(f, "protocol error: {msg}"),
}
}
}
impl std::error::Error for Error {}
const DELIM: u8 = 0;
fn set_raw(fd: RawFd) -> Result<()> {
unsafe {
let mut term = std::mem::zeroed::<libc::termios>();
if libc::tcgetattr(fd, &mut term) != 0 {
return Err(Error::Io(io::Error::last_os_error()));
}
libc::cfmakeraw(&mut term);
term.c_cc[libc::VMIN] = 1;
term.c_cc[libc::VTIME] = 0;
if libc::tcsetattr(fd, libc::TCSANOW, &term) != 0 {
return Err(Error::Io(io::Error::last_os_error()));
}
}
Ok(())
}
fn flush(fd: BorrowedFd<'_>) -> Result<()> {
let mut p = [PollFd::new(fd, PollFlags::POLLIN)];
let mut tmp = [0u8; 1024];
fn flush(fd: RawFd) -> Result<()> {
println!("Flushing pending input…");
let mut buf = [0u8; 1024];
let mut total = 0usize;
loop {
match poll(&mut p, 0u8)? {
0 => return Ok(()),
_ => {
let _ = read(fd, &mut tmp)?;
let ready = loop {
let mut pfd = libc::pollfd {
fd,
events: libc::POLLIN,
revents: 0,
};
let res = unsafe { libc::poll(&mut pfd, 1, 0) };
if res < 0 {
let err = io::Error::last_os_error();
if err.kind() == io::ErrorKind::Interrupted {
continue;
}
return Err(Error::Io(err));
}
break res;
};
if ready == 0 {
if total == 0 {
println!("Flush complete; nothing pending.");
} else {
println!("Flush complete; drained {total} bytes.");
}
return Ok(());
}
loop {
let read = unsafe { libc::read(fd, buf.as_mut_ptr().cast(), buf.len()) };
if read < 0 {
let err = io::Error::last_os_error();
match err.kind() {
io::ErrorKind::Interrupted => continue,
io::ErrorKind::WouldBlock => break,
_ => return Err(Error::Io(err)),
}
} else if read == 0 {
break;
} else {
total += read as usize;
if read as usize != buf.len() {
break;
}
}
}
}
}
#[inline]
fn write_all_fd(fd: BorrowedFd<'_>, mut buf: &[u8]) -> Result<()> {
fn write_all_fd(fd: RawFd, mut buf: &[u8]) -> Result<()> {
while !buf.is_empty() {
match write(fd, buf) {
Ok(0) => return Err(Error::Framing),
Ok(n) => buf = &buf[n..],
Err(Errno::EINTR) => continue,
Err(e) => return Err(e.into()),
}
}
Ok(())
}
#[inline]
fn read_exact_fd(fd: BorrowedFd<'_>, mut buf: &mut [u8]) -> Result<()> {
while !buf.is_empty() {
match read(fd, buf) {
Ok(0) => return Err(Error::Framing),
Ok(n) => {
let tmp = buf;
buf = &mut tmp[n..];
let written = unsafe { libc::write(fd, buf.as_ptr().cast(), buf.len()) };
if written < 0 {
let err = io::Error::last_os_error();
match err.kind() {
io::ErrorKind::Interrupted => continue,
io::ErrorKind::WouldBlock => continue,
_ => return Err(Error::Io(err)),
}
Err(Errno::EINTR) | Err(Errno::EAGAIN) => continue,
Err(e) => return Err(e.into()),
} else if written == 0 {
return Err(Error::Framing);
} else {
buf = &buf[written as usize..];
}
}
Ok(())
}
#[inline]
fn write_len(fd: BorrowedFd<'_>, len: usize) -> Result<()> {
let l = u32::try_from(len).map_err(|_| Error::Framing)?;
write_all_fd(fd, &l.to_le_bytes())
}
#[inline]
fn read_len(fd: BorrowedFd<'_>) -> Result<usize> {
let mut b = [0u8; 4];
read_exact_fd(fd, &mut b)?;
Ok(u32::from_le_bytes(b) as usize)
}
fn write_message(fd: BorrowedFd<'_>, msg: &impl Serialize) -> Result<()> {
fn write_message(fd: RawFd, msg: &impl Serialize) -> Result<()> {
let payload = serde_json::to_vec(msg)?;
write_len(fd, payload.len())?;
println!("Sending message: {}", String::from_utf8_lossy(&payload));
write_all_fd(fd, &[DELIM])?;
write_all_fd(fd, &payload)?;
write_all_fd(fd, &[DELIM])?;
Ok(())
}
fn read_message(fd: BorrowedFd<'_>) -> Result<RpcResponse> {
let mut p = [PollFd::new(fd, PollFlags::POLLIN)];
poll(&mut p, Option::<u8>::None)?; // infinite
let len = read_len(fd)?;
let mut buf = vec![0u8; len];
read_exact_fd(fd, &mut buf)?;
Ok(serde_json::from_slice(&buf)?)
fn wait_for_readable(fd: RawFd) -> Result<()> {
loop {
let mut pfd = libc::pollfd {
fd,
events: libc::POLLIN,
revents: 0,
};
let res = unsafe { libc::poll(&mut pfd, 1, -1) };
if res < 0 {
let err = io::Error::last_os_error();
if err.kind() == io::ErrorKind::Interrupted {
continue;
}
return Err(Error::Io(err));
}
if res > 0 {
return Ok(());
}
}
}
fn read_message(fd: RawFd) -> Result<RpcResponse> {
println!("Waiting for response…");
wait_for_readable(fd)?;
let mut buf = Vec::new();
let mut byte = [0u8; 1];
loop {
let read = unsafe { libc::read(fd, byte.as_mut_ptr().cast(), 1) };
if read < 0 {
let err = io::Error::last_os_error();
match err.kind() {
io::ErrorKind::Interrupted => continue,
io::ErrorKind::WouldBlock => {
wait_for_readable(fd)?;
continue;
}
_ => return Err(Error::Io(err)),
}
} else if read == 0 {
return Err(Error::Framing);
} else {
let value = byte[0];
if value == DELIM {
if buf.is_empty() {
continue;
}
println!("Received message: {}", String::from_utf8_lossy(&buf));
return Ok(serde_json::from_slice(&buf)?);
} else {
buf.push(value);
}
}
}
}
fn main() -> Result<()> {
println!("Opening /dev/hvc0…");
let file = OpenOptions::new()
.read(true)
.write(true)
.open("/dev/hvc0")?;
let fd = file.as_fd();
let fd = file.as_raw_fd();
println!("Configuring raw console mode…");
set_raw(fd)?;
println!("Console is now in raw mode.");
flush(fd)?;
println!("Requesting device list…");
write_message(fd, &RpcRequest::List)?;
match read_message(fd)? {
RpcResponse::List { data } => {