std: xous: add services support

Xous has a concept of `services` that provide various features.
Processes may connect to these services by name or by address. Most
services require a name server in order to connect.

Add a file with the most common services, and provide a way to connect
to a service by querying the name server.

Signed-off-by: Sean Cross <sean@xobs.io>
This commit is contained in:
Sean Cross 2022-11-07 11:06:38 +08:00
parent dfff5bf62f
commit 10dad67f89
5 changed files with 264 additions and 0 deletions

View File

@ -3,6 +3,9 @@
pub mod ffi;
#[stable(feature = "rust1", since = "1.0.0")]
pub mod services;
/// A prelude for conveniently writing platform-specific code.
///
/// Includes all extension traits, and some important type definitions.

View File

@ -0,0 +1,132 @@
use crate::os::xous::ffi::Connection;
use core::sync::atomic::{AtomicU32, Ordering};
mod log;
pub(crate) use log::*;
mod systime;
pub(crate) use systime::*;
mod ticktimer;
pub(crate) use ticktimer::*;
mod ns {
const NAME_MAX_LENGTH: usize = 64;
use crate::os::xous::ffi::{lend_mut, Connection};
// By making this repr(C), the layout of this struct becomes well-defined
// and no longer shifts around.
// By marking it as `align(4096)` we define that it will be page-aligned,
// meaning it can be sent between processes. We make sure to pad out the
// entire struct so that memory isn't leaked to the name server.
#[repr(C, align(4096))]
struct ConnectRequest {
data: [u8; 4096],
}
impl ConnectRequest {
pub fn new(name: &str) -> Self {
let mut cr = ConnectRequest { data: [0u8; 4096] };
let name_bytes = name.as_bytes();
// Copy the string into our backing store.
for (&src_byte, dest_byte) in name_bytes.iter().zip(&mut cr.data[0..NAME_MAX_LENGTH]) {
*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.
for (&src_byte, dest_byte) in (name.len().min(NAME_MAX_LENGTH) as u32)
.to_le_bytes()
.iter()
.zip(&mut cr.data[NAME_MAX_LENGTH..])
{
*dest_byte = src_byte;
}
cr
}
}
pub fn connect_with_name_impl(name: &str, blocking: bool) -> Option<Connection> {
let mut request = ConnectRequest::new(name);
let opcode = if blocking {
6 /* BlockingConnect */
} else {
7 /* TryConnect */
};
let cid = if blocking { super::name_server() } else { super::try_name_server()? };
lend_mut(cid, opcode, &mut request.data, 0, name.len().min(NAME_MAX_LENGTH))
.expect("unable to perform lookup");
// Read the result code back from the nameserver
let result = u32::from_le_bytes(request.data[0..4].try_into().unwrap());
if result == 0 {
// If the result was successful, then the CID is stored in the next 4 bytes
Some(u32::from_le_bytes(request.data[4..8].try_into().unwrap()).into())
} else {
None
}
}
pub fn connect_with_name(name: &str) -> Option<Connection> {
connect_with_name_impl(name, true)
}
pub fn try_connect_with_name(name: &str) -> Option<Connection> {
connect_with_name_impl(name, false)
}
}
/// Attempt to connect to a server by name. If the server does not exist, this will
/// block until the server is created.
///
/// Note that this is different from connecting to a server by address. Server
/// addresses are always 16 bytes long, whereas server names are arbitrary-length
/// strings up to 64 bytes in length.
#[stable(feature = "rust1", since = "1.0.0")]
pub fn connect(name: &str) -> Option<Connection> {
ns::connect_with_name(name)
}
/// Attempt to connect to a server by name. If the server does not exist, this will
/// immediately return `None`.
///
/// Note that this is different from connecting to a server by address. Server
/// addresses are always 16 bytes long, whereas server names are arbitrary-length
/// strings.
#[stable(feature = "rust1", since = "1.0.0")]
pub fn try_connect(name: &str) -> Option<Connection> {
ns::try_connect_with_name(name)
}
static NAME_SERVER_CONNECTION: AtomicU32 = AtomicU32::new(0);
/// Return a `Connection` to the name server. If the name server has not been started,
/// then this call will block until the name server has been started. The `Connection`
/// will be shared among all connections in a process, so it is safe to call this
/// multiple times.
pub(crate) fn name_server() -> Connection {
let cid = NAME_SERVER_CONNECTION.load(Ordering::Relaxed);
if cid != 0 {
return cid.into();
}
let cid = crate::os::xous::ffi::connect("xous-name-server".try_into().unwrap()).unwrap();
NAME_SERVER_CONNECTION.store(cid.into(), Ordering::Relaxed);
cid
}
fn try_name_server() -> Option<Connection> {
let cid = NAME_SERVER_CONNECTION.load(Ordering::Relaxed);
if cid != 0 {
return Some(cid.into());
}
if let Ok(Some(cid)) = crate::os::xous::ffi::try_connect("xous-name-server".try_into().unwrap())
{
NAME_SERVER_CONNECTION.store(cid.into(), Ordering::Relaxed);
Some(cid)
} else {
None
}
}

View File

@ -0,0 +1,63 @@
use crate::os::xous::ffi::Connection;
use core::sync::atomic::{AtomicU32, Ordering};
/// Group `usize` bytes into a `usize` and return it, beginning
/// from `offset` * sizeof(usize) bytes from the start. For example,
/// `group_or_null([1,2,3,4,5,6,7,8], 1)` on a 32-bit system will
/// return a usize with 5678 packed into it.
fn group_or_null(data: &[u8], offset: usize) -> usize {
let start = offset * core::mem::size_of::<usize>();
let mut out_array = [0u8; core::mem::size_of::<usize>()];
if start < data.len() {
for (dest, src) in out_array.iter_mut().zip(&data[start..]) {
*dest = *src;
}
}
usize::from_le_bytes(out_array)
}
pub(crate) enum LogScalar<'a> {
/// A panic occurred, and a panic log is forthcoming
BeginPanic,
/// Some number of bytes will be appended to the log message
AppendPanicMessage(&'a [u8]),
}
impl<'a> Into<[usize; 5]> for LogScalar<'a> {
fn into(self) -> [usize; 5] {
match self {
LogScalar::BeginPanic => [1000, 0, 0, 0, 0],
LogScalar::AppendPanicMessage(c) =>
// Text is grouped into 4x `usize` words. The id is 1100 plus
// the number of characters in this message.
// Ignore errors since we're already panicking.
{
[
1100 + c.len(),
group_or_null(&c, 0),
group_or_null(&c, 1),
group_or_null(&c, 2),
group_or_null(&c, 3),
]
}
}
}
}
/// Return a `Connection` to the log server, which is used for printing messages to
/// the console and reporting panics. If the log server has not yet started, this
/// will block until the server is running. It is safe to call this multiple times,
/// because the address is shared among all threads in a process.
pub(crate) fn log_server() -> Connection {
static LOG_SERVER_CONNECTION: AtomicU32 = AtomicU32::new(0);
let cid = LOG_SERVER_CONNECTION.load(Ordering::Relaxed);
if cid != 0 {
return cid.into();
}
let cid = crate::os::xous::ffi::connect("xous-log-server ".try_into().unwrap()).unwrap();
LOG_SERVER_CONNECTION.store(cid.into(), Ordering::Relaxed);
cid
}

View File

@ -0,0 +1,28 @@
use crate::os::xous::ffi::{connect, Connection};
use core::sync::atomic::{AtomicU32, Ordering};
pub(crate) enum SystimeScalar {
GetUtcTimeMs,
}
impl Into<[usize; 5]> for SystimeScalar {
fn into(self) -> [usize; 5] {
match self {
SystimeScalar::GetUtcTimeMs => [3, 0, 0, 0, 0],
}
}
}
/// Return a `Connection` to the systime server. This server is used for reporting the
/// realtime clock.
pub(crate) fn systime_server() -> Connection {
static SYSTIME_SERVER_CONNECTION: AtomicU32 = AtomicU32::new(0);
let cid = SYSTIME_SERVER_CONNECTION.load(Ordering::Relaxed);
if cid != 0 {
return cid.into();
}
let cid = connect("timeserverpublic".try_into().unwrap()).unwrap();
SYSTIME_SERVER_CONNECTION.store(cid.into(), Ordering::Relaxed);
cid
}

View File

@ -0,0 +1,38 @@
use crate::os::xous::ffi::Connection;
use core::sync::atomic::{AtomicU32, Ordering};
pub(crate) enum TicktimerScalar {
ElapsedMs,
SleepMs(usize),
LockMutex(usize /* cookie */),
UnlockMutex(usize /* cookie */),
WaitForCondition(usize /* cookie */, usize /* timeout (ms) */),
NotifyCondition(usize /* cookie */, usize /* count */),
}
impl Into<[usize; 5]> for TicktimerScalar {
fn into(self) -> [usize; 5] {
match self {
TicktimerScalar::ElapsedMs => [0, 0, 0, 0, 0],
TicktimerScalar::SleepMs(msecs) => [1, msecs, 0, 0, 0],
TicktimerScalar::LockMutex(cookie) => [6, cookie, 0, 0, 0],
TicktimerScalar::UnlockMutex(cookie) => [7, cookie, 0, 0, 0],
TicktimerScalar::WaitForCondition(cookie, timeout_ms) => [8, cookie, timeout_ms, 0, 0],
TicktimerScalar::NotifyCondition(cookie, count) => [9, cookie, count, 0, 0],
}
}
}
/// Return a `Connection` to the ticktimer server. This server is used for synchronization
/// primitives such as sleep, Mutex, and Condvar.
pub(crate) fn ticktimer_server() -> Connection {
static TICKTIMER_SERVER_CONNECTION: AtomicU32 = AtomicU32::new(0);
let cid = TICKTIMER_SERVER_CONNECTION.load(Ordering::Relaxed);
if cid != 0 {
return cid.into();
}
let cid = crate::os::xous::ffi::connect("ticktimer-server".try_into().unwrap()).unwrap();
TICKTIMER_SERVER_CONNECTION.store(cid.into(), Ordering::Relaxed);
cid
}