Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update SPIDEV driver for Character Device v2 ABI; cache FDs #959

Merged
merged 7 commits into from
Mar 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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