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