[][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:

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:

  1. 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.

  2. 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(mut dispatch: F) -> () where F: for<'a> FnMut<(&'a mut u8,)>, for<'a> <F as FnOnce<(&'a mut u8,)>>::Output: Future<Output = Result<(), ()>>, { // Session wrapper code } But the compiler seems to have trouble reasoning about associated types in an HRTB context (maybe that's just not possible ? Not sure).

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 port to get signaled, then accepts a new session on the port, creates a new object backing the session using T::default(), and finally spawns a new session wrapper future using new_session_wrapper().

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 T::default(), and spawn a top-level future handling that sesion with new_session_wrapper().

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 T::default(), and spawn a top-level future handling that sesion with new_session_wrapper().