[−][src]Module sunrise_libuser::ipc::server
IPC Server primitives
The IPC System on horizon is made of Ports pair and Session pairs. Each pair has a client and a server side:
- For Ports, the client is used to connect, returning a client Session, while the server is used to accept connections, returning a server Session
- For Sessions, the client is used to send IPC requests, while the server is used to receive and reply to those requests.
An IPC Server is made of a future executor on which we spawn futures to handle Port and Session. Those futures, created through [fn port_handler] and [fn new_session_wrapper], will take care of accepting new sessions from a ServerPort, and answering IPC requests sent on the ServerSession.
Port Handling
Most interfaces start with a Port, which is basically an object to which
clients can connect to, creating a Session pair. Ports can come from two
places: It can either be kernel-managed, or it can be sm-managed. Almost
all ports are sm-managed, the only exceptions being sm:
itself.
Kernel-managed ports are created through the [fn managed_port_handler] function. This will internally call crate::syscalls::manage_named_port() to acquire a crate::types::ServerPort. Sm-managed ports are created through [fn port_handler], which call crate::sm::IUserInterfaceProxy::register_service() to acquire their ServerPort.
Once the ServerPort is acquired, the port handling functions will run on a loop, accepting new connections, creating a backing Object for the sessions, and spawning a new future on the event loop with [fn new_session_wrapper].
use alloc::boxed::Box; use sunrise_libuser::futures::WaitableManager; use sunrise_libuser::futures_rs::future::FutureObj; use sunrise_libuser::ipc::server::port_handler; use sunrise_libuser::example::IExample1; /// Every time the port accepts a connection and a session is created, it /// will spawn a HelloInterface. #[derive(Debug, Default, Clone)] struct HelloInterface; impl IExample1 for HelloInterface {} fn main() { let mut man = WaitableManager::new(); let handler = port_handler(man.work_queue(), "hello", HelloInterface::dispatch).unwrap(); man.work_queue().spawn(FutureObj::new(Box::new(handler))); man.run(); }
Session Handling
A Session server is represented by an Object implementing an Interface, receiving and replying to Remote Process Call (RPC) requests on a crate::types::ServerSession. A session server is created either through a port handler accepting a session, or through the [fn new_session_wrapper] function, which will receive requests, call the Object's dispatcher function, and reply with the answer.
Interfaces
IPC Servers expose an API to a given service to other processes using an RPC
interface. The interface is defined using a SwIPC id file which can be found
in the ipcdefs
folder at the root of the repository. This SwIPC file will
then get compiled by swipc-gen into a rust file containing a Client struct
and two Server traits (one being synchronous, the other asynchronous). Those
will generally be exposed from the sunrise_libuser
crate.
Those traits contain two elements:
-
A function for every function in the SwIPC interface, having roughly the same signature (but with SwIPC types translated to rust). The user is expected to implement all those functions to have a complete interface implementation.
-
A function called
dispatch
. This function will be called by the Session Wrapper, and is in charge of parsing the IPC message data to extract all the arguments and call the correct function from the trait implementation.
extern crate alloc; use alloc::boxed::Box; use sunrise_libuser::futures::{WorkQueue, WaitableManager}; use sunrise_libuser::futures_rs::future::FutureObj; use sunrise_libuser::ipc::server::port_handler; use sunrise_libuser::example::IExample2; use sunrise_libuser::error::Error; use log::*; #[derive(Debug, Default, Clone)] struct HelloInterface; impl IExample2 for HelloInterface { fn function(&mut self, _manager: WorkQueue<'static>) -> Result<(), Error> { info!("hello"); Ok(()) } fn function2(&mut self, _manager: WorkQueue<'static>, val1: u32, val2: u32) -> Result<(bool, bool), Error> { info!("hello"); Ok((false, true)) } } fn main() { let mut man = WaitableManager::new(); let handler = port_handler(man.work_queue(), "hello", HelloInterface::dispatch).unwrap(); man.work_queue().spawn(FutureObj::new(Box::new(handler))); man.run(); }
Objects
An Object backs every Session. This object is the structure which implements the Interface trait. It contains the state of that specific session, and may be mutated by any IPC request. A common pattern is to have an IPC request contain an initialization method containing various parameters to configure the rest of the operations available on that session.
Note that a single interface may be implemented by multiple different
Object. This can be used to implement different access control based on the
interface used to access the service, for instance. Nintendo uses this
pattern: bsd:u
and bsd:s
use the same interface, but have different
access rights.
Subsessions
While the "root" session is generally created from a Port Handler, the user is free to create and return new subsessions. This can be done by creating a session pair with crate::syscalls::create_session(), spawning a new Session Handler with [fn new_session_wrapper], and returning the client-side session handle. Here's an example:
extern crate alloc; use alloc::boxed::Box; use sunrise_libuser::futures::WorkQueue; use sunrise_libuser::futures_rs::future::FutureObj; use sunrise_libuser::example::{IExample3, IExample3Subsession, IExample3SubsessionProxy}; use sunrise_libuser::syscalls; use sunrise_libuser::error::Error; use sunrise_libuser::ipc::server::new_session_wrapper; #[derive(Debug, Default, Clone)] struct HelloInterface; impl IExample3 for HelloInterface { fn function(&mut self, work_queue: WorkQueue<'static>) -> Result<IExample3SubsessionProxy, Error> { let (server, client) = syscalls::create_session(false, 0)?; let wrapper = new_session_wrapper(work_queue.clone(), server, Subsession, Subsession::dispatch); work_queue.spawn(FutureObj::new(Box::new(wrapper))); Ok(IExample3SubsessionProxy::from(client)) } } #[derive(Debug, Clone)] struct Subsession; impl IExample3Subsession for Subsession {}
Asynchronous Traits
A server might want to wait for asynchronous events to occur before
answering: for instance, the read()
function of a filesystem might want
to wait for an crate::types::IRQEvent to get signaled before getting the
data from the disk and returning it to the client.
This is doable by using the Asynchronous traits. Those return a Future
instead of directly returning the Result. This has one huge downside: the
futures need to be Boxed, incuring a needless heap allocation. This should
get fixed when impl Trait
in traits or async fn
in traits is
implemented.
Here's an example usage:
#![feature(async_await)] extern crate alloc; use core::future::Future; use alloc::boxed::Box; use sunrise_libuser::futures::WorkQueue; use sunrise_libuser::futures_rs::future::FutureObj; use sunrise_libuser::example::IExample4Async; use sunrise_libuser::types::SharedMemory; use sunrise_libuser::error::{Error, KernelError}; #[derive(Debug, Default, Clone)] struct HelloInterface; fn do_async_stuff() -> impl Future<Output=()> + Send { futures::future::ready(()) } impl IExample4Async for HelloInterface { fn function<'a>(&'a mut self, manager: WorkQueue<'static>, val: &u8) -> FutureObj<'a, Result<SharedMemory, Error>> { FutureObj::new(Box::new(async move { do_async_stuff().await; Err(KernelError::PortRemoteDead.into()) })) } }
Modules
hrtb_hack | Ideally, that's what we would want to write
async fn new_session_wrapper |
Structs
Align16 | Wrapper struct that forces the alignment to 0x10. Somewhat necessary for the IPC command buffer. |
Functions
common_port_handler | Infinite loop future that waits for |
control_dispatch | Implement the Control ipc cmd types. |
encode_bytes | Encode an 8-character service string into an u64 |
managed_port_handler | Creates a port through syscalls::manage_named_port() with the given name,
and returns a future which will handle the port - that is, it
will continuously accept new sessions on the port, and create backing
objects through |
new_session_wrapper | Creates a new top-level future that handles session. |
port_handler | Creates a port through crate::sm::IUserInterfaceProxy::register_service()
with the given name, and returns a future which will handle the port - that
is, it will continuously accept new sessions on the port, and create backing
objects through |