blob: 5f227c1168f7469a889bbf76ca20f4d36a1cafd6 [file] [log] [blame] [view]
<!---
Copyright 2017, Data61
Commonwealth Scientific and Industrial Research Organisation (CSIRO)
ABN 41 687 119 230.
This software may be distributed and modified according to the terms of
the BSD 2-Clause license. Note that NO WARRANTY is provided.
See "LICENSE_BSD2.txt" for details.
@TAG(DATA61_BSD)
-->
# 1. MISSION STATEMENT AND CURRENT STATUS.
The Serial Server thread is able to connect to a character device and act as a
multiplexer for writes to that device.
Generally the server's error messages are very descriptive, and you should be
able to tell what went wrong if you triggered an error.
## 1.1. CURRENT SUPPORTED FEATURES:
* Binding to a platform serial device.
* Writing to the platform serial device.
* Serializing access to the serial device from multiple clients.
## 1.2. CURRENTLY UNSUPPORTED FEATURES:
* Reading from the platform serial device.
* Multiple server instances.
* Write-back buffering or any kind of cache-type buffering really.
# 2. TOP LEVEL DESIGN
> **Caution**:
>
> All `vka_t`, `vspace_t` and `simple_t` instances provided to this library
> must remain functional for the duration of the Server thread's lifetime.
## 2.1. ROLES.
The library works on the basis of 3 roles being played by 3 parties:
* The "Parent", which spawns the server thread by calling
`serial_server_parent_spawn_thread()`. It is also generally, though not necessarily,
going to be the job of the Parent, to provide CSpace slots for the
Server to mint badged caps to its Endpoint into, but this may be done by any other
thread that exists in the same VSpace as the Server (because the Server's
badge-value allocation metadata exists in the Server's VSpace).
* The "Server", which is spawned by the Parent, and multiplexes accesses to the
underlying serial device. The Server also provides a badge value allocator that
the Parent *must* use when it is minting the Endpoint capabilities to the Clients.
* The "Clients", which connect to the Server and print through it. The Clients
need to have an endpoint minted to them by the Parent, or else they need to
somehow acquire a badged capability to the Server's Endpoint, the badge of which
was generated by the library.
Finally, some party must spawn the client threads themselves; this party is,
while a recognized actor, not a necessary part of the library specification.
## 2.2. ROLE QUICK REFERENCE (TL;DR):
Basic quick walkthrough of what you need to do, in order to get each role
working. Written role-specific so you only need to read the section you care
about.
### 2.2.1. SERVER QUICK REFERENCE:
You don't need to do anything with the server. If you want to know how to
_spawn_ the server, that's the role of the Parent.
### 2.2.2. PARENT QUICK REFERENCE:
Header: `#include <sel4utils/serial_server/parent.h>`.
Of the three roles, the Parent does the most work, and is responsible for:
* Spawning the Server.
* Generally, but not necessarily: allocating CSpace slots in the clients' CSpaces
into which the Server library will mint badged Endpoint capabilities.
To spawn the Server, you'll need:
* A `simple_t` instance that has information about the Parent.
* A `vka_t` instance that manages the Parent's CSpace.
* A `vspace_t` instance that manages the Parent's VSpace.
When you have all of those, go ahead and spawn the Server thread like this:
int error;
error = serial_server_parent_spawn_thread(...);
if (error != 0) {
ZF_LOGF("Failed to spawn the server with error %d.", error);
}
ZF_LOGF("Serial server spawned.");
> #### Behaviour / Side effects
>
> * The server works with the assumption that it will be spawned in the Parent's
> VSpace and CSpace, and it expects to be given the Parent's vspace_t, vka_t
> and simple_t.
> * When the Server thread is spawned, the library automatically creates an
> Endpoint object that it will listen on, and stores it a capability to this
> Endpoint internally.
> * Keep all vka_t, vspace_t and simple_t instances that are passed to this
> library alive for the duration of the lifetime of the Server thread.
Next you'll want to mint badged capabilities to the Server's Endpoint object,
to all the Clients you intend to spawn. The library takes care of this in a
two-fold manner:
* It provides convenience functions for minting new, badged copies of the Server
thread's Endpoint cap.
* It keeps an internal badge value allocator, which the aforementioned
convenience functions use to generate unique badge values for clients.
The library provides several convenience functions that will help you mint these easily:
* `serial_server_parent_vka_mint_endpoint(vka_t client_vka, cspacepath_t *result)`:
This function will ALLOCATE and mint a new, badged copy of the Server's Endpoint and return
the path into for it in `result`. Make sure you pass the **CLIENT**'s vka, and
not the Server's vka, since you're trying to mint the new cap to the client.
* `serial_server_allocate_client_badged_ep(cspacepath_t dest_path)`:
This function will mint a new, badged copy of the Server's Endpoint into the
designated slot -- this is meant to be used when the caller has already pre-allocated
a slot that s/he would like the server to use.
* `serial_server_parent_mint_endpoint_to_process(sel4utils_process_t *p)`:
This function relies on `sel4utils_mint_cap_to_process()`, but basically fills out
the arguments for you. By implication, it also takes up a slot in the destination
process's CSpace, so **if you had some policy for your client threads' slots,
you need to factor this function call into that policy**.
In this quick-reference, we'll use `serial_server_parent_vka_mint_endpoint()`
since it's super-convenient. We will be using 3 Client threads in our example:
#define SERSERV_README_N_CLIENTS (3)
vka_t client_vkas[SERSERV_README_N_CLIENTS];
cspacepath_t client_server_ep_cspaths[SERSERV_README_N_CLIENTS];
for (int i = 0; i < SERSERV_README_N_CLIENTS; i++) {
/* I'm not going to cover how to initialize vkas here. */
SETUP_YOUR_CLIENT'S_VKA(&client_vkas[i]);
/* Ask the Serrver to Mint badged endpoints to the clients: the library
* automatically both allocates a unique badge value and mints the
* capability for us.
*
* We chose here to use `serial_server_parent_vka_mint_endpoint()`, but
* the other functions provided by the library would have worked fine.
*/
error = serial_server_parent_vka_mint_endpoint(&client_vkas[i],
&client_server_ep_cspaths[i]);
}
/* At the end of this loop, each Client has a badged capability to the
* Endpoint that the server is listening on. (The client processes/threads
* have not been spawned yet.)
*
* You want to keep the CPtrs to the badged endpoints somewhere safe,
* because you'll need them later.
*/
Make sure that you save the CPtrs to the badged Endpoints that the library
returned to you: you'll need them later on. If your new Client will live in
a new VSpace or CSpace, **make sure you plan for how to make the these caps
visible to them**.
Finally, either the Parent or some other actor will have to actually spawn the
clients. I won't be covering how to spawn TCBs/CSpaces/VSpaces, etc here, but
it'll look something like:
for (int i = 0; i < SERSERV_README_N_CLIENTS; i++) {
MAKE_SERVER's_BADGED_ENDPOINT_CAPABILITY_VISIBLE_TO_CLIENT(i);
SPAWN_CLIENT_THREAD_OR_PROCESS(i);
}
### 2.2.3. CLIENT QUICK REFERENCE:
Header: `#include <sel4utils/serial_server/client.h>`.
The Client role is extremely simple: you retrieve the badged Endpoint capability
that was minted for you by the Parent, and then you call
`serial_server_client_connect()` to connect to the server. From then on, you can
call `serial_server_printf()` to ask the server to print.
A call to `serial_server_client_connect()` requires:
* The client's badged Server endpoint capability.
* An initialized vka_t instance which is able to allocate resources on behalf
of the client. If the client lives in the same CSpace as the Parent, this will
be the Parent's VKA.
* An initialize vspace_t instance which is able to allocate memory on behalf of
the client. If the client lives in the same VSpace as the Parent, this will
be the Parent's VSpace.
* An uninitialized `serial_client_context_t`, which is your context-cookie
to the server.
You can use the library from a client like this:
void client_main(void)
{
seL4_CPtr my_server_ep_cap;
vka_t *my_vka;
vspace_t *my_vspace;
serial_client_context_t my_conn;
int error;
RETRIEVE_MY_BADGED_EP_CAP(&my_server_ep_cap);
my_vka = OBTAIN_A_VKA_THAT_WORKS_FOR_MY_CSPACE();
my_vspace = OBTAIN_A_VSPACE_THAT_WORKS_FOR_MY_VSPACE();
error = serial_server_client_connect(my_server_ep_cap,
my_vka, my_vspace,
&my_conn);
if (error != 0) {
ZF_LOGF("Failed to connect to the server.");
}
/* From here on, you're free to print. */
serial_server_printf(&my_conn, "Hello world from %s.\n", "John Doe");
}
> #### Behaviour / Side effects
>
> * `serial_server_client_connect()` establishes a shared-memory window
> between the client and server. Make sure that you have enough virtual
> memory in both VSpaces, and make sure you have enough physical memory.
> At present, the shared mem window is 1 page in size.
> * `serial_server_client_connect()` also sends capabilities to the server via
> IPC. Be sure that the badged Endpoint capabilities generated for each
> client have the **GRANT** right on them.
# 3. HIGH LEVEL SERIAL SERVER MECHANICS:
## 3.1 DISCONNECTING:
Disconnecting causes the Server to tear down its connection to a specific
Client. That client will thenceforth be ignored by the Server, just as if it had
never connected in the first place. The client may subsequently reconnect if it
so desires.
> #### Behaviour / Side effects:
>
> After being disconnected, the only operation that a former Client can invoke
> on the Server is the `serial_server_client_connect()` function.
### 3.1.1: DISCONNECTING, SERVER ROLE AND PARENT ROLE:
You don't need to do anything with the Server or Parent when you wish to
disconnect a Client from the Server.
### 3.1.2: DISCONNECTING, CLIENT ROLE:
To disconnect a client from the Server, simply retrieve a pointer to your
connection token, and then call `serial_server_disconnect()`. (Your connection
token was filled out and returned to you when you called
`serial_server_client_connect()`).
void client_main(void)
{
serial_client_context_t conn;
serial_server_client_connect(&conn, ...);
/* ... */
serial_server_disconnect(&conn);
}
That's it.
## 3.2 KILLING THE SERVER:
Killing the server causes the Server thread to first stop listening for
requests (it exits its message loop), and then unilaterally tear down all
connections to its current clients, before Suspending itself.
This operation may only be performed by a current Client of the Server. If your
Parent wishes to kill the Server, it itself will have to also connect to the
Server.
### 3.2.1 KILLING, SERVER ROLE AND PARENT ROLE:
You don't need to do anything with the Server or Parent when you wish to kill the
Server. If you wish to kill the Server from the Parent, then the Parent will
also itself need to connect to the Server.
### 3.2.2 KILLING, CLIENT ROLE:
To kill the Server, simply obtain a handle to your connection token, and then
call `serial_server_kill()`:
void client_main(void)
{
serial_client_context_t conn;
serial_server_client_connect(&conn);
/* ... */
serial_server_kill(&conn);
}