1
0
mirror of https://github.com/RIOT-OS/RIOT.git synced 2024-12-29 04:50:03 +01:00

cpu/native: add Linux GPIO implementation

This commit is contained in:
Benjamin Valentin 2019-10-04 15:48:46 +02:00 committed by Benjamin Valentin
parent 8b00d14014
commit ea25e8580c
9 changed files with 498 additions and 4 deletions

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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 <benpicco@googlemail.com>
*/
#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 */
/** @} */

View File

@ -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 <linux/gpio.h>
/**
* @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
*/

View File

@ -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 */
/** @} */

View File

@ -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 <benpicco@googlemail.com>
*/
#define _GNU_SOURCE
#include <errno.h>
#include <fcntl.h>
#include <linux/gpio.h>
#include <stdbool.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/types.h>
#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 */
/** @} */

View File

@ -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");

View File

@ -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 <id>] [-d] [-e|-E] [-o] [-c <tty>]\n");
#ifdef MODULE_PERIPH_GPIO_LINUX
real_printf(" [-g <gpiochip>]\n");
#endif
#if defined(MODULE_SOCKET_ZEP) && (SOCKET_ZEP_MAX > 0)
real_printf(" -z [[<laddr>:<lport>,]<raddr>:<rport>]\n");
for (int i = 0; i < SOCKET_ZEP_MAX - 1; i++) {
@ -295,6 +307,11 @@ void usage_exit(int status)
" -c <tty>, --uart-tty=<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>, --gpio=<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 [<laddr>:<lport>,]<raddr>:<rport> --zep=[<laddr>:<lport>,]<raddr>:<rport>\n"
" provide a ZEP interface with local address and port (<laddr>, <lport>)\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;