Skip to content

Commit

Permalink
Update SPIDEV driver for Character Device v2 ABI; cache FDs (#959)
Browse files Browse the repository at this point in the history
* update GPIO char dev ABI to v2
* show detailed exception msgs
* cache gpio pins in use
* allow user to select different /dev/gpiochipX
* close GPIO chip FD when not being used.
  • Loading branch information
2bndy5 authored Mar 13, 2024
1 parent 9d0aa8d commit 05efe34
Show file tree
Hide file tree
Showing 4 changed files with 180 additions and 65 deletions.
7 changes: 7 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,13 @@ if(DEFINED RF24_SPI_SPEED)
RF24_SPI_SPEED=${RF24_SPI_SPEED}
)
endif()
# allow user customization of default GPIO chip used with the SPIDEV driver
if(DEFINED RF24_SPIDEV_GPIO_CHIP)
message(STATUS "RF24_SPIDEV_GPIO_CHIP set to ${RF24_SPIDEV_GPIO_CHIP}")
target_compile_definitions(${LibTargetName} PUBLIC
RF24_SPIDEV_GPIO_CHIP="${RF24_SPIDEV_GPIO_CHIP}"
)
endif()
# conditionally disable interruot support (a pigpio specific feature)
if("${LibPIGPIO}" STREQUAL "LibPIGPIO-NOTFOUND" OR DEFINED RF24_NO_INTERRUPT)
message(STATUS "Disabling IRQ pin support")
Expand Down
2 changes: 0 additions & 2 deletions utility/SPIDEV/RF24_arch_config.h
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,6 @@
#endif

typedef uint16_t prog_uint16_t;
typedef uint16_t rf24_gpio_pin_t;
#define RF24_PIN_INVALID 0xFFFF

#define PSTR(x) (x)
#define printf_P printf
Expand Down
193 changes: 135 additions & 58 deletions utility/SPIDEV/gpio.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,97 +6,174 @@
*
*/
#include <linux/gpio.h>
#include "gpio.h"
#include <unistd.h> // close()
#include <fcntl.h> // open()
#include <sys/ioctl.h> // ioctl()
#include <errno.h> // errno, strerror()
#include <string.h> // std::string, strcpy()
#include <map>
#include "gpio.h"

const char* dev_name = "/dev/gpiochip4";
// instantiate some global structs to setup cache
// doing this globally ensures the data struct is zero-ed out
typedef int gpio_fd; // for readability
std::map<rf24_gpio_pin_t, gpio_fd> cachedPins;
struct gpio_v2_line_request request;
struct gpio_v2_line_values data;
struct gpiochip_info chipMeta;

GPIO::GPIO()
void GPIOChipCache::openDevice()
{
if (fd < 0) {
fd = open(chip, O_RDONLY);
if (fd < 0) {
std::string msg = "Can't open device ";
msg += chip;
msg += "; ";
msg += strerror(errno);
throw GPIOException(msg);
}
}
}

GPIO::~GPIO()
void GPIOChipCache::closeDevice()
{
if (fd >= 0) {
close(fd);
fd = -1;
}
}

void GPIO::open(int port, int DDR)
GPIOChipCache::GPIOChipCache()
{
int fd;
fd = ::open(dev_name, O_RDONLY);
if (fd >= 0) {
::close(fd);
try {
openDevice();
}
else {
dev_name = "/dev/gpiochip0";
fd = ::open(dev_name, O_RDONLY);
if (fd >= 0) {
::close(fd);
}
else {
throw GPIOException("can't open /dev/gpiochip");
catch (GPIOException& exc) {
chip = "/dev/gpiochip0";
openDevice();
}
request.num_lines = 1;
strcpy(request.consumer, "RF24 lib");
data.mask = 1ULL; // only change value for specified pin

// cache chip info
int ret = ioctl(fd, GPIO_GET_CHIPINFO_IOCTL, &chipMeta);
if (ret < 0) {
std::string msg = "Could not gather info about ";
msg += chip;
throw GPIOException(msg);
return;
}
closeDevice(); // in case other apps want to access it
}

GPIOChipCache::~GPIOChipCache()
{
closeDevice();
for (std::map<rf24_gpio_pin_t, gpio_fd>::iterator i = cachedPins.begin(); i != cachedPins.end(); ++i) {
if (i->second > 0) {
close(i->second);
}
}
}

void GPIO::close(int port)
// GPIO chip cache manager
GPIOChipCache gpioCache;

GPIO::GPIO()
{
}

int GPIO::read(int port)
GPIO::~GPIO()
{
}

struct gpiohandle_request rq;
struct gpiohandle_data data;
int fd, ret;
fd = ::open(dev_name, O_RDONLY);
if (fd >= 0) {
rq.lineoffsets[0] = port;
rq.flags = GPIOHANDLE_REQUEST_INPUT;
rq.lines = 1;
ret = ioctl(fd, GPIO_GET_LINEHANDLE_IOCTL, &rq);
if (ret == -1) {
throw GPIOException("Can't get line handle from IOCTL");
return ret;
}
::close(fd);
ret = ioctl(rq.fd, GPIOHANDLE_GET_LINE_VALUES_IOCTL, &data);
if (ret == -1) {
throw GPIOException("Can't get line value from IOCTL");
return ret;
void GPIO::open(rf24_gpio_pin_t port, int DDR)
{
if (port > chipMeta.lines) {
std::string msg = "pin number " + std::to_string(port) + " not available for " + gpioCache.chip;
throw GPIOException(msg);
return;
}

// check if pin is already in use
std::map<rf24_gpio_pin_t, gpio_fd>::iterator pin = cachedPins.find(port);
if (pin == cachedPins.end()) { // pin not in use; add it to cached request
request.offsets[0] = port;
request.fd = 0;
}
else {
request.fd = pin->second;
}

gpioCache.openDevice();
int ret;
if (request.fd <= 0) {
ret = ioctl(gpioCache.fd, GPIO_V2_GET_LINE_IOCTL, &request);
if (ret == -1 || request.fd <= 0) {
std::string msg = "[GPIO::open] Can't get line handle from IOCTL; ";
msg += strerror(errno);
throw GPIOException(msg);
return;
}
::close(rq.fd);
return data.values[0];
}
return -1;
gpioCache.closeDevice(); // in case other apps want to access it

// set the pin and direction
request.config.flags = DDR ? GPIO_V2_LINE_FLAG_OUTPUT : GPIO_V2_LINE_FLAG_INPUT;

ret = ioctl(request.fd, GPIO_V2_LINE_SET_CONFIG_IOCTL, &request.config);
if (ret == -1) {
std::string msg = "[gpio::open] Can't set line config; ";
msg += strerror(errno);
throw GPIOException(msg);
return;
}
cachedPins.insert(std::pair<rf24_gpio_pin_t, gpio_fd>(port, request.fd));
}

void GPIO::write(int port, int value)
void GPIO::close(rf24_gpio_pin_t port)
{
// This is not really used in RF24 convention (designed for embedded apps).
// Instead rely on gpioCache destructor (see above)
}

struct gpiohandle_request rq;
struct gpiohandle_data data;
int fd, ret;
fd = ::open(dev_name, O_RDONLY);
if (fd < 0) {
throw GPIOException("Can't open dev");
return;
int GPIO::read(rf24_gpio_pin_t port)
{
std::map<rf24_gpio_pin_t, gpio_fd>::iterator pin = cachedPins.find(port);
if (pin == cachedPins.end() || pin->second <= 0) {
throw GPIOException("[GPIO::read] pin not initialized! Use GPIO::open() first");
return -1;
}
rq.lineoffsets[0] = port;
rq.flags = GPIOHANDLE_REQUEST_OUTPUT;
rq.lines = 1;
ret = ioctl(fd, GPIO_GET_LINEHANDLE_IOCTL, &rq);

data.bits = 0ULL;

int ret = ioctl(pin->second, GPIO_V2_LINE_GET_VALUES_IOCTL, &data);
if (ret == -1) {
throw GPIOException("Can't get line handle from IOCTL");
std::string msg = "[GPIO::read] Can't get line value from IOCTL; ";
msg += strerror(errno);
throw GPIOException(msg);
return ret;
}
return data.bits & 1ULL;
}

void GPIO::write(rf24_gpio_pin_t port, int value)
{
std::map<rf24_gpio_pin_t, gpio_fd>::iterator pin = cachedPins.find(port);
if (pin == cachedPins.end() || pin->second <= 0) {
throw GPIOException("[GPIO::write] pin not initialized! Use GPIO::open() first");
return;
}
::close(fd);
data.values[0] = value;
ret = ioctl(rq.fd, GPIOHANDLE_SET_LINE_VALUES_IOCTL, &data);

data.bits = value;

int ret = ioctl(pin->second, GPIO_V2_LINE_SET_VALUES_IOCTL, &data);
if (ret == -1) {
throw GPIOException("Can't set line value from IOCTL");
std::string msg = "[GPIO::write] Can't set line value from IOCTL; ";
msg += strerror(errno);
throw GPIOException(msg);
return;
}
::close(rq.fd);
}
43 changes: 38 additions & 5 deletions utility/SPIDEV/gpio.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,18 @@
#include <cstdio>
#include <map>
#include <stdexcept>
#include <cstdint>

typedef uint16_t rf24_gpio_pin_t;
#define RF24_PIN_INVALID 0xFFFF

#ifndef RF24_SPIDEV_GPIO_CHIP
/**
* The default GPIO chip to use. Defaults to `/dev/gpiochip4` (for RPi5).
* Falls back to `/dev/gpiochip0` if this value is somehow incorrect.
*/
#define RF24_SPIDEV_GPIO_CHIP "/dev/gpiochip4"
#endif

/** Specific exception for SPI errors */
class GPIOException : public std::runtime_error
Expand All @@ -27,7 +39,28 @@ class GPIOException : public std::runtime_error
}
};

typedef int GPIOfdCache_t;
/// A struct to manage the GPIO chip file descriptor.
/// This struct's destructor should close any cached GPIO pin requests' file descriptors.
struct GPIOChipCache
{
const char* chip = RF24_SPIDEV_GPIO_CHIP;
int fd = -1;

/// Open the File Descriptor for the GPIO chip
void openDevice();

/// Close the File Descriptor for the GPIO chip
void closeDevice();

/// should be called automatically on program start.
/// Here, we do some one-off configuration.
GPIOChipCache();

/// Should be called automatically on program exit.
/// What we need here is to make sure that the File Descriptors used to
/// control GPIO pins are properly closed.
~GPIOChipCache();
};

class GPIO
{
Expand All @@ -41,13 +74,13 @@ class GPIO

GPIO();

static void open(int port, int DDR);
static void open(rf24_gpio_pin_t port, int DDR);

static void close(int port);
static void close(rf24_gpio_pin_t port);

static int read(int port);
static int read(rf24_gpio_pin_t port);

static void write(int port, int value);
static void write(rf24_gpio_pin_t port, int value);

virtual ~GPIO();

Expand Down

0 comments on commit 05efe34

Please sign in to comment.