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