Basic Functionality
diff --git a/src/Makefile.am b/src/Makefile.am
index a94061d..d7872a6 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -4,3 +4,19 @@
libgpioplus_la_SOURCES =
libgpioplus_la_LIBADD = $(COMMON_LIBS)
+nobase_include_HEADERS += gpioplus/chip.hpp
+libgpioplus_la_SOURCES += gpioplus/chip.cpp
+
+nobase_include_HEADERS += gpioplus/event.hpp
+libgpioplus_la_SOURCES += gpioplus/event.cpp
+
+nobase_include_HEADERS += gpioplus/handle.hpp
+libgpioplus_la_SOURCES += gpioplus/handle.cpp
+
+nobase_include_HEADERS += gpioplus/internal/fd.hpp
+libgpioplus_la_SOURCES += gpioplus/internal/fd.cpp
+
+nobase_include_HEADERS += gpioplus/internal/sys.hpp
+libgpioplus_la_SOURCES += gpioplus/internal/sys.cpp
+
+nobase_include_HEADERS += gpioplus/test/sys.hpp
diff --git a/src/gpioplus/chip.cpp b/src/gpioplus/chip.cpp
new file mode 100644
index 0000000..051ef57
--- /dev/null
+++ b/src/gpioplus/chip.cpp
@@ -0,0 +1,61 @@
+#include <cstring>
+#include <fcntl.h>
+#include <gpioplus/chip.hpp>
+#include <linux/gpio.h>
+#include <string>
+#include <system_error>
+
+namespace gpioplus
+{
+
+LineFlags::LineFlags(uint32_t flags) :
+ kernel(flags & GPIOLINE_FLAG_KERNEL), output(flags & GPIOLINE_FLAG_IS_OUT),
+ active_low(flags & GPIOLINE_FLAG_ACTIVE_LOW),
+ open_drain(flags & GPIOLINE_FLAG_OPEN_DRAIN),
+ open_source(flags & GPIOLINE_FLAG_OPEN_SOURCE)
+{
+}
+
+Chip::Chip(unsigned id, const internal::Sys* sys) :
+ fd(std::string{"/dev/gpiochip"}.append(std::to_string(id)).c_str(),
+ O_RDONLY | O_CLOEXEC, sys)
+{
+}
+
+ChipInfo Chip::getChipInfo() const
+{
+ struct gpiochip_info info;
+ memset(&info, 0, sizeof(info));
+
+ int r = fd.getSys()->gpio_get_chipinfo(*fd, &info);
+ if (r < 0)
+ {
+ throw std::system_error(-r, std::generic_category(),
+ "gpio_get_chipinfo");
+ }
+
+ return ChipInfo{info.name, info.label, info.lines};
+}
+
+LineInfo Chip::getLineInfo(uint32_t offset) const
+{
+ struct gpioline_info info;
+ memset(&info, 0, sizeof(info));
+ info.line_offset = offset;
+
+ int r = fd.getSys()->gpio_get_lineinfo(*fd, &info);
+ if (r < 0)
+ {
+ throw std::system_error(-r, std::generic_category(),
+ "gpio_get_lineinfo");
+ }
+
+ return LineInfo{info.flags, info.name, info.consumer};
+}
+
+const internal::Fd& Chip::getFd() const
+{
+ return fd;
+}
+
+} // namespace gpioplus
diff --git a/src/gpioplus/chip.hpp b/src/gpioplus/chip.hpp
new file mode 100644
index 0000000..7c913a0
--- /dev/null
+++ b/src/gpioplus/chip.hpp
@@ -0,0 +1,49 @@
+#pragma once
+#include <cstdint>
+#include <gpioplus/internal/fd.hpp>
+#include <gpioplus/internal/sys.hpp>
+#include <string>
+
+namespace gpioplus
+{
+
+struct ChipInfo
+{
+ std::string name;
+ std::string label;
+ uint32_t lines;
+};
+
+struct LineFlags
+{
+ bool kernel;
+ bool output;
+ bool active_low;
+ bool open_drain;
+ bool open_source;
+
+ LineFlags(uint32_t flags);
+};
+
+struct LineInfo
+{
+ LineFlags flags;
+ std::string name;
+ std::string consumer;
+};
+
+class Chip
+{
+ public:
+ Chip(unsigned id, const internal::Sys* sys = &internal::sys_impl);
+
+ ChipInfo getChipInfo() const;
+ LineInfo getLineInfo(uint32_t offset) const;
+
+ const internal::Fd& getFd() const;
+
+ private:
+ internal::Fd fd;
+};
+
+} // namespace gpioplus
diff --git a/src/gpioplus/event.cpp b/src/gpioplus/event.cpp
new file mode 100644
index 0000000..3fc4062
--- /dev/null
+++ b/src/gpioplus/event.cpp
@@ -0,0 +1,93 @@
+#include <cstring>
+#include <gpioplus/event.hpp>
+#include <linux/gpio.h>
+#include <optional>
+#include <stdexcept>
+#include <system_error>
+
+namespace gpioplus
+{
+
+uint32_t EventFlags::toInt() const
+{
+ uint32_t ret = 0;
+ if (rising_edge)
+ {
+ ret |= GPIOEVENT_REQUEST_RISING_EDGE;
+ }
+ if (falling_edge)
+ {
+ ret |= GPIOEVENT_REQUEST_FALLING_EDGE;
+ }
+ return ret;
+}
+
+static int build(const Chip& chip, uint32_t line_offset,
+ HandleFlags handle_flags, EventFlags event_flags,
+ const char* consumer_label)
+{
+ struct gpioevent_request req;
+ memset(&req, 0, sizeof(req));
+ req.lineoffset = line_offset;
+ req.handleflags = handle_flags.toInt();
+ req.eventflags = event_flags.toInt();
+ strncpy(req.consumer_label, consumer_label, sizeof(req.consumer_label) - 1);
+
+ int r = chip.getFd().getSys()->gpio_get_lineevent(*chip.getFd(), &req);
+ if (r < 0)
+ {
+ throw std::system_error(-r, std::generic_category(),
+ "gpio_get_lineevent");
+ }
+
+ return req.fd;
+}
+
+Event::Event(const Chip& chip, uint32_t line_offset, HandleFlags handle_flags,
+ EventFlags event_flags, const char* consumer_label) :
+ fd(build(chip, line_offset, handle_flags, event_flags, consumer_label),
+ chip.getFd().getSys())
+{
+}
+
+const internal::Fd& Event::getFd() const
+{
+ return fd;
+}
+
+std::optional<Event::Data> Event::read() const
+{
+ struct gpioevent_data data;
+ ssize_t read = fd.getSys()->read(*fd, &data, sizeof(data));
+ if (read == -1)
+ {
+ if (errno == EAGAIN)
+ {
+ return std::nullopt;
+ }
+ throw std::system_error(errno, std::generic_category(),
+ "gpioevent read");
+ }
+ if (read != sizeof(data))
+ {
+ throw std::runtime_error("Event read didn't get enough data");
+ }
+
+ return Data{data.timestamp, data.id};
+}
+
+uint8_t Event::getValue() const
+{
+ struct gpiohandle_data data;
+ memset(&data, 0, sizeof(data));
+ int r = fd.getSys()->gpiohandle_get_line_values(*fd, &data);
+ if (r < 0)
+ {
+ throw std::system_error(-r, std::generic_category(),
+ "gpiohandle_get_line_values");
+ }
+
+ return data.values[0];
+}
+
+} // namespace gpioplus
diff --git a/src/gpioplus/event.hpp b/src/gpioplus/event.hpp
new file mode 100644
index 0000000..06d0a3a
--- /dev/null
+++ b/src/gpioplus/event.hpp
@@ -0,0 +1,40 @@
+#pragma once
+#include <cstdint>
+#include <gpioplus/chip.hpp>
+#include <gpioplus/handle.hpp>
+#include <gpioplus/internal/fd.hpp>
+#include <optional>
+
+namespace gpioplus
+{
+
+struct EventFlags
+{
+ bool rising_edge;
+ bool falling_edge;
+
+ uint32_t toInt() const;
+};
+
+class Event
+{
+ public:
+ Event(const Chip& chip, uint32_t line_offset, HandleFlags handle_flags,
+ EventFlags event_flags, const char* consumer_label);
+
+ const internal::Fd& getFd() const;
+
+ struct Data
+ {
+ uint64_t timestamp;
+ uint32_t id;
+ };
+ std::optional<Data> read() const;
+
+ uint8_t getValue() const;
+
+ private:
+ internal::Fd fd;
+};
+
+} // namespace gpioplus
diff --git a/src/gpioplus/handle.cpp b/src/gpioplus/handle.cpp
new file mode 100644
index 0000000..30dc12f
--- /dev/null
+++ b/src/gpioplus/handle.cpp
@@ -0,0 +1,133 @@
+#include <cstring>
+#include <gpioplus/handle.hpp>
+#include <linux/gpio.h>
+#include <stdexcept>
+#include <system_error>
+
+namespace gpioplus
+{
+
+HandleFlags::HandleFlags()
+{
+}
+
+HandleFlags::HandleFlags(LineFlags line_flags) :
+ output(line_flags.output), active_low(line_flags.active_low),
+ open_drain(line_flags.open_drain), open_source(line_flags.open_source)
+{
+}
+
+uint32_t HandleFlags::toInt() const
+{
+ uint32_t ret = 0;
+ if (output)
+ {
+ ret |= GPIOHANDLE_REQUEST_OUTPUT;
+ }
+ else
+ {
+ ret |= GPIOHANDLE_REQUEST_INPUT;
+ }
+ if (active_low)
+ {
+ ret |= GPIOHANDLE_REQUEST_ACTIVE_LOW;
+ }
+ if (open_drain)
+ {
+ ret |= GPIOHANDLE_REQUEST_OPEN_DRAIN;
+ }
+ if (open_source)
+ {
+ ret |= GPIOHANDLE_REQUEST_OPEN_SOURCE;
+ }
+ return ret;
+}
+
+static int build(const Chip& chip, const std::vector<Handle::Line>& lines,
+ HandleFlags flags, const char* consumer_label)
+{
+ if (lines.size() > GPIOHANDLES_MAX)
+ {
+ throw std::runtime_error("Too many requested gpio handles");
+ }
+
+ struct gpiohandle_request req;
+ memset(&req, 0, sizeof(req));
+ for (size_t i = 0; i < lines.size(); ++i)
+ {
+ req.lineoffsets[i] = lines[i].offset;
+ req.default_values[i] = lines[i].default_value;
+ }
+ req.flags = flags.toInt();
+ strncpy(req.consumer_label, consumer_label, sizeof(req.consumer_label) - 1);
+ req.lines = lines.size();
+
+ int r = chip.getFd().getSys()->gpio_get_linehandle(*chip.getFd(), &req);
+ if (r < 0)
+ {
+ throw std::system_error(-r, std::generic_category(),
+ "gpio_get_linehandle");
+ }
+
+ return req.fd;
+}
+
+Handle::Handle(const Chip& chip, const std::vector<Line>& lines,
+ HandleFlags flags, const char* consumer_label) :
+ fd(build(chip, lines, flags, consumer_label), chip.getFd().getSys()),
+ nlines(lines.size())
+{
+}
+
+const internal::Fd& Handle::getFd() const
+{
+ return fd;
+}
+
+std::vector<uint8_t> Handle::getValues() const
+{
+ std::vector<uint8_t> values(nlines);
+ getValues(values);
+ return values;
+}
+
+void Handle::getValues(std::vector<uint8_t>& values) const
+{
+ struct gpiohandle_data data;
+ memset(&data, 0, sizeof(data));
+ int r = fd.getSys()->gpiohandle_get_line_values(*fd, &data);
+ if (r < 0)
+ {
+ throw std::system_error(-r, std::generic_category(),
+ "gpiohandle_get_line_values");
+ }
+
+ values.resize(nlines);
+ for (size_t i = 0; i < nlines; ++i)
+ {
+ values[i] = data.values[i];
+ }
+}
+
+void Handle::setValues(const std::vector<uint8_t>& values) const
+{
+ if (values.size() != nlines)
+ {
+ throw std::runtime_error("Handle.setValues: Invalid input size");
+ }
+
+ struct gpiohandle_data data;
+ memset(&data, 0, sizeof(data));
+ for (size_t i = 0; i < nlines; ++i)
+ {
+ data.values[i] = values[i];
+ }
+ int r = fd.getSys()->gpiohandle_set_line_values(*fd, &data);
+ if (r < 0)
+ {
+ throw std::system_error(-r, std::generic_category(),
+ "gpiohandle_get_line_values");
+ }
+}
+
+} // namespace gpioplus
diff --git a/src/gpioplus/handle.hpp b/src/gpioplus/handle.hpp
new file mode 100644
index 0000000..7f22a64
--- /dev/null
+++ b/src/gpioplus/handle.hpp
@@ -0,0 +1,44 @@
+#pragma once
+#include <cstdint>
+#include <gpioplus/chip.hpp>
+#include <gpioplus/internal/fd.hpp>
+#include <vector>
+
+namespace gpioplus
+{
+
+struct HandleFlags
+{
+ bool output;
+ bool active_low;
+ bool open_drain;
+ bool open_source;
+
+ HandleFlags();
+ explicit HandleFlags(LineFlags line_flags);
+ uint32_t toInt() const;
+};
+
+class Handle
+{
+ public:
+ struct Line
+ {
+ uint32_t offset;
+ uint8_t default_value;
+ };
+ Handle(const Chip& chip, const std::vector<Line>& lines, HandleFlags flags,
+ const char* consumer_label);
+
+ const internal::Fd& getFd() const;
+
+ std::vector<uint8_t> getValues() const;
+ void getValues(std::vector<uint8_t>& values) const;
+ void setValues(const std::vector<uint8_t>& values) const;
+
+ private:
+ internal::Fd fd;
+ uint32_t nlines;
+};
+
+} // namespace gpioplus
diff --git a/src/gpioplus/internal/fd.cpp b/src/gpioplus/internal/fd.cpp
new file mode 100644
index 0000000..5646bd9
--- /dev/null
+++ b/src/gpioplus/internal/fd.cpp
@@ -0,0 +1,129 @@
+#include <cerrno>
+#include <fcntl.h>
+#include <gpioplus/internal/fd.hpp>
+#include <system_error>
+#include <utility>
+
+namespace gpioplus
+{
+namespace internal
+{
+
+Fd::Fd(const char* pathname, int flags, const Sys* sys) :
+ sys(sys), fd(sys->open(pathname, flags))
+{
+ if (fd < 0)
+ {
+ throw std::system_error(errno, std::generic_category(), "Opening FD");
+ }
+}
+
+Fd::Fd(int fd, const Sys* sys) : sys(sys), fd(fd)
+{
+}
+
+Fd::~Fd()
+{
+ reset();
+}
+
+static int dup(int oldfd, const Sys* sys)
+{
+ int fd = sys->dup(oldfd);
+ if (fd < 0)
+ {
+ throw std::system_error(errno, std::generic_category(), "Duping FD");
+ }
+ return fd;
+}
+
+Fd::Fd(const Fd& other) : sys(other.sys), fd(dup(other.fd, sys))
+{
+}
+
+Fd& Fd::operator=(const Fd& other)
+{
+ if (this != &other)
+ {
+ reset();
+ sys = other.sys;
+ fd = dup(other.fd, sys);
+ }
+ return *this;
+}
+
+Fd::Fd(Fd&& other) : sys(other.sys), fd(std::move(other.fd))
+{
+ other.fd = -1;
+}
+
+Fd& Fd::operator=(Fd&& other)
+{
+ if (this != &other)
+ {
+ reset();
+ sys = other.sys;
+ fd = std::move(other.fd);
+ other.fd = -1;
+ }
+ return *this;
+}
+
+int Fd::operator*() const
+{
+ return fd;
+}
+
+const Sys* Fd::getSys() const
+{
+ return sys;
+}
+
+void Fd::setBlocking(bool enabled) const
+{
+ if (enabled)
+ {
+ setFlags(getFlags() & ~O_NONBLOCK);
+ }
+ else
+ {
+ setFlags(getFlags() | O_NONBLOCK);
+ }
+}
+
+void Fd::setFlags(int flags) const
+{
+ int r = sys->fcntl_setfl(fd, flags);
+ if (r == -1)
+ {
+ throw std::system_error(errno, std::generic_category(), "fcntl_setfl");
+ }
+}
+
+int Fd::getFlags() const
+{
+ int flags = sys->fcntl_getfl(fd);
+ if (flags == -1)
+ {
+ throw std::system_error(errno, std::generic_category(), "fcntl_getfl");
+ }
+ return flags;
+}
+
+void Fd::reset()
+{
+ if (fd < 0)
+ {
+ return;
+ }
+
+ int ret = sys->close(fd);
+ fd = -1;
+ if (ret != 0)
+ {
+ throw std::system_error(errno, std::generic_category(), "Closing FD");
+ }
+}
+
+} // namespace internal
+} // namespace gpioplus
diff --git a/src/gpioplus/internal/fd.hpp b/src/gpioplus/internal/fd.hpp
new file mode 100644
index 0000000..7f54b81
--- /dev/null
+++ b/src/gpioplus/internal/fd.hpp
@@ -0,0 +1,37 @@
+#pragma once
+#include <gpioplus/internal/sys.hpp>
+
+namespace gpioplus
+{
+namespace internal
+{
+
+class Fd
+{
+ public:
+ Fd(const char* pathname, int flags, const Sys* sys);
+ Fd(int fd, const Sys* sys);
+ ~Fd();
+
+ Fd(const Fd& other);
+ Fd& operator=(const Fd& other);
+ Fd(Fd&& other);
+ Fd& operator=(Fd&& other);
+
+ int operator*() const;
+ const Sys* getSys() const;
+
+ void setBlocking(bool enabled) const;
+
+ private:
+ const Sys* sys;
+ int fd;
+
+ void setFlags(int flags) const;
+ int getFlags() const;
+
+ void reset();
+};
+
+} // namespace internal
+} // namespace gpioplus
diff --git a/src/gpioplus/internal/sys.cpp b/src/gpioplus/internal/sys.cpp
new file mode 100644
index 0000000..1035072
--- /dev/null
+++ b/src/gpioplus/internal/sys.cpp
@@ -0,0 +1,78 @@
+#include <fcntl.h>
+#include <gpioplus/internal/sys.hpp>
+#include <linux/gpio.h>
+#include <sys/ioctl.h>
+#include <unistd.h>
+
+namespace gpioplus
+{
+namespace internal
+{
+
+int SysImpl::open(const char* pathname, int flags) const
+{
+ return ::open(pathname, flags);
+}
+
+int SysImpl::dup(int oldfd) const
+{
+ return ::dup(oldfd);
+}
+
+int SysImpl::close(int fd) const
+{
+ return ::close(fd);
+}
+
+int SysImpl::read(int fd, void* buf, size_t count) const
+{
+ return ::read(fd, buf, count);
+}
+
+int SysImpl::fcntl_setfl(int fd, int flags) const
+{
+ return ::fcntl(fd, F_SETFL, flags);
+}
+
+int SysImpl::fcntl_getfl(int fd) const
+{
+ return ::fcntl(fd, F_GETFL);
+}
+
+int SysImpl::gpiohandle_get_line_values(int fd,
+ struct gpiohandle_data* data) const
+{
+ return ::ioctl(fd, GPIOHANDLE_GET_LINE_VALUES_IOCTL, data);
+}
+
+int SysImpl::gpiohandle_set_line_values(int fd,
+ struct gpiohandle_data* data) const
+{
+ return ::ioctl(fd, GPIOHANDLE_SET_LINE_VALUES_IOCTL, data);
+}
+
+int SysImpl::gpio_get_chipinfo(int fd, struct gpiochip_info* info) const
+{
+ return ::ioctl(fd, GPIO_GET_CHIPINFO_IOCTL, info);
+}
+
+int SysImpl::gpio_get_lineinfo(int fd, struct gpioline_info* info) const
+{
+ return ioctl(fd, GPIO_GET_LINEINFO_IOCTL, info);
+}
+
+int SysImpl::gpio_get_linehandle(int fd,
+ struct gpiohandle_request* request) const
+{
+ return ioctl(fd, GPIO_GET_LINEHANDLE_IOCTL, request);
+}
+
+int SysImpl::gpio_get_lineevent(int fd, struct gpioevent_request* request) const
+{
+ return ioctl(fd, GPIO_GET_LINEEVENT_IOCTL, request);
+}
+
+SysImpl sys_impl;
+
+} // namespace internal
+} // namespace gpioplus
diff --git a/src/gpioplus/internal/sys.hpp b/src/gpioplus/internal/sys.hpp
new file mode 100644
index 0000000..37733b6
--- /dev/null
+++ b/src/gpioplus/internal/sys.hpp
@@ -0,0 +1,62 @@
+#pragma once
+#include <cstddef>
+#include <linux/gpio.h>
+
+namespace gpioplus
+{
+namespace internal
+{
+
+class Sys
+{
+ public:
+ virtual ~Sys() = default;
+
+ virtual int open(const char* pathname, int flags) const = 0;
+ virtual int dup(int oldfd) const = 0;
+ virtual int close(int fd) const = 0;
+ virtual int read(int fd, void* buf, size_t count) const = 0;
+ virtual int fcntl_setfl(int fd, int flags) const = 0;
+ virtual int fcntl_getfl(int fd) const = 0;
+
+ virtual int
+ gpiohandle_get_line_values(int fd,
+ struct gpiohandle_data* data) const = 0;
+ virtual int
+ gpiohandle_set_line_values(int fd,
+ struct gpiohandle_data* data) const = 0;
+ virtual int gpio_get_chipinfo(int fd, struct gpiochip_info* info) const = 0;
+ virtual int gpio_get_lineinfo(int fd, struct gpioline_info* info) const = 0;
+ virtual int
+ gpio_get_linehandle(int fd,
+ struct gpiohandle_request* request) const = 0;
+ virtual int gpio_get_lineevent(int fd,
+ struct gpioevent_request* request) const = 0;
+};
+
+class SysImpl : public Sys
+{
+ public:
+ int open(const char* pathname, int flags) const override;
+ int dup(int oldfd) const override;
+ int close(int fd) const override;
+ int read(int fd, void* buf, size_t count) const override;
+ int fcntl_setfl(int fd, int flags) const override;
+ int fcntl_getfl(int fd) const override;
+
+ int gpiohandle_get_line_values(int fd,
+ struct gpiohandle_data* data) const override;
+ int gpiohandle_set_line_values(int fd,
+ struct gpiohandle_data* data) const override;
+ int gpio_get_chipinfo(int fd, struct gpiochip_info* info) const override;
+ int gpio_get_lineinfo(int fd, struct gpioline_info* info) const override;
+ int gpio_get_linehandle(int fd,
+ struct gpiohandle_request* request) const override;
+ int gpio_get_lineevent(int fd,
+ struct gpioevent_request* request) const override;
+};
+
+extern SysImpl sys_impl;
+
+} // namespace internal
+} // namespace gpioplus