First commit

This commit is contained in:
2025-10-29 14:55:11 +01:00
commit b8aba85880
7 changed files with 382 additions and 0 deletions

12
.cargo/config.toml Normal file
View File

@@ -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"]

1
.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
/target

144
Cargo.lock generated Normal file
View File

@@ -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"

15
Cargo.toml Normal file
View File

@@ -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"]}

6
Cross.toml Normal file
View File

@@ -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"

2
rust-toolchain.toml Normal file
View File

@@ -0,0 +1,2 @@
[toolchain]
channel = "nightly"

202
src/main.rs Normal file
View File

@@ -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<DeviceEntry> },
Methods { data: Vec<MethodEntry> },
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<String>,
#[serde(default)]
pub description: Option<String>,
}
#[derive(Deserialize, Debug)]
pub struct MethodEntry {
pub name: String,
#[serde(default)]
pub description: Option<String>,
#[serde(default)]
pub parameters: Vec<MethodParameter>,
#[serde(default)]
pub return_type: Option<String>,
}
#[derive(Deserialize, Debug)]
pub struct MethodParameter {
#[serde(default)]
pub name: Option<String>,
#[serde(default)]
pub description: Option<String>,
#[serde(default)]
pub r#type: Option<String>,
}
type Result<T> = result::Result<T, Error>;
#[derive(Debug)]
enum Error {
Io(io::Error),
Nix(Errno),
Json(serde_json::Error),
Framing,
Protocol(&'static str),
}
impl From<io::Error> for Error {
fn from(e: io::Error) -> Self {
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)?;
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<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<()> {
let payload = serde_json::to_vec(msg)?;
write_len(fd, payload.len())?;
write_all_fd(fd, &payload)?;
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 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")),
}
}