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