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