| // Copyright lowRISC contributors. | 
 | // Licensed under the Apache License, Version 2.0, see LICENSE for details. | 
 | // SPDX-License-Identifier: Apache-2.0 | 
 |  | 
 | #include "uartdpi.h" | 
 |  | 
 | #ifdef __linux__ | 
 | #include <pty.h> | 
 | #elif __APPLE__ | 
 | #include <util.h> | 
 | #endif | 
 |  | 
 | #include <assert.h> | 
 | #include <errno.h> | 
 | #include <fcntl.h> | 
 | #include <stdbool.h> | 
 | #include <stdio.h> | 
 | #include <stdlib.h> | 
 | #include <string.h> | 
 | #include <unistd.h> | 
 |  | 
 | void *uartdpi_create(const char *name, const char *log_file_path) { | 
 |   struct uartdpi_ctx *ctx = | 
 |       (struct uartdpi_ctx *)malloc(sizeof(struct uartdpi_ctx)); | 
 |   assert(ctx); | 
 |  | 
 |   int rv; | 
 |  | 
 |   // Initialize UART pseudo-terminal | 
 |   struct termios tty; | 
 |   cfmakeraw(&tty); | 
 |  | 
 |   rv = openpty(&ctx->host, &ctx->device, 0, &tty, 0); | 
 |   assert(rv != -1); | 
 |  | 
 |   rv = ttyname_r(ctx->device, ctx->ptyname, 64); | 
 |   assert(rv == 0 && "ttyname_r failed"); | 
 |  | 
 |   int cur_flags = fcntl(ctx->host, F_GETFL, 0); | 
 |   assert(cur_flags != -1 && "Unable to read current flags."); | 
 |   int new_flags = fcntl(ctx->host, F_SETFL, cur_flags | O_NONBLOCK); | 
 |   assert(new_flags != -1 && "Unable to set FD flags"); | 
 |  | 
 |   printf( | 
 |       "\n" | 
 |       "UART: Created %s for %s. Connect to it with any terminal program, e.g.\n" | 
 |       "$ screen %s\n", | 
 |       ctx->ptyname, name, ctx->ptyname); | 
 |  | 
 |   // Open log file (if requested) | 
 |   ctx->log_file = NULL; | 
 |   bool write_log_file = strlen(log_file_path) != 0; | 
 |   if (write_log_file) { | 
 |     if (strcmp(log_file_path, "-") == 0) { | 
 |       ctx->log_file = stdout; | 
 |       printf("UART: Additionally writing all UART output to STDOUT.\n"); | 
 |  | 
 |     } else { | 
 |       FILE *log_file; | 
 |       log_file = fopen(log_file_path, "w"); | 
 |       if (!log_file) { | 
 |         fprintf(stderr, "UART: Unable to open log file at %s: %s\n", | 
 |                 log_file_path, strerror(errno)); | 
 |       } else { | 
 |         // Switch log file output to line buffering to ensure lines written to | 
 |         // the UART device show up in the log file as soon as a newline | 
 |         // character is written. | 
 |         rv = setvbuf(log_file, NULL, _IOLBF, 0); | 
 |         assert(rv == 0); | 
 |  | 
 |         ctx->log_file = log_file; | 
 |         printf("UART: Additionally writing all UART output to '%s'.\n", | 
 |                log_file_path); | 
 |       } | 
 |     } | 
 |   } | 
 |  | 
 |   return (void *)ctx; | 
 | } | 
 |  | 
 | void uartdpi_close(void *ctx_void) { | 
 |   struct uartdpi_ctx *ctx = (struct uartdpi_ctx *)ctx_void; | 
 |   if (!ctx) { | 
 |     return; | 
 |   } | 
 |  | 
 |   close(ctx->host); | 
 |   close(ctx->device); | 
 |  | 
 |   if (ctx->log_file) { | 
 |     // Always ensure the log file is flushed (most important when writing | 
 |     // to STDOUT) | 
 |     fflush(ctx->log_file); | 
 |     if (ctx->log_file != stdout) { | 
 |       fclose(ctx->log_file); | 
 |     } | 
 |   } | 
 |  | 
 |   free(ctx); | 
 | } | 
 |  | 
 | int uartdpi_can_read(void *ctx_void) { | 
 |   struct uartdpi_ctx *ctx = (struct uartdpi_ctx *)ctx_void; | 
 |  | 
 |   int rv = read(ctx->host, &ctx->tmp_read, 1); | 
 |   return (rv == 1); | 
 | } | 
 |  | 
 | char uartdpi_read(void *ctx_void) { | 
 |   struct uartdpi_ctx *ctx = (struct uartdpi_ctx *)ctx_void; | 
 |  | 
 |   return ctx->tmp_read; | 
 | } | 
 |  | 
 | void uartdpi_write(void *ctx_void, char c) { | 
 |   int rv; | 
 |  | 
 |   struct uartdpi_ctx *ctx = (struct uartdpi_ctx *)ctx_void; | 
 |  | 
 |   rv = write(ctx->host, &c, 1); | 
 |   assert(rv == 1 && "Write to pseudo-terminal failed."); | 
 |  | 
 |   if (ctx->log_file) { | 
 |     rv = fwrite(&c, sizeof(char), 1, ctx->log_file); | 
 |     assert(rv == 1 && "Write to log file failed."); | 
 |   } | 
 | } |