blob: fa6985852f3ce3d38c1f6089dfa867c7e42c3c25 [file] [log] [blame]
// 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.");
}
}