blob: 4b326f578949c3056ae3cf8994fab696db2d6734 [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 "gpiodpi.h"
#ifdef __linux__
#include <pty.h>
#elif __APPLE__
#include <util.h>
#endif
#include <assert.h>
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
// This file does a lot of bit setting and getting; these macros are intended to
// make that a little more readable.
#define GET_BIT(word, bit_idx) (((word) >> (bit_idx)) & 1)
#define SET_BIT(word, bit_idx) ((word) |= (1 << (bit_idx)))
#define CLR_BIT(word, bit_idx) ((word) &= ~(1 << (bit_idx)))
struct gpiodpi_ctx {
// The number of pins we're driving.
int n_bits;
// The last known value of the pins, in little-endian order.
uint32_t driven_pin_values;
// File descriptors and paths for the device-to-host and host-to-device
// FIFOs.
int dev_to_host_fifo;
char dev_to_host_path[PATH_MAX];
int host_to_dev_fifo;
char host_to_dev_path[PATH_MAX];
};
/**
* Creates a new UNIX FIFO file at |path_buf|, and opens it with |flags|.
*
* @return a file descriptor for the FIFO, or -1 if any syscall failed.
*/
static int open_fifo(char *path_buf, int flags) {
int fifo_status = mkfifo(path_buf, 0644);
if (fifo_status != 0) {
if (errno == EEXIST) {
fprintf(stderr, "GPIO: Reusing existing FIFO at %s\n", path_buf);
} else {
fprintf(stderr, "GPIO: Unable to create FIFO at %s: %s\n", path_buf,
strerror(errno));
return -1;
}
}
int fd = open(path_buf, flags);
if (fd < 0) {
// Delete the fifo we created; ignore errors.
unlink(path_buf);
fprintf(stderr, "GPIO: Unable to open FIFO at %s: %s\n", path_buf,
strerror(errno));
return -1;
}
return fd;
}
/**
* Print out a usage message for the GPIO interface.
*
* @arg rfifo the path to the "read" side (w.r.t the host).
* @arg wfifo the path to the "write" side (w.r.t the host).
* @arg n_bits the number of pins supported.
*/
static void print_usage(char *rfifo, char *wfifo, int n_bits) {
printf("\n");
printf(
"GPIO: FIFO pipes created at %s (read) and %s (write) for %d-bit wide "
"GPIO.\n",
rfifo, wfifo, n_bits);
printf(
"GPIO: To measure the values of the pins as driven by the device, run\n");
printf("$ cat %s # '0' low, '1' high, 'X' floating\n", rfifo);
printf("GPIO: To drive the pins, run a command like\n");
printf("$ echo 'h09 l31' > %s # Pull the pin 9 high, and pin 31 low.\n",
wfifo);
}
void *gpiodpi_create(const char *name, int n_bits) {
struct gpiodpi_ctx *ctx =
(struct gpiodpi_ctx *)malloc(sizeof(struct gpiodpi_ctx));
assert(ctx);
// n_bits > 32 requires more sophisticated handling of svBitVecVal which we
// currently don't do.
assert(n_bits <= 32 && "n_bits must be <= 32");
ctx->n_bits = n_bits;
ctx->driven_pin_values = 0;
char cwd_buf[PATH_MAX];
char *cwd = getcwd(cwd_buf, sizeof(cwd_buf));
assert(cwd != NULL);
int path_len;
path_len = snprintf(ctx->dev_to_host_path, PATH_MAX, "%s/%s-read", cwd, name);
assert(path_len > 0 && path_len <= PATH_MAX);
path_len =
snprintf(ctx->host_to_dev_path, PATH_MAX, "%s/%s-write", cwd, name);
assert(path_len > 0 && path_len <= PATH_MAX);
ctx->dev_to_host_fifo = open_fifo(ctx->dev_to_host_path, O_RDWR);
if (ctx->dev_to_host_fifo < 0) {
return NULL;
}
ctx->host_to_dev_fifo = open_fifo(ctx->host_to_dev_path, O_RDWR);
if (ctx->host_to_dev_fifo < 0) {
return NULL;
}
int flags = fcntl(ctx->host_to_dev_fifo, F_GETFL, 0);
fcntl(ctx->host_to_dev_fifo, F_SETFL, flags | O_NONBLOCK);
print_usage(ctx->dev_to_host_path, ctx->host_to_dev_path, ctx->n_bits);
return (void *)ctx;
}
void gpiodpi_device_to_host(void *ctx_void, svBitVecVal *gpio_data,
svBitVecVal *gpio_oe) {
struct gpiodpi_ctx *ctx = (struct gpiodpi_ctx *)ctx_void;
assert(ctx);
// Write 0, 1, or X (when oe is not set) for each GPIO pin, in big endian
// order (i.e., pin 0 is the last character written). Finish it with a
// newline.
char gpio_str[32 + 1];
char *pin_char = gpio_str;
for (int i = ctx->n_bits - 1; i >= 0; --i, ++pin_char) {
if (!GET_BIT(gpio_oe[0], i)) {
*pin_char = 'X';
} else if (GET_BIT(gpio_data[0], i)) {
*pin_char = '1';
} else {
*pin_char = '0';
}
}
*pin_char = '\n';
ssize_t written = write(ctx->dev_to_host_fifo, gpio_str, ctx->n_bits + 1);
assert(written == ctx->n_bits + 1);
}
/**
* Parses an unsigned decimal number from |text|, advancing it forward as
* necessary.
*
* Returns upon encountering any non-decimal digit.
*/
static uint32_t parse_dec(char **text) {
if (text == NULL || *text == NULL) {
return 0;
}
uint32_t value = 0;
for (; **text != '\0'; ++*text) {
char c = **text;
uint32_t digit;
if (c >= '0' && c <= '9') {
digit = (c - '0');
} else {
break;
}
value *= 10;
value += digit;
}
return value;
}
uint32_t gpiodpi_host_to_device_tick(void *ctx_void, svBitVecVal *gpio_oe) {
struct gpiodpi_ctx *ctx = (struct gpiodpi_ctx *)ctx_void;
assert(ctx);
char gpio_str[32 + 2];
ssize_t read_len = read(ctx->host_to_dev_fifo, gpio_str, 32 + 1);
if (read_len < 0) {
return ctx->driven_pin_values;
}
gpio_str[read_len] = '\0';
char *gpio_text = gpio_str;
for (; *gpio_text != '\0'; ++gpio_text) {
switch (*gpio_text) {
case '\n':
case '\r':
case '\0':
goto parse_loop_end;
case 'l':
case 'L': {
++gpio_text;
int idx = parse_dec(&gpio_text);
if (!GET_BIT(gpio_oe[0], idx)) {
fprintf(stderr,
"GPIO: Host tried to pull disabled pin low: pin %2d\n", idx);
}
CLR_BIT(ctx->driven_pin_values, idx);
break;
}
case 'h':
case 'H': {
++gpio_text;
int idx = parse_dec(&gpio_text);
if (!GET_BIT(gpio_oe[0], idx)) {
fprintf(stderr,
"GPIO: Host tried to pull disabled pin high: pin %2d\n", idx);
}
SET_BIT(ctx->driven_pin_values, idx);
break;
}
default:
break;
}
}
parse_loop_end:
return ctx->driven_pin_values;
}
void gpiodpi_close(void *ctx_void) {
struct gpiodpi_ctx *ctx = (struct gpiodpi_ctx *)ctx_void;
if (ctx == NULL) {
return;
}
if (close(ctx->dev_to_host_fifo) != 0) {
printf("GPIO: Failed to close FIFO file at %s: %s\n", ctx->dev_to_host_path,
strerror(errno));
}
if (close(ctx->host_to_dev_fifo) != 0) {
printf("GPIO: Failed to close FIFO file at %s: %s\n", ctx->host_to_dev_path,
strerror(errno));
}
if (unlink(ctx->dev_to_host_path) != 0) {
printf("GPIO: Failed to unlink FIFO file at %s: %s\n",
ctx->dev_to_host_path, strerror(errno));
}
if (unlink(ctx->host_to_dev_path) != 0) {
printf("GPIO: Failed to unlink FIFO file at %s: %s\n",
ctx->host_to_dev_path, strerror(errno));
}
free(ctx);
}