diff --git a/CMakeLists.txt b/CMakeLists.txt index 71736d5d5..cd90442ea 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -30,7 +30,21 @@ endif() option(BUILD_SHARED_LIBS "Build shared libraries" ON) -add_library(iio attr.c backend.c block.c channel.c device.c context.c buffer.c mask.c utilities.c scan.c sort.c task.c stream.c +add_library(iio + attr.c + backend.c + block.c + buffer.c + channel.c + context.c + device.c + events.c + mask.c + scan.c + sort.c + stream.c + task.c + utilities.c ${CMAKE_CURRENT_BINARY_DIR}/iio-config.h ) diff --git a/bindings/python/iio.py b/bindings/python/iio.py index aae654f9a..208799b01 100644 --- a/bindings/python/iio.py +++ b/bindings/python/iio.py @@ -17,7 +17,9 @@ Structure, c_char_p, c_uint, + c_uint64, c_int, + c_int64, c_long, c_longlong, c_size_t, @@ -34,6 +36,7 @@ memmove as _memmove, byref as _byref, ) +from contextlib import contextmanager from ctypes.util import find_library from enum import Enum from os import strerror as _strerror @@ -108,6 +111,8 @@ class _Channel(Structure): class _ChannelsMask(Structure): pass +class _EventStream(Structure): + pass class _Buffer(Structure): pass @@ -140,6 +145,47 @@ class DataFormat(Structure): ("offset", c_double), ] +class EventType(Enum): + """Represents the type of an IIO event.""" + + IIO_EV_TYPE_THRESH = 0 + IIO_EV_TYPE_MAG = 1 + IIO_EV_TYPE_ROC = 2 + IIO_EV_TYPE_THRESH_ADAPTIVE = 3 + IIO_EV_TYPE_MAG_ADAPTIVE = 4 + IIO_EV_TYPE_CHANGE = 5 + IIO_EV_TYPE_MAG_REFERENCED = 6 + IIO_EV_TYPE_GESTURE = 7 + +class EventDirection(Enum): + """Represents the direction of an IIO event.""" + + IIO_EV_DIR_EITHER = 0 + IIO_EV_DIR_RISING = 1 + IIO_EV_DIR_FALLING = 2 + IIO_EV_DIR_NONE = 3 + IIO_EV_DIR_SINGLETAP = 4 + IIO_EV_DIR_DOUBLETAP = 5 + +class Event(Structure): + """Represents an IIO event.""" + + _fields_ = [('id', c_uint64), ('timestamp', c_int64)] + + type = property( + lambda self: EventType((self.id >> 56) & 0xff), + None, + None, + "The type of the IIO event.\n\ttype=iio.EventType(Enum)", + ) + + direction = property( + lambda self: EventDirection((self.id >> 48) & 0x7f), + None, + None, + "The direction of the IIO event.\n\ttype=iio.EventDirection(Enum)", + ) + class ChannelModifier(Enum): """Contains the modifier type of an IIO channel.""" @@ -238,6 +284,8 @@ class ChannelType(Enum): _DevicePtr = _POINTER(_Device) _ChannelPtr = _POINTER(_Channel) _ChannelsMaskPtr = _POINTER(_ChannelsMask) +_EventPtr = _POINTER(Event) +_EventStreamPtr = _POINTER(_EventStream) _BufferPtr = _POINTER(_Buffer) _BlockPtr = _POINTER(_Block) _DataFormatPtr = _POINTER(DataFormat) @@ -400,6 +448,11 @@ class ChannelType(Enum): _d_get_attr.argtypes = (_DevicePtr, c_uint) _d_get_attr.errcheck = _check_null +_d_create_evstream = _lib.iio_device_create_event_stream +_d_create_evstream.restype = _EventStreamPtr +_d_create_evstream.argtypes = (_DevicePtr,) +_d_create_evstream.errcheck = _check_ptr_err + _d_debug_attr_count = _lib.iio_device_get_debug_attrs_count _d_debug_attr_count.restype = c_uint _d_debug_attr_count.argtypes = (_DevicePtr,) @@ -631,6 +684,18 @@ class ChannelType(Enum): _stream_get_next_block.argtypes = (_StreamPtr,) _stream_get_next_block.errcheck = _check_ptr_err +_evstream_destroy = _lib.iio_event_stream_destroy +_evstream_destroy.argtypes = (_EventStreamPtr,) + +_evstream_read = _lib.iio_event_stream_read +_evstream_read.restype = c_int +_evstream_read.argtypes = (_EventStreamPtr, _EventPtr, c_bool) +_evstream_read.errcheck = _check_negative + +_ev_get_channel = _lib.iio_event_get_channel +_ev_get_channel.restype = _ChannelPtr +_ev_get_channel.argtypes = (_EventPtr, _DevicePtr, c_bool) +_ev_get_channel.errcheck = _check_null # pylint: enable=invalid-name @@ -1082,6 +1147,56 @@ def __next__(self): return Block(self._buffer, self._block_size, next_hdl) +class EventStream(_IIO_Object): + """Class used to read IIO events. Cannot be instantiated directly.""" + + def __init__(self): + # Prevent the EventStream from being initialized manually. + raise NotImplementedError + + @classmethod + def _create(cls, device, stream): + self = cls.__new__(cls) + super(EventStream, self).__init__(stream, device) + + return self + + def read(self, nonblock=True): + """ + Read one event from the stream. + + :param nonblock: type=bool + If set, return None if there are no events in the stream. + If unset, it will wait until an event arrives. + + returns: type=(iio.Event, (iio.Channel, iio.Channel)) + The first element of the tuple is the Event read. + The second element is a tuple itself, that contains the + two channels related to the event. For non-differential + events the second channel will always be None. + """ + event = Event() + + try: + _evstream_read(self._hdl, _byref(event), nonblock) + except OSError as err: + if err.errno == 11: # EAGAIN + return None + + raise + + chn1 = None + chn2 = None + + try: + dev = self._parent + chn1 = Channel(dev, _ev_get_channel(_byref(event), dev._device, False)) + chn2 = Channel(dev, _ev_get_channel(_byref(event), dev._device, True)) + except OSError: + pass + + return (event, (chn1, chn2)) + class _DeviceOrTrigger(_IIO_Object): def __init__(self, _ctx, _device): super(_DeviceOrTrigger, self).__init__(_device, _ctx) @@ -1238,6 +1353,20 @@ def _get_trigger(self): return dev return None + @contextmanager + def event_stream(self): + """ + Create an events stream. + + returns: type=contextlib._GeneratorContextManager + A generator for a EventStream instance. + """ + try: + stream = _d_create_evstream(self._device) + yield EventStream._create(self, stream) + finally: + _evstream_destroy(stream) + trigger = property( _get_trigger, _set_trigger, diff --git a/channel.c b/channel.c index db9e7b8e6..233a1f717 100644 --- a/channel.c +++ b/channel.c @@ -50,6 +50,10 @@ static const char * const iio_chan_type_name_spec[] = { [IIO_POSITIONRELATIVE] = "positionrelative", [IIO_PHASE] = "phase", [IIO_MASSCONCENTRATION] = "massconcentration", + [IIO_DELTA_ANGL] = "delta_angl", + [IIO_DELTA_VELOCITY] = "delta_velocity", + [IIO_COLORTEMP] = "colortemp", + [IIO_CHROMATICITY] = "chromaticity", }; static const char * const hwmon_chan_type_name_spec[] = { diff --git a/events.c b/events.c new file mode 100644 index 000000000..4b130eb74 --- /dev/null +++ b/events.c @@ -0,0 +1,144 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/* + * libiio - Library for interfacing industrial I/O (IIO) devices + * + * Copyright (C) 2023 Analog Devices, Inc. + * Author: Paul Cercueil + */ + +#include "iio-private.h" + +#include +#include +#include + +struct iio_event_stream_pdata; + +struct iio_event_stream { + const struct iio_device *dev; + struct iio_event_stream_pdata *pdata; +}; + +/* Corresponds to IIO_EVENT_CODE_EXTRACT_CHAN() and + * IIO_EVENT_CODE_EXTRACT_CHAN2() macros of */ +static inline int16_t +iio_event_get_channel_id(const struct iio_event *event, unsigned int channel) +{ + return (int16_t)(event->id >> (channel << 4)); +} + +/* Corresponds to IIO_EVENT_CODE_EXTRACT_DIFF() of */ +static inline bool +iio_event_is_differential(const struct iio_event *event) +{ + return event->id & BIT(55); +} + +/* Corresponds to IIO_EVENT_CODE_EXTRACT_MODIFIER() of */ +static inline enum iio_modifier +iio_event_get_modifier(const struct iio_event *event) +{ + return (enum iio_modifier)((event->id >> 40) & 0xff); +} + +/* Corresponds to IIO_EVENT_CODE_EXTRACT_CHAN_TYPE() of */ +static inline enum iio_chan_type +iio_event_get_chan_type(const struct iio_event *event) +{ + return (enum iio_chan_type)((event->id >> 32) & 0xff); +} + +const struct iio_channel * +iio_event_get_channel(const struct iio_event *event, + const struct iio_device *dev, bool diff) +{ + const struct iio_channel *chn = NULL; + const char *ptr; + unsigned int i; + int16_t chid; + + if (diff && !iio_event_is_differential(event)) + return NULL; + + chid = iio_event_get_channel_id(event, diff); + if (chid < 0) + return NULL; + + if ((unsigned int)chid >= dev->nb_channels) { + dev_warn(dev, "Unexpected IIO event channel ID\n"); + return NULL; + } + + for (i = 0; i < dev->nb_channels; i++) { + chn = dev->channels[i]; + + if (chn->type != iio_event_get_chan_type(event) + || chn->modifier != iio_event_get_modifier(event)) { + continue; + } + + for (ptr = chn->id; *ptr && isalpha((unsigned char)*ptr); ) + ptr++; + + if (!*ptr && chid <= 0) + break; + + if ((uint16_t)chid == strtoul(ptr, NULL, 10)) + break; + } + + if (chn) { + chn_dbg(chn, "Found channel %s for event\n", + iio_channel_get_id(chn)); + } else { + dev_dbg(dev, "Unable to find channel for event\n"); + } + + return chn; +} + +struct iio_event_stream * +iio_device_create_event_stream(const struct iio_device *dev) +{ + struct iio_event_stream *stream; + int err; + + if (!dev->ctx->ops->open_ev) + return iio_ptr(-ENOSYS); + + stream = zalloc(sizeof(*stream)); + if (!stream) + return iio_ptr(-ENOMEM); + + stream->dev = dev; + + stream->pdata = dev->ctx->ops->open_ev(dev); + err = iio_err(stream->pdata); + if (err) { + free(stream); + return iio_ptr(err); + } + + return stream; +} + +void iio_event_stream_destroy(struct iio_event_stream *stream) +{ + if (stream->dev->ctx->ops->close_ev) + stream->dev->ctx->ops->close_ev(stream->pdata); + + free(stream); +} + +int iio_event_stream_read(struct iio_event_stream *stream, + struct iio_event *out_event, + bool nonblock) +{ + if (!stream->dev->ctx->ops->read_ev) + return -ENOSYS; + + if (!out_event) + return -EINVAL; + + return stream->dev->ctx->ops->read_ev(stream->pdata, out_event, nonblock); +} diff --git a/iio-private.h b/iio-private.h index f5b271d68..3a5330f8b 100644 --- a/iio-private.h +++ b/iio-private.h @@ -34,7 +34,7 @@ #define is_little_endian() (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) #endif -#define BIT(x) (1 << (x)) +#define BIT(x) (1ull << (x)) #define BIT_MASK(bit) BIT((bit) % 32) #define BIT_WORD(bit) ((bit) / 32) diff --git a/iiod-client.c b/iiod-client.c index 72b92e1f1..5039f0ace 100644 --- a/iiod-client.c +++ b/iiod-client.c @@ -30,6 +30,9 @@ struct iiod_client { struct iio_mutex *lock; struct iiod_responder *responder; + + /* TODO: atomic? */ + uint16_t next_evstream_idx; }; struct iiod_client_io { @@ -69,6 +72,14 @@ struct iio_block_pdata { bool enqueued; }; +struct iio_event_stream_pdata { + struct iiod_client *client; + const struct iio_device *dev; + struct iio_event event; + struct iiod_io *io; + uint16_t idx; +}; + void iiod_client_mutex_lock(struct iiod_client *client) { iio_mutex_lock(client->lock); @@ -263,6 +274,7 @@ struct iiod_client * iiod_client_new(const struct iio_context_params *params, client->ops = ops; client->desc = desc; client->responder = NULL; + client->next_evstream_idx = (uint16_t)-1; err = iiod_client_enable_binary(client); if (err) @@ -1693,3 +1705,132 @@ bool iiod_client_uses_binary_interface(const struct iiod_client *client) { return !!client->responder; } + +static int iiod_client_request_event_read(struct iio_event_stream_pdata *pdata) +{ + struct iiod_command cmd = { + .op = IIOD_OP_READ_EVENT, + .dev = (uint8_t) iio_device_get_index(pdata->dev), + }; + struct iiod_buf buf = { + .ptr = &pdata->event, + .size = sizeof(pdata->event), + }; + int err; + + err = iiod_io_get_response_async(pdata->io, &buf, 1); + if (err) + return err; + + err = iiod_io_send_command(pdata->io, &cmd, NULL, 0); + if (err) + return err; + + return 0; +} + +struct iio_event_stream_pdata * +iiod_client_open_event_stream(struct iiod_client *client, + const struct iio_device *dev) +{ + struct iio_event_stream_pdata *pdata; + uint16_t idx = client->next_evstream_idx--; + struct iiod_command cmd = { + .op = IIOD_OP_CREATE_EVSTREAM, + .dev = (uint8_t) iio_device_get_index(dev), + }; + int err; + + if (!iiod_client_uses_binary_interface(client)) + return iio_ptr(-ENOSYS); + + pdata = zalloc(sizeof(*pdata)); + if (!pdata) + return iio_ptr(-ENOMEM); + + pdata->dev = dev; + pdata->client = client; + pdata->idx = idx; + + pdata->io = iiod_responder_create_io(client->responder, idx); + err = iio_err(pdata->io); + if (err) + goto err_free_pdata; + + err = iiod_io_exec_simple_command(pdata->io, &cmd); + if (err) + goto err_destroy_io; + + /* Use infinite timeout for blocking events reads, as those only make + * sense when running in a thread, and we don't want them to return + * without any event. */ + iiod_io_set_timeout(pdata->io, 0); + + /* Send a request to read an event. This bootstraps the event reading + * mechanism. */ + err = iiod_client_request_event_read(pdata); + if (err) { + iiod_client_close_event_stream(pdata); + return iio_ptr(err); + } + + return pdata; + +err_destroy_io: + iiod_io_cancel(pdata->io); + iiod_io_unref(pdata->io); +err_free_pdata: + free(pdata); + return iio_ptr(err); +} + +void iiod_client_close_event_stream(struct iio_event_stream_pdata *pdata) +{ + struct iiod_command cmd = { + .op = IIOD_OP_FREE_EVSTREAM, + .dev = (uint8_t) iio_device_get_index(pdata->dev), + .code = pdata->idx, + }; + struct iiod_io *io; + + /* Close the event stream using the default I/O, since there may + * be a blocking event read operation pending on the I/O pipe dedicated + * to this event stream. */ + io = iiod_responder_get_default_io(pdata->client->responder); + + iiod_io_exec_simple_command(io, &cmd); + + iiod_io_cancel(pdata->io); + iiod_io_unref(pdata->io); + free(pdata); +} + +int iiod_client_read_event(struct iio_event_stream_pdata *pdata, + struct iio_event *out_event, + bool nonblock) +{ + struct iiod_io *io = pdata->io; + int ret = 0; + + /* Get a reference to the I/O stream, so that it's not freed while + * we're using it */ + iiod_io_ref(io); + + if (!nonblock) + ret = (int)iiod_io_wait_for_response(io); + else if (!iiod_io_has_response(io)) + ret = -EAGAIN; + + if (ret >= 0) { + /* We have a response from the last command. + * Copy the event to the output buffer. */ + *out_event = pdata->event; + + /* Request a new event. */ + ret = iiod_client_request_event_read(pdata); + } + + iiod_io_unref(io); + + return ret; +} diff --git a/iiod-responder.c b/iiod-responder.c index c16ac9458..1bd6e0c63 100644 --- a/iiod-responder.c +++ b/iiod-responder.c @@ -612,6 +612,12 @@ iiod_responder_set_timeout(struct iiod_responder *priv, unsigned int timeout_ms) priv->default_io->timeout_ms = timeout_ms; } +void +iiod_io_set_timeout(struct iiod_io *io, unsigned int timeout_ms) +{ + io->timeout_ms = timeout_ms; +} + struct iiod_responder * iiod_responder_create(const struct iiod_responder_ops *ops, void *d) { diff --git a/iiod-responder.h b/iiod-responder.h index 14ac62fe4..42c8c1221 100644 --- a/iiod-responder.h +++ b/iiod-responder.h @@ -50,6 +50,10 @@ enum iiod_opcode { IIOD_OP_TRANSFER_BLOCK, IIOD_OP_ENQUEUE_BLOCK_CYCLIC, + IIOD_OP_CREATE_EVSTREAM, + IIOD_OP_FREE_EVSTREAM, + IIOD_OP_READ_EVENT, + IIOD_NB_OPCODES, }; @@ -81,6 +85,7 @@ void iiod_responder_destroy(struct iiod_responder *responder); /* Set the timeout for I/O operations (default is 0 == infinite) */ void iiod_responder_set_timeout(struct iiod_responder *priv, unsigned int timeout_ms); +void iiod_io_set_timeout(struct iiod_io *io, unsigned int timeout_ms); /* Read the current value of the micro-second counter */ uint64_t iiod_responder_read_counter_us(void); diff --git a/iiod/ops.h b/iiod/ops.h index b274205b5..b02db5566 100644 --- a/iiod/ops.h +++ b/iiod/ops.h @@ -69,7 +69,7 @@ struct block_entry { struct buffer_entry { SLIST_ENTRY(buffer_entry) entry; struct parser_pdata *pdata; - struct iio_device *dev; + const struct iio_device *dev; struct iio_buffer *buf; struct iio_task *enqueue_task, *dequeue_task; uint32_t *words; @@ -79,6 +79,16 @@ struct buffer_entry { pthread_mutex_t lock; }; +struct evstream_entry { + SLIST_ENTRY(evstream_entry) entry; + struct parser_pdata *pdata; + const struct iio_device *dev; + struct iio_event_stream *stream; + struct iio_task *task; + struct iiod_io *io; + uint16_t client_id; +}; + struct parser_pdata { struct iio_context *ctx; bool stop, binary, verbose; diff --git a/iiod/responder.c b/iiod/responder.c index 561190503..276c23998 100644 --- a/iiod/responder.c +++ b/iiod/responder.c @@ -31,6 +31,11 @@ static SLIST_HEAD(BufferList, buffer_entry) bufferlist; /* Protect bufferlist from parallel access */ static pthread_mutex_t buflist_lock = PTHREAD_MUTEX_INITIALIZER; +static SLIST_HEAD(EventStreamList, evstream_entry) evlist; + +/* Protect evlist from parallel access */ +static pthread_mutex_t evlist_lock = PTHREAD_MUTEX_INITIALIZER; + static void free_block_entry(struct block_entry *entry) { iiod_io_cancel(entry->io); @@ -329,7 +334,7 @@ static void handle_create_buffer(struct parser_pdata *pdata, struct iiod_io *io = iiod_command_get_default_io(cmd_data); const struct iio_context *ctx = pdata->ctx; struct iio_channels_mask *mask; - struct iio_device *dev; + const struct iio_device *dev; struct iio_channel *chn; struct buffer_entry *entry; struct iio_buffer *buf; @@ -719,13 +724,17 @@ static void handle_transfer_block(struct parser_pdata *pdata, buf = get_iio_buffer(pdata, cmd, &entry); ret = iio_err(buf); - if (ret) - goto out_send_response; + if (ret) { + IIO_ERROR("handle_transfer_block: Could not find IIO buffer\n"); + return; + } block = get_iio_block(pdata, entry, cmd, &block_entry); ret = iio_err(block); - if (ret) - goto out_send_response; + if (ret) { + IIO_ERROR("handle_transfer_block: Could not find IIO block\n"); + return; + } readbuf.ptr = &bytes_used; readbuf.size = 8; @@ -765,6 +774,182 @@ static void handle_transfer_block(struct parser_pdata *pdata, iiod_io_send_response_code(block_entry->io, ret); } +static int evstream_read(void *priv, void *d) +{ + struct evstream_entry *entry = priv; + struct iio_event event; + struct iiod_buf buf = { + .ptr = &event, + .size = sizeof(event), + }; + int ret; + + ret = iio_event_stream_read(entry->stream, &event, false); + if (ret < 0) + return iiod_io_send_response_code(entry->io, ret); + + return iiod_io_send_response(entry->io, sizeof(event), &buf, 1); +} + +static void handle_create_evstream(struct parser_pdata *pdata, + const struct iiod_command *cmd, + struct iiod_command_data *cmd_data) +{ + const struct iio_context *ctx = pdata->ctx; + const struct iio_device *dev; + struct evstream_entry *entry; + struct iiod_io *io; + int ret = -EINVAL; + + io = iiod_command_create_io(cmd, cmd_data); + ret = iio_err(io); + if (ret) { + /* TODO: How to handle this error? */ + return; + } + + dev = iio_context_get_device(ctx, cmd->dev); + if (!dev) + goto out_send_response; + + entry = zalloc(sizeof(*entry)); + if (!entry) { + ret = -ENOMEM; + goto out_send_response; + } + + entry->io = io; + entry->dev = dev; + entry->client_id = cmd->client_id; + entry->pdata = pdata; + + entry->stream = iio_device_create_event_stream(dev); + ret = iio_err(entry->stream); + if (ret) { + free(entry); + goto out_send_response; + } + + entry->task = iio_task_create(evstream_read, entry, + "evstream-read-thd"); + ret = iio_err(entry->task); + if (ret) { + iio_event_stream_destroy(entry->stream); + free(entry); + goto out_send_response; + } + + iio_task_start(entry->task); + + /* Keep a reference to the iiod_io until the evstream is freed. */ + iiod_io_ref(io); + + pthread_mutex_lock(&evlist_lock); + SLIST_INSERT_HEAD(&evlist, entry, entry); + pthread_mutex_unlock(&evlist_lock); + +out_send_response: + iiod_io_send_response_code(io, ret); + iiod_io_unref(io); +} + +static struct evstream_entry * get_evstream(struct parser_pdata *pdata, + const struct iiod_command *cmd, + uint16_t idx, bool remove) +{ + const struct iio_context *ctx = pdata->ctx; + const struct iio_device *dev; + struct evstream_entry *entry; + + dev = iio_context_get_device(ctx, cmd->dev); + if (!dev) + return NULL; + + pthread_mutex_lock(&evlist_lock); + SLIST_FOREACH(entry, &evlist, entry) { + if (entry->client_id == idx + && entry->dev == dev + && entry->pdata == pdata) { + break; + } + } + + if (entry && remove) + SLIST_REMOVE(&evlist, entry, evstream_entry, entry); + + pthread_mutex_unlock(&evlist_lock); + + return entry; +} + +static void free_evstream(struct evstream_entry *entry) +{ + + iio_event_stream_destroy(entry->stream); + iiod_io_cancel(entry->io); + + iio_task_stop(entry->task); + iio_task_destroy(entry->task); + + iiod_io_unref(entry->io); + free(entry); +} + +static void handle_free_evstream(struct parser_pdata *pdata, + const struct iiod_command *cmd, + struct iiod_command_data *cmd_data) +{ + struct evstream_entry *entry, *each; + struct iiod_io *io = iiod_command_get_default_io(cmd_data); + int ret = 0; + + entry = get_evstream(pdata, cmd, cmd->code, true); + if (!entry) { + ret = -EBADF; + goto out_send_response; + } + + free_evstream(entry); + +out_send_response: + iiod_io_send_response_code(io, ret); +} + +static void handle_read_event(struct parser_pdata *pdata, + const struct iiod_command *cmd, + struct iiod_command_data *cmd_data) +{ + struct evstream_entry *entry; + int ret; + + entry = get_evstream(pdata, cmd, cmd->client_id, false); + if (!entry) { + /* TODO: How to handle this error? */ + return; + } + + if (cmd->code) { + struct iio_event event; + struct iiod_buf buf = { + .ptr = &event, + .size = sizeof(event), + }; + + /* Nonblock mode: run iio_event_stream_read() inline, + * and respond here. */ + ret = iio_event_stream_read(entry->stream, &event, true); + if (ret < 0) + iiod_io_send_response_code(entry->io, ret); + else + iiod_io_send_response(entry->io, sizeof(event), &buf, 1); + } else { + /* Blocking mode: defer the answer. */ + ret = iio_task_enqueue_autoclear(entry->task, entry); + if (ret) + iiod_io_send_response_code(entry->io, ret); + } +} + typedef void (*iiod_opcode_fn)(struct parser_pdata *, const struct iiod_command *, struct iiod_command_data *cmd_data); @@ -792,6 +977,10 @@ static const iiod_opcode_fn iiod_op_functions[] = { [IIOD_OP_FREE_BLOCK] = handle_free_block, [IIOD_OP_TRANSFER_BLOCK] = handle_transfer_block, [IIOD_OP_ENQUEUE_BLOCK_CYCLIC] = handle_transfer_block, + + [IIOD_OP_CREATE_EVSTREAM] = handle_create_evstream, + [IIOD_OP_FREE_EVSTREAM] = handle_free_evstream, + [IIOD_OP_READ_EVENT] = handle_read_event, }; static int iiod_cmd(const struct iiod_command *cmd, @@ -828,6 +1017,7 @@ static const struct iiod_responder_ops iiod_responder_ops = { static void iiod_responder_free_resources(struct parser_pdata *pdata) { struct buffer_entry *buf_entry, *buf_next; + struct evstream_entry *ev_entry, *ev_next; pthread_mutex_lock(&buflist_lock); @@ -843,6 +1033,20 @@ static void iiod_responder_free_resources(struct parser_pdata *pdata) } pthread_mutex_unlock(&buflist_lock); + + pthread_mutex_lock(&evlist_lock); + + for (ev_entry = SLIST_FIRST(&evlist); ev_entry; ev_entry = ev_next) { + ev_next = SLIST_NEXT(ev_entry, entry); + + /* Only free the event streams that this client created */ + if (ev_entry->pdata == pdata) { + SLIST_REMOVE(&evlist, ev_entry, evstream_entry, entry); + free_evstream(ev_entry); + } + } + + pthread_mutex_unlock(&evlist_lock); } int binary_parse(struct parser_pdata *pdata) diff --git a/include/iio/iio-backend.h b/include/iio/iio-backend.h index 8df8031af..73b5fec6c 100644 --- a/include/iio/iio-backend.h +++ b/include/iio/iio-backend.h @@ -53,6 +53,7 @@ struct iio_buffer_pdata; struct iio_context_pdata; struct iio_device_pdata; struct iio_channel_pdata; +struct iio_event_stream_pdata; enum iio_backend_api_ver { IIO_BACKEND_API_V1 = 1, @@ -124,6 +125,12 @@ struct iio_backend_ops { int (*dequeue_block)(struct iio_block_pdata *pdata, bool nonblock); int (*get_dmabuf_fd)(struct iio_block_pdata *pdata); + + struct iio_event_stream_pdata *(*open_ev)(const struct iio_device *dev); + void (*close_ev)(struct iio_event_stream_pdata *pdata); + int (*read_ev)(struct iio_event_stream_pdata *pdata, + struct iio_event *out_event, + bool nonblock); }; /** diff --git a/include/iio/iio.h b/include/iio/iio.h index 49393251e..bd2b355a1 100644 --- a/include/iio/iio.h +++ b/include/iio/iio.h @@ -92,6 +92,7 @@ struct iio_context; struct iio_device; struct iio_channel; struct iio_channels_mask; +struct iio_event_stream; struct iio_buffer; struct iio_scan; struct iio_stream; @@ -195,6 +196,10 @@ enum iio_chan_type { IIO_POSITIONRELATIVE, IIO_PHASE, IIO_MASSCONCENTRATION, + IIO_DELTA_ANGL, + IIO_DELTA_VELOCITY, + IIO_COLORTEMP, + IIO_CHROMATICITY, IIO_CHAN_TYPE_UNKNOWN = INT_MAX }; @@ -1290,6 +1295,87 @@ static inline bool iio_device_is_hwmon(const struct iio_device *dev) } +/** @} *//* ------------------------------------------------------------------*/ +/* ---------------------------- IIO events support ---------------------------*/ +/** @defgroup Events Functions to read IIO events + * @{ + * @struct iio_event + * @brief Represents a IIO event. + * + * This structure is the same as 'iio_event_data' from . + */ +struct iio_event { + uint64_t id; + int64_t timestamp; +}; + +/** + * @brief Get the type of a given IIO event + * @param event A pointer to an iio_event structure + * @return An enum iio_event_type. + * + * NOTE:Corresponds to the IIO_EVENT_CODE_EXTRACT_TYPE macro of + * . */ +static inline enum iio_event_type +iio_event_get_type(const struct iio_event *event) +{ + return (enum iio_event_type)((event->id >> 56) & 0xff); +} + +/** + * @brief Get the direction of a given IIO event + * @param event A pointer to an iio_event structure + * @return An enum iio_event_direction. + * + * NOTE:Corresponds to the IIO_EVENT_CODE_EXTRACT_DIR macro of + * . */ +static inline enum iio_event_direction +iio_event_get_direction(const struct iio_event *event) +{ + return (enum iio_event_direction)((event->id >> 48) & 0x7f); +} + +/** + * @brief Get a pointer to the IIO channel that corresponds to this event. + * @param event A pointer to an iio_event structure + * @param dev A pointer to the iio_device structure that delivered the event + * @param diff If set, retrieve the differential channel + * @return On success, a pointer to an iio_channel structure + * @return On error, NULL is returned */ +__api __check_ret const struct iio_channel * +iio_event_get_channel(const struct iio_event *event, + const struct iio_device *dev, bool diff); + +/** + * @brief Create an events stream for the given IIO device. + * @param dev A pointer to an iio_device structure + * @return On success, a pointer to an iio_event_stream structure + * @return On failure, a pointer-encoded error is returned */ +__api __check_ret struct iio_event_stream * +iio_device_create_event_stream(const struct iio_device *dev); + +/** + * @brief Destroy the given event stream. + * @param stream A pointer to an iio_event_stream structure */ +__api void iio_event_stream_destroy(struct iio_event_stream *stream); + +/** + * @brief Read an event from the event stream. + * @param stream A pointer to an iio_event_stream structure + * @param out_event An pointer to an iio_event structure, that will be filled by + * this function. + * @param nonblock if True, the operation won't block and return -EBUSY if + * there is currently no event in the queue. + * @return On success, 0 is returned + * @return On error, a negative errno code is returned. + * + * NOTE: it is possible to stop a blocking call of iio_event_stream_read + * by calling iio_event_stream_destroy in a different thread or signal handler. + * In that case, iio_event_stream_read will return -EINTR. */ +__api int iio_event_stream_read(struct iio_event_stream *stream, + struct iio_event *out_event, + bool nonblock); + /** @} *//* ------------------------------------------------------------------*/ /* ------------------------- Low-level functions -----------------------------*/ /** @defgroup Debug Debug and low-level functions diff --git a/include/iio/iiod-client.h b/include/iio/iiod-client.h index 30be7d2a0..7582f7c60 100644 --- a/include/iio/iiod-client.h +++ b/include/iio/iiod-client.h @@ -16,6 +16,7 @@ struct iiod_client; struct iiod_client_io; struct iiod_client_pdata; +struct iio_event_stream_pdata; struct iiod_client_ops { ssize_t (*write)(struct iiod_client_pdata *desc, @@ -94,6 +95,14 @@ __api ssize_t iiod_client_readbuf(struct iiod_client_buffer_pdata *pdata, __api ssize_t iiod_client_writebuf(struct iiod_client_buffer_pdata *pdata, const void *src, size_t len); +struct iio_event_stream_pdata * +iiod_client_open_event_stream(struct iiod_client *client, + const struct iio_device *dev); +void iiod_client_close_event_stream(struct iio_event_stream_pdata *pdata); +int iiod_client_read_event(struct iio_event_stream_pdata *pdata, + struct iio_event *out_event, + bool nonblock); + #undef __api #endif /* _IIOD_CLIENT_H */ diff --git a/local.c b/local.c index ffc183293..89b337684 100644 --- a/local.c +++ b/local.c @@ -35,6 +35,7 @@ #define NB_BLOCKS 4 +#define IIO_GET_EVENT_FD_IOCTL _IOR('i', 0x90, int) #define IIO_BUFFER_GET_FD_IOCTL _IOWR('i', 0x91, int) /* Forward declarations */ @@ -47,6 +48,15 @@ local_create_context(const struct iio_context_params *params, const char *args); static int local_context_scan(const struct iio_context_params *params, struct iio_scan *ctx, const char *args); +struct iio_device_pdata { + int fd; +}; + +struct iio_event_stream_pdata { + const struct iio_device *dev; + int fd, cancel_fd; +}; + struct iio_channel_pdata { char *enable_fn; struct iio_attr_list protected; @@ -91,6 +101,8 @@ static void local_free_pdata(struct iio_device *device) for (i = 0; i < device->nb_channels; i++) local_free_channel_pdata(device->channels[i]); + + free(device->pdata); } static void local_shutdown(struct iio_context *ctx) @@ -101,6 +113,8 @@ static void local_shutdown(struct iio_context *ctx) for (i = 0; i < iio_context_get_devices_count(ctx); i++) { struct iio_device *dev = iio_context_get_device(ctx, i); + if (dev->pdata && dev->pdata->fd >= 0) + close(dev->pdata->fd); local_free_pdata(dev); } } @@ -1106,31 +1120,36 @@ static int add_buffer_attr(void *d, const char *path) } static int add_attr_or_channel_helper(struct iio_device *dev, - const char *path, bool dir_is_scan_elements) + const char *path, const char *prefix, + bool dir_is_scan_elements) { char buf[1024]; const char *name = strrchr(path, '/') + 1; - if (dir_is_scan_elements) { - iio_snprintf(buf, sizeof(buf), "scan_elements/%s", name); - path = buf; - } else { - if (!is_channel(dev, name, true)) - return add_attr_to_device(dev, name); - path = name; - } + if (!dir_is_scan_elements && !is_channel(dev, name, true)) + return add_attr_to_device(dev, name); + + iio_snprintf(buf, sizeof(buf), "%s%s", prefix, name); - return add_channel(dev, name, path, dir_is_scan_elements); + return add_channel(dev, name, buf, dir_is_scan_elements); } static int add_attr_or_channel(void *d, const char *path) { - return add_attr_or_channel_helper((struct iio_device *) d, path, false); + return add_attr_or_channel_helper((struct iio_device *) d, + path, "", false); +} + +static int add_event(void *d, const char *path) +{ + return add_attr_or_channel_helper((struct iio_device *) d, + path, "events/", false); } static int add_scan_element(void *d, const char *path) { - return add_attr_or_channel_helper((struct iio_device *) d, path, true); + return add_attr_or_channel_helper((struct iio_device *) d, + path, "scan_elements/", true); } static int foreach_in_dir(const struct iio_context *ctx, @@ -1219,6 +1238,23 @@ static int add_buffer_attributes(struct iio_device *dev, const char *devpath) return 0; } +static int add_events(struct iio_device *dev, const char *devpath) +{ + const struct iio_context *ctx = iio_device_get_context(dev); + struct stat st; + char buf[1024]; + + iio_snprintf(buf, sizeof(buf), "%s/events", devpath); + + if (!stat(buf, &st) && S_ISDIR(st.st_mode)) { + int ret = foreach_in_dir(ctx, dev, buf, false, add_event); + if (ret < 0) + return ret; + } + + return 0; +} + static int create_device(void *d, const char *path) { unsigned int i; @@ -1243,6 +1279,10 @@ static int create_device(void *d, const char *path) if (ret < 0) goto err_free_device; + ret = add_events(dev, path); + if (ret < 0) + goto err_free_scan_elements; + ret = add_scan_elements(dev, path); if (ret < 0) goto err_free_scan_elements; @@ -1303,19 +1343,23 @@ static int add_debug(void *d, const char *path) return foreach_in_dir(ctx, dev, path, false, add_debug_attr); } -static void local_cancel_buffer(struct iio_buffer_pdata *pdata) +static void local_signal_cancel_fd(const struct iio_device *dev, int fd) { - const struct iio_device *dev = pdata->dev; uint64_t event = 1; int ret; - ret = write(pdata->cancel_fd, &event, sizeof(event)); + ret = write(fd, &event, sizeof(event)); if (ret == -1) { /* If this happens something went very seriously wrong */ dev_perror(dev, -errno, "Unable to signal cancellation event"); } } +static void local_cancel_buffer(struct iio_buffer_pdata *pdata) +{ + local_signal_cancel_fd(pdata->dev, pdata->cancel_fd); +} + static char * local_get_description(const struct iio_context *ctx) { char *description; @@ -1369,10 +1413,12 @@ local_create_buffer(const struct iio_device *dev, unsigned int idx, { struct iio_buffer_pdata *pdata; const struct iio_channel *chn; - char buf[1024]; - int err, fd, cancel_fd, new_fd = idx; + int err, cancel_fd, fd = idx; unsigned int i; + if (dev->pdata->fd < 0) + return iio_ptr(dev->pdata->fd); + pdata = zalloc(sizeof(*pdata)); if (!pdata) return iio_ptr(-ENOMEM); @@ -1392,20 +1438,17 @@ local_create_buffer(const struct iio_device *dev, unsigned int idx, goto err_free_mmap_pdata; } - iio_snprintf(buf, sizeof(buf), "/dev/%s", dev->id); - fd = open(buf, O_RDWR | O_CLOEXEC | O_NONBLOCK); - if (fd == -1) { - err = -errno; - goto err_close_eventfd; - } - - err = ioctl_nointr(fd, IIO_BUFFER_GET_FD_IOCTL, &new_fd); + err = ioctl_nointr(dev->pdata->fd, IIO_BUFFER_GET_FD_IOCTL, &fd); if (err == 0) { - close(fd); - fd = new_fd; pdata->multi_buffer = true; - } else if (idx > 0) { - goto err_close; + } else if (idx == 0) { + fd = dup(dev->pdata->fd); + if (fd == -1) { + err = -errno; + goto err_close_eventfd; + } + } else { + goto err_close_eventfd; } pdata->cancel_fd = cancel_fd; @@ -1531,6 +1574,88 @@ int local_dequeue_block(struct iio_block_pdata *pdata, bool nonblock) return -ENOSYS; } +static struct iio_event_stream_pdata * +local_open_events_fd(const struct iio_device *dev) +{ + struct iio_event_stream_pdata *pdata; + int err; + + if (dev->pdata->fd < 0) + return iio_ptr(dev->pdata->fd); + + pdata = zalloc(sizeof(*pdata)); + if (!pdata) + return iio_ptr(-ENOMEM); + + pdata->dev = dev; + + pdata->cancel_fd = eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK); + if (pdata->cancel_fd == -1) { + err = -errno; + free(pdata); + return iio_ptr(err); + } + + err = ioctl_nointr(dev->pdata->fd, IIO_GET_EVENT_FD_IOCTL, &pdata->fd); + if (err < 0) { + close(pdata->cancel_fd); + free(pdata); + return iio_ptr(err); + } + + return pdata; +} + +static void local_close_events_fd(struct iio_event_stream_pdata *pdata) +{ + local_signal_cancel_fd(pdata->dev, pdata->cancel_fd); + close(pdata->cancel_fd); + + close(pdata->fd); + free(pdata); +} + +static int local_read_event(struct iio_event_stream_pdata *pdata, + struct iio_event *out_event, bool nonblock) +{ + struct pollfd pollfd[2] = { + { + .fd = pdata->fd, + .events = POLLIN, + }, { + .fd = pdata->cancel_fd, + .events = POLLIN, + } + }; + int ret, timeout_rel = nonblock ? 0 : -1; + + do { + ret = poll(pollfd, 2, timeout_rel); + } while (ret == -1 && errno == EINTR); + + if ((pollfd[1].revents & (POLLIN | POLLNVAL))) + return -EINTR; + + if (ret < 0) + return -errno; + + if (!ret) { + /* No event available */ + return nonblock ? -EAGAIN : -EBUSY; + } + + + if (!(pollfd[0].revents & POLLIN)) { + /* Unknown error */ + return -EIO; + } + + if (read(pdata->fd, out_event, sizeof(*out_event)) < 0) /* Flawfinder: ignore */ + return -errno; + + return 0; +} + static const struct iio_backend_ops local_ops = { .scan = local_context_scan, .create = local_create_context, @@ -1552,6 +1677,10 @@ static const struct iio_backend_ops local_ops = { .readbuf = local_readbuf, .writebuf = local_writebuf, + + .open_ev = local_open_events_fd, + .close_ev = local_close_events_fd, + .read_ev = local_read_event, }; const struct iio_backend iio_local_backend = { @@ -1562,6 +1691,37 @@ const struct iio_backend iio_local_backend = { .default_timeout_ms = 1000, }; +static int local_open_buffer(const struct iio_device *dev) +{ + char buf[1024]; + int fd; + + iio_snprintf(buf, sizeof(buf), "/dev/%s", dev->id); + fd = open(buf, O_RDWR | O_CLOEXEC | O_NONBLOCK); + if (fd == -1) + return -errno; + + return fd; +} + +static int init_devices(struct iio_context *ctx) +{ + struct iio_device *dev; + unsigned int i; + + for (i = 0; i < ctx->nb_devices; i++) { + dev = ctx->devices[i]; + + dev->pdata = malloc(sizeof(*dev->pdata)); + if (!dev->pdata) + return -ENOMEM; + + dev->pdata->fd = local_open_buffer(dev); + } + + return 0; +} + static int populate_context_attrs(struct iio_context *ctx, const char *file) { struct INI *ini; @@ -1677,6 +1837,10 @@ local_create_context(const struct iio_context_params *params, const char *args) if (ret < 0) goto err_context_destroy; + ret = init_devices(ctx); + if (ret < 0) + goto err_context_destroy; + return ctx; err_context_destroy: diff --git a/network.c b/network.c index 4f97ac27e..d6482b25a 100644 --- a/network.c +++ b/network.c @@ -496,6 +496,15 @@ struct iio_block_pdata * network_create_block(struct iio_buffer_pdata *pdata, return iiod_client_create_block(pdata->pdata, size, data); } +static struct iio_event_stream_pdata * +network_open_events_fd(const struct iio_device *dev) +{ + const struct iio_context *ctx = iio_device_get_context(dev); + struct iio_context_pdata *pdata = iio_context_get_pdata(ctx); + + return iiod_client_open_event_stream(pdata->iiod_client, dev); +} + static const struct iio_backend_ops network_ops = { .scan = IF_ENABLED(HAVE_DNS_SD, dnssd_context_scan), .create = network_create_context, @@ -518,6 +527,10 @@ static const struct iio_backend_ops network_ops = { .free_block = iiod_client_free_block, .enqueue_block = iiod_client_enqueue_block, .dequeue_block = iiod_client_dequeue_block, + + .open_ev = network_open_events_fd, + .close_ev = iiod_client_close_event_stream, + .read_ev = iiod_client_read_event, }; __api_export_if(WITH_NETWORK_BACKEND_DYNAMIC) diff --git a/serial.c b/serial.c index 59178557a..3ce96f208 100644 --- a/serial.c +++ b/serial.c @@ -236,6 +236,15 @@ serial_create_block(struct iio_buffer_pdata *buf, size_t size, void **data) return iiod_client_create_block(buf->pdata, size, data); } +static struct iio_event_stream_pdata * +serial_open_events_fd(const struct iio_device *dev) +{ + const struct iio_context *ctx = iio_device_get_context(dev); + struct iio_context_pdata *pdata = iio_context_get_pdata(ctx); + + return iiod_client_open_event_stream(pdata->iiod_client, dev); +} + static const struct iio_backend_ops serial_ops = { .create = serial_create_context_from_args, .read_attr = serial_read_attr, @@ -252,6 +261,10 @@ static const struct iio_backend_ops serial_ops = { .free_block = iiod_client_free_block, .enqueue_block = iiod_client_enqueue_block, .dequeue_block = iiod_client_dequeue_block, + + .open_ev = serial_open_events_fd, + .close_ev = iiod_client_close_event_stream, + .read_ev = iiod_client_read_event, }; __api_export_if(WITH_SERIAL_BACKEND_DYNAMIC) diff --git a/usb.c b/usb.c index b9baf97f6..d8e255909 100644 --- a/usb.c +++ b/usb.c @@ -501,6 +501,15 @@ usb_create_block(struct iio_buffer_pdata *pdata, size_t size, void **data) return iiod_client_create_block(pdata->pdata, size, data); } +static struct iio_event_stream_pdata * +usb_open_events_fd(const struct iio_device *dev) +{ + const struct iio_context *ctx = iio_device_get_context(dev); + struct iio_context_pdata *pdata = iio_context_get_pdata(ctx); + + return iiod_client_open_event_stream(pdata->io_ctx.iiod_client, dev); +} + static const struct iio_backend_ops usb_ops = { .scan = usb_context_scan, .create = usb_create_context_from_args, @@ -523,6 +532,10 @@ static const struct iio_backend_ops usb_ops = { .free_block = iiod_client_free_block, .enqueue_block = iiod_client_enqueue_block, .dequeue_block = iiod_client_dequeue_block, + + .open_ev = usb_open_events_fd, + .close_ev = iiod_client_close_event_stream, + .read_ev = iiod_client_read_event, }; __api_export_if(WITH_USB_BACKEND_DYNAMIC) diff --git a/utils/CMakeLists.txt b/utils/CMakeLists.txt index 9e548335e..41a327700 100644 --- a/utils/CMakeLists.txt +++ b/utils/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.10) -set(IIO_TESTS_TARGETS iio_genxml iio_info iio_attr iio_rwdev iio_reg) +set(IIO_TESTS_TARGETS iio_genxml iio_info iio_attr iio_rwdev iio_reg iio_event) if (PTHREAD_LIBRARIES OR ANDROID) LIST(APPEND IIO_TESTS_TARGETS iio_stresstest) endif() diff --git a/utils/gen_code.c b/utils/gen_code.c index 7cb10d245..ee82eb268 100644 --- a/utils/gen_code.c +++ b/utils/gen_code.c @@ -297,9 +297,11 @@ void gen_function(const char* prefix, const char* target, if (wbuf) { fprintf(fd, " # Write string to %s attribute:\n", prefix); if (!strcmp(prefix, "device") || !strcmp(prefix, "channel")) { - fprintf(fd, " %s.attrs[\"%s\"].value = str(\"%s\")\n", target, attr, wbuf); + fprintf(fd, " %s.attrs[\"%s\"].value = str(\"%s\")\n", target, + iio_attr_get_name(attr), wbuf); } else if (!strcmp(prefix, "device_debug")) { - fprintf(fd, " %s.debug_attrs[\"%s\"].value = str(\"%s\")\n", target, attr, wbuf); + fprintf(fd, " %s.debug_attrs[\"%s\"].value = str(\"%s\")\n", target, + iio_attr_get_name(attr), wbuf); } else { fprintf(fd, " # Write for %s / %s not implemented yet\n", prefix, target); } diff --git a/utils/iio_event.c b/utils/iio_event.c new file mode 100644 index 000000000..0001f9d26 --- /dev/null +++ b/utils/iio_event.c @@ -0,0 +1,181 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * iio_event - Part of the industrial I/O (IIO) utilities + * + * Copyright (C) 2023 Analog Devices, Inc. + * Author: Paul Cercueil + */ + +#include +#include +#include +#include +#include +#include + +#include "iio_common.h" + +#define MY_NAME "iio_event" + +static struct iio_event_stream *stream; +static int exit_code = EXIT_FAILURE; + +static const struct option options[] = { + {0, 0, 0, 0}, +}; + +static const char *options_descriptions[] = { + "\n" +}; + +static void quit_stream(int sig) +{ + exit_code = sig; + + if (stream) + iio_event_stream_destroy(stream); + stream = NULL; +} + +static const char * const iio_ev_type_text[] = { + [IIO_EV_TYPE_THRESH] = "thresh", + [IIO_EV_TYPE_MAG] = "mag", + [IIO_EV_TYPE_ROC] = "roc", + [IIO_EV_TYPE_THRESH_ADAPTIVE] = "thresh_adaptive", + [IIO_EV_TYPE_MAG_ADAPTIVE] = "mag_adaptive", + [IIO_EV_TYPE_CHANGE] = "change", + [IIO_EV_TYPE_MAG_REFERENCED] = "mag_referenced", + [IIO_EV_TYPE_GESTURE] = "gesture", +}; + +static const char * const iio_ev_dir_text[] = { + [IIO_EV_DIR_EITHER] = "either", + [IIO_EV_DIR_RISING] = "rising", + [IIO_EV_DIR_FALLING] = "falling", + [IIO_EV_DIR_SINGLETAP] = "singletap", + [IIO_EV_DIR_DOUBLETAP] = "doubletap", +}; + +static void print_event(const struct iio_device *dev, + const struct iio_event *event) +{ + const struct iio_channel *chn; + enum iio_event_type type = iio_event_get_type(event); + enum iio_event_direction dir = iio_event_get_direction(event); + + printf("Event: time: %lld", event->timestamp); + + chn = iio_event_get_channel(event, dev, false); + if (chn) + printf(", channel(s): %s", iio_channel_get_id(chn)); + + chn = iio_event_get_channel(event, dev, true); + if (chn) + printf("-%s", iio_channel_get_id(chn)); + + printf(", evtype: %s", iio_ev_type_text[type]); + + if (dir != IIO_EV_DIR_NONE) + printf(", direction: %s", iio_ev_dir_text[dir]); + + printf("\n"); +} + +int main(int argc, char **argv) +{ + const struct iio_device *dev; + struct iio_context *ctx; + struct iio_event event; + struct option *opts; + char **argw, *name; + int c, ret; + + argw = dup_argv(MY_NAME, argc, argv); + if (!argw) + return EXIT_FAILURE; + + ctx = handle_common_opts(MY_NAME, argc, argw, "", + options, options_descriptions, &ret); + opts = add_common_options(options); + if (!opts) { + fprintf(stderr, "Failed to add common options\n"); + ret = EXIT_FAILURE; + goto out_ctx_destroy; + } + while ((c = getopt_long(argc, argw, "+" COMMON_OPTIONS, /* Flawfinder: ignore */ + opts, NULL)) != -1) { + switch (c) { + /* All these are handled in the common */ + case 'h': + case 'V': + case 'u': + case 'T': + break; + case 'S': + case 'a': + if (!optarg && argc > optind && argv[optind] != NULL + && argv[optind][0] != '-') + optind++; + break; + case '?': + printf("Unknown argument '%c'\n", c); + ret = EXIT_FAILURE; + free(opts); + goto out_ctx_destroy; + } + } + free(opts); + + if ((argc - optind) != 1) { + usage(MY_NAME, options, options_descriptions); + ret = EXIT_FAILURE; + goto out_ctx_destroy; + } + + name = cmn_strndup(argw[optind], NAME_MAX); + dev = iio_context_find_device(ctx, name); + free(name); + if (!dev) { + ctx_err(ctx, "Unable to find device\n"); + ret = EXIT_FAILURE; + goto out_ctx_destroy; + } + + stream = iio_device_create_event_stream(dev); + ret = iio_err(stream); + if (ret) { + dev_perror(dev, ret, "Unable to create event stream"); + ret = EXIT_FAILURE; + goto out_ctx_destroy; + } + + signal(SIGINT, quit_stream); + signal(SIGTERM, quit_stream); +#ifndef _WIN32 + signal(SIGHUP, quit_stream); +#endif + + for (;;) { + ret = iio_event_stream_read(stream, &event, false); + if (ret == -EINTR) + break; + + if (ret < 0) { + dev_perror(dev, ret, "Unable to read event"); + ret = EXIT_FAILURE; + goto out_stream_destroy; + } + + print_event(dev, &event); + } + +out_stream_destroy: + if (stream) + iio_event_stream_destroy(stream); +out_ctx_destroy: + if (ctx) + iio_context_destroy(ctx); +out_free_argw: + free_argw(argc, argw); + return ret; +}