--- /dev/null
+/*
+ GUSE example: GPIO character device in Userspace
+ Copyright (C) 2025 Nikita Shubin <n.shubin@yadro.com>
+
+ This program can be distributed under the terms of the GNU GPLv2.
+ See the file COPYING.
+
+*/
+
+/** @file
+ *
+ * This example demonstrates how to implement a character device in
+ * userspace ("GUSE"). This is only allowed for root. The character
+ * device should appear in /dev under the specified name. It can be
+ * tested with the guse_client.c program.
+ *
+ * Mount the file system with:
+ *
+ * guse -f --name=mydevice
+ *
+ * You should now have a new /dev/mydevice character device. To "unmount" it,
+ * kill the "guse" process.
+ *
+ * To compile this example, run
+ *
+ * gcc -Wall guse.c `pkg-config fuse3 --cflags --libs` -o guse
+ *
+ * ## Source code ##
+ * \include guse.c
+ */
+
+
+#define FUSE_USE_VERSION 31
+#define GUSE_LINES_NUM 8
+
+#include <guse_lowlevel.h>
+#include <fuse.h>
+#include <fuse_opt.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h> // for ffs
+#include <pthread.h>
+#include <poll.h>
+#include <unistd.h>
+#include <errno.h>
+
+#include <linux/gpio.h>
+
+#include "ioctl.h"
+
+#define BIT(nr) (1UL << (nr))
+#define BIT_ULL(nr) (1ULL << (nr))
+
+#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))
+
+#define _ffs(x) _Generic((x), \
+ char: ffs, \
+ int: ffs, \
+ long int: ffsl, \
+ long long int: ffsll, \
+ default: ffsll)(x)
+
+#define __ffs(x, size) (_ffs(x) == 0 ? size : _ffs(x) - 1)
+
+#define __popcount(x) _Generic((x), \
+ char: __builtin_popcount, \
+ int: __builtin_popcount, \
+ unsigned int: __builtin_popcount, \
+ unsigned long: __builtin_popcountl, \
+ default: __builtin_popcountl)(x)
+
+#define BITS_PER_BYTE 8
+#define BITS_PER_TYPE(type) (sizeof(type) * BITS_PER_BYTE)
+#define DIV_ROUND_UP(n, d) (((n) + (d) - 1) / (d))
+#define BITS_TO_LONGS(nr) DIV_ROUND_UP(nr, BITS_PER_TYPE(long))
+#define BITS_TO_BYTES(nr) DIV_ROUND_UP(nr, BITS_PER_BYTE)
+
+#define BIT_MASK(nr) (nr == BITS_PER_TYPE(1ULL) ? 0x0 : ~((1ULL << nr) - 1))
+
+#define first_set_bit_from(addr, bit, size) __ffs((addr) & BIT_MASK(bit), size)
+
+#define for_each_set_bit(bit, addr, size) \
+ for ((bit) = __ffs((addr), (size)); \
+ (bit) < (size); \
+ (bit) = first_set_bit_from((addr), (bit + 1), (size)))
+
+#define for_each_set_bit_from(bit, addr, size) \
+ for ((bit) = first_set_bit_from((addr), (bit), (size)); \
+ (bit) < (size); \
+ (bit) = first_set_bit_from((addr), (bit), (size)))
+
+static pthread_mutex_t guse_mutex;
+static pthread_attr_t attr;
+static pthread_t guse_producer;
+static _Atomic bool guse_stop = false;
+
+static void *gusexmp_buf;
+static size_t gusexmp_size;
+
+#define GUSE_MAX_WATCH 64
+#define GUSE_MAX_EVENTS 64
+
+static uint8_t num_watched;
+static struct watch_request {
+ uint64_t inode;
+ struct fuse_pollhandle *ph;
+
+ uint64_t watched_lines;
+
+ uint32_t num_lines;
+ uint32_t offsets[GPIO_V2_LINES_MAX];
+
+ uint32_t num_events;
+ struct gpio_v2_line_info_changed events[GUSE_MAX_EVENTS];
+} watch_requests[GUSE_MAX_WATCH];
+
+static struct watch_request *find_watch(uint64_t inode)
+{
+ int i;
+ struct watch_request *req = NULL;
+
+ for (i = 0; i < GUSE_MAX_WATCH; i++) {
+ req = &watch_requests[i];
+ if (req->inode == inode)
+ return req;
+ }
+
+ return NULL;
+}
+
+static struct watch_request *allocate_watch(uint64_t inode)
+{
+ int i;
+ struct watch_request *req = NULL;
+
+ for (i = 0; i < GUSE_MAX_WATCH; i++) {
+ req = &watch_requests[i];
+ if (!req->inode) {
+ req->inode = inode;
+ num_watched++;
+ return req;
+ }
+ }
+
+ return NULL;
+}
+
+static void free_watch(struct watch_request *req)
+{
+ req->inode = 0;
+ if (req->ph)
+ fuse_pollhandle_destroy(req->ph);
+ req->ph = NULL;
+ req->watched_lines = 0;
+ req->num_lines = 0;
+ req->num_events = 0;
+ num_watched--;
+}
+
+#define GUSE_MAX_LINEREQ 64
+
+static uint8_t num_requests;
+static struct line_request {
+ uint64_t inode;
+ struct fuse_pollhandle *ph;
+
+ uint64_t fallen_mask;
+ uint64_t risen_mask;
+ uint64_t config_mask;
+ uint64_t mask;
+
+ uint32_t num_lines;
+ uint32_t offsets[GPIO_V2_LINES_MAX];
+
+ uint32_t num_events;
+ struct gpio_v2_line_event events[GUSE_MAX_EVENTS];
+} line_requests[GUSE_MAX_LINEREQ];
+
+static struct line_request *find_linereq(uint64_t inode)
+{
+ int i;
+ struct line_request *req = NULL;
+
+ for (i = 0; i < GUSE_MAX_LINEREQ; i++) {
+ req = &line_requests[i];
+ if (req->inode == inode)
+ return req;
+ }
+
+ return NULL;
+}
+
+static struct line_request *allocate_linereq(uint64_t inode)
+{
+ int i;
+ struct line_request *req = NULL;
+
+ for (i = 0; i < GUSE_MAX_LINEREQ; i++) {
+ req = &line_requests[i];
+ if (!req->inode) {
+ req->inode = inode;
+ num_requests++;
+ return req;
+ }
+ }
+
+ return NULL;
+}
+
+static void free_linereq(struct line_request *req)
+{
+ req->inode = 0;
+ if (req->ph)
+ fuse_pollhandle_destroy(req->ph);
+ req->ph = NULL;
+ req->fallen_mask = 0;
+ req->risen_mask = 0;
+ req->config_mask = 0;
+ req->num_lines = 0;
+ req->num_events = 0;
+ num_requests--;
+}
+
+static uint64_t timespec_to_ns(struct timespec ts)
+{
+ return (uint64_t)ts.tv_nsec + 1000000000LL * (uint64_t)ts.tv_sec;
+}
+
+static const char *usage =
+"usage: gusexmp [options]\n"
+"\n"
+"options:\n"
+" --help|-h print this help message\n"
+" --maj=MAJ|-M MAJ device major number\n"
+" --min=MIN|-m MIN device minor number\n"
+" --name=NAME|-n NAME device name (mandatory)\n"
+" -d -o debug enable debug output (implies -f)\n"
+" -f foreground operation\n"
+" -s disable multi-threaded operation\n"
+"\n";
+
+static void *guse_producer_thread(void *data);
+
+static void gusexmp_init(void *userdata, struct fuse_conn_info *conn)
+{
+ (void)userdata;
+
+ /* Disable the receiving and processing of FUSE_INTERRUPT requests */
+ conn->no_interrupt = 1;
+
+ errno = pthread_create(&guse_producer, &attr, guse_producer_thread, NULL);
+ if (errno) {
+ perror("pthread_create");
+ }
+}
+
+static void gusexmp_destroy(void *private_data)
+{
+ (void)private_data;
+
+ guse_stop = true;
+
+ pthread_join(guse_producer, NULL);
+}
+
+static void gusexmp_open(fuse_req_t req, fuse_ino_t ino,
+ struct fuse_file_info *fi)
+{
+ printf("%s: %lu\n", __func__, ino);
+
+ fuse_reply_open(req, fi);
+}
+
+static void gusexmp_release(fuse_req_t req, fuse_ino_t ino,
+ struct fuse_file_info *fi)
+{
+ struct line_request *lreq = find_linereq(ino);
+ struct line_request *wreq = find_watch(ino);
+
+ pthread_mutex_lock(&guse_mutex);
+
+ if (lreq) {
+ printf("%s: lreq %lu\n", __func__, ino);
+
+ for (int i = 0; i < num_watched; i++) {
+ struct watch_request *wreq = &watch_requests[i];
+ unsigned events = 0;
+ struct timespec ts;
+ uint64_t ts_ns;
+
+ timespec_get(&ts, TIME_UTC);
+ ts_ns = timespec_to_ns(ts);
+
+ for (int j = 0; j < lreq->num_lines; j++) {
+ if (BIT(lreq->offsets[j]) & wreq->watched_lines) {
+ struct gpio_v2_line_info_changed *event = &wreq->events[wreq->num_events++];
+
+ event->timestamp_ns = ts_ns;
+ event->event_type = GPIO_V2_LINE_CHANGED_RELEASED;
+
+ event->info.offset = lreq->offsets[j];
+
+ events++;
+ }
+ }
+
+ if (events && wreq->ph) {
+ fuse_notify_poll(wreq->ph);
+ fuse_pollhandle_destroy(wreq->ph);
+ wreq->ph= NULL;
+ }
+
+ }
+
+ free_linereq(lreq);
+ }
+
+ if (wreq) {
+ printf("%s: wreq %lu\n", __func__, ino);
+ free_watch(wreq);
+ }
+
+ pthread_mutex_unlock(&guse_mutex);
+
+ fuse_reply_err(req, 0);
+}
+
+static void gusexmp_read(fuse_req_t req, fuse_ino_t ino, size_t size, off_t off,
+ struct fuse_file_info *fi)
+{
+ struct line_request *lreq = find_linereq(ino);
+ struct watch_request *wreq = find_watch(ino);
+
+ (void)fi;
+
+ if (!lreq && !wreq) {
+ fuse_reply_err(req, ENOSYS);
+ return;
+ }
+
+ pthread_mutex_lock(&guse_mutex);
+
+ if (lreq) {
+ printf("%s: lreq num_events: %lu\n", __func__, lreq->num_events);
+ if (lreq->num_events) {
+ fuse_reply_buf(req, &lreq->events, sizeof(lreq->events[0]) * lreq->num_events);
+ lreq->num_events = 0;
+ } else
+ fuse_reply_buf(req, NULL, 0);
+ }
+
+ if (wreq) {
+ printf("%s: wreq num_events: %lu\n", __func__, wreq->num_events);
+ if (wreq->num_events) {
+ fuse_reply_buf(req, &wreq->events, sizeof(wreq->events[0]) * wreq->num_events);
+ wreq->num_events = 0;
+ } else
+ fuse_reply_buf(req, NULL, 0);
+ }
+
+ pthread_mutex_unlock(&guse_mutex);
+}
+
+static const struct gpiochip_info info = {
+ .name = "GUSE",
+ .label = "GUSE_EXAMPLE",
+ .lines = GUSE_LINES_NUM,
+};
+
+#define LINE_INFO(_offset, _name, _consumer, _flags, _changed, _value) \
+ { \
+ .name = _name, \
+ .consumer = _consumer, \
+ .offset = _offset, \
+ .flags = _flags, \
+ .padding[0] = _changed, \
+ .padding[1] = _value, \
+ }
+
+static struct gpio_v2_line_info line_info[GUSE_LINES_NUM] = {
+ LINE_INFO(0, "guse0", "", GPIO_V2_LINE_FLAG_OUTPUT, 1, 0),
+ LINE_INFO(1, "guse1", "", GPIO_V2_LINE_FLAG_OUTPUT, 0, 0),
+ LINE_INFO(2, "guse2", "", GPIO_V2_LINE_FLAG_OUTPUT, 0, 0),
+ LINE_INFO(3, "guse3", "", GPIO_V2_LINE_FLAG_OUTPUT, 0, 0),
+ LINE_INFO(4, "guse4", "", GPIO_V2_LINE_FLAG_INPUT, 0, 0),
+ LINE_INFO(5, "guse5", "", GPIO_V2_LINE_FLAG_INPUT, 0, 0),
+ LINE_INFO(6, "guse6", "", GPIO_V2_LINE_FLAG_INPUT, 0, 0),
+ LINE_INFO(7, "guse7", "", GPIO_V2_LINE_FLAG_INPUT, 0, 0),
+};
+
+static inline int set_value(unsigned idx, uint8_t value)
+{
+ if (idx > ARRAY_SIZE(line_info))
+ return -1;
+
+ line_info[idx].padding[1] = value;
+
+ return 0;
+}
+
+static inline int get_value(unsigned idx)
+{
+ if (idx > ARRAY_SIZE(line_info))
+ return -1;
+
+ return line_info[idx].padding[1];
+}
+
+static void gusexmp_lineinfo(fuse_req_t req, const void *in_buf)
+{
+ struct gpio_v2_line_info *in = (struct gpio_v2_line_info *)in_buf;
+ uint32_t offset = in->offset;
+
+ if (offset > info.lines) {
+ fuse_reply_err(req, EINVAL);
+ return;
+ }
+
+ fuse_reply_ioctl(req, 0, &line_info[offset], sizeof(line_info[0]));
+}
+
+static int gpio_v2_line_config_output_value(const struct gpio_v2_line_config *lc,
+ unsigned int line_idx)
+{
+ unsigned int i;
+ uint64_t mask = BIT_ULL(line_idx);
+
+ for (i = 0; i < lc->num_attrs; i++) {
+ if ((lc->attrs[i].attr.id == GPIO_V2_LINE_ATTR_ID_OUTPUT_VALUES) &&
+ (lc->attrs[i].mask & mask))
+ return !!(lc->attrs[i].attr.values & mask);
+ }
+
+ return 0;
+}
+
+/* GPIO_V2_LINE_FLAG_INPUT & GPIO_V2_LINE_EDGE_FLAGS */
+static void gusexmp_linerequest(fuse_req_t req, fuse_ino_t ino,
+ const void *in_buf)
+{
+ struct gpio_v2_line_request *in = (struct gpio_v2_line_request *)in_buf;
+ struct gpio_v2_line_request request = { 0 };
+ struct line_request *lreq;
+ uint64_t mask = 0;
+ int i, j, ret = EINVAL;
+
+ memcpy(&request, in, sizeof(request));
+
+ /* line request not available for the device itself */
+ if (ino & GUSE_DEVICE_INODE_FLAG)
+ goto out_fail;
+
+ /* sanity check */
+ if (in->num_lines > GUSE_LINES_NUM)
+ goto out_fail;
+
+ /* TODO: forbid switching OUTPUTS to INPUTS */
+ lreq = allocate_linereq(ino);
+ if (!req) {
+ ret = EMFILE;
+ goto out_fail;
+ }
+
+ for (i = 0; i < in->num_lines; i++) {
+ mask |= BIT_ULL(request.offsets[i]);
+ }
+
+ memcpy(lreq->offsets, request.offsets, sizeof(request.offsets));
+
+ if (request.config.flags & GPIO_V2_LINE_FLAG_INPUT) {
+ if (request.config.flags & GPIO_V2_LINE_FLAG_EDGE_RISING)
+ lreq->risen_mask = mask;
+
+ if (request.config.flags & GPIO_V2_LINE_FLAG_EDGE_FALLING)
+ lreq->fallen_mask = mask;
+ } else if (request.config.flags & GPIO_V2_LINE_FLAG_OUTPUT) {
+ lreq->mask = mask;
+ }
+
+ lreq->num_lines = in->num_lines;
+ fuse_reply_ioctl(req, 0, &request, sizeof(request));
+
+ /* notify watch */
+ for (i = 0; i < num_watched; i++) {
+ struct watch_request *wreq = &watch_requests[i];
+ uint64_t watch_mask = wreq->watched_lines & mask;
+ unsigned int bit = 0;
+ struct timespec ts;
+ uint64_t ts_ns;
+
+ timespec_get(&ts, TIME_UTC);
+ ts_ns = timespec_to_ns(ts);
+
+ pthread_mutex_lock(&guse_mutex);
+
+ for_each_set_bit(bit, watch_mask, GUSE_LINES_NUM) {
+ struct gpio_v2_line_info_changed *event = &wreq->events[wreq->num_events++];
+
+ event->timestamp_ns = ts_ns;
+ event->event_type = GPIO_V2_LINE_CHANGED_REQUESTED;
+
+ event->info.offset = bit;
+ }
+
+ if (bit && wreq->ph) {
+ fuse_notify_poll(wreq->ph);
+ fuse_pollhandle_destroy(wreq->ph);
+ wreq->ph= NULL;
+ }
+
+ pthread_mutex_unlock(&guse_mutex);
+ }
+
+ return;
+
+out_fail:
+ fuse_reply_err(req, ret);
+}
+
+static void guseexmp_linevalues(fuse_req_t req, fuse_ino_t ino,
+ const void *in_buf, bool is_read)
+{
+ struct gpio_v2_line_values *in_val = (struct gpio_v2_line_values *)in_buf;
+ struct gpio_v2_line_values request;
+ struct line_request *lreq = NULL;
+ unsigned int bit = 0;
+ uint64_t bits = in_val->bits;
+ uint64_t mask = in_val->mask;
+
+ lreq = find_linereq(ino);
+ if (!lreq) {
+ fuse_reply_err(req, EINVAL);
+ return;
+ }
+
+ printf("%s: %lu bits=0x%x mask=0x%x\n", __func__, ino, bits, mask);
+
+ if (is_read) {
+ bits = 0;
+ }
+
+ for_each_set_bit(bit, mask, lreq->num_lines) {
+ uint32_t idx = lreq->offsets[bit];
+
+ if (is_read) {
+ int val = get_value(idx);
+ if (val >= 0)
+ bits |= val << bit;
+ printf("%s: get_value(%u, %u)\n", __func__, idx, val);
+ } else {
+ uint8_t val = !!(bits & BIT_ULL(bit));
+ set_value(idx, val);
+ printf("%s: set_value(%u, %u)\n", __func__, idx, val);
+ }
+ }
+
+ request.bits = bits;
+ request.mask = mask;
+
+ printf("%s: request bits=0x%x mask=0x%x\n", __func__, bits, mask);
+
+ fuse_reply_ioctl(req, 0, &request, sizeof(request));
+}
+
+/*
+ioctl(3, GPIO_V2_GET_LINEINFO_WATCH_IOCTL, {offset=0} => {name="", consumer="?", flags=GPIO_V2_LINE_FLAG_USED|GPIO_V2_LINE_FLAG_OUTPUT, num_attrs=0}) = 0
+ioctl(3, GPIO_V2_GET_LINEINFO_WATCH_IOCTL, {offset=1} => {name="", consumer="?", flags=GPIO_V2_LINE_FLAG_USED|GPIO_V2_LINE_FLAG_OUTPUT, num_attrs=0}) = 0
+ioctl(3, GPIO_V2_GET_LINEINFO_WATCH_IOCTL, {offset=2} => {name="", consumer="?", flags=GPIO_V2_LINE_FLAG_USED|GPIO_V2_LINE_FLAG_OUTPUT, num_attrs=0}) = 0
+ioctl(3, GPIO_V2_GET_LINEINFO_WATCH_IOCTL, {offset=3} => {name="", consumer="?", flags=GPIO_V2_LINE_FLAG_USED|GPIO_V2_LINE_FLAG_OUTPUT, num_attrs=0}) = 0
+*/
+static void guseexmp_linewatch(fuse_req_t req, fuse_ino_t ino,
+ const void *in_buf, bool is_watch)
+{
+ struct gpio_v2_line_info *in = (struct gpio_v2_line_info *)in_buf;
+ struct gpio_v2_line_info request = { 0 };
+ struct watch_request *wreq = NULL;
+ int i, ret = EINVAL;
+
+ /* sanity check */
+ if (in->offset> GUSE_LINES_NUM)
+ goto out_fail;
+
+ wreq = find_watch(ino);
+ if (!wreq) {
+ wreq = allocate_watch(ino);
+ }
+
+ if (!wreq) {
+ ret = EMFILE;
+ goto out_fail;
+ }
+
+ if (is_watch) {
+ wreq->watched_lines |= BIT_ULL(in->offset);
+ } else {
+ wreq->watched_lines &= ~BIT_ULL(in->offset);
+ }
+
+ printf("%s: %lu offset=%u watched_lines=0x%x watch=%u\n",
+ __func__, ino, in->offset, wreq->watched_lines, is_watch);
+
+ request.offset = in->offset;
+ fuse_reply_ioctl(req, 0, &request, sizeof(request));
+
+ return;
+
+out_fail:
+ fuse_reply_err(req, ret);
+}
+
+/* */
+static void gusexmp_ioctl(fuse_req_t req, fuse_ino_t ino, unsigned int cmd,
+ void *arg, struct fuse_file_info *fi, unsigned flags,
+ const void *in_buf, size_t in_bufsz, size_t out_bufsz)
+{
+ bool is_read = false, is_watch = false;
+
+ (void)fi;
+
+ if (flags & FUSE_IOCTL_COMPAT) {
+ fuse_reply_err(req, ENOSYS);
+ return;
+ }
+
+ switch(cmd) {
+ case GPIO_GET_CHIPINFO_IOCTL:
+ fuse_reply_ioctl(req, 0, &info, sizeof(info));
+ break;
+ case GPIO_V2_GET_LINEINFO_IOCTL:
+ gusexmp_lineinfo(req, in_buf);
+ break;
+ /*
+ * GPIO_V2_GET_LINE_IOCTL is also processed by guse module.
+ */
+ case GPIO_V2_GET_LINE_IOCTL:
+ gusexmp_linerequest(req, ino, in_buf);
+ break;
+ case GPIO_V2_LINE_GET_VALUES_IOCTL:
+ is_read = true;
+ /* fallthrough */
+ case GPIO_V2_LINE_SET_VALUES_IOCTL:
+ guseexmp_linevalues(req, ino, in_buf, is_read);
+ break;
+ case GPIO_V2_GET_LINEINFO_WATCH_IOCTL:
+ is_watch = true;
+ case GPIO_GET_LINEINFO_UNWATCH_IOCTL:
+ guseexmp_linewatch(req, ino, in_buf, is_watch);
+ break;
+ case GPIO_V2_LINE_SET_CONFIG_IOCTL:
+ default:
+ fuse_reply_err(req, EINVAL);
+ }
+}
+
+static void gusexmp_poll(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi,
+ struct fuse_pollhandle *ph)
+{
+ struct line_request *lreq = NULL;
+ struct watch_request *wreq = NULL;
+ struct fuse_pollhandle *oldph = NULL;
+ uint32_t watched = 0;
+
+ pthread_mutex_lock(&guse_mutex);
+
+ if (ino & GUSE_DEVICE_INODE_FLAG) {
+ wreq = find_watch(ino);
+ if (wreq) {
+ oldph = wreq->ph;
+ watched = wreq->num_events;
+ }
+ } else {
+ lreq = find_linereq(ino);
+ if (lreq) {
+ oldph = lreq->ph;
+ watched = lreq->num_events;
+ }
+ }
+
+ printf("%s: %lu\n", __func__, ino);
+
+ if (!lreq && !wreq)
+ fuse_reply_poll(req, POLLERR);
+
+ if (ph != NULL) {
+ if (oldph)
+ fuse_pollhandle_destroy(oldph);
+
+ if (lreq)
+ lreq->ph = ph;
+
+ if (wreq)
+ wreq->ph = ph;
+ }
+
+ if (watched)
+ fuse_reply_poll(req, POLLIN);
+ else
+ fuse_reply_poll(req, 0);
+
+ pthread_mutex_unlock(&guse_mutex);
+}
+
+static void *guse_producer_thread(void *data)
+{
+ const struct timespec interval = { 1, 0 };
+ unsigned idx = 0, nr = 1;
+ unsigned toggle = 0;
+ struct timespec ts;
+
+ (void) data;
+ while (!guse_stop) {
+ enum gpio_v2_line_event_id event_id = GPIO_V2_LINE_EVENT_RISING_EDGE;
+ int i, j;
+ uint64_t ts_ns;
+
+ timespec_get(&ts, TIME_UTC);
+ ts_ns = timespec_to_ns(ts);
+
+ if (toggle++ % 2)
+ event_id = GPIO_V2_LINE_EVENT_FALLING_EDGE;
+
+ pthread_mutex_lock(&guse_mutex);
+
+ for (i = 0; i < GUSE_LINES_NUM; i++) {
+ uint32_t line_bit = BIT(i);
+
+ /* generate fake config conditions */
+ if (line_info[i].padding[0]) {
+ for (j = 0; j < num_watched; j++) {
+ struct watch_request *wreq = &watch_requests[j];
+
+ if (wreq->watched_lines & line_bit) {
+ struct gpio_v2_line_info_changed *event = &wreq->events[wreq->num_events++];
+
+ event->timestamp_ns = ts_ns;
+ event->event_type = GPIO_V2_LINE_CHANGED_CONFIG;
+
+ event->info.offset = i;
+
+ printf("%s: push event GPIO_V2_LINE_CHANGED_CONFIG\n", __func__);
+ }
+ }
+ }
+
+ /* produce event for each input */
+ if (line_info[i].flags & GPIO_V2_LINE_FLAG_OUTPUT)
+ continue;
+
+ for (j = 0; j < num_requests; j++) {
+ struct line_request *lreq = &line_requests[j];
+ uint64_t mask;
+
+ if (event_id == GPIO_V2_LINE_EVENT_RISING_EDGE) {
+ mask = lreq->risen_mask;
+ } else {
+ mask = lreq->fallen_mask;
+ }
+
+ if (line_bit & mask) {
+ if (lreq->num_events >= GUSE_MAX_LINEREQ) {
+ fprintf(stderr, "Error: GUSE_MAX_LINEREQ\n");
+ continue;
+ }
+
+ struct gpio_v2_line_event *event = &lreq->events[lreq->num_events++];
+
+ event->timestamp_ns = ts_ns;
+ event->offset = i;
+ event->id = event_id;
+ }
+ }
+ }
+
+ for (i = 0; i < num_requests; i++) {
+ struct line_request *lreq = &line_requests[i];
+
+ if (lreq->num_events && lreq->ph) {
+ fuse_notify_poll(lreq->ph);
+ fuse_pollhandle_destroy(lreq->ph);
+ lreq->ph= NULL;
+ }
+ }
+
+ for (i = 0; i < num_watched; i++) {
+ struct watch_request *wreq = &watch_requests[i];
+
+ if (wreq->num_events && wreq->ph) {
+ fuse_notify_poll(wreq->ph);
+ fuse_pollhandle_destroy(wreq->ph);
+ wreq->ph= NULL;
+ }
+ }
+
+ pthread_mutex_unlock(&guse_mutex);
+
+ nanosleep(&interval, NULL);
+ }
+
+ return NULL;
+}
+
+struct gusexmp_param {
+ unsigned major;
+ unsigned minor;
+ char *dev_name;
+ int is_help;
+};
+
+#define GUSEXMP_OPT(t, p) { t, offsetof(struct gusexmp_param, p), 1 }
+
+static const struct fuse_opt gusexmp_opts[] = {
+ GUSEXMP_OPT("-M %u", major),
+ GUSEXMP_OPT("--maj=%u", major),
+ GUSEXMP_OPT("-m %u", minor),
+ GUSEXMP_OPT("--min=%u", minor),
+ GUSEXMP_OPT("-n %s", dev_name),
+ GUSEXMP_OPT("--name=%s", dev_name),
+ FUSE_OPT_KEY("-h", 0),
+ FUSE_OPT_KEY("--help", 0),
+ FUSE_OPT_END
+};
+
+static int gusexmp_process_arg(void *data, const char *arg, int key,
+ struct fuse_args *outargs)
+{
+ struct gusexmp_param *param = data;
+
+ (void)outargs;
+ (void)arg;
+
+ switch (key) {
+ case 0:
+ param->is_help = 1;
+ fprintf(stderr, "%s", usage);
+ return fuse_opt_add_arg(outargs, "-ho");
+ default:
+ return 1;
+ }
+}
+
+static const struct guse_cdev_lowlevel_ops gusexmp_clop = {
+ .init = gusexmp_init,
+ .destroy = gusexmp_destroy,
+ .open = gusexmp_open,
+ .release = gusexmp_release,
+ .read = gusexmp_read,
+ .ioctl = gusexmp_ioctl,
+ .poll = gusexmp_poll,
+};
+
+int main(int argc, char **argv)
+{
+ struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+ struct gusexmp_param param = { 0, 0, NULL, 0 };
+ char dev_name[128] = "DEVNAME=";
+ const char *dev_info_argv[] = { dev_name };
+ struct guse_info ci;
+ int ret = 1;
+
+ if (fuse_opt_parse(&args, ¶m, gusexmp_opts, gusexmp_process_arg)) {
+ printf("failed to parse option\n");
+ free(param.dev_name);
+ goto out;
+ }
+
+ if (!param.is_help) {
+ if (!param.dev_name) {
+ fprintf(stderr, "Error: device name missing\n");
+ goto out;
+ }
+ strncat(dev_name, param.dev_name, sizeof(dev_name) - sizeof("DEVNAME="));
+ free(param.dev_name);
+ }
+
+ errno = pthread_mutex_init(&guse_mutex, NULL);
+ if (errno) {
+ perror("pthread_mutex_init");
+ return 1;
+ }
+
+ errno = pthread_attr_init(&attr);
+ if (errno) {
+ perror("pthread_attr_init");
+ return 1;
+ }
+
+ memset(&ci, 0, sizeof(ci));
+ ci.dev_major = param.major;
+ ci.dev_minor = param.minor;
+ ci.dev_info_argc = 1;
+ ci.dev_info_argv = dev_info_argv;
+
+ ret = guse_lowlevel_main(args.argc, args.argv, &ci, &gusexmp_clop, NULL);
+
+out:
+ fuse_opt_free_args(&args);
+ return ret;
+}