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 6 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
206 changes: 147 additions & 59 deletions utility/SPIDEV/gpio.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,91 @@
*
*/
#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"

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

// A struct to keep a track of the open/closed file descriptor for specified GPIO chip
struct GlobalCache
{
const char* chip = RF24_SPIDEV_GPIO_CHIP;
int fd = -1;

// Open the File Descriptor for the GPIO chip
void 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);
}
}
}

/// Close the File Descriptor for the GPIO chip
void closeDevice()
{
if (fd >= 0) {
close(fd);
fd = -1;
}
}

// should be called automatically on program start.
// Here, we do some one-off configuration.
GlobalCache()
{
try {
openDevice();
}
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

const char* dev_name = "/dev/gpiochip4";
// 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
}

// 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.
~GlobalCache()
{
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);
}
}
}
} gpioCache;

GPIO::GPIO()
{
Expand All @@ -21,82 +100,91 @@ GPIO::~GPIO()
{
}

void GPIO::open(int port, int DDR)
void GPIO::open(rf24_gpio_pin_t port, int DDR)
{
int fd;
fd = ::open(dev_name, O_RDONLY);
if (fd >= 0) {
::close(fd);
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 {
dev_name = "/dev/gpiochip0";
fd = ::open(dev_name, O_RDONLY);
if (fd >= 0) {
::close(fd);
}
else {
throw GPIOException("can't open /dev/gpiochip");
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;
}
}
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::close(int port)
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)
}

int GPIO::read(int port)
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;
}

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;
}
::close(rq.fd);
return data.values[0];
data.bits = 0ULL;

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

void GPIO::write(int port, int value)
void GPIO::write(rf24_gpio_pin_t port, int value)
{

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");
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;
}
rq.lineoffsets[0] = port;
rq.flags = GPIOHANDLE_REQUEST_OUTPUT;
rq.lines = 1;
ret = ioctl(fd, GPIO_GET_LINEHANDLE_IOCTL, &rq);
if (ret == -1) {
throw GPIOException("Can't get line handle from IOCTL");
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);
}
20 changes: 16 additions & 4 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 @@ -41,13 +53,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