blob: 8d78ef865abb05c9c54f2270886d9d06f8c1f846 [file]
// Licensed under the Apache-2.0 license
// SPDX-License-Identifier: Apache-2.0
#![no_std]
use usart_api::{UsartError, UsartOp, UsartRequestHeader, UsartResponseHeader, MAX_PAYLOAD_SIZE};
use userspace::syscall;
use userspace::time::Instant;
const MAX_BUF_SIZE: usize = 512;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ClientError {
IpcError(pw_status::Error),
ServerError(UsartError),
InvalidResponse,
BufferTooSmall,
}
impl From<pw_status::Error> for ClientError {
fn from(e: pw_status::Error) -> Self {
Self::IpcError(e)
}
}
pub struct UsartClient {
handle: u32,
}
impl UsartClient {
pub const fn new(handle: u32) -> Self {
Self { handle }
}
pub fn configure(&self, baud_rate: u32) -> Result<(), ClientError> {
let arg0 = (baud_rate & 0xffff) as u16;
let arg1 = (baud_rate >> 16) as u16;
self.call_no_payload(UsartOp::Configure, arg0, arg1)
}
pub fn write(&self, data: &[u8]) -> Result<usize, ClientError> {
if data.len() > MAX_PAYLOAD_SIZE {
return Err(ClientError::BufferTooSmall);
}
let mut req = [0u8; MAX_BUF_SIZE];
let mut resp = [0u8; MAX_BUF_SIZE];
let hdr = UsartRequestHeader::new(UsartOp::Write, 0, 0, data.len() as u16);
req[..UsartRequestHeader::SIZE].copy_from_slice(zerocopy::IntoBytes::as_bytes(&hdr));
req[UsartRequestHeader::SIZE..UsartRequestHeader::SIZE + data.len()].copy_from_slice(data);
let resp_len = syscall::channel_transact(
self.handle,
&req[..UsartRequestHeader::SIZE + data.len()],
&mut resp,
Instant::MAX,
)?;
parse_no_payload_response(&resp[..resp_len])?;
Ok(data.len())
}
pub fn read(&self, out: &mut [u8]) -> Result<usize, ClientError> {
self.read_with_timeout(out, Instant::MAX)
}
pub fn read_with_timeout(
&self,
out: &mut [u8],
deadline: Instant,
) -> Result<usize, ClientError> {
if out.len() > MAX_PAYLOAD_SIZE {
return Err(ClientError::BufferTooSmall);
}
let mut req = [0u8; MAX_BUF_SIZE];
let mut resp = [0u8; MAX_BUF_SIZE];
let hdr = UsartRequestHeader::new(UsartOp::Read, out.len() as u16, 0, 0);
req[..UsartRequestHeader::SIZE].copy_from_slice(zerocopy::IntoBytes::as_bytes(&hdr));
let resp_len = syscall::channel_transact(
self.handle,
&req[..UsartRequestHeader::SIZE],
&mut resp,
deadline,
)?;
parse_payload_response(&resp[..resp_len], out)
}
/// Non-blocking read.
///
/// Sends a `TryRead` request to the server. If bytes are available in the
/// hardware FIFO right now the server responds immediately and this call
/// returns `Ok(n)`. If the FIFO is empty the server arms the RX interrupt
/// and blocks the IPC transaction on the server side; this call returns
/// once the interrupt fires and data has been copied into `out`.
///
/// From the caller's perspective this function always returns with data
/// (or an error); the "non-blocking" nature means the **server** is freed
/// to handle other events while waiting rather than spinning in a hardware
/// read loop.
pub fn try_read(&self, out: &mut [u8]) -> Result<usize, ClientError> {
self.try_read_with_timeout(out, Instant::MAX)
}
pub fn try_read_with_timeout(
&self,
out: &mut [u8],
deadline: Instant,
) -> Result<usize, ClientError> {
if out.len() > MAX_PAYLOAD_SIZE {
return Err(ClientError::BufferTooSmall);
}
let mut req = [0u8; MAX_BUF_SIZE];
let mut resp = [0u8; MAX_BUF_SIZE];
let hdr = UsartRequestHeader::new(UsartOp::TryRead, out.len() as u16, 0, 0);
req[..UsartRequestHeader::SIZE].copy_from_slice(zerocopy::IntoBytes::as_bytes(&hdr));
let resp_len = syscall::channel_transact(
self.handle,
&req[..UsartRequestHeader::SIZE],
&mut resp,
deadline,
)?;
parse_payload_response(&resp[..resp_len], out)
}
fn call_no_payload(&self, op: UsartOp, arg0: u16, arg1: u16) -> Result<(), ClientError> {
let mut req = [0u8; MAX_BUF_SIZE];
let mut resp = [0u8; MAX_BUF_SIZE];
let hdr = UsartRequestHeader::new(op, arg0, arg1, 0);
req[..UsartRequestHeader::SIZE].copy_from_slice(zerocopy::IntoBytes::as_bytes(&hdr));
let resp_len = syscall::channel_transact(
self.handle,
&req[..UsartRequestHeader::SIZE],
&mut resp,
Instant::MAX,
)?;
parse_no_payload_response(&resp[..resp_len])
}
}
fn parse_no_payload_response(resp: &[u8]) -> Result<(), ClientError> {
if resp.len() < UsartResponseHeader::SIZE {
return Err(ClientError::InvalidResponse);
}
let hdr_bytes = &resp[..UsartResponseHeader::SIZE];
let Some(hdr) = zerocopy::Ref::<_, UsartResponseHeader>::from_bytes(hdr_bytes).ok() else {
return Err(ClientError::InvalidResponse);
};
if hdr.is_success() {
Ok(())
} else {
Err(ClientError::ServerError(hdr.error_code()))
}
}
fn parse_payload_response(resp: &[u8], out: &mut [u8]) -> Result<usize, ClientError> {
if resp.len() < UsartResponseHeader::SIZE {
return Err(ClientError::InvalidResponse);
}
let hdr_bytes = &resp[..UsartResponseHeader::SIZE];
let Some(hdr) = zerocopy::Ref::<_, UsartResponseHeader>::from_bytes(hdr_bytes).ok() else {
return Err(ClientError::InvalidResponse);
};
if !hdr.is_success() {
return Err(ClientError::ServerError(hdr.error_code()));
}
let len = hdr.payload_length();
if len > out.len() || resp.len() < UsartResponseHeader::SIZE + len {
return Err(ClientError::InvalidResponse);
}
out[..len].copy_from_slice(&resp[UsartResponseHeader::SIZE..UsartResponseHeader::SIZE + len]);
Ok(len)
}