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.
Caution:
All
vka_t
,vspace_t
andsimple_t
instances provided to this library must remain functional for the duration of the Server thread's lifetime.
The library works on the basis of 3 roles being played by 3 parties:
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).Finally, some party must spawn the client threads themselves; this party is, while a recognized actor, not a necessary part of the library specification.
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.
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.
Header: #include <sel4utils/serial_server/parent.h>
.
Of the three roles, the Parent does the most work, and is responsible for:
To spawn the Server, you'll need:
simple_t
instance that has information about the Parent.vka_t
instance that manages the Parent's CSpace.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:
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 Server 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); }
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:
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.
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.
You don't need to do anything with the Server or Parent when you wish to disconnect a Client from the Server.
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.
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.
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.
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); }