1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
//! IPC primitives
//!
//! Mostly lifted from the Nintendo Switch.
//! http://switchbrew.org/index.php?title=IPC_Marshalling contains documentation
//! for how it works on the official hardware. I'll try my best to explain the
//! ideas here.
//!
//! The switch IPC mechanism is separated with two main types, Ports and
//! Sessions, both having a client and a server side. A port is used to establish
//! a Session.
//!
//! # Ports
//!
//! A Port represents an endpoint which can be connected to, in order to
//! establish a Session. It is split in two different part: ServerPort and
//! ClientPort. The ClientPort has a `connect` operation, while a ServerPort has
//! an `accept` operation.
//!
//! Those work as a rendez-vous, meaning both operations wait for each-other:
//! The `connect` operation blocks until a ServerPort calls `accept`. Similarly,
//! the `accept` operation waits until a ClientPort `connect`s. Once the two
//! operation meet, a `Session` is created. The `accept` operation will return a
//! `ServerSession`, while the `connect` operation returns a `ClientSession`.
//!
//! Additionally, a ServerPort implements the Waitable trait, allowing it to be
//! used with the `event::wait` function. This will wait until the associated
//! ClientPort had its connect operation called. TODO: The ClientPort should also
//! implement Waitable, I believe. In Horizon/NX, it implements KSynchronization.
//!
//! ```rust
//! let (server, client) = Port::new();
//! let client_sess = client.connect();
//! // In a separate thread
//! let server_sess = server.accept();
//! ```
//!
//! # Session
//!
//! A Session represents an established connection. It is split in two different
//! part: ServerSession and ClientSession. The ClientSession has a `send_request`
//! operation (with various variants), while a ClientSession has a `reply` and a
//! `receive` operation (again, with various variants).
//!
//! ServerSession implements the Waitable trait, allowing it to be used with the
//! `event::wait` function. TODO: The ClientSession should also implement Waitable.
//!
//! ```rust
//! use kernel::ipc::session;
//! let (server, client) = session::new();
//! 
//! ```
//!
//! # Managed Ports
//!
//! Sessions and Ports are cool, but we're lacking some kind of entrypoint: In
//! order to do IPC, we need a handle to another service's ClientPort. But we
//! have no such handle when starting a process!
//!
//! To fix this, the kernel has a global registry of ports. Such ports are called
//! "Managed Ports". In a normal userland, only one service (the Service Manager)
//! would register themselves as a Managed Port, but the kernel allows any number
//! of those to be registered at any given time, so long as each has a unique
//! name.
//!
//! Managed Ports aren't very special, the only difference is the syscalls used
//! to interact with them: You can register a managed port, which returns a
//! ServerPort handle, and you can connect to a managed port, which returns a
//! ClientSession handle.
//!
//! ```
//! use kernel::ipc;
//! let serverport = ipc::create_named_port(b"test\0\0\0\0\0\0\0\0")?;
//! loop {
//!     let serversess = serverport.accept()?;
//! }
//! // In another thread
//! let clientsess = ipc::connect_to_named_port(b"test\0\0\0\0\0\0\0\0\0\0\0\0")?;
//! ```

use crate::sync::SpinRwLock;
use alloc::string::String;
use crate::error::UserspaceError;
use hashbrown::HashMap;

pub mod session;
pub mod port;

pub use self::session::{ClientSession, ServerSession};
pub use self::port::{ClientPort, ServerPort};

lazy_static! {
    // TODO: StringWrapper<[u8; 12]>
    static ref NAMED_PORTS: SpinRwLock<HashMap<String, ClientPort>> = SpinRwLock::new(HashMap::new());
}

/// Creates a named port.
///
/// Registers a new named port. Name should contain a \0 delimiting the end of
/// the string.
///
/// # Errors
///
/// Returns ExceedingMaximum if the name doesn't contain a \0.
pub fn create_named_port(name: [u8; 12], max_sessions: u32) -> Result<ServerPort, UserspaceError> {
    let name = match name.iter().position(|v| *v == 0) {
        Some(pos) => String::from_utf8_lossy(&name[..pos]),
        None => return Err(UserspaceError::ExceedingMaximum)
    };

    let (server, client) = port::new(max_sessions);
    NAMED_PORTS.write().insert(name.into_owned(), client);
    Ok(server)
}

/// Connects to a named port.
///
/// Returns a new ClientSession. Note that this is a blocking call that
/// rendez-vous with the associated ServerPort. In other words, it waits until
/// the associated ServerPort calls accept.
///
/// # Errors
///
/// Returns ExceedingMaximum if the name doesn't contain a \0.
///
/// Returns NoSuchEntry if the associated Named Port is not registered.
///
/// Returns PortRemoteDead if all handles to the associated ServerPort are
/// closed.
pub fn connect_to_named_port(name: [u8; 12]) -> Result<ClientSession, UserspaceError> {
    let name = match name.iter().position(|v| *v == 0) {
        Some(pos) => String::from_utf8_lossy(&name[..pos]),
        None => return Err(UserspaceError::ExceedingMaximum)
    };

    match NAMED_PORTS.read().get(name.as_ref()) {
        Some(client) => Ok(client.connect()?),
        None => Err(UserspaceError::NoSuchEntry)
    }
}