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