blob: cdcf4d1602d9e1b6b8d294927ca98a1a4146c495 [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 <sel4/sel4.h>
#include <sel4utils/strerror.h>
#include <vka/vka.h>
#include <vka/capops.h>
#include <vka/object.h>
#include <vspace/vspace.h>
#include "serial_server.h"
#include <serial_server/client.h>
/** Single-call connection to the server thread, using the parent as a
* proxy.
*
* Sends IPC to the parent thread, asking it to connect us to the server
* thread. It does this by allocating some memory to be used as shmem, and then
* sending a Frame cap to that memory, to the Parent, which then maps it into
* the server.
*
* The Parent then replies by sending us an Endpoint cap which can be used to
* communicate directly with the server thread from then on.
*/
int
serial_server_client_connect(seL4_CPtr badged_server_ep_cap,
vka_t *client_vka, vspace_t *client_vspace,
serial_client_context_t *conn)
{
seL4_Error error;
int shmem_n_pages;
uintptr_t shmem_tmp_vaddr;
seL4_MessageInfo_t tag;
cspacepath_t frame_cspath;
if (badged_server_ep_cap == 0 || client_vka == NULL || client_vspace == NULL
|| conn == NULL) {
return seL4_InvalidArgument;
}
memset(conn, 0, sizeof(serial_client_context_t));
shmem_n_pages = BYTES_TO_4K_PAGES(SERIAL_SERVER_SHMEM_MAX_SIZE);
if (shmem_n_pages > seL4_MsgMaxExtraCaps) {
ZF_LOGE(SERSERVC"connect: Currently unsupported shared memory size: "
"IPC cap transfer capability is inadequate.");
return seL4_RangeError;
}
conn->shmem = vspace_new_pages(client_vspace,
seL4_AllRights,
shmem_n_pages,
seL4_PageBits);
if (conn->shmem == NULL) {
ZF_LOGE(SERSERVC"connect: Failed to alloc shmem.");
return seL4_NotEnoughMemory;
}
assert(IS_ALIGNED((uintptr_t)conn->shmem, seL4_PageBits));
/* Look up the Frame cap behind each page in the shmem range, and marshal
* all of those Frame caps to the parent. The parent will then map those
* Frames into its VSpace and establish a shmem link.
*/
shmem_tmp_vaddr = (uintptr_t)conn->shmem;
for (int i = 0; i < shmem_n_pages; i++) {
vka_cspace_make_path(client_vka,
vspace_get_cap(client_vspace,
(void *)shmem_tmp_vaddr),
&frame_cspath);
seL4_SetCap(i, frame_cspath.capPtr);
shmem_tmp_vaddr += BIT(seL4_PageBits);
}
/* Call the server asking it to establish the shmem mapping with us, and
* get us connected up.
*/
seL4_SetMR(SSMSGREG_FUNC, FUNC_CONNECT_REQ);
seL4_SetMR(SSMSGREG_CONNECT_REQ_SHMEM_SIZE,
SERIAL_SERVER_SHMEM_MAX_SIZE);
/* extraCaps doubles up as the number of shmem pages. */
tag = seL4_MessageInfo_new(0, 0,
shmem_n_pages,
SSMSGREG_CONNECT_REQ_END);
tag = seL4_Call(badged_server_ep_cap, tag);
/* It makes sense to verify that the message we're getting back is an
* ACK response to our request message.
*/
if (seL4_GetMR(SSMSGREG_FUNC) != FUNC_CONNECT_ACK) {
error = seL4_IllegalOperation;
ZF_LOGE(SERSERVC"connect: Reply message was not a CONNECT_ACK as "
"expected.");
goto out;
}
/* When the parent replies, we check to see if it was successful, etc. */
error = seL4_MessageInfo_get_label(tag);
if (error != (int)SERIAL_SERVER_NOERROR) {
ZF_LOGE(SERSERVC"connect ERR %d: Failed to connect to the server.",
error);
if (error == (int)SERIAL_SERVER_ERROR_SHMEM_TOO_LARGE) {
ZF_LOGE(SERSERVC"connect: Your requested shmem mapping size is too "
"large.\n\tServer's max shmem size is %luB.",
(long)seL4_GetMR(SSMSGREG_CONNECT_ACK_MAX_SHMEM_SIZE));
}
goto out;
}
conn->shmem_size = SERIAL_SERVER_SHMEM_MAX_SIZE;
vka_cspace_make_path(client_vka, badged_server_ep_cap,
&conn->badged_server_ep_cspath);
return seL4_NoError;
out:
if (conn->shmem != NULL) {
vspace_unmap_pages(client_vspace, (void *)conn->shmem, shmem_n_pages,
seL4_PageBits, VSPACE_FREE);
}
return error;
}
/** Performs the IPC register setup for a write() call to the server.
*
* The Server's ABI for the write() request has changed a little: the server
* now returns the number of bytes it wrote out to the serial in a msg-reg,
* aside from also returning an error code in the "label" of the header.
*
* @param conn Initialized connection token returned by
* serial_server_client_connect().
* @param len length of the data in the buffer.
* @param server_nbytes_written The value the server reports that it actually
* wrote to the serial device.
* @return 0 on success, or integer error value on error.
*/
static ssize_t
serial_server_write_ipc_invoke(serial_client_context_t *conn, ssize_t len)
{
seL4_MessageInfo_t tag;
seL4_SetMR(SSMSGREG_FUNC, FUNC_WRITE_REQ);
seL4_SetMR(SSMSGREG_WRITE_REQ_BUFF_LEN, len);
tag = seL4_MessageInfo_new(0, 0, 0, SSMSGREG_WRITE_REQ_END);
tag = seL4_Call(conn->badged_server_ep_cspath.capPtr, tag);
if (seL4_GetMR(SSMSGREG_FUNC) != FUNC_WRITE_ACK) {
ZF_LOGE(SERSERVC"printf: Reply message was not a WRITE_ACK as "
"expected.");
return - seL4_IllegalOperation;
}
if (seL4_MessageInfo_get_label(tag) != 0) {
return - seL4_MessageInfo_get_label(tag);
}
return seL4_GetMR(SSMSGREG_WRITE_ACK_N_BYTES_WRITTEN);
}
ssize_t
serial_server_printf(serial_client_context_t *conn, const char *fmt, ...)
{
ssize_t expanded_fmt_length;
va_list args;
/* We simplify everything by just vsnprintf()ing, instead of marshaling
* a variable length format string and a variable length argument list.
*/
if (fmt == NULL || conn == NULL || conn->shmem == NULL) {
ZF_LOGE(SERSERVC"printf: NULL passed for required arguments.\n"
"\tIs connection handle valid?");
return -seL4_InvalidArgument;
}
va_start(args, fmt);
expanded_fmt_length = vsnprintf((char *)conn->shmem, conn->shmem_size,
fmt, args);
va_end(args);
if (expanded_fmt_length < 0) {
return -1;
}
if ((size_t)expanded_fmt_length >= conn->shmem_size) {
ZF_LOGE(SERSERVC"printf: This printf call's total expanded length (%zd) "
"exceeds your %zd bytes shmem buffer.\n\tMessage not sent to "
"server.",
expanded_fmt_length,
conn->shmem_size);
return -seL4_RangeError;
}
/* Else, send it off to the server. */
return serial_server_write_ipc_invoke(conn, expanded_fmt_length);
}
ssize_t serial_server_flush(serial_client_context_t *conn, ssize_t len)
{
if (len > conn->shmem_size) {
return -seL4_RangeError;
}
if (len == 0) {
return 0;
}
return serial_server_write_ipc_invoke(conn, len);
}
ssize_t
serial_server_write(serial_client_context_t *conn, const char *in_buff, ssize_t len)
{
if (in_buff == NULL || conn == NULL || conn->shmem == NULL) {
ZF_LOGE(SERSERVC"printf: NULL passed for required arguments.\n"
"\tIs connection handle valid?");
return -seL4_InvalidArgument;
}
if (len > conn->shmem_size) {
return -seL4_RangeError;
}
if (len == 0) {
return 0;
}
memcpy((void *)conn->shmem, in_buff, len);
/* Else, send it off to the server. */
return serial_server_write_ipc_invoke(conn, len);
}
void
serial_server_disconnect(serial_client_context_t *conn)
{
seL4_MessageInfo_t tag;
if (conn == NULL) {
return;
}
seL4_SetMR(SSMSGREG_FUNC, FUNC_DISCONNECT_REQ);
tag = seL4_MessageInfo_new(0, 0, 0, SSMSGREG_DISCONNECT_REQ_END);
tag = seL4_Call(conn->badged_server_ep_cspath.capPtr, tag);
if (seL4_GetMR(SSMSGREG_FUNC) != FUNC_DISCONNECT_ACK) {
ZF_LOGE(SERSERVC"disconnect: reply message was not a DISCONNECT_ACK "
"as expected.");
}
}
int
serial_server_kill(serial_client_context_t *conn)
{
seL4_MessageInfo_t tag;
if (conn == NULL) {
return seL4_InvalidArgument;
}
seL4_SetMR(SSMSGREG_FUNC, FUNC_KILL_REQ);
tag = seL4_MessageInfo_new(0, 0, 0, SSMSGREG_KILL_REQ_END);
tag = seL4_Call(conn->badged_server_ep_cspath.capPtr, tag);
if (seL4_GetMR(SSMSGREG_FUNC) != FUNC_KILL_ACK) {
ZF_LOGE(SERSERVC"kill: Reply message was not a KILL_ACK as expected.");
return seL4_IllegalOperation;
}
return seL4_MessageInfo_get_label(tag);
}