|  | /* | 
|  | * 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) | 
|  | */ | 
|  | #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); | 
|  | } |