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