blob: 0170c60b63ae1759a6aade76173ee8a3861618d3 [file] [log] [blame]
/*
* Copyright 2017, Data61, CSIRO (ABN 41 687 119 230)
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <autoconf.h>
#include <sel4serialserver/gen_config.h>
#include <stdio.h>
#include <string.h>
#include <sel4/sel4.h>
#include <vka/vka.h>
#include <vka/object.h>
#include <vka/capops.h>
#include <utils/arith.h>
#include <utils/ansi.h>
#include <sel4utils/api.h>
#include <sel4utils/strerror.h>
#include <sel4platsupport/platsupport.h>
#include "serial_server.h"
#include <serial_server/client.h>
#include <serial_server/parent.h>
/* Define global instance. */
static serial_server_context_t serial_server;
static char *colors[] = {
ANSI_COLOR(RED),
ANSI_COLOR(GREEN),
ANSI_COLOR(YELLOW),
ANSI_COLOR(BLUE),
ANSI_COLOR(MAGENTA),
ANSI_COLOR(CYAN),
ANSI_COLOR(RED, BOLD),
ANSI_COLOR(GREEN, BOLD),
ANSI_COLOR(YELLOW, BOLD),
ANSI_COLOR(BLUE, BOLD),
ANSI_COLOR(MAGENTA, BOLD),
ANSI_COLOR(CYAN, BOLD),
ANSI_COLOR(RED, ITALIC),
ANSI_COLOR(GREEN, ITALIC),
ANSI_COLOR(YELLOW, ITALIC),
ANSI_COLOR(BLUE, ITALIC),
ANSI_COLOR(MAGENTA, ITALIC),
ANSI_COLOR(CYAN, ITALIC),
ANSI_COLOR(RED, UNDERLINE),
ANSI_COLOR(GREEN, UNDERLINE),
ANSI_COLOR(YELLOW, UNDERLINE),
ANSI_COLOR(BLUE, UNDERLINE),
ANSI_COLOR(MAGENTA, UNDERLINE),
ANSI_COLOR(CYAN, UNDERLINE),
};
#define NUM_COLORS ARRAY_SIZE(colors)
#define BADGE_TO_COLOR(badge) (colors[(badge) % NUM_COLORS])
serial_server_context_t *get_serial_server(void)
{
return &serial_server;
}
static inline seL4_MessageInfo_t recv(seL4_Word *sender_badge)
{
return api_recv(get_serial_server()->server_ep_obj.cptr, sender_badge, get_serial_server()->server_thread.reply.cptr);
}
static inline void reply(seL4_MessageInfo_t tag)
{
api_reply(get_serial_server()->server_thread.reply.cptr, tag);
}
serial_server_registry_entry_t *serial_server_registry_get_entry_by_badge(seL4_Word badge_value)
{
if (badge_value == SERIAL_SERVER_BADGE_VALUE_EMPTY
|| get_serial_server()->registry == NULL
|| badge_value > get_serial_server()->registry_n_entries) {
return NULL;
}
/* If the badge value has been released, return NULL. */
if (get_serial_server()->registry[badge_value - 1].badge_value
== SERIAL_SERVER_BADGE_VALUE_EMPTY) {
return NULL;
}
return &get_serial_server()->registry[badge_value - 1];
}
bool serial_server_badge_is_allocated(seL4_Word badge_value)
{
serial_server_registry_entry_t *tmp;
tmp = serial_server_registry_get_entry_by_badge(badge_value);
if (tmp == NULL) {
return false;
}
return tmp->badge_value != SERIAL_SERVER_BADGE_VALUE_EMPTY;
}
seL4_Word serial_server_badge_value_get_unused(void)
{
if (get_serial_server()->registry == NULL) {
return SERIAL_SERVER_BADGE_VALUE_EMPTY;
}
for (int i = 0; i < get_serial_server()->registry_n_entries; i++) {
if (get_serial_server()->registry[i].badge_value != SERIAL_SERVER_BADGE_VALUE_EMPTY) {
continue;
}
/* Badge value 0 will never be allocated, so index 0 is actually
* badge 1, and index 1 is badge 2, and so on ad infinitum.
*/
get_serial_server()->registry[i].badge_value = i + 1;
return get_serial_server()->registry[i].badge_value;
}
return SERIAL_SERVER_BADGE_VALUE_EMPTY;
}
seL4_Word serial_server_badge_value_alloc(void)
{
serial_server_registry_entry_t *tmp;
seL4_Word ret;
ret = serial_server_badge_value_get_unused();
if (ret != SERIAL_SERVER_BADGE_VALUE_EMPTY) {
/* Success */
return ret;
}
tmp = realloc(get_serial_server()->registry,
sizeof(*get_serial_server()->registry)
* (get_serial_server()->registry_n_entries + 1));
if (tmp == NULL) {
ZF_LOGD(SERSERVS"badge_value_alloc: Failed resize pool.");
return SERIAL_SERVER_BADGE_VALUE_EMPTY;
}
get_serial_server()->registry = tmp;
get_serial_server()->registry[get_serial_server()->registry_n_entries].badge_value =
SERIAL_SERVER_BADGE_VALUE_EMPTY;
get_serial_server()->registry_n_entries++;
/* If it fails again (some other caller raced us and got the new ID before
* we did) that's tough luck -- the caller should probably look into
* serializing the calls.
*/
return serial_server_badge_value_get_unused();
}
void serial_server_badge_value_free(seL4_Word badge_value)
{
serial_server_registry_entry_t *tmp;
if (badge_value == SERIAL_SERVER_BADGE_VALUE_EMPTY) {
return;
}
tmp = serial_server_registry_get_entry_by_badge(badge_value);
if (tmp == NULL) {
return;
}
tmp->badge_value = SERIAL_SERVER_BADGE_VALUE_EMPTY;
}
static void serial_server_registry_insert(seL4_Word badge_value, void *shmem,
seL4_CPtr *shmem_frame_caps,
size_t shmem_size)
{
serial_server_registry_entry_t *tmp;
tmp = serial_server_registry_get_entry_by_badge(badge_value);
/* If this is NULL, something went very wrong, because this function is only
* called after the server checks to ensure that the badge value has been
* allocated in the registry. Technically, this should never happen, but
* an assert doesn't hurt.
*/
assert(tmp != NULL);
tmp->badge_value = badge_value;
tmp->shmem = shmem;
tmp->shmem_size = shmem_size;
tmp->shmem_frame_caps = shmem_frame_caps;
}
static void serial_server_registry_remove(seL4_Word badge_value)
{
serial_server_registry_entry_t *tmp;
tmp = serial_server_registry_get_entry_by_badge(badge_value);
if (tmp == NULL) {
return;
}
serial_server_badge_value_free(badge_value);
}
static void serial_server_set_frame_recv_path(void)
{
seL4_SetCapReceivePath(get_serial_server()->server_cspace,
get_serial_server()->frame_cap_recv_cspaths[0].capPtr,
get_serial_server()->frame_cap_recv_cspaths[0].capDepth);
}
/** Processes all FUNC_CONNECT_REQ IPC messages. Establishes
* shared mem mappings with new clients and sets up book-keeping metadata.
*
* Clients calling connect() will pass us a list of Frame caps which we must
* map in order to establish shared mem with those clients. In this function,
* the library maps the client's frames into the server's VSpace.
*/
seL4_Error serial_server_func_connect(seL4_MessageInfo_t tag,
seL4_Word client_badge_value,
size_t client_shmem_size)
{
seL4_Error error;
size_t client_shmem_n_pages;
void *shmem_tmp = NULL;
seL4_CPtr *client_frame_caps = NULL;
cspacepath_t client_frame_cspath_tmp;
if (client_shmem_size == 0) {
ZF_LOGW(SERSERVS"connect: Invalid shared mem window size of 0B.\n");
return seL4_InvalidArgument;
}
client_shmem_n_pages = BYTES_TO_4K_PAGES(client_shmem_size);
/* The client should be allocated a badge value by the Parent, before it
* attempts to connect to the Server.
*
* The reason being that when the badge is allocated, the metadata array
* is resized as well, so badge allocation is also metadata allocation.
*/
if (client_badge_value == SERIAL_SERVER_BADGE_VALUE_EMPTY
|| !serial_server_badge_is_allocated(client_badge_value)) {
ZF_LOGW(SERSERVS"connect: Please allocate a badge value to this new "
"client.\n");
return -1;
}
/* Make sure that the client didn't request a shmem mapping larger than the
* server is willing to handle.
*/
if (client_shmem_n_pages > BYTES_TO_4K_PAGES(get_serial_server()->shmem_max_size)) {
/* If the client asks for a shmem mapping too large, we refuse, and
* send the value of shmem_max_size in SSMSGREG_RESPONSE
* so it can try again.
*/
ZF_LOGW(SERSERVS"connect: New client badge %x is asking to establish "
"shmem mapping of %dB, but server max accepted shmem size is "
"%dB.",
client_badge_value, client_shmem_size,
get_serial_server()->shmem_max_size);
return SERIAL_SERVER_ERROR_SHMEM_TOO_LARGE;
}
if (seL4_MessageInfo_get_extraCaps(tag) != client_shmem_n_pages) {
ZF_LOGW(SERSERVS"connect: Received %d Frame caps from client "
"badge %x.\n\tbut client requested shmem mapping of %d "
"frames. Possible cap transfer error.",
seL4_MessageInfo_get_extraCaps(tag), client_badge_value,
client_shmem_n_pages);
return seL4_InvalidCapability;
}
/* Prepare an array of the client's shmem Frame caps to be mapped into our
* VSpace.
*/
client_frame_caps = calloc((client_shmem_n_pages + 1), sizeof(seL4_CPtr));
if (client_frame_caps == NULL) {
ZF_LOGE(SERSERVS"connect: Failed to alloc frame cap list for client "
"shmem.");
return seL4_NotEnoughMemory;
}
for (size_t i = 0; i < client_shmem_n_pages; i++) {
/* For each frame, we need to make an seL4_CNode_Copy of it before
* we zero-out the receive slots, or else when we delete the receive
* slots, the frames will be revoked and unmapped.
*/
error = vka_cspace_alloc_path(get_serial_server()->server_vka,
&client_frame_cspath_tmp);
if (error != 0) {
ZF_LOGE(SERSERVS"connect: Failed to alloc CSpace slot for frame "
"%zd of %zd received from client badge %lx.",
i + 1, client_shmem_n_pages, (long)client_badge_value);
goto out;
}
/* Copy the frame cap from the recv slot to the perm slot now. */
error = vka_cnode_move(&client_frame_cspath_tmp,
&get_serial_server()->frame_cap_recv_cspaths[i]);
if (error != 0) {
ZF_LOGE(SERSERVS"connect: Failed to move %zuth frame-cap received "
" from client badge %lx.", i + 1, (long)client_badge_value);
goto out;
}
client_frame_caps[i] = client_frame_cspath_tmp.capPtr;
ZF_LOGD("connect: moved received client Frame cap %d from recv slot %"PRIxPTR" to slot %"PRIxPTR".",
i + 1, get_serial_server()->frame_cap_recv_cspaths[i].capPtr,
client_frame_caps[i]);
}
/* Map the frames into the vspace. */
shmem_tmp = vspace_map_pages(get_serial_server()->server_vspace, client_frame_caps,
NULL,
seL4_AllRights, client_shmem_n_pages,
seL4_PageBits,
true);
if (shmem_tmp == NULL) {
ZF_LOGE(SERSERVS"connect: Failed to map shmem.");
error = seL4_NotEnoughMemory;
goto out;
}
serial_server_registry_insert(client_badge_value, shmem_tmp,
client_frame_caps, client_shmem_size);
ZF_LOGI(SERSERVS"connect: New client: badge %x, shmem %p, %d pages.",
client_badge_value, shmem_tmp, client_shmem_n_pages);
return seL4_NoError;
out:
if (shmem_tmp != NULL) {
vspace_unmap_pages(get_serial_server()->server_vspace, shmem_tmp,
client_shmem_n_pages, seL4_PageBits,
VSPACE_PRESERVE);
}
if (client_frame_caps != NULL) {
for (size_t i = 0; i < client_shmem_n_pages; i++) {
/* Because client_frame_caps was alloc'd with calloc, we can depend on
* the unused slots being filled with 0s. Break on first unallocated.
*/
if (client_frame_caps[i] == 0) {
break;
}
client_frame_cspath_tmp.capPtr = client_frame_caps[i];
vka_cspace_free_path(get_serial_server()->server_vka,
client_frame_cspath_tmp);
}
}
free(client_frame_caps);
return error;
}
static int serial_server_func_write(serial_server_registry_entry_t *client_data,
size_t message_len, size_t *bytes_written)
{
*bytes_written = 0;
if (client_data == NULL || bytes_written == NULL) {
ZF_LOGE(SERSERVS"printf: Got NULL for required argument.");
return seL4_InvalidArgument;
}
if (message_len > client_data->shmem_size) {
return seL4_RangeError;
}
/* Write out */
if (config_set(CONFIG_SERIAL_SERVER_COLOURED_OUTPUT)) {
printf("%s", COLOR_RESET);
printf("%s", BADGE_TO_COLOR(client_data->badge_value));
}
fwrite((void *)client_data->shmem, message_len, 1, stdout);
if (config_set(CONFIG_SERIAL_SERVER_COLOURED_OUTPUT)) {
printf("%s", COLOR_RESET);
}
*bytes_written = message_len;
return 0;
}
static void serial_server_func_disconnect(serial_server_registry_entry_t *client_data)
{
/* Tear down shmem and release the badge value for reuse. */
vspace_unmap_pages(get_serial_server()->server_vspace,
(void *)client_data->shmem,
BYTES_TO_4K_PAGES(client_data->shmem_size),
seL4_PageBits, get_serial_server()->server_vka);
free(client_data->shmem_frame_caps);
serial_server_registry_remove(client_data->badge_value);
}
static void serial_server_func_kill(void)
{
/* Tear down all existing connections. */
for (int i = 0; i < get_serial_server()->registry_n_entries; i++) {
serial_server_registry_entry_t *curr = &get_serial_server()->registry[i];
if (curr->badge_value == SERIAL_SERVER_BADGE_VALUE_EMPTY
|| BYTES_TO_4K_PAGES(curr->shmem_size) == 0) {
continue;
}
serial_server_func_disconnect(&get_serial_server()->registry[i]);
}
}
/** Debugging function -- prints out the state of the registry and the server's
* current list of clients.
*
* Can either print shallow or deep.
* @param dump_frame_caps If true, print the array of frames caps that underlie
* the shared mem mapping to each client.
*/
void serial_server_registry_dump(bool dump_frame_caps)
{
serial_server_registry_entry_t *tmp;
if (get_serial_server()->registry == NULL) {
ZF_LOGD("Registry is NULL.");
}
for (int i = 0; i < get_serial_server()->registry_n_entries; i++) {
tmp = &get_serial_server()->registry[i];
ZF_LOGD("Reg: idx %d, badge %d, shmem_size %d, shmem %p, caps %p.",
i, tmp->badge_value, tmp->shmem_size, tmp->shmem,
tmp->shmem_frame_caps);
if (!dump_frame_caps) {
continue;
}
for (int j = 0; j < BYTES_TO_4K_PAGES(tmp->shmem_size); j++) {
ZF_LOGD("Reg badge %d: frame cap %d: %"PRIxPTR".",
tmp->badge_value, j + 1, tmp->shmem_frame_caps[j]);
}
}
}
void serial_server_main(void)
{
seL4_MessageInfo_t tag;
seL4_Word sender_badge;
enum serial_server_funcs func;
int keep_going = 1;
UNUSED seL4_Error error;
serial_server_registry_entry_t *client_data = NULL;
size_t buff_len, bytes_written;
/* Bind to the serial driver. */
error = platsupport_serial_setup_simple(get_serial_server()->server_vspace,
get_serial_server()->server_simple,
get_serial_server()->server_vka);
if (error != 0) {
ZF_LOGE(SERSERVS"main: Failed to bind to serial.");
} else {
ZF_LOGI(SERSERVS"main: Bound to the serial driver.");
}
/* The Parent will seL4_Call() the us, the Server, right after spawning us.
* It will expect us to seL4_Reply() with an error status code - we will
* send this Reply regardless of the outcome of the platform serial bind
* operation.
*
* First call seL4_Recv() to get the Reply cap back to the Parent, and then
* seL4_Reply to report our status.
*/
recv(&sender_badge);
seL4_SetMR(SSMSGREG_FUNC, FUNC_SERVER_SPAWN_SYNC_ACK);
tag = seL4_MessageInfo_new(error, 0, 0, SSMSGREG_SPAWN_SYNC_ACK_END);
reply(tag);
/* If the bind failed, this thread has essentially failed its mandate, so
* there is no reason to leave it scheduled. Kill it (to whatever extent
* that is possible).
*/
if (error != 0) {
seL4_TCB_Suspend(get_serial_server()->server_thread.tcb.cptr);
}
ZF_LOGI(SERSERVS"main: Entering main loop and accepting requests.");
while (keep_going) {
/* Set the CNode slots where caps from clients will go */
serial_server_set_frame_recv_path();
tag = recv(&sender_badge);
ZF_LOGD(SERSERVS "main: Got message from %x", sender_badge);
func = seL4_GetMR(SSMSGREG_FUNC);
/* Lookup the registry entry for this sender to make sure that the sender
* has a shmem buffer registered with us. If not, ignore the message.
* New connection requests are of course, exempt from the requirement to
* already have an established connection.
*/
if (func != FUNC_CONNECT_REQ) {
client_data = serial_server_registry_get_entry_by_badge(sender_badge);
if (client_data == NULL) {
ZF_LOGW(SERSERVS"main: Got message from unregistered client "
"badge %x. Ignoring.",
sender_badge);
continue;
}
}
switch (func) {
case FUNC_CONNECT_REQ:
ZF_LOGD(SERSERVS"main: Got connect request from client badge %x.",
sender_badge);
error = serial_server_func_connect(tag,
sender_badge,
seL4_GetMR(SSMSGREG_CONNECT_REQ_SHMEM_SIZE));
seL4_SetMR(SSMSGREG_FUNC, FUNC_CONNECT_ACK);
seL4_SetMR(SSMSGREG_CONNECT_ACK_MAX_SHMEM_SIZE,
get_serial_server()->shmem_max_size);
tag = seL4_MessageInfo_new(error, 0, 0, SSMSGREG_CONNECT_ACK_END);
reply(tag);
break;
case FUNC_WRITE_REQ:
/* The Server's ABI for the write() request is as follows:
*
* The server returns the number of bytes it actually wrote out to the
* serial in a msg-reg (SSMSGREG_WRITE_ACK_N_BYTES_WRITTEN),
* aside from also returning an error code in the "label" of the header.
*
* This was done because attempting to overload the "label" of the
* header to hold a value that can be either:
* (1) a negative integer error code, (2) a positive size_t
* byte length
* Was not very clean especially since the bit-width of the "label"
* field shouldn't be assumed lightly, so separating the error code
* from the number of bytes written was the cleaner approach.
*
* At the client end, the client can decide to overload an ssize_t
* to encode both types of values.
*/
ZF_LOGD(SERSERVS"main: Got write request from client badge %x.",
sender_badge);
buff_len = seL4_GetMR(SSMSGREG_WRITE_REQ_BUFF_LEN);
error = serial_server_func_write(client_data, buff_len,
&bytes_written);
seL4_SetMR(SSMSGREG_FUNC, FUNC_WRITE_ACK);
seL4_SetMR(SSMSGREG_WRITE_ACK_N_BYTES_WRITTEN, bytes_written);
tag = seL4_MessageInfo_new(error, 0, 0, SSMSGREG_WRITE_ACK_END);
reply(tag);
break;
case FUNC_DISCONNECT_REQ:
ZF_LOGD(SERSERVS"main: Got disconnect request from client badge %x.",
sender_badge);
serial_server_func_disconnect(client_data);
seL4_SetMR(SSMSGREG_FUNC, FUNC_DISCONNECT_ACK);
tag = seL4_MessageInfo_new(error, 0, 0, SSMSGREG_DISCONNECT_ACK_END);
reply(tag);
break;
case FUNC_KILL_REQ:
ZF_LOGI(SERSERVS"main: Got KILL request from client badge %x.",
sender_badge);
/* The actual contents of the Reply don't matter here. */
seL4_SetMR(SSMSGREG_FUNC, FUNC_KILL_ACK);
tag = seL4_MessageInfo_new(0, 0, 0, SSMSGREG_KILL_ACK_END);
reply(tag);
/* Break out of the loop */
keep_going = 0;
break;
default:
ZF_LOGW(SERSERVS "main: Unknown function %d requested.", func);
break;
}
}
serial_server_func_kill();
/* After we break out of the loop, seL4_TCB_Suspend ourselves */
ZF_LOGI(SERSERVS"main: Suspending.");
seL4_TCB_Suspend(get_serial_server()->server_thread.tcb.cptr);
}