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);
}