blob: c1a2b8ece94b8bb5dadd2069b686bc4e6f0f4b4f [file] [log] [blame]
/*
* Copyright 2017, Data61, CSIRO (ABN 41 687 119 230)
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <stdio.h>
#include <string.h>
#include <stdarg.h>
#include <stdlib.h>
#include <stdbool.h>
#include <sel4/sel4.h>
#include <sel4utils/strerror.h>
#include <vka/vka.h>
#include <vka/object.h>
#include <vka/object_capops.h>
#include "serial_server.h"
#include <serial_server/parent.h>
seL4_Error
serial_server_parent_spawn_thread(simple_t *parent_simple, vka_t *parent_vka,
vspace_t *parent_vspace,
uint8_t priority)
{
const size_t shmem_max_size = SERIAL_SERVER_SHMEM_MAX_SIZE;
seL4_Error error;
size_t shmem_max_n_pages;
cspacepath_t parent_cspace_cspath;
seL4_MessageInfo_t tag;
if (parent_simple == NULL || parent_vka == NULL || parent_vspace == NULL) {
return seL4_InvalidArgument;
}
memset(get_serial_server(), 0, sizeof(serial_server_context_t));
/* Get a CPtr to the parent's root cnode. */
shmem_max_n_pages = BYTES_TO_4K_PAGES(shmem_max_size);
vka_cspace_make_path(parent_vka, 0, &parent_cspace_cspath);
get_serial_server()->server_vka = parent_vka;
get_serial_server()->server_vspace = parent_vspace;
get_serial_server()->server_cspace = parent_cspace_cspath.root;
get_serial_server()->server_simple = parent_simple;
/* Allocate the Endpoint that the server will be listening on. */
error = vka_alloc_endpoint(parent_vka, &get_serial_server()->server_ep_obj);
if (error != 0) {
ZF_LOGE(SERSERVP"spawn_thread: failed to alloc endpoint, err=%d.",
error);
return error;
}
/* And also allocate a badged copy of the Server's endpoint that the Parent
* can use to send to the Server. This is used to allow the Server to report
* back to the Parent on whether or not the Server successfully bound to a
* platform serial driver.
*
* This badged endpoint will be reused by the library as the Parent's badged
* Endpoint cap, if the Parent itself ever chooses to connect() to the
* Server later on.
*/
get_serial_server()->parent_badge_value = serial_server_badge_value_alloc();
if (get_serial_server()->parent_badge_value == SERIAL_SERVER_BADGE_VALUE_EMPTY) {
error = seL4_NotEnoughMemory;
goto out;
}
error = vka_mint_object(parent_vka, &get_serial_server()->server_ep_obj,
&get_serial_server()->_badged_server_ep_cspath,
seL4_AllRights,
get_serial_server()->parent_badge_value);
if (error != 0) {
ZF_LOGE(SERSERVP"spawn_thread: Failed to mint badged Endpoint cap to "
"server.\n"
"\tParent cannot confirm Server thread successfully spawned.");
goto out;
}
/* Allocate enough Cnode slots in our CSpace to enable us to receive
* frame caps from our clients, sufficient to cover "shmem_max_size".
* The problem here is that we're sort of forced to assume that we get
* these slots contiguously. If they're not, we have a problem.
*
* If a client tries to send us too many frames, we respond with an error,
* and indicate our shmem_max_size in the SSMSGREG_RESPONSE
* message register.
*/
get_serial_server()->frame_cap_recv_cspaths = calloc(shmem_max_n_pages,
sizeof(cspacepath_t));
if (get_serial_server()->frame_cap_recv_cspaths == NULL) {
error = seL4_NotEnoughMemory;
goto out;
}
for (size_t i = 0; i < shmem_max_n_pages; i++) {
error = vka_cspace_alloc_path(parent_vka,
&get_serial_server()->frame_cap_recv_cspaths[i]);
if (error != 0) {
ZF_LOGE(SERSERVP"spawn_thread: Failed to alloc enough cnode slots "
"to receive shmem frame caps equal to %zd bytes.",
shmem_max_size);
goto out;
}
}
sel4utils_thread_config_t config = thread_config_default(parent_simple, parent_cspace_cspath.root,
seL4_NilData, get_serial_server()->server_ep_obj.cptr, priority);
error = sel4utils_configure_thread_config(parent_vka, parent_vspace, parent_vspace,
config, &get_serial_server()->server_thread);
if (error != 0) {
ZF_LOGE(SERSERVP"spawn_thread: sel4utils_configure_thread failed "
"with %d.", error);
goto out;
}
NAME_THREAD(get_serial_server()->server_thread.tcb.cptr, "serial server");
error = sel4utils_start_thread(&get_serial_server()->server_thread,
(sel4utils_thread_entry_fn)&serial_server_main,
NULL, NULL, 1);
if (error != 0) {
ZF_LOGE(SERSERVP"spawn_thread: sel4utils_start_thread failed with "
"%d.", error);
goto out;
}
/* When the Server is spawned, it will reply to tell us whether or not it
* successfully bound itself to the platform serial device. Block here
* and wait for that reply.
*/
seL4_SetMR(SSMSGREG_FUNC, FUNC_SERVER_SPAWN_SYNC_REQ);
tag = seL4_MessageInfo_new(0, 0, 0, SSMSGREG_SPAWN_SYNC_REQ_END);
tag = seL4_Call(get_serial_server()->_badged_server_ep_cspath.capPtr, tag);
/* Did all go well with the server? */
if (seL4_GetMR(SSMSGREG_FUNC) != FUNC_SERVER_SPAWN_SYNC_ACK) {
ZF_LOGE(SERSERVP"spawn_thread: Server thread sync message after spawn "
"was not a SYNC_ACK as expected.");
error = seL4_InvalidArgument;
goto out;
}
error = seL4_MessageInfo_get_label(tag);
if (error != 0) {
ZF_LOGE(SERSERVP"spawn_thread: Server thread failed to bind to the "
"platform serial device.");
goto out;
}
get_serial_server()->shmem_max_size = shmem_max_size;
get_serial_server()->shmem_max_n_pages = shmem_max_n_pages;
return 0;
out:
if (get_serial_server()->frame_cap_recv_cspaths != NULL) {
for (size_t i = 0; i < shmem_max_n_pages; i++) {
/* Since the array was allocated with calloc(), it was zero'd out. So
* those indexes that didn't get allocated will have NULL in them.
* Break early on the first index that has NULL.
*/
if (get_serial_server()->frame_cap_recv_cspaths[i].capPtr == 0) {
break;
}
vka_cspace_free_path(parent_vka, get_serial_server()->frame_cap_recv_cspaths[i]);
}
}
free(get_serial_server()->frame_cap_recv_cspaths);
if (get_serial_server()->_badged_server_ep_cspath.capPtr != 0) {
vka_cspace_free_path(parent_vka, get_serial_server()->_badged_server_ep_cspath);
}
if (get_serial_server()->parent_badge_value != SERIAL_SERVER_BADGE_VALUE_EMPTY) {
serial_server_badge_value_free(get_serial_server()->parent_badge_value);
}
vka_free_object(parent_vka, &get_serial_server()->server_ep_obj);
return error;
}
int
serial_server_parent_vka_mint_endpoint(vka_t *client_vka,
cspacepath_t *badged_server_ep_cspath)
{
if (client_vka == NULL || badged_server_ep_cspath == NULL) {
return seL4_InvalidArgument;
}
seL4_Word new_badge_value = serial_server_badge_value_alloc();
if (new_badge_value == SERIAL_SERVER_BADGE_VALUE_EMPTY) {
return -1;
}
/* Mint the Endpoint to a new client. */
return vka_mint_object_inter_cspace(get_serial_server()->server_vka,
&get_serial_server()->server_ep_obj,
client_vka,
badged_server_ep_cspath,
seL4_AllRights,
new_badge_value);
}
static inline void
parent_ep_obj_to_cspath(cspacepath_t *result)
{
if (result == NULL) {
return;
}
vka_cspace_make_path(get_serial_server()->server_vka,
get_serial_server()->server_ep_obj.cptr,
result);
}
int
serial_server_allocate_client_badged_ep(cspacepath_t dest_slot)
{
cspacepath_t server_ep_cspath;
seL4_Word new_badge_value = serial_server_badge_value_alloc();
if (new_badge_value == SERIAL_SERVER_BADGE_VALUE_EMPTY) {
return -1;
}
parent_ep_obj_to_cspath(&server_ep_cspath);
return vka_cnode_mint(&dest_slot, &server_ep_cspath, seL4_AllRights,
new_badge_value);
}
seL4_CPtr
serial_server_parent_mint_endpoint_to_process(sel4utils_process_t *p)
{
if (p == NULL) {
return 0;
}
cspacepath_t server_ep_cspath;
seL4_Word new_badge_value = serial_server_badge_value_alloc();
if (new_badge_value == SERIAL_SERVER_BADGE_VALUE_EMPTY) {
return -1;
}
parent_ep_obj_to_cspath(&server_ep_cspath);
return sel4utils_mint_cap_to_process(p, server_ep_cspath,
seL4_AllRights,
new_badge_value);
}