diff --git a/cpu/native/Kconfig b/cpu/native/Kconfig index 373c76530f..0b5decb8b8 100644 --- a/cpu/native/Kconfig +++ b/cpu/native/Kconfig @@ -51,6 +51,8 @@ config NATIVE_OS_DARWIN config NATIVE_OS_LINUX bool + select HAS_PERIPH_GPIO + select HAS_PERIPH_GPIO_IRQ select HAS_PERIPH_SPI config NATIVE_OS_FREEBSD diff --git a/cpu/native/Makefile.dep b/cpu/native/Makefile.dep index a1afcf7088..29508bd0f3 100644 --- a/cpu/native/Makefile.dep +++ b/cpu/native/Makefile.dep @@ -1,6 +1,12 @@ -ifneq (,$(filter periph_spi,$(USEMODULE))) - USEMODULE += periph_spidev_linux +ifeq ($(OS),Linux) + ifneq (,$(filter periph_gpio,$(USEMODULE))) + USEMODULE += periph_gpio_linux + endif + ifneq (,$(filter periph_spi,$(USEMODULE))) + USEMODULE += periph_spidev_linux + endif endif + ifeq (,$(filter stdio_%,$(USEMODULE))) USEMODULE += stdio_native endif diff --git a/cpu/native/Makefile.features b/cpu/native/Makefile.features index 220dbbd58e..c8a5dff77d 100644 --- a/cpu/native/Makefile.features +++ b/cpu/native/Makefile.features @@ -17,7 +17,9 @@ FEATURES_PROVIDED += periph_pm FEATURES_PROVIDED += periph_pwm FEATURES_PROVIDED += ssp -# Access to hardware SPI bus is only supported on Linux hosts ifeq ($(OS),Linux) + # Access to hardware SPI bus is only supported on Linux hosts FEATURES_PROVIDED += periph_spi + # Hardware GPIO access is only available on Linux hosts + FEATURES_PROVIDED += periph_gpio periph_gpio_irq endif diff --git a/cpu/native/include/gpiodev_linux.h b/cpu/native/include/gpiodev_linux.h new file mode 100644 index 0000000000..456bdf659c --- /dev/null +++ b/cpu/native/include/gpiodev_linux.h @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2019 Benjamin Valentin + * + * This file is subject to the terms and conditions of the GNU Lesser General + * Public License v2.1. See the file LICENSE in the top level directory for + * more details. + */ + +/** + * @defgroup drivers_gpio_linux Linux User Mode GPIO Driver + * @ingroup cpu_native + * @brief Implementation of GPIO access from Linux User Space + * + * This module allows to connect a RIOT application that runs on a Linux host to + * the physical GPIO pins of that host. To do so, the application has to be + * compiled for the native board in a Linux environment. + * + * GPIO support is automatically included if either a module requiring the + * `periph_gpio` feature is added to the application or if it is explicitly + * listed as `FEATURES_REQUIRED` in the application's Makefile. + * + * At runtime, the process has to be connected to a specific GPIO bank on the host + * machine. GPIO banks are exposed as `/dev/gpiochipN` character files, where N + * is the bank ID to which several GPIO pins are connected. + * + * Example: + * + * ``` + * $ ./riot_native_app --gpio=/dev/gpiochip0 --gpio=/dev/gpiochip1 + * ``` + * + * This will add `/dev/gpiochip0` and `/dev/gpiochip1` as PORT(0) and PORT(1) in RIOT. + * The first pin can be used with PIN(0,0) as `gpio_t`, the second one on the first + * port would be PIN(0,1) and so on. + * + * Please refer to your board's documentation for the mapping of the pins. + * + * @{ + * + * @file + * @brief Implementation of GPIO access from Linux User Space + * + * @author Benjamin Valentin + */ + +#ifndef GPIODEV_LINUX_H +#define GPIODEV_LINUX_H + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief register `/dev/gpiochip*` device to be used for GPIO + * + * @param[in] device The gpiochip device to open. + * + * @return 0 on success, error otherwise + */ +int gpio_linux_setup(const char* device); + +/** + * @brief shutdown GPIO subsystem + * + * This closes all GPIO fds. + */ +void gpio_linux_teardown(void); + +#ifdef __cplusplus +} +#endif + +#endif /* GPIODEV_LINUX_H */ +/** @} */ diff --git a/cpu/native/include/periph_cpu.h b/cpu/native/include/periph_cpu.h index 8d34804fe0..87fceea658 100644 --- a/cpu/native/include/periph_cpu.h +++ b/cpu/native/include/periph_cpu.h @@ -32,6 +32,61 @@ extern "C" { #define CPUID_LEN (4U) #endif +/* GPIO configuration only if the module is available (=Linux) */ +#if defined(MODULE_PERIPH_GPIO_LINUX) || defined(DOXYGEN) +#include + +/** + * @name GPIO Configuration + * @{ + */ + +/** + * @brief The offset between Port and Pin + */ +#define GPIO_PORT_SHIFT (24) + +/** + * @brief Define a custom GPIO_PIN macro for native + */ +#define GPIO_PIN(port, pin) (gpio_t)((port << GPIO_PORT_SHIFT) | pin) + +#define HAVE_GPIO_MODE_T +#ifndef GPIOHANDLE_REQUEST_PULL_DOWN +#define GPIOHANDLE_REQUEST_PULL_DOWN (0xFF) +#endif +#ifndef GPIOHANDLE_REQUEST_PULL_UP +#define GPIOHANDLE_REQUEST_PULL_UP (0xFF) +#endif + +/** + * @brief Available pin modes + * + * Generally, a pin can be configured to be input or output. In output mode, a + * pin can further be put into push-pull or open drain configuration. Though + * this is supported by most platforms, this is not always the case, so driver + * implementations may return an error code if a mode is not supported. + */ +typedef enum { + GPIO_IN = GPIOHANDLE_REQUEST_INPUT, + GPIO_IN_PD = GPIOHANDLE_REQUEST_INPUT | GPIOHANDLE_REQUEST_PULL_DOWN, + GPIO_IN_PU = GPIOHANDLE_REQUEST_INPUT | GPIOHANDLE_REQUEST_PULL_UP, + GPIO_OUT = GPIOHANDLE_REQUEST_OUTPUT, + GPIO_OD = GPIOHANDLE_REQUEST_OPEN_DRAIN, + GPIO_OD_PU = GPIOHANDLE_REQUEST_OPEN_DRAIN | GPIOHANDLE_REQUEST_PULL_UP +} gpio_mode_t; + +#define HAVE_GPIO_FLANK_T +typedef enum { + GPIO_FALLING = GPIOEVENT_EVENT_FALLING_EDGE, /**< emit interrupt on falling flank */ + GPIO_RISING = GPIOEVENT_EVENT_RISING_EDGE, /**< emit interrupt on rising flank */ + GPIO_BOTH = GPIO_FALLING | GPIO_RISING /**< emit interrupt on both flanks */ +} gpio_flank_t; + +/** @} */ + +#endif /* MODULE_PERIPH_GPIO_LINUX | DOXYGEN */ + /** * @brief Prevent shared timer functions from being used */ diff --git a/cpu/native/periph/gpio.c b/cpu/native/periph/gpio.c index 39f353cbc9..8f9b82bb6b 100644 --- a/cpu/native/periph/gpio.c +++ b/cpu/native/periph/gpio.c @@ -19,6 +19,8 @@ #include "periph/gpio.h" +#ifndef MODULE_PERIPH_GPIO_LINUX + int gpio_init(gpio_t pin, gpio_mode_t mode) { (void) pin; (void) mode; @@ -51,4 +53,7 @@ void gpio_write(gpio_t pin, int value) { (void) pin; (void) value; } + +#endif /* !MODULE_PERIPH_GPIO_LINUX */ + /** @} */ diff --git a/cpu/native/periph/gpio_linux.c b/cpu/native/periph/gpio_linux.c new file mode 100644 index 0000000000..f9424d77d4 --- /dev/null +++ b/cpu/native/periph/gpio_linux.c @@ -0,0 +1,318 @@ +/* + * Copyright (C) 2019 Benjamin Valentin + * + * This file is subject to the terms and conditions of the GNU Lesser General + * Public License v2.1. See the file LICENSE in the top level directory for + * more details. + */ + +/** + * @ingroup cpu_native + * @ingroup drivers_periph_gpio + * @{ + * + * @file + * @brief native GPIO implementation + * + * @author Benjamin Valentin + */ + +#define _GNU_SOURCE + +#include +#include +#include +#include +#include +#include +#include + +#include "native_internal.h" +#include "periph/gpio.h" + +typedef struct { + gpio_cb_t cb; + void *arg; + bool enabled; +} native_gpio_cb_t; + +typedef struct { + int fd; + unsigned num_pins; + int *pins; +#ifdef MODULE_PERIPH_GPIO_IRQ + native_gpio_cb_t **cbs; +#endif +} native_port_t; + +static native_port_t *ports; +static unsigned port_numof; + +#define _port(p) (p >> GPIO_PORT_SHIFT) +#define _pin(p) (p & ((1 << GPIO_PORT_SHIFT) - 1)) + +int gpio_linux_setup(const char* gpiochip) +{ + struct gpiochip_info info; + int fd = real_open(gpiochip, O_RDWR); + + if (fd < 0) { + real_printf("GPIO: cannot open %s\n", gpiochip); + return fd; + } + + if (real_ioctl(fd, GPIO_GET_CHIPINFO_IOCTL, &info) < 0) { + real_close(fd); + real_printf("GPIO: can not query %s\n", gpiochip); + return -ENODEV; + } + + printf("GPIO: found %d pins on %s [%s]\n", info.lines, info.name, info.label); + + void *tmp = reallocarray(ports, port_numof + 1, sizeof(*ports)); + + if (tmp == NULL) { + real_close(fd); + real_printf("GPIO: out of memory\n"); + return -ENOMEM; + } + + ports = tmp; + ports[port_numof].fd = fd; + ports[port_numof].num_pins = info.lines; + ports[port_numof].pins = calloc(info.lines, sizeof(*ports[0].pins)); +#ifdef MODULE_PERIPH_GPIO_IRQ + ports[port_numof].cbs = calloc(info.lines, sizeof(*ports[0].cbs)); +#endif + ++port_numof; + + return 0; +} + +void gpio_linux_teardown(void) +{ + for (unsigned i = 0; i < port_numof; ++i) { + for (unsigned j = 0; j < ports[i].num_pins; ++j) { + + if (ports[i].pins[j] > 0) { + real_close(ports[i].pins[j]); + } +#ifdef MODULE_PERIPH_GPIO_IRQ + if (ports[i].cbs[j]) { + real_free(ports[i].cbs[j]); + } +#endif + } + + if (ports[i].pins) { + real_free(ports[i].pins); + } + + if (ports[i].fd) { + real_close(ports[i].fd); + } +#ifdef MODULE_PERIPH_GPIO_IRQ + if (ports[i].cbs) { + real_free(ports[i].cbs); + } +#endif + } + + if (ports) { + real_free(ports); + ports = NULL; + } + + port_numof = 0; +} + +int gpio_init(gpio_t pin, gpio_mode_t mode) +{ + int res; + const unsigned p = _pin(pin); + struct gpiohandle_request req = { + .lineoffsets[0] = p, + .flags = mode, + .lines = 1 + }; + native_port_t *port; + + if (mode == 0xFF) { + return -EINVAL; + } + + if (_port(pin) >= port_numof) { + return -EINVAL; + } + + port = &ports[_port(pin)]; + + if (p >= port->num_pins) { + return -EINVAL; + } + + /* if the pin is already configured, close it first */ + if (port->pins[p] > 0) { + real_close(port->pins[p]); + port->pins[p] = 0; + } + + res = real_ioctl(port->fd, GPIO_GET_LINEHANDLE_IOCTL, &req); + + if (res < 0) { + return res; + } + + port->pins[p] = req.fd; + return 0; +} + +int gpio_read(gpio_t pin) +{ + struct gpiohandle_data data; + + if (_port(pin) >= port_numof) { + return -EINVAL; + } + + int fd = ports[_port(pin)].pins[_pin(pin)]; + + if (fd <= 0) { + return -EINVAL; + } + + real_ioctl(fd, GPIOHANDLE_GET_LINE_VALUES_IOCTL, &data); + + return data.values[0]; +} + +static void _set(gpio_t pin, uint8_t val) +{ + int fd = ports[_port(pin)].pins[_pin(pin)]; + + if (fd > 0) { + real_ioctl(fd, GPIOHANDLE_SET_LINE_VALUES_IOCTL, &val); + } +} + +void gpio_set(gpio_t pin) +{ + _set(pin, 1); +} + +void gpio_clear(gpio_t pin) +{ + _set(pin, 0); +} + +void gpio_toggle(gpio_t pin) +{ + _set(pin, !gpio_read(pin)); +} + +void gpio_write(gpio_t pin, int value) +{ + _set(pin, value); +} + +#ifdef MODULE_PERIPH_GPIO_IRQ +#include "async_read.h" + +static void _async_read_wrapper(int fd, void *arg) { + native_gpio_cb_t *cb = arg; + + struct gpioevent_data event; + real_read(fd, &event, sizeof(event)); + + if (cb->cb && cb->enabled) { + cb->cb(cb->arg); + } + + native_async_read_continue(fd); +} + +int gpio_init_int(gpio_t pin, gpio_mode_t mode, gpio_flank_t flank, + gpio_cb_t cb, void *arg) +{ + int res; + const unsigned p = _pin(pin); + struct gpioevent_request req = { + .lineoffset = p, + .handleflags = mode, + .eventflags = flank + }; + native_port_t *port; + + if (mode == 0xFF) { + return -EINVAL; + } + + if (_port(pin) >= port_numof) { + return -EINVAL; + } + + port = &ports[_port(pin)]; + + if (p >= port->num_pins) { + return -EINVAL; + } + + /* if the pin is already configured, close it first */ + if (port->pins[p] > 0) { + real_close(port->pins[p]); + port->pins[p] = 0; + } + + res = real_ioctl(port->fd, GPIO_GET_LINEEVENT_IOCTL, &req); + + if (res < 0) { + return res; + } + + port->pins[p] = req.fd; + + if (port->cbs[p] == NULL) { + port->cbs[p] = malloc(sizeof(native_gpio_cb_t)); + } + + port->cbs[p]->cb = cb; + port->cbs[p]->arg = arg; + port->cbs[p]->enabled = true; + + native_async_read_setup(); + native_async_read_add_int_handler(req.fd, port->cbs[p], _async_read_wrapper); + + return 0; +} + +static void _set_irq_enabled(gpio_t pin, bool enabled) +{ + native_port_t *port; + const unsigned p = _pin(pin); + + if (_port(pin) >= port_numof) { + return; + } + + port = &ports[_port(pin)]; + + if (p >= port->num_pins) { + return; + } + + port->cbs[p]->enabled = enabled; +} + +void gpio_irq_enable(gpio_t pin) +{ + _set_irq_enabled(pin, true); +} + +void gpio_irq_disable(gpio_t pin) +{ + _set_irq_enabled(pin, false); +} + +#endif /* MODULE_PERIPH_GPIO_IRQ */ + +/** @} */ diff --git a/cpu/native/periph/pm.c b/cpu/native/periph/pm.c index 72c2d8a989..ff4bfe32aa 100644 --- a/cpu/native/periph/pm.c +++ b/cpu/native/periph/pm.c @@ -27,9 +27,11 @@ #include "tty_uart.h" #ifdef MODULE_PERIPH_SPIDEV_LINUX -/* Only manage SPI if it is part of the build */ #include "spidev_linux.h" #endif +#ifdef MODULE_PERIPH_GPIO_LINUX +#include "gpiodev_linux.h" +#endif #define ENABLE_DEBUG (0) #include "debug.h" @@ -51,6 +53,9 @@ void pm_off(void) puts("\nnative: exiting"); #ifdef MODULE_PERIPH_SPIDEV_LINUX spidev_linux_teardown(); +#endif +#ifdef MODULE_PERIPH_GPIO_LINUX + gpio_linux_teardown(); #endif real_exit(EXIT_SUCCESS); } @@ -63,6 +68,9 @@ void pm_reboot(void) #ifdef MODULE_PERIPH_SPIDEV_LINUX spidev_linux_teardown(); #endif +#ifdef MODULE_PERIPH_GPIO_LINUX + gpio_linux_teardown(); +#endif if (real_execve(_native_argv[0], _native_argv, NULL) == -1) { err(EXIT_FAILURE, "reboot: execve"); diff --git a/cpu/native/startup.c b/cpu/native/startup.c index a9fb963dd3..12c773499a 100644 --- a/cpu/native/startup.c +++ b/cpu/native/startup.c @@ -81,6 +81,9 @@ netdev_tap_params_t netdev_tap_params[NETDEV_TAP_MAX]; #ifdef MODULE_PERIPH_SPIDEV_LINUX #include "spidev_linux.h" #endif +#ifdef MODULE_PERIPH_GPIO_LINUX +#include "gpiodev_linux.h" +#endif #ifdef MODULE_SOCKET_ZEP #include "socket_zep_params.h" @@ -92,6 +95,9 @@ extern char eeprom_file[EEPROM_FILEPATH_MAX_LEN]; #endif static const char short_opts[] = ":hi:s:deEoc:" +#ifdef MODULE_PERIPH_GPIO_LINUX + "g:" +#endif #ifdef MODULE_MTD_NATIVE "m:" #endif @@ -115,6 +121,9 @@ static const struct option long_opts[] = { { "stderr-noredirect", no_argument, NULL, 'E' }, { "stdout-pipe", no_argument, NULL, 'o' }, { "uart-tty", required_argument, NULL, 'c' }, +#ifdef MODULE_PERIPH_GPIO_LINUX + { "gpio", required_argument, NULL, 'g' }, +#endif #ifdef MODULE_MTD_NATIVE { "mtd", required_argument, NULL, 'm' }, #endif @@ -260,6 +269,9 @@ void usage_exit(int status) } #endif real_printf(" [-i ] [-d] [-e|-E] [-o] [-c ]\n"); +#ifdef MODULE_PERIPH_GPIO_LINUX + real_printf(" [-g ]\n"); +#endif #if defined(MODULE_SOCKET_ZEP) && (SOCKET_ZEP_MAX > 0) real_printf(" -z [[:,]:]\n"); for (int i = 0; i < SOCKET_ZEP_MAX - 1; i++) { @@ -295,6 +307,11 @@ void usage_exit(int status) " -c , --uart-tty=\n" " specify TTY device for UART. This argument can be used multiple\n" " times (up to UART_NUMOF)\n" +#ifdef MODULE_PERIPH_GPIO_LINUX +" -g , --gpio=\n" +" specify gpiochip device for GPIO access. This argument can be used multiple times.\n" +" Example: --gpio=/dev/gpiochip0 uses gpiochip0 for port 0\n" +#endif #if defined(MODULE_SOCKET_ZEP) && (SOCKET_ZEP_MAX > 0) " -z [:,]: --zep=[:,]:\n" " provide a ZEP interface with local address and port (, )\n" @@ -463,6 +480,13 @@ __attribute__((constructor)) static void startup(int argc, char **argv, char **e } force_stderr = true; break; +#ifdef MODULE_PERIPH_GPIO_LINUX + case 'g': + if (gpio_linux_setup(optarg) < 0) { + usage_exit(EXIT_FAILURE); + } + break; +#endif case 'o': stdouttype = _STDIOTYPE_FILE; break;