From b8aba85880a875d5e738aa0d7d3727f11bbad515 Mon Sep 17 00:00:00 2001 From: Jika Date: Wed, 29 Oct 2025 14:55:11 +0100 Subject: [PATCH] First commit --- .cargo/config.toml | 12 +++ .gitignore | 1 + Cargo.lock | 144 +++++++++++++++++++++++++++++++ Cargo.toml | 15 ++++ Cross.toml | 6 ++ rust-toolchain.toml | 2 + src/main.rs | 202 ++++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 382 insertions(+) create mode 100644 .cargo/config.toml create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 Cross.toml create mode 100644 rust-toolchain.toml create mode 100644 src/main.rs diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 0000000..8640b80 --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,12 @@ +[build] +target = "riscv64gc-unknown-linux-musl" +rustflags = [ + "-Zlocation-detail=none", + "-Zfmt-debug=none", + "-Zunstable-options", + "-Cpanic=immediate-abort", +] + +[unstable] +build-std = ["core", "alloc", "std", "panic_abort"] +build-std-features = ["optimize_for_size"] diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..21e7f7d --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,144 @@ +# This file is automatically @generated by Cargo. +# 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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" + +[[package]] +name = "libc" +version = "0.2.177" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" + +[[package]] +name = "memchr" +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", + "serde", + "serde_json", +] + +[[package]] +name = "proc-macro2" +version = "1.0.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce25767e7b499d1b604768e7cde645d14cc8584231ea6b295e9c9eb22c02e1d1" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "ryu" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.145" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", + "serde_core", +] + +[[package]] +name = "syn" +version = "2.0.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da58917d35242480a05c2897064da0a80589a2a0476c9a3f2fdc83b53502e917" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "462eeb75aeb73aea900253ce739c8e18a67423fadf006037cd3ff27e82748a06" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..a896d3e --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "oc2r-rust" +version = "0.1.0" +edition = "2024" + +[profile.release] +strip = true +opt-level = "z" +lto = true + +codegen-units = 1 +[dependencies] +serde = { version = "1", features = ["derive"]} +serde_json = "1" +nix = { version = "0.30", features = ["term", "poll"]} diff --git a/Cross.toml b/Cross.toml new file mode 100644 index 0000000..90a8777 --- /dev/null +++ b/Cross.toml @@ -0,0 +1,6 @@ +[target.riscv64gc-unknown-linux-musl] +image = "ghcr.io/cross-rs/riscv64gc-unknown-linux-musl:edge" + +[build] +build-std = true +default-target = "riscv64gc-unknown-linux-musl" diff --git a/rust-toolchain.toml b/rust-toolchain.toml new file mode 100644 index 0000000..5d56faf --- /dev/null +++ b/rust-toolchain.toml @@ -0,0 +1,2 @@ +[toolchain] +channel = "nightly" diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..e7b9dec --- /dev/null +++ b/src/main.rs @@ -0,0 +1,202 @@ +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}; + +// Requests ----------------------------------------------------------- +#[derive(Serialize, Debug)] +#[serde(tag = "type", rename_all = "lowercase")] +pub enum RpcRequest<'a> { + List, + Methods { + data: &'a str, + }, + Invoke { + #[serde(rename = "data")] + payload: InvokePayload<'a>, + }, +} + +#[derive(Serialize, Debug)] +pub struct InvokePayload<'a> { + #[serde(rename = "deviceId")] + pub device_id: &'a str, + pub name: &'a str, + pub parameters: &'a [serde_json::Value], +} + +// Responses ---------------------------------------------------------- +#[derive(Deserialize, Debug)] +#[serde(tag = "type", rename_all = "lowercase")] +pub enum RpcResponse { + List { data: Vec }, + Methods { data: Vec }, + Result { data: serde_json::Value }, + Error { data: String }, +} + +#[derive(Deserialize, Debug)] +pub struct DeviceEntry { + #[serde(rename = "deviceId")] + pub device_id: String, + #[serde(default)] + pub type_names: Vec, + #[serde(default)] + pub description: Option, +} + +#[derive(Deserialize, Debug)] +pub struct MethodEntry { + pub name: String, + #[serde(default)] + pub description: Option, + #[serde(default)] + pub parameters: Vec, + #[serde(default)] + pub return_type: Option, +} + +#[derive(Deserialize, Debug)] +pub struct MethodParameter { + #[serde(default)] + pub name: Option, + #[serde(default)] + pub description: Option, + #[serde(default)] + pub r#type: Option, +} + +type Result = result::Result; + +#[derive(Debug)] +enum Error { + Io(io::Error), + Nix(Errno), + Json(serde_json::Error), + Framing, + Protocol(&'static str), +} +impl From for Error { + fn from(e: io::Error) -> Self { + Error::Io(e) + } +} +impl From for Error { + fn from(e: Errno) -> Self { + Error::Nix(e) + } +} +impl From 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)?; + Ok(()) +} + +fn flush(fd: BorrowedFd<'_>) -> Result<()> { + let mut p = [PollFd::new(fd, PollFlags::POLLIN)]; + let mut tmp = [0u8; 1024]; + loop { + match poll(&mut p, 0u8)? { + 0 => return Ok(()), + _ => { + let _ = read(fd, &mut tmp)?; + } + } + } +} + +#[inline] +fn write_all_fd(fd: BorrowedFd<'_>, 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..]; + } + Err(Errno::EINTR) | Err(Errno::EAGAIN) => continue, + Err(e) => return Err(e.into()), + } + } + 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 { + 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<()> { + let payload = serde_json::to_vec(msg)?; + write_len(fd, payload.len())?; + write_all_fd(fd, &payload)?; + Ok(()) +} + +fn read_message(fd: BorrowedFd<'_>) -> Result { + let mut p = [PollFd::new(fd, PollFlags::POLLIN)]; + poll(&mut p, Option::::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 main() -> Result<()> { + let file = OpenOptions::new() + .read(true) + .write(true) + .open("/dev/hvc0")?; + let fd = file.as_fd(); + + set_raw(fd)?; + flush(fd)?; + + write_message(fd, &RpcRequest::List)?; + match read_message(fd)? { + RpcResponse::List { data } => { + println!("devices: {data:#?}"); + Ok(()) + } + RpcResponse::Error { data: _ } => Err(Error::Protocol("rpc error")), + RpcResponse::Methods { .. } => Err(Error::Protocol("unexpected methods response")), + RpcResponse::Result { .. } => Err(Error::Protocol("unexpected result response")), + } +}