mirror of
https://github.com/saymrwulf/xous-core.git
synced 2026-11-15 08:30:02 +00:00
xous-ipc: add an initial implementation
This adds a first draft of `xous-ipc`, which specializes the IPC features of Xous. Signed-off-by: Sean Cross <sean@xobs.io>
This commit is contained in:
parent
8c947377d7
commit
7bbcd951fe
4 changed files with 553 additions and 0 deletions
13
xous-ipc/Cargo.toml
Normal file
13
xous-ipc/Cargo.toml
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
[package]
|
||||
authors = ["Sean Cross <sean@xobs.io>"]
|
||||
description = "System call interface for Xous"
|
||||
edition = "2018"
|
||||
license = "MIT OR Apache-2.0"
|
||||
name = "xous-ipc"
|
||||
version = "0.4.0"
|
||||
|
||||
[dependencies]
|
||||
bitflags = {version = "1"}
|
||||
# log = {version = "0.4"}
|
||||
rkyv = {version = "0.4.3", features = ["const_generics"], default-features = false}
|
||||
xous = {path = "../xous-rs"}
|
||||
226
xous-ipc/src/buffer.rs
Normal file
226
xous-ipc/src/buffer.rs
Normal file
|
|
@ -0,0 +1,226 @@
|
|||
use rkyv::{ser::Serializer, Fallible};
|
||||
use xous::{
|
||||
map_memory, send_message, unmap_memory, Error, MemoryAddress, MemoryFlags, MemoryMessage,
|
||||
MemoryRange, MemorySize, Message, Result, CID,
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Buffer<'a> {
|
||||
range: MemoryRange,
|
||||
valid: MemoryRange,
|
||||
offset: Option<MemoryAddress>,
|
||||
slice: &'a mut [u8],
|
||||
should_drop: bool,
|
||||
memory_message: Option<&'a mut MemoryMessage>,
|
||||
}
|
||||
|
||||
pub struct XousDeserializer;
|
||||
|
||||
// Unreachable enum pattern, swap out for the never type (!) whenever that gets stabilized
|
||||
#[derive(Debug)]
|
||||
pub enum XousUnreachable {}
|
||||
|
||||
impl rkyv::Fallible for XousDeserializer {
|
||||
type Error = XousUnreachable;
|
||||
}
|
||||
|
||||
impl<'a> Buffer<'a> {
|
||||
#[allow(dead_code)]
|
||||
pub fn new(len: usize) -> Self {
|
||||
let remainder = if ((len & 0xFFF) == 0) && (len > 0) {
|
||||
0
|
||||
} else {
|
||||
0x1000 - (len & 0xFFF)
|
||||
};
|
||||
|
||||
let flags = MemoryFlags::R | MemoryFlags::W;
|
||||
|
||||
// Allocate enough memory to hold the requested data
|
||||
let new_mem = map_memory(
|
||||
None,
|
||||
None,
|
||||
// Ensure our byte size is a multiple of 4096
|
||||
len + remainder,
|
||||
flags,
|
||||
)
|
||||
.expect("Buffer: error in new()/map_memory");
|
||||
|
||||
let mut valid = new_mem;
|
||||
valid.size = MemorySize::new(len + remainder).unwrap();
|
||||
Buffer {
|
||||
range: new_mem,
|
||||
slice: unsafe {
|
||||
core::slice::from_raw_parts_mut(new_mem.as_mut_ptr(), len + remainder)
|
||||
},
|
||||
valid,
|
||||
offset: None,
|
||||
should_drop: true,
|
||||
memory_message: None,
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub unsafe fn from_memory_message(mem: &'a MemoryMessage) -> Self {
|
||||
Buffer {
|
||||
range: mem.buf,
|
||||
slice: core::slice::from_raw_parts_mut(mem.buf.as_mut_ptr(), mem.buf.len()),
|
||||
valid: mem.buf,
|
||||
offset: mem.offset,
|
||||
should_drop: false,
|
||||
memory_message: None,
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub unsafe fn from_memory_message_mut(mem: &'a mut MemoryMessage) -> Self {
|
||||
Buffer {
|
||||
range: mem.buf,
|
||||
slice: core::slice::from_raw_parts_mut(mem.buf.as_mut_ptr(), mem.buf.len()),
|
||||
valid: mem.buf,
|
||||
offset: mem.offset,
|
||||
should_drop: false,
|
||||
memory_message: Some(mem),
|
||||
}
|
||||
}
|
||||
|
||||
/// Perform a mutable lend of this Buffer to the server.
|
||||
#[allow(dead_code)]
|
||||
pub fn lend_mut(&mut self, connection: CID, id: u32) -> core::result::Result<Result, Error> {
|
||||
let msg = MemoryMessage {
|
||||
id: id as usize,
|
||||
buf: self.valid,
|
||||
offset: self.offset,
|
||||
valid: MemorySize::new(self.slice.len()),
|
||||
};
|
||||
|
||||
// Update the offset pointer if the server modified it.
|
||||
let result = send_message(connection, Message::MutableBorrow(msg));
|
||||
if let Ok(Result::MemoryReturned(offset, _valid)) = result {
|
||||
self.offset = offset;
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn lend(&self, connection: CID, id: u32) -> core::result::Result<Result, Error> {
|
||||
let msg = MemoryMessage {
|
||||
id: id as usize,
|
||||
buf: self.valid,
|
||||
offset: self.offset,
|
||||
valid: MemorySize::new(self.slice.len()),
|
||||
};
|
||||
send_message(connection, Message::Borrow(msg))
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn send(mut self, connection: CID, id: u32) -> core::result::Result<Result, Error> {
|
||||
let msg = MemoryMessage {
|
||||
id: id as usize,
|
||||
buf: self.valid,
|
||||
offset: self.offset,
|
||||
valid: MemorySize::new(self.slice.len()),
|
||||
};
|
||||
let result = send_message(connection, Message::Move(msg))?;
|
||||
|
||||
// prevents it from being Dropped.
|
||||
self.should_drop = false;
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn try_from<S>(src: S) -> core::result::Result<Self, ()>
|
||||
where
|
||||
S: rkyv::Serialize<rkyv::ser::serializers::BufferSerializer<Buffer<'a>>>,
|
||||
{
|
||||
let buf = Self::new(4096);
|
||||
let mut ser = rkyv::ser::serializers::BufferSerializer::new(buf);
|
||||
let pos = ser.serialize_value(&src).or(Err(()))?;
|
||||
let mut buf = ser.into_inner();
|
||||
buf.offset = MemoryAddress::new(pos);
|
||||
Ok(buf)
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn serialize_from<S>(&mut self, src: S) -> core::result::Result<(), &'static str>
|
||||
where
|
||||
S: rkyv::Serialize<rkyv::ser::serializers::BufferSerializer<&'a mut [u8]>>,
|
||||
{
|
||||
// We must have a `memory_message` to update in order for this to work.
|
||||
// Otherwise, we risk having the pointer go to somewhere invalid.
|
||||
if self.memory_message.is_none() {
|
||||
// Create this message using `from_memory_message_mut()` instead of
|
||||
// `from_memory_message()`.
|
||||
Err("couldn't serialize because buffer wasn't mutable")?;
|
||||
}
|
||||
// Unsafe Warning: Create a copy of the backing slice to hand to the deserializer.
|
||||
// This is required because the deserializer consumes the buffer and returns it
|
||||
// later as part of `.into_inner()`.
|
||||
// The "correct" way to do this would be to implement `rkyv::Serializer` an `rkyv::Fallible`
|
||||
// for ourselves.
|
||||
let copied_slice =
|
||||
unsafe { core::slice::from_raw_parts_mut(self.slice.as_mut_ptr(), self.slice.len()) };
|
||||
let mut ser = rkyv::ser::serializers::BufferSerializer::new(copied_slice);
|
||||
let pos = ser.serialize_value(&src).or(Err("couldn't serialize"))?;
|
||||
self.offset = MemoryAddress::new(pos);
|
||||
if let Some(ref mut msg) = self.memory_message.as_mut() {
|
||||
msg.offset = MemoryAddress::new(pos);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn try_into<T, U>(&self) -> core::result::Result<&U, ()>
|
||||
where
|
||||
T: rkyv::Archive<Archived = U>,
|
||||
{
|
||||
let pos = self.offset.map(|o| o.get()).unwrap_or_default();
|
||||
let r = unsafe { rkyv::archived_value::<T>(self.slice, pos) };
|
||||
Ok(r)
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn deserialize<T, U>(&self) -> core::result::Result<T, ()>
|
||||
where
|
||||
T: rkyv::Archive<Archived = U>,
|
||||
U: rkyv::Deserialize<T, dyn Fallible<Error = XousUnreachable>>,
|
||||
{
|
||||
let pos = self.offset.map(|o| o.get()).unwrap_or_default();
|
||||
let r = unsafe { rkyv::archived_value::<T>(self.slice, pos) };
|
||||
Ok(r.deserialize(&mut XousDeserializer {}).unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> core::convert::AsRef<[u8]> for Buffer<'a> {
|
||||
fn as_ref(&self) -> &[u8] {
|
||||
self.slice
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> core::convert::AsMut<[u8]> for Buffer<'a> {
|
||||
fn as_mut(&mut self) -> &mut [u8] {
|
||||
self.slice
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> core::ops::Deref for Buffer<'a> {
|
||||
type Target = [u8];
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&*self.slice
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> core::ops::DerefMut for Buffer<'a> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut *self.slice
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Drop for Buffer<'a> {
|
||||
fn drop(&mut self) {
|
||||
if self.should_drop {
|
||||
unmap_memory(self.range).expect("Buffer: failed to drop memory");
|
||||
}
|
||||
}
|
||||
}
|
||||
8
xous-ipc/src/lib.rs
Normal file
8
xous-ipc/src/lib.rs
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
#![cfg_attr(target_os = "none", no_std)]
|
||||
|
||||
mod buffer;
|
||||
pub use buffer::*;
|
||||
pub use buffer::XousDeserializer;
|
||||
|
||||
mod string;
|
||||
pub use string::*;
|
||||
306
xous-ipc/src/string.rs
Normal file
306
xous-ipc/src/string.rs
Normal file
|
|
@ -0,0 +1,306 @@
|
|||
use xous::{Error, MemoryMessage, Result, CID};
|
||||
|
||||
use core::pin::Pin;
|
||||
use rkyv::ser::Serializer;
|
||||
use rkyv::archived_value;
|
||||
use rkyv::ArchiveUnsized;
|
||||
use rkyv::SerializeUnsized;
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct String<const N: usize> {
|
||||
bytes: [u8; N],
|
||||
len: u32, // length in bytes, not characters
|
||||
}
|
||||
|
||||
impl<const N: usize> String<N> {
|
||||
pub fn new() -> String<N> {
|
||||
String {
|
||||
bytes: [0; N],
|
||||
len: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_str<T>(src: T) -> String<N> where T: AsRef<str> {
|
||||
let src = src.as_ref();
|
||||
let mut s = Self::new();
|
||||
// Copy the string into our backing store.
|
||||
for (&src_byte, dest_byte) in src.as_bytes().iter().zip(&mut s.bytes) {
|
||||
*dest_byte = src_byte;
|
||||
}
|
||||
// Set the string length to the length of the passed-in String,
|
||||
// or the maximum possible length. Which ever is smaller.
|
||||
s.len = s.bytes.len().min(src.as_bytes().len()) as u32;
|
||||
|
||||
// If the string is not valid, set its length to 0.
|
||||
if s.as_str().is_err() {
|
||||
s.len = 0;
|
||||
}
|
||||
|
||||
s
|
||||
}
|
||||
|
||||
pub fn as_bytes(&self) -> [u8; N] {
|
||||
self.bytes
|
||||
}
|
||||
|
||||
pub fn as_str(&self) -> core::result::Result<&str, core::str::Utf8Error> {
|
||||
core::str::from_utf8(&self.bytes[0..self.len as usize])
|
||||
}
|
||||
|
||||
pub fn len(&self) -> usize {
|
||||
self.len as usize
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.len == 0
|
||||
}
|
||||
|
||||
/// Convert a `MemoryMessage` into a `String`
|
||||
pub fn from_message(
|
||||
message: &mut MemoryMessage,
|
||||
) -> core::result::Result<String<N>, core::str::Utf8Error> {
|
||||
let buf = unsafe { crate::Buffer::from_memory_message(message) };
|
||||
let bytes = Pin::new(buf.as_ref());
|
||||
let value = unsafe { archived_value::<String<N>>(&bytes, message.id as usize) };
|
||||
let s = String::<N>::from_str(value.as_str());
|
||||
Ok(s)
|
||||
}
|
||||
|
||||
/// Perform an immutable lend of this String to the specified server.
|
||||
/// This function will block until the server returns.
|
||||
/// Note that this convenience should only be used if the server only ever
|
||||
/// expects to deal with one type of String, ever. Otherwise, this should be
|
||||
/// implemented in the API and wrapped in an Enum to help decorate the functional
|
||||
/// target of the string. An example of a server that uses this convencience function
|
||||
/// is the logger.
|
||||
pub fn lend(
|
||||
&self,
|
||||
connection: CID,
|
||||
// id: crate::MessageId,
|
||||
) -> core::result::Result<Result, Error> {
|
||||
let mut writer = rkyv::ser::serializers::BufferSerializer::new(crate::Buffer::new(N));
|
||||
let pos = writer
|
||||
.serialize_value(self)
|
||||
.expect("xous::String -- couldn't archive self");
|
||||
let xous_buffer = writer.into_inner();
|
||||
|
||||
// note that "id" is actually used as the position into the rkyv buffer
|
||||
xous_buffer.lend(connection, pos as u32)
|
||||
}
|
||||
|
||||
/// Move this string from the client into the server.
|
||||
pub fn send(
|
||||
self,
|
||||
connection: CID,
|
||||
// id: crate::MessageId,
|
||||
) -> core::result::Result<Result, Error> {
|
||||
let mut writer = rkyv::ser::serializers::BufferSerializer::new(crate::Buffer::new(N));
|
||||
let pos = writer
|
||||
.serialize_value(&self)
|
||||
.expect("xous::String -- couldn't archive self");
|
||||
let xous_buffer = writer.into_inner();
|
||||
|
||||
xous_buffer.send(connection, pos as u32)
|
||||
}
|
||||
|
||||
/// Clear the contents of this String and set the length to 0
|
||||
pub fn clear(&mut self) {
|
||||
self.len = 0;
|
||||
self.bytes = [0; N];
|
||||
}
|
||||
|
||||
pub fn to_str(&self) -> &str {
|
||||
unsafe { core::str::from_utf8_unchecked(&self.bytes[0..self.len()]) }
|
||||
}
|
||||
|
||||
/// awful, O(N) implementation because we have to iterate through the entire string
|
||||
/// and decode variable-length utf8 characters, until we can't.
|
||||
pub fn pop(&mut self) -> Option<char> {
|
||||
if self.len() < 1 {
|
||||
return None;
|
||||
}
|
||||
// first, make a copy of the string
|
||||
let tempbytes: [u8; N] = self.bytes;
|
||||
let tempstr = unsafe { core::str::from_utf8_unchecked(&tempbytes[0..self.len()]) }.clone();
|
||||
// clear our own string
|
||||
self.len = 0;
|
||||
self.bytes = [0; N];
|
||||
|
||||
// now copy over character by character, until just before the last character
|
||||
let mut char_iter = tempstr.chars();
|
||||
let mut maybe_c = char_iter.next();
|
||||
loop {
|
||||
match maybe_c {
|
||||
Some(c) => {
|
||||
let next_c = char_iter.next();
|
||||
match next_c {
|
||||
Some(_thing) => {
|
||||
self.push(c).unwrap(); // always succeeds because we're re-encoding our string
|
||||
maybe_c = next_c;
|
||||
}
|
||||
None => return next_c,
|
||||
}
|
||||
}
|
||||
None => {
|
||||
return None; // we should actually never get here because len() == 0 case already covered
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn push(&mut self, ch: char) -> core::result::Result<usize, Error> {
|
||||
match ch.len_utf8() {
|
||||
1 => {
|
||||
if self.len() < self.bytes.len() {
|
||||
self.bytes[self.len()] = ch as u8;
|
||||
self.len += 1;
|
||||
Ok(1)
|
||||
} else {
|
||||
Err(Error::OutOfMemory)
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
let mut bytes: usize = 0;
|
||||
let mut data: [u8; 4] = [0; 4];
|
||||
let subslice = ch.encode_utf8(&mut data);
|
||||
if self.len() + subslice.len() < self.bytes.len() {
|
||||
for c in subslice.bytes() {
|
||||
self.bytes[self.len()] = c;
|
||||
self.len += 1;
|
||||
bytes += 1;
|
||||
}
|
||||
Ok(bytes)
|
||||
} else {
|
||||
Err(Error::OutOfMemory)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn append(&mut self, s: &str) -> core::result::Result<usize, Error> {
|
||||
let mut bytes_added = 0;
|
||||
for ch in s.chars() {
|
||||
if let Ok(bytes) = self.push(ch) {
|
||||
bytes_added += bytes;
|
||||
} else {
|
||||
return Err(Error::OutOfMemory);
|
||||
}
|
||||
}
|
||||
Ok(bytes_added)
|
||||
}
|
||||
|
||||
pub fn push_byte(&mut self, b: u8) -> core::result::Result<(), Error> {
|
||||
if self.len() < self.bytes.len() {
|
||||
self.bytes[self.len()] = b;
|
||||
self.len += 1;
|
||||
Ok(())
|
||||
} else {
|
||||
Err(Error::OutOfMemory)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize> core::fmt::Display for String<N> {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
write!(f, "{}", self.to_str())
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize> core::fmt::Write for String<N> {
|
||||
fn write_str(&mut self, s: &str) -> core::result::Result<(), core::fmt::Error> {
|
||||
for c in s.bytes() {
|
||||
if self.len() < self.bytes.len() {
|
||||
self.bytes[self.len()] = c;
|
||||
self.len += 1;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize> core::fmt::Debug for String<N> {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
write!(f, "{}", self.to_str())
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize> core::convert::AsRef<str> for String<N> {
|
||||
fn as_ref(&self) -> &str {
|
||||
self.to_str()
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize> PartialEq for String<N> {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.bytes[..self.len as usize] == other.bytes[..other.len as usize]
|
||||
&& self.len == other.len
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize> Eq for String<N> {}
|
||||
|
||||
#[repr(C)]
|
||||
pub struct ArchivedString {
|
||||
ptr: rkyv::RelPtr<str>,
|
||||
}
|
||||
|
||||
impl ArchivedString {
|
||||
// Provide a `str` view of an `ArchivedString`.
|
||||
pub fn as_str(&self) -> &str {
|
||||
unsafe {
|
||||
// The as_ptr() function of RelPtr will get a pointer
|
||||
// to its memory.
|
||||
&*self.ptr.as_ptr()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct StringResolver {
|
||||
bytes_pos: usize,
|
||||
_metadata_resolver: rkyv::MetadataResolver<str>,
|
||||
}
|
||||
|
||||
/// Turn a `String` into an archived object
|
||||
impl<const N: usize> rkyv::Archive for String<N> {
|
||||
type Archived = ArchivedString;
|
||||
type Resolver = StringResolver;
|
||||
|
||||
fn resolve(&self, pos: usize, resolver: Self::Resolver) -> Self::Archived {
|
||||
Self::Archived {
|
||||
ptr: unsafe {
|
||||
self.as_str().unwrap().resolve_unsized(
|
||||
pos + rkyv::offset_of!(Self::Archived, ptr),
|
||||
resolver.bytes_pos,
|
||||
(),
|
||||
)
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Turn a stream of bytes into an `ArchivedString`.
|
||||
impl<S: rkyv::ser::Serializer + ?Sized, const N: usize> rkyv::Serialize<S> for String<N> {
|
||||
fn serialize(&self, serializer: &mut S) -> core::result::Result<Self::Resolver, S::Error> {
|
||||
// This is where we want to write the bytes of our string and return
|
||||
// a resolver that knows where those bytes were written.
|
||||
// We also need to serialize the metadata for our str.
|
||||
Ok(StringResolver {
|
||||
bytes_pos: self.as_str().unwrap().serialize_unsized(serializer)?,
|
||||
_metadata_resolver: self.as_str().unwrap().serialize_metadata(serializer)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
// Turn an `ArchivedString` back into a String
|
||||
use rkyv::Fallible;
|
||||
impl<D: Fallible + ?Sized, const N: usize> rkyv::Deserialize<String<N>, D> for ArchivedString {
|
||||
fn deserialize(&self, _deserializer: &mut D) -> core::result::Result<String<N>, D::Error> {
|
||||
Ok(String::<N>::from_str(self.as_str()))
|
||||
}
|
||||
}
|
||||
|
||||
// a "no-op" deserializer for in-place data, required for rkyv 0.4.1
|
||||
// perhaps in rkyv 0.5 this will go away, see https://github.com/djkoloski/rkyv/issues/67
|
||||
pub struct XousDeserializer;
|
||||
impl rkyv::Fallible for XousDeserializer {
|
||||
type Error = xous::Error;
|
||||
}
|
||||
Loading…
Reference in a new issue