guse: add example nshubin/guse
authorNikita Shubin <n.shubin@yadro.com>
Sat, 22 Feb 2025 05:12:42 +0000 (08:12 +0300)
committerNikita Shubin <n.shubin@yadro.com>
Thu, 13 Mar 2025 08:07:16 +0000 (11:07 +0300)
Signed-off-by: Nikita Shubin <n.shubin@yadro.com>
example/guse.c [new file with mode: 0644]
example/meson.build

diff --git a/example/guse.c b/example/guse.c
new file mode 100644 (file)
index 0000000..97b389d
--- /dev/null
@@ -0,0 +1,900 @@
+/*
+  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, &param, 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;
+}
index 5c02cc1f31da68fad09ec34274110c4be0fab2cf..0b827796e97db41c67a63242646c5e1914c6f05a 100644 (file)
@@ -1,7 +1,8 @@
 examples = [ 'passthrough', 'passthrough_fh',
              'hello', 'hello_ll',
              'printcap', 'ioctl_client', 'poll_client',
-             'ioctl', 'cuse', 'cuse_client' ]
+             'ioctl', 'cuse', 'cuse_client',
+             'guse' ]
 
 if not platform.endswith('bsd') and platform != 'dragonfly'
     examples += [ 'passthrough_ll', 'hello_ll_uds' ]