mirror of
https://github.com/saymrwulf/xous-core.git
synced 2026-11-08 04:56:43 +00:00
Merge branch 'main' into rkyv-0.8-migration
This commit is contained in:
commit
67e0004f73
4 changed files with 267 additions and 39 deletions
|
|
@ -1,7 +1,7 @@
|
|||
[package]
|
||||
edition = "2021"
|
||||
name = "flatipc-derive"
|
||||
version = "0.1.0"
|
||||
version = "0.1.2"
|
||||
authors = ["Sean Cross <sean@xobs.io>"]
|
||||
description = "Custom derive for traits from the flatipc crate"
|
||||
license = "BSD-2-Clause OR Apache-2.0 OR MIT"
|
||||
|
|
@ -11,8 +11,8 @@ repository = "https://github.com/betrusted/xous-flatipc"
|
|||
proc-macro = true
|
||||
|
||||
[dependencies]
|
||||
proc-macro2 = "*"
|
||||
quote = "*"
|
||||
proc-macro2 = "1"
|
||||
quote = "1"
|
||||
syn = { version = "2", features = ["parsing", "extra-traits"] }
|
||||
|
||||
[features]
|
||||
|
|
|
|||
|
|
@ -4,12 +4,22 @@ use proc_macro::TokenStream;
|
|||
use quote::{format_ident, quote};
|
||||
use syn::{parse_macro_input, spanned::Spanned, DeriveInput};
|
||||
|
||||
fn ast_hash(ast: &syn::DeriveInput) -> u32 {
|
||||
fn ast_hash(ast: &syn::DeriveInput) -> usize {
|
||||
use std::hash::{Hash, Hasher};
|
||||
let mut hasher = std::collections::hash_map::DefaultHasher::new();
|
||||
ast.hash(&mut hasher);
|
||||
let full_hash = hasher.finish();
|
||||
((full_hash >> 32) as u32) ^ (full_hash as u32)
|
||||
|
||||
#[cfg(target_pointer_width = "64")]
|
||||
{
|
||||
full_hash as usize
|
||||
}
|
||||
#[cfg(target_pointer_width = "32")]
|
||||
{
|
||||
(((full_hash >> 32) as u32) ^ (full_hash as u32)) as usize
|
||||
}
|
||||
#[cfg(not(any(target_pointer_width = "32", target_pointer_width = "64")))]
|
||||
compile_error!("Unsupported target_pointer_width");
|
||||
}
|
||||
|
||||
#[proc_macro_derive(IpcSafe)]
|
||||
|
|
@ -52,11 +62,10 @@ fn derive_ipc_inner(ast: DeriveInput) -> Result<proc_macro2::TokenStream, proc_m
|
|||
syn::Data::Union(r#union) => generate_transmittable_checks_union(&ast, r#union)?,
|
||||
};
|
||||
|
||||
let padded_version = generate_padded_version(&ast)?;
|
||||
|
||||
let ipc_struct = generate_ipc_struct(&ast)?;
|
||||
Ok(quote! {
|
||||
#transmittable_checks
|
||||
#padded_version
|
||||
#ipc_struct
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -222,10 +231,10 @@ fn generate_transmittable_checks_union(
|
|||
})
|
||||
}
|
||||
|
||||
fn generate_padded_version(ast: &DeriveInput) -> Result<proc_macro2::TokenStream, proc_macro2::TokenStream> {
|
||||
fn generate_ipc_struct(ast: &DeriveInput) -> Result<proc_macro2::TokenStream, proc_macro2::TokenStream> {
|
||||
let visibility = ast.vis.clone();
|
||||
let ident = ast.ident.clone();
|
||||
let padded_ident = format_ident!("Ipc{}", ast.ident);
|
||||
let ipc_ident = format_ident!("Ipc{}", ast.ident);
|
||||
let ident_size = quote! { core::mem::size_of::< #ident >() };
|
||||
let padded_size = quote! { (#ident_size + (4096 - 1)) & !(4096 - 1) };
|
||||
let padding_size = quote! { #padded_size - #ident_size };
|
||||
|
|
@ -286,73 +295,101 @@ fn generate_padded_version(ast: &DeriveInput) -> Result<proc_macro2::TokenStream
|
|||
}
|
||||
};
|
||||
|
||||
let memory_messages = if cfg!(feature = "xous") {
|
||||
quote! {
|
||||
fn from_memory_message<'a>(msg: &'a xous::MemoryMessage) -> Option<&'a Self> {
|
||||
if msg.buf.len() < core::mem::size_of::< #ipc_ident >() {
|
||||
return None;
|
||||
}
|
||||
let signature = msg.offset.map(|offset| offset.get()).unwrap_or_default();
|
||||
if signature != #hash {
|
||||
return None;
|
||||
}
|
||||
unsafe { Some(&*(msg.buf.as_ptr() as *const #ipc_ident)) }
|
||||
}
|
||||
|
||||
fn from_memory_message_mut<'a>(msg: &'a mut xous::MemoryMessage) -> Option<&'a mut Self> {
|
||||
if msg.buf.len() < core::mem::size_of::< #ipc_ident >() {
|
||||
return None;
|
||||
}
|
||||
let signature = msg.offset.map(|offset| offset.get()).unwrap_or_default();
|
||||
if signature != #hash {
|
||||
return None;
|
||||
}
|
||||
unsafe { Some(&mut *(msg.buf.as_mut_ptr() as *mut #ipc_ident)) }
|
||||
}
|
||||
}
|
||||
} else {
|
||||
quote! {}
|
||||
};
|
||||
|
||||
Ok(quote! {
|
||||
#[repr(C, align(4096))]
|
||||
#visibility struct #padded_ident {
|
||||
#visibility struct #ipc_ident {
|
||||
original: #ident,
|
||||
padding: [u8; #padding_size],
|
||||
}
|
||||
|
||||
impl core::ops::Deref for #padded_ident {
|
||||
impl core::ops::Deref for #ipc_ident {
|
||||
type Target = #ident ;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.original
|
||||
}
|
||||
}
|
||||
|
||||
impl core::ops::DerefMut for #padded_ident {
|
||||
impl core::ops::DerefMut for #ipc_ident {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.original
|
||||
}
|
||||
}
|
||||
|
||||
impl flatipc::IntoIpc for #ident {
|
||||
type IpcType = #padded_ident;
|
||||
type IpcType = #ipc_ident;
|
||||
fn into_ipc(self) -> Self::IpcType {
|
||||
#padded_ident {
|
||||
#ipc_ident {
|
||||
original: self,
|
||||
padding: [0; #padding_size],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl flatipc::Ipc for #padded_ident {
|
||||
unsafe impl flatipc::Ipc for #ipc_ident {
|
||||
type Original = #ident ;
|
||||
|
||||
fn from_slice<'a>(data: &'a [u8], signature: usize) -> Option<&'a Self> {
|
||||
if data.len() < core::mem::size_of::< #padded_ident >() {
|
||||
if data.len() < core::mem::size_of::< #ipc_ident >() {
|
||||
return None;
|
||||
}
|
||||
if signature as u32 != #hash {
|
||||
if signature != #hash {
|
||||
return None;
|
||||
}
|
||||
unsafe { Some(&*(data.as_ptr() as *const u8 as *const #padded_ident)) }
|
||||
unsafe { Some(&*(data.as_ptr() as *const u8 as *const #ipc_ident)) }
|
||||
}
|
||||
|
||||
unsafe fn from_buffer_unchecked<'a>(data: &'a [u8]) -> &'a Self {
|
||||
&*(data.as_ptr() as *const u8 as *const #padded_ident)
|
||||
&*(data.as_ptr() as *const u8 as *const #ipc_ident)
|
||||
}
|
||||
|
||||
fn from_slice_mut<'a>(data: &'a mut [u8], signature: usize) -> Option<&'a mut Self> {
|
||||
if data.len() < core::mem::size_of::< #padded_ident >() {
|
||||
if data.len() < core::mem::size_of::< #ipc_ident >() {
|
||||
return None;
|
||||
}
|
||||
if signature as u32 != #hash {
|
||||
if signature != #hash {
|
||||
return None;
|
||||
}
|
||||
unsafe { Some(&mut *(data.as_mut_ptr() as *mut u8 as *mut #padded_ident)) }
|
||||
unsafe { Some(&mut *(data.as_mut_ptr() as *mut u8 as *mut #ipc_ident)) }
|
||||
}
|
||||
|
||||
unsafe fn from_buffer_mut_unchecked<'a>(data: &'a mut [u8]) -> &'a mut Self {
|
||||
unsafe { &mut *(data.as_mut_ptr() as *mut u8 as *mut #padded_ident) }
|
||||
unsafe { &mut *(data.as_mut_ptr() as *mut u8 as *mut #ipc_ident) }
|
||||
}
|
||||
|
||||
fn lend(&self, connection: flatipc::CID, opcode: usize) -> Result<(), flatipc::Error> {
|
||||
let signature = self.signature() as usize;
|
||||
let signature = self.signature();
|
||||
let data = unsafe {
|
||||
core::slice::from_raw_parts(
|
||||
self as *const #padded_ident as *const u8,
|
||||
core::mem::size_of::< #padded_ident >(),
|
||||
self as *const #ipc_ident as *const u8,
|
||||
core::mem::size_of::< #ipc_ident >(),
|
||||
)
|
||||
};
|
||||
#lend
|
||||
|
|
@ -360,11 +397,11 @@ fn generate_padded_version(ast: &DeriveInput) -> Result<proc_macro2::TokenStream
|
|||
}
|
||||
|
||||
fn try_lend(&self, connection: flatipc::CID, opcode: usize) -> Result<(), flatipc::Error> {
|
||||
let signature = self.signature() as usize;
|
||||
let signature = self.signature();
|
||||
let data = unsafe {
|
||||
core::slice::from_raw_parts(
|
||||
self as *const #padded_ident as *const u8,
|
||||
core::mem::size_of::< #padded_ident >(),
|
||||
self as *const #ipc_ident as *const u8,
|
||||
core::mem::size_of::< #ipc_ident >(),
|
||||
)
|
||||
};
|
||||
#try_lend
|
||||
|
|
@ -372,10 +409,10 @@ fn generate_padded_version(ast: &DeriveInput) -> Result<proc_macro2::TokenStream
|
|||
}
|
||||
|
||||
fn lend_mut(&mut self, connection: flatipc::CID, opcode: usize) -> Result<(), flatipc::Error> {
|
||||
let signature = self.signature() as usize;
|
||||
let signature = self.signature();
|
||||
let mut data = unsafe {
|
||||
core::slice::from_raw_parts_mut(
|
||||
self as *mut #padded_ident as *mut u8,
|
||||
self as *mut #ipc_ident as *mut u8,
|
||||
#padded_size,
|
||||
)
|
||||
};
|
||||
|
|
@ -384,10 +421,10 @@ fn generate_padded_version(ast: &DeriveInput) -> Result<proc_macro2::TokenStream
|
|||
}
|
||||
|
||||
fn try_lend_mut(&mut self, connection: flatipc::CID, opcode: usize) -> Result<(), flatipc::Error> {
|
||||
let signature = self.signature() as usize;
|
||||
let signature = self.signature();
|
||||
let mut data = unsafe {
|
||||
core::slice::from_raw_parts_mut(
|
||||
self as *mut #padded_ident as *mut u8,
|
||||
self as *mut #ipc_ident as *mut u8,
|
||||
#padded_size,
|
||||
)
|
||||
};
|
||||
|
|
@ -407,9 +444,11 @@ fn generate_padded_version(ast: &DeriveInput) -> Result<proc_macro2::TokenStream
|
|||
self.original
|
||||
}
|
||||
|
||||
fn signature(&self) -> u32 {
|
||||
fn signature(&self) -> usize {
|
||||
#hash
|
||||
}
|
||||
|
||||
#memory_messages
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,17 +1,129 @@
|
|||
//! Xous supports sending Messages from Clients to Servers. If a message is
|
||||
//! a `MemoryMessage`, then the Server may respond by updating the buffer
|
||||
//! with a response message and returning the buffer.
|
||||
//!
|
||||
//! A number of serialization options are available, ranging from sending
|
||||
//! raw arrays of bytes all the way to sending full Protobuf messages.
|
||||
//!
|
||||
//! This crate takes a middle road and allows for sending rich Rust types
|
||||
//! such as full enums and structs without doing extensive checks. This is
|
||||
//! based on the theory that in normal operating systems, ABIs do not contain
|
||||
//! any sort of verification, and it is undefined behaviour to send a malformed
|
||||
//! request to a loaded module.
|
||||
//!
|
||||
//! An object can be made into an IPC object by implementing the `Ipc` trait.
|
||||
//! The primary method of doing this is by adding `#[derive(flatipc::Ipc)]`
|
||||
//! to the definition. Such an object may be included in both the Client and
|
||||
//! the Server so that they share the same view of the object.
|
||||
//!
|
||||
//! Any object may be made into an IPC object provided it follows the following
|
||||
//! requirements:
|
||||
//!
|
||||
//! - **The object is `#[repr(C)]`** - This is required to ensure the objecty
|
||||
//! has a well-defined layout in memory. Other representations may shift
|
||||
//! depending on optimizations.
|
||||
//! - **The object only contains fields that are `IpcSafe`** - This trait is
|
||||
//! implemented on primitive types that are able to be sent across an IPC
|
||||
//! boundary. This includes all integers, floats, and booleans. It also
|
||||
//! includes arrays of `IpcSafe` types, `Option<T>` where `T` is `IpcSafe`,
|
||||
//! and `Result<T, E>` where `T` and `E` are `IpcSafe`. Pointers and
|
||||
//! references are not `IpcSafe` and may not be used.
|
||||
//!
|
||||
//! When deriving `Ipc`, a new type will be created with the same name as
|
||||
//! the original type prefixed with `Ipc`. For example, if you derive `Ipc`
|
||||
//! on a type named `Foo`, the new type will be named `IpcFoo`.
|
||||
//!
|
||||
//! Objects that implement `Ipc` must be page-aligned and must be a full multiple
|
||||
//! of a page size in length. This is to ensure that the object can be mapped
|
||||
//! transparently between the Client and the Server without dragging any extra
|
||||
//! memory along with it.
|
||||
//!
|
||||
//! `Ipc` objects implement `Deref` and `DerefMut` to the original object, so
|
||||
//! they can be used in place of the original object in most cases by derefencing
|
||||
//! the `Ipc` object.
|
||||
//!
|
||||
//! Because `String` and `Vec` contain pointers, they are not `IpcSafe`. As such,
|
||||
//! replacements are made available in this crate that are `IpcSafe`.
|
||||
//!
|
||||
//! <br>
|
||||
//!
|
||||
//! # Example of a Derived Ipc Object
|
||||
//!
|
||||
//! ```ignore
|
||||
//! #[repr(C)]
|
||||
//! #[derive(flatipc::Ipc)]
|
||||
//! pub struct Foo {
|
||||
//! a: u32,
|
||||
//! b: u64,
|
||||
//! }
|
||||
//!
|
||||
//! // Connect to an example server
|
||||
//! let conn = xous::connect(xous::SID::from_bytes(b"example---server").unwrap())?;
|
||||
//!
|
||||
//! // Construct our object
|
||||
//! let foo = Foo { a: 1, b: 1234567890 };
|
||||
//!
|
||||
//! // Create an IPC representation of the original object, consuming
|
||||
//! // the original object in the process.
|
||||
//! let mut foo_ipc = foo.into_ipc();
|
||||
//!
|
||||
//! // Lend the object to the server with opcode 42.
|
||||
//! foo_ipc.lend(conn, 42)?;
|
||||
//!
|
||||
//! // Note that we can still access the original object.
|
||||
//! println!("a: {}", foo_ipc.a);
|
||||
//!
|
||||
//! // When we're done with the IPC object, we can get the original object back.
|
||||
//! let foo = foo_ipc.into_original();
|
||||
//! ```
|
||||
//!
|
||||
//! # Example of Server Usage
|
||||
//!
|
||||
//! ```ignore
|
||||
//! // Ideally this comes from a common `API` file that both the
|
||||
//! // client and server share.
|
||||
//! #[repr(C)]
|
||||
//! #[derive(flatipc::Ipc)]
|
||||
//! pub struct Foo {
|
||||
//! a: u32,
|
||||
//! b: u64,
|
||||
//! }
|
||||
//!
|
||||
//! let mut msg_opt = None;
|
||||
//! let mut server = xous::create_server_with_sid(b"example---server").unwrap();
|
||||
//! loop {
|
||||
//! let envelope = xous::reply_and_receive_next(server, &mut msg_opt).unwrap();
|
||||
//! let Some(msg) = msg_opt else { continue };
|
||||
//!
|
||||
//! // Take the memory portion of the message, continuing if it's not a memory message.
|
||||
//! let Some(msg_memory) = msg.memory_message() else { continue };
|
||||
//!
|
||||
//! // Turn the `MemoryMessage` into an `IpcFoo` object. Note that this is the `Ipc`-prefixed
|
||||
//! // version of the original object. If the object is not the correct type, `None` will be
|
||||
//! // returned and the message will be returned to the sender in ghe next loop.
|
||||
//! let Some(foo) = IpcFoo::from_memory_message(msg_slice, signature) else { continue };
|
||||
//!
|
||||
//! // Do something with the object.
|
||||
//! }
|
||||
//! ```
|
||||
|
||||
/// An object is Sendable if it is guaranteed to be flat and contains no pointers.
|
||||
/// This trait can be placed on objects that have invalid representations such as
|
||||
/// bools (which can only be 0 or 1) but it is up to the implementer to ensure that
|
||||
/// the correct object arrives on the other side.
|
||||
pub unsafe trait IpcSafe {}
|
||||
|
||||
// Enable calling this crate as `flatipc` in tests.
|
||||
extern crate self as flatipc;
|
||||
|
||||
// Allow doing `#[derive(flatipc::Ipc)]` instead of `#[derive(flatipc_derive::Ipc)]`
|
||||
pub use flatipc_derive::{Ipc, IpcSafe};
|
||||
#[cfg(feature = "xous")]
|
||||
mod backend {
|
||||
pub use xous::Error;
|
||||
pub use xous::CID;
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "xous"))]
|
||||
mod backend {
|
||||
pub mod mock;
|
||||
|
|
@ -56,8 +168,12 @@ where
|
|||
{
|
||||
}
|
||||
|
||||
pub trait Ipc {
|
||||
/// What this memory message is a representation of.
|
||||
/// An object that can be sent across an IPC boundary, and can be reconstituted
|
||||
/// on the other side without copying. An object with this trait must be page-aligned,
|
||||
/// must be a multiple of the page size in length, and must not contain any pointers.
|
||||
pub unsafe trait Ipc {
|
||||
/// What this memory message is a representation of. This is used to turn
|
||||
/// this object back into the original object.
|
||||
type Original;
|
||||
|
||||
/// Create an Ipc variant from the original object. Succeeds only if
|
||||
|
|
@ -87,7 +203,8 @@ pub trait Ipc {
|
|||
/// Consume the memory version and return the original object.
|
||||
fn into_original(self) -> Self::Original;
|
||||
|
||||
/// Lend the buffer to the specified server.
|
||||
/// Lend the buffer to the specified server. The connection should already be
|
||||
/// open and the server should be ready to receive the buffer.
|
||||
fn lend(&self, connection: CID, opcode: usize) -> Result<(), backend::Error>;
|
||||
|
||||
/// Try to lend the buffer to the specified server, returning an error
|
||||
|
|
@ -104,9 +221,23 @@ pub trait Ipc {
|
|||
|
||||
/// Return the signature of this memory message. Useful for verifying
|
||||
/// that the correct message is being received.
|
||||
fn signature(&self) -> u32;
|
||||
fn signature(&self) -> usize;
|
||||
|
||||
#[cfg(feature = "xous")]
|
||||
/// Build an `Ipc` object from a `xous::MemoryMessage`. Verifies the signature and
|
||||
/// returns `None` if there is no match.
|
||||
fn from_memory_message<'a>(msg: &'a xous::MemoryMessage) -> Option<&'a Self>;
|
||||
|
||||
#[cfg(feature = "xous")]
|
||||
/// Build a mutable `Ipc` object from a mutable `xous::MemoryMessage`. Verifies the
|
||||
/// signature and returns `None` if there is no match. The returned object has a
|
||||
/// lifetime that's tied to the `MemoryMessage`.
|
||||
fn from_memory_message_mut<'a>(msg: &'a mut xous::MemoryMessage) -> Option<&'a mut Self>;
|
||||
}
|
||||
|
||||
/// Objects that have `IntoIpc` may be turned into an object that can be passed
|
||||
/// across an IPC barrier. This consumes the object and returns a new object that
|
||||
/// may be dereferenced to the original object.
|
||||
pub trait IntoIpc {
|
||||
type IpcType;
|
||||
fn into_ipc(self) -> Self::IpcType;
|
||||
|
|
|
|||
58
libs/flatipc/src/test/mock.rs
Normal file
58
libs/flatipc/src/test/mock.rs
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
use std::sync::{LazyLock, Mutex};
|
||||
|
||||
pub struct Server {
|
||||
lend: Box<dyn Send + Fn(usize, usize, usize, &[u8]) -> (usize, usize)>,
|
||||
lend_mut: Box<dyn Send + Fn(usize, usize, usize, &mut [u8]) -> (usize, usize)>,
|
||||
}
|
||||
|
||||
impl Server {
|
||||
pub fn new(
|
||||
lend: Box<dyn Send + Fn(usize, usize, usize, &[u8]) -> (usize, usize)>,
|
||||
lend_mut: Box<dyn Send + Fn(usize, usize, usize, &mut [u8]) -> (usize, usize)>,
|
||||
) -> Self {
|
||||
Server { lend, lend_mut }
|
||||
}
|
||||
}
|
||||
|
||||
pub struct IpcMachine {
|
||||
servers: Vec<Server>,
|
||||
}
|
||||
|
||||
pub(crate) static IPC_MACHINE: LazyLock<Mutex<IpcMachine>> =
|
||||
LazyLock::new(|| Mutex::new(IpcMachine::new()));
|
||||
|
||||
impl IpcMachine {
|
||||
fn new() -> Self {
|
||||
IpcMachine {
|
||||
servers: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_server(&mut self, server: Server) -> usize {
|
||||
let server_id = self.servers.len();
|
||||
self.servers.push(server);
|
||||
server_id
|
||||
}
|
||||
|
||||
pub fn lend(
|
||||
&self,
|
||||
server_id: usize,
|
||||
opcode: usize,
|
||||
a: usize,
|
||||
b: usize,
|
||||
data: &[u8],
|
||||
) -> (usize, usize) {
|
||||
(self.servers[server_id].lend)(opcode, a, b, data)
|
||||
}
|
||||
|
||||
pub fn lend_mut(
|
||||
&self,
|
||||
server_id: usize,
|
||||
opcode: usize,
|
||||
a: usize,
|
||||
b: usize,
|
||||
data: &mut [u8],
|
||||
) -> (usize, usize) {
|
||||
(self.servers[server_id].lend_mut)(opcode, a, b, data)
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue