Fix SPI and GPIO handling to support recent kernels.

This driver was previously using the SYSFS API to interact with GPIO. This API is deprecated since kernel 4.8 but was still working up to 5.10. The SYSFS API was removed in more recent version and is now replaced by a character device interface.

In this commit, I replaced calls to the old SYSFS API to this new GPIO interface. I also added a small test program to test GPIO (display info about all GPIO chips and tries to detect the gpiochip corresponding to the CH341 of the USB adapter).

Also, the initialization of the SPI bus now binds to spidev driver since this is needed to make /dev/spidevx.y available since kernel 5.1.
pull/3/head
Jean-François Milants 2022-04-09 16:32:34 +02:00
rodzic a1ccddba7a
commit 5a7f49301b
10 zmienionych plików z 307 dodań i 82 usunięć

Wyświetl plik

@ -1,3 +1,4 @@
add_subdirectory(communicator)
add_subdirectory(pinephone-communicator)
add_subdirectory(usb-communicator)
add_subdirectory(usb-communicator)
add_subdirectory(gpio)

Wyświetl plik

@ -6,8 +6,9 @@ class PinedioLoraRadio;
class Communicator {
public:
explicit Communicator(PineDio::LoRa::PinedioLoraRadio &radio);
~Communicator();
void Run();
void Stop();
private:
PineDio::LoRa::PinedioLoraRadio &radio;

Wyświetl plik

@ -17,6 +17,12 @@ Communicator::Communicator(PineDio::LoRa::PinedioLoraRadio &radio) : radio{radio
receiveTask.reset(new std::thread([this](){Receive();}));
}
Communicator::~Communicator() {
running = false;
receiveTask->join();
}
void Communicator::Run() {
running = true;
@ -39,8 +45,10 @@ void Communicator::Run() {
}
void Communicator::Receive() {
while(true){
auto data = radio.Receive();
while(running){
auto data = radio.Receive(std::chrono::milliseconds{100});
if(data.empty())
continue;
std::cout << "Data received on LoRa radio : " << std::endl;
std::cout << "\tHEX: ";
for(auto d : data) {
@ -55,3 +63,7 @@ void Communicator::Receive() {
}
}
void Communicator::Stop() {
running = false;
}

Wyświetl plik

@ -0,0 +1,5 @@
project(gpio)
cmake_minimum_required(VERSION 3.21)
add_executable(gpio
main.cpp)

99
apps/gpio/main.cpp 100644
Wyświetl plik

@ -0,0 +1,99 @@
#include <iostream>
#include <dirent.h>
#include <linux/gpio.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <vector>
struct FileDescriptor {
int fd;
};
void PrintChipInfo(const std::string& chip);
void PrintLineInfo(FileDescriptor chip, int line);
std::string GetCh341ChipFilename();
std::vector<std::string> GetChipChipNames();
int main() {
auto chips = GetChipChipNames();
std::cout << "Number of GPIO chips : " << chips.size() << std::endl;
for(const auto& chip : chips) {
PrintChipInfo(chip);
}
try {
auto ch341Index = GetCh341ChipFilename();
std::cout << std::endl << std::endl << " --> CH341 GPIO chip detected at index " << ch341Index << std::endl;
} catch(const std::runtime_error& error) {
std::cerr << "Error : " << error.what();
}
return 0;
}
std::vector<std::string> GetChipChipNames() {
auto gpioFilenameFilter = [](const struct dirent *entry) -> int {
std::string filename = entry->d_name;
return filename.rfind("gpiochip", 0) == 0;
};
static const char* devString = "/dev/";
std::vector<std::string> filenames;
struct dirent **entries;
auto nb = scandir(devString, &entries, gpioFilenameFilter, alphasort);
for(int i = 0; i < nb; i++) {
std::string name = std::string{devString} + std::string{entries[i]->d_name};
filenames.push_back(name);
}
return filenames;
}
void PrintChipInfo(const std::string& chip) {
struct gpiochip_info info;
auto fd = open(chip.c_str(), O_RDWR);
auto res = ioctl(fd, GPIO_GET_CHIPINFO_IOCTL, &info);
if(res == 0) {
std::cout << chip << " : " << info.name << " (" << info.label
<< ") : " << info.lines << " lines" << std::endl;
for(int i = 0; i < info.lines; i++) {
PrintLineInfo({fd}, i);
}
} else {
std::cerr << "Error while getting chip info for " << chip << std::endl;
}
}
void PrintLineInfo(FileDescriptor fileDescriptor, int line) {
struct gpioline_info info;
info.line_offset = line;
auto res = ioctl(fileDescriptor.fd, GPIO_GET_LINEINFO_IOCTL, &info);
if(res == 0) {
std::cout << "\t[" << line << "] " << info.name << " - " << info.consumer << " - " << info.flags << std::endl;
} else {
std::cerr << "Error while getting line info for line " << line;
}
}
bool GetChipInfo(const std::string chip, struct gpiochip_info& info) {
auto fd = open(chip.c_str(), O_RDWR);
auto res = ioctl(fd, GPIO_GET_CHIPINFO_IOCTL, &info);
return res == 0;
}
std::string GetCh341ChipFilename() {
static const char* ch341ChipLabel = "ch341";
static const size_t ch341NbLines = 16;
auto chips = GetChipChipNames();
for(auto& chip : chips) {
struct gpiochip_info info;
if(GetChipInfo(chip, info)) {
if(std::string{info.label} == std::string{ch341ChipLabel} && info.lines == ch341NbLines)
return chip;
}
}
throw std::runtime_error("Cannot find a GPIO chip corresponding to the CH341");
}

Wyświetl plik

@ -2,12 +2,21 @@
#include <iostream>
#include "PineDio/LoRa/UsbAdapter.h"
#include "PineDio/LoRa/Exceptions.h"
#include <signal.h>
PineDio::LoRa::UsbAdapter usbAdapter;
PineDio::LoRa::PinedioLoraRadio radio(usbAdapter);
PineDio::LoRa::Communicator communicator(radio);
void sig_handler(int signum) {
std::cout << "Press ENTER to exit..." << std::endl;
communicator.Stop();
}
int main() {
signal(SIGINT, sig_handler);
try {
PineDio::LoRa::UsbAdapter usbAdapter;
PineDio::LoRa::PinedioLoraRadio radio(usbAdapter);
PineDio::LoRa::Communicator communicator(radio);
communicator.Run();
} catch(const PineDio::LoRa::InitializationException& ex) {
std::cerr << "Initialization error : " << ex.what() << std::endl;

Wyświetl plik

@ -12,7 +12,7 @@ public:
virtual void Initialize();
virtual void Send(std::vector<uint8_t> data);
virtual std::vector<uint8_t> Receive();
virtual std::vector<uint8_t> Receive(std::chrono::milliseconds timeout);
private:
SX126x& radio;
bool dataReceived {false};

Wyświetl plik

@ -1,19 +1,25 @@
#pragma once
#include <PineDio/LoRa/PinedioLoraRadio.h>
#include <memory>
#include <linux/gpio.h>
namespace PineDio::LoRa {
class UsbAdapter : public SX126x {
public:
UsbAdapter();
~UsbAdapter() override;
private:
uint8_t HalGpioRead(GpioPinFunction_t func) override;
void HalGpioWrite(GpioPinFunction_t func, uint8_t value) override;
void HalSpiTransfer(uint8_t *buffer_in, const uint8_t *buffer_out, uint16_t size) override;
void OpenSpi();
void OpenGpio();
int handle;
struct gpiohandle_request resetGpio;
struct gpiohandle_request busyGpio;
void RxDone();
};

Wyświetl plik

@ -43,7 +43,7 @@ void PinedioLoraRadio::Initialize() {
static char* message = "Hello, I'm a Pinephone!";
static const char* message = "Hello, I'm a Pinephone!";
auto s = strlen(message);
SX126x::PacketParams_t packetParams;
@ -63,9 +63,10 @@ void PinedioLoraRadio::Send(const std::vector<uint8_t> data) {
transmitBuffer = data; //copy
}
std::vector<uint8_t> PinedioLoraRadio::Receive() {
std::cout << "[PinedioLoraRadio] Receive()" << std::endl;
std::vector<uint8_t> PinedioLoraRadio::Receive(std::chrono::milliseconds timeout) {
bool running = true;
auto startTime = std::chrono::steady_clock::now();
std::vector<uint8_t> buffer;
while(running) {
radio.ProcessIrqs();
@ -87,13 +88,18 @@ std::vector<uint8_t> PinedioLoraRadio::Receive() {
}
if(dataReceived) {
buffer = receivedBuffer;
break;
}
if(std::chrono::steady_clock::now() - startTime > timeout)
break;
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
dataReceived = false;
return receivedBuffer; //copy
return buffer; //copy
}
void PinedioLoraRadio::OnDataReceived() {

Wyświetl plik

@ -3,97 +3,121 @@
#include <unistd.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <linux/types.h>
#include <linux/spi/spidev.h>
#include <dirent.h>
#include <linux/gpio.h>
using namespace PineDio::LoRa;
namespace {
const char *spiDriverOverrideFilePath ="/sys/class/spi_master/spi0/spi0.0/driver_override";
const char *spidevOverride = "spidev";
const char *spiBindFilePath = "/sys/bus/spi/drivers/spidev/bind";
const char *spiUnbindFilePath = "/sys/bus/spi/drivers/spidev/unbind";
const char *spiBind = "spi0.0";
std::vector<std::string> GetChipChipNames() {
auto gpioFilenameFilter = [](const struct dirent *entry) -> int {
std::string filename = entry->d_name;
return filename.rfind("gpiochip", 0) == 0;
};
static const char *devString = "/dev/";
std::vector<std::string> filenames;
struct dirent **entries;
auto nb = scandir(devString, &entries, gpioFilenameFilter, alphasort);
for (int i = 0; i < nb; i++) {
std::string name = std::string{devString} + std::string{entries[i]->d_name};
filenames.push_back(name);
}
return filenames;
}
bool GetChipInfo(const std::string chip, struct gpiochip_info &info) {
auto fd = open(chip.c_str(), O_RDWR);
auto res = ioctl(fd, GPIO_GET_CHIPINFO_IOCTL, &info);
return res == 0;
}
std::string GetCh341ChipFilename() {
static const char *ch341ChipLabel = "ch341";
static const size_t ch341NbLines = 16;
auto chips = GetChipChipNames();
for (auto &chip : chips) {
struct gpiochip_info info;
if (GetChipInfo(chip, info)) {
if (std::string{info.label} == std::string{ch341ChipLabel} &&
info.lines == ch341NbLines)
return chip;
}
}
throw std::runtime_error("Cannot find a GPIO chip corresponding to the CH341");
}
int GetGpioLineIndex(const std::string &chip, const char *name) {
struct gpiochip_info info;
auto fd = open(chip.c_str(), O_RDWR);
auto res = ioctl(fd, GPIO_GET_CHIPINFO_IOCTL, &info);
if(res != 0)
throw std::runtime_error("IOCTL error (GPIO_GET_CHIPINFO_IOCTL failed)");
for (int i = 0; i < info.lines; i++) {
struct gpioline_info infoLine;
infoLine.line_offset = i;
res = ioctl(fd, GPIO_GET_LINEINFO_IOCTL, &infoLine);
if(res != 0)
throw std::runtime_error("IOCTL error (GPIO_GET_LINEINFO_IOCTL failed)");
if (strcmp(name, infoLine.name) == 0)
return i;
}
throw std::runtime_error("Cannot find GPIO line");
}
}
UsbAdapter::UsbAdapter() {
std::cout << "[UsbAdapter] ctor()" << std::endl;
OpenGpio();
OpenSpi();
}
// TODO remove code duplication for exporting GPIO
if(access("/sys/class/gpio/ini", F_OK) != 0 ) {
int fd;
if ((fd = open("/sys/class/gpio/export", O_WRONLY)) == -1) {
perror("Error while opening GPIO export file");
// TODO ERROR
}
if (write(fd, "240", 3) == -1) { // TODO how to find the GPIO number programatically?
perror("Error while exporting GPIO \"ini\"");
// TODO ERROR
}
close(fd);
}
UsbAdapter::~UsbAdapter() {
close(handle);
auto fd = open(spiUnbindFilePath, O_WRONLY);
write(fd, spiBind, strlen(spiBind));
if(access("/sys/class/gpio/slct", F_OK) != 0 ) {
int fd;
if ((fd = open("/sys/class/gpio/export", O_WRONLY)) == -1) {
perror("Error while opening GPIO export file");
// TODO ERROR
}
if (write(fd, "244", 3) == -1) { // TODO how to find the GPIO number programatically?
perror("Error while exporting GPIO \"sclt\"");
// TODO ERROR
}
close(fd);
}
handle = open("/dev/spidev0.0", O_RDWR);
if(handle == -1) {
// TODO error
printf("SPI IOCTL error %s\n", strerror(errno));
}
uint8_t mmode = SPI_MODE_0;
uint8_t lsb = 0;
uint8_t bitsperword = 8;
ioctl(handle, SPI_IOC_RD_BITS_PER_WORD, &bitsperword);
ioctl(handle, SPI_IOC_WR_MODE, &mmode);
ioctl(handle, SPI_IOC_WR_LSB_FIRST, &lsb);
close(fd);
}
uint8_t UsbAdapter::HalGpioRead(SX126x::GpioPinFunction_t func) {
if(func != GpioPinFunction_t::GPIO_PIN_BUSY) {
std::cout << "ERROR" << std::endl;
throw;
}
if(func != GpioPinFunction_t::GPIO_PIN_BUSY)
throw std::runtime_error("Invalid call to HalGpioRead()");
int fd;
if ((fd = open("/sys/class/gpio/slct/value", O_RDWR)) == -1) {
perror("Error while opening GPIO \"busy\"");
return -1;
}
struct gpiohandle_data data;
memset(&data, 0, sizeof(data));
auto res = ioctl(busyGpio.fd, GPIOHANDLE_GET_LINE_VALUES_IOCTL, &data);
if(res != 0)
throw std::runtime_error("IOCTL error (GPIOHANDLE_GET_LINE_VALUES_IOCTL failed)");
char buf;
if (read(fd, &buf, 1) == -1) {
perror("Error while reading GPIO \"busy\"");
return -1;
}
close(fd);
int value = (buf == '0') ? 0 : 1;
int value = data.values[0];
std::this_thread::sleep_for(std::chrono::milliseconds{1}); // Why do I need this sleep()?
return value;
}
void UsbAdapter::HalGpioWrite(SX126x::GpioPinFunction_t func, uint8_t value) {
if(func != GpioPinFunction_t::GPIO_PIN_RESET) {
std::cout << "ERROR" << std::endl;
throw;
}
if(func != GpioPinFunction_t::GPIO_PIN_RESET)
throw std::runtime_error("Invalid call to HalGpioWrite()");
int fd;
if ((fd = open("/sys/class/gpio/ini/value", O_RDWR)) == -1) {
perror("Error while opening GPIO \"reset\"");
}
if (write(fd, value ? "1" : "0", 1) == -1) {
perror ("Error while writing GPIO \"reset\"");
}
struct gpiohandle_data data;
data.values[0] = value;
auto res = ioctl(resetGpio.fd, GPIOHANDLE_SET_LINE_VALUES_IOCTL, &data);
if(res != 0)
throw std::runtime_error("IOCTL error (GPIOHANDLE_SET_LINE_VALUES_IOCTL failed)");
}
void UsbAdapter::HalSpiTransfer(uint8_t *buffer_in, const uint8_t *buffer_out, uint16_t size) {
@ -108,6 +132,68 @@ void UsbAdapter::HalSpiTransfer(uint8_t *buffer_in, const uint8_t *buffer_out, u
spi_trans.cs_change = true;
spi_trans.len = size;
int status = ioctl (handle, SPI_IOC_MESSAGE(1), &spi_trans);
int res = ioctl(handle, SPI_IOC_MESSAGE(1), &spi_trans);
if(res < 0)
throw std::runtime_error("IOCTL error (SPI_IOC_MESSAGE failed)");
}
void UsbAdapter::OpenSpi() {
auto fd = open(spiDriverOverrideFilePath, O_WRONLY);
if(fd == -1)
throw std::runtime_error("Cannot open SPI device (driver_override open error)");
auto res = write(fd, spidevOverride, strlen(spidevOverride));
if(res <= 0)
throw std::runtime_error("Cannot open SPI device (driver_override write error)");
close(fd);
fd = open(spiBindFilePath, O_WRONLY);
if(fd == -1)
throw std::runtime_error("Cannot open SPI device (bind open error)");
res = write(fd, spiBind, strlen(spiBind));
if(res <= 0)
throw std::runtime_error("Cannot open SPI device (bind write error)");
close(fd);
handle = open("/dev/spidev0.0", O_RDWR);
if(handle == -1)
throw std::runtime_error("Cannot open SPI device (spidev open error)");
uint8_t mmode = SPI_MODE_0;
uint8_t lsb = 0;
uint8_t bitsperword = 8;
ioctl(handle, SPI_IOC_RD_BITS_PER_WORD, &bitsperword);
ioctl(handle, SPI_IOC_WR_MODE, &mmode);
ioctl(handle, SPI_IOC_WR_LSB_FIRST, &lsb);
}
void UsbAdapter::OpenGpio() {
auto chipName = GetCh341ChipFilename();
std::cout << "CH341 detected at " << chipName << std::endl;
auto resetlineIndex = GetGpioLineIndex(chipName, "ini");
std::cout << "GPIO RESET detected at index " << resetlineIndex << std::endl;
struct gpiochip_info info;
auto fd = open(chipName.c_str(), O_RDWR);
resetGpio.flags = GPIOHANDLE_REQUEST_OUTPUT;
resetGpio.lines = 1;
resetGpio.lineoffsets[0] = resetlineIndex;
resetGpio.default_values[0] = 0;
strcpy(resetGpio.consumer_label, "LoRa SPI driver");
ioctl(fd, GPIO_GET_LINEHANDLE_IOCTL, &resetGpio);
auto busylineIndex = GetGpioLineIndex(chipName, "slct");
std::cout << "GPIO BUSY detected at index " << busylineIndex << std::endl;
busyGpio.flags = GPIOHANDLE_REQUEST_INPUT;
busyGpio.lines = 1;
busyGpio.lineoffsets[0] = busylineIndex;
busyGpio.default_values[0] = 0;
strcpy(busyGpio.consumer_label, "LoRa SPI driver");
ioctl(fd, GPIO_GET_LINEHANDLE_IOCTL, &busyGpio);
}