diff options
Diffstat (limited to 'src/local.c')
-rw-r--r-- | src/local.c | 2058 |
1 files changed, 2058 insertions, 0 deletions
diff --git a/src/local.c b/src/local.c new file mode 100644 index 0000000..839ddd5 --- /dev/null +++ b/src/local.c @@ -0,0 +1,2058 @@ +/* + * libiio - Library for interfacing industrial I/O (IIO) devices + * + * Copyright (C) 2014 Analog Devices, Inc. + * Author: Paul Cercueil <paul.cercueil@analog.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * */ + +#include "debug.h" +#include "iio-private.h" + +#include <dirent.h> +#include <errno.h> +#include <limits.h> +#include <poll.h> +#include <stdbool.h> +#include <stddef.h> +#include <stdio.h> +#include <string.h> +#include <sys/eventfd.h> +#include <sys/ioctl.h> +#include <sys/mman.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> +#include <string.h> +#include <sys/utsname.h> +#include <time.h> +#include <unistd.h> +#include <fcntl.h> +#ifdef WITH_LOCAL_CONFIG +#include <ini.h> +#endif + +#define DEFAULT_TIMEOUT_MS 1000 + +#define NB_BLOCKS 4 + +#define BLOCK_ALLOC_IOCTL _IOWR('i', 0xa0, struct block_alloc_req) +#define BLOCK_FREE_IOCTL _IO('i', 0xa1) +#define BLOCK_QUERY_IOCTL _IOWR('i', 0xa2, struct block) +#define BLOCK_ENQUEUE_IOCTL _IOWR('i', 0xa3, struct block) +#define BLOCK_DEQUEUE_IOCTL _IOWR('i', 0xa4, struct block) + +#define BLOCK_FLAG_CYCLIC BIT(1) + +/* Forward declarations */ +static ssize_t local_read_dev_attr(const struct iio_device *dev, + const char *attr, char *dst, size_t len, enum iio_attr_type type); +static ssize_t local_read_chn_attr(const struct iio_channel *chn, + const char *attr, char *dst, size_t len); +static ssize_t local_write_dev_attr(const struct iio_device *dev, + const char *attr, const char *src, size_t len, enum iio_attr_type type); +static ssize_t local_write_chn_attr(const struct iio_channel *chn, + const char *attr, const char *src, size_t len); + +struct block_alloc_req { + uint32_t type, + size, + count, + id; +}; + +struct block { + uint32_t id, + size, + bytes_used, + type, + flags, + offset; + uint64_t timestamp; +}; + +struct iio_context_pdata { + unsigned int rw_timeout_ms; +}; + +struct iio_device_pdata { + int fd; + bool blocking; + unsigned int samples_count; + unsigned int max_nb_blocks; + unsigned int allocated_nb_blocks; + + struct block *blocks; + void **addrs; + int last_dequeued; + bool is_high_speed, cyclic, cyclic_buffer_enqueued, buffer_enabled; + + int cancel_fd; +}; + +struct iio_channel_pdata { + char *enable_fn; + struct iio_channel_attr *protected_attrs; + unsigned int nb_protected_attrs; +}; + +static const char * const device_attrs_blacklist[] = { + "dev", + "uevent", +}; + +static const char * const buffer_attrs_reserved[] = { + "length", + "enable", +}; + +static int ioctl_nointr(int fd, unsigned long request, void *data) +{ + int ret; + + do { + ret = ioctl(fd, request, data); + } while (ret == -1 && errno == EINTR); + + return ret; +} + +static void local_free_channel_pdata(struct iio_channel *chn) +{ + if (chn->pdata) { + free(chn->pdata->enable_fn); + free(chn->pdata); + } +} + +static void local_free_pdata(struct iio_device *device) +{ + unsigned int i; + + for (i = 0; i < device->nb_channels; i++) + local_free_channel_pdata(device->channels[i]); + + if (device->pdata) { + free(device->pdata->blocks); + free(device->pdata->addrs); + free(device->pdata); + } +} + +static void local_shutdown(struct iio_context *ctx) +{ + /* Free the backend data stored in every device structure */ + unsigned int i; + + for (i = 0; i < ctx->nb_devices; i++) { + struct iio_device *dev = ctx->devices[i]; + + iio_device_close(dev); + local_free_pdata(dev); + } + + free(ctx->pdata); +} + +/** Shrinks the first nb characters of a string + * e.g. strcut("foobar", 4) replaces the content with "ar". */ +static void strcut(char *str, int nb) +{ + char *ptr = str + nb; + while (*ptr) + *str++ = *ptr++; + *str = 0; +} + +static int set_channel_name(struct iio_channel *chn) +{ + struct iio_channel_pdata *pdata = chn->pdata; + size_t prefix_len = 0; + const char *attr0; + const char *ptr; + unsigned int i; + + if (chn->nb_attrs + pdata->nb_protected_attrs < 2) + return 0; + + if (chn->nb_attrs) + attr0 = ptr = chn->attrs[0].name; + else + attr0 = ptr = pdata->protected_attrs[0].name; + + while (true) { + bool can_fix = true; + size_t len; + + ptr = strchr(ptr, '_'); + if (!ptr) + break; + + len = ptr - attr0 + 1; + for (i = 1; can_fix && i < chn->nb_attrs; i++) + can_fix = !strncmp(attr0, chn->attrs[i].name, len); + + for (i = !chn->nb_attrs; + can_fix && i < pdata->nb_protected_attrs; i++) { + can_fix = !strncmp(attr0, + pdata->protected_attrs[i].name, len); + } + + if (!can_fix) + break; + + prefix_len = len; + ptr = ptr + 1; + } + + if (prefix_len) { + char *name; + + name = malloc(prefix_len); + if (!name) + return -ENOMEM; + strncpy(name, attr0, prefix_len - 1); + name[prefix_len - 1] = '\0'; + DEBUG("Setting name of channel %s to %s\n", chn->id, name); + chn->name = name; + + /* Shrink the attribute name */ + for (i = 0; i < chn->nb_attrs; i++) + strcut(chn->attrs[i].name, prefix_len); + for (i = 0; i < pdata->nb_protected_attrs; i++) + strcut(pdata->protected_attrs[i].name, prefix_len); + } + + return 0; +} + +/* + * Used to generate the timeout parameter for operations like poll. Returns the + * number of ms until it is timeout_rel ms after the time specified in start. If + * timeout_rel is 0 returns -1 to indicate no timeout. + * + * The timeout that is specified for IIO operations is the maximum time a buffer + * push() or refill() operation should take before returning. poll() is used to + * wait for either data activity or for the timeout to elapse. poll() might get + * interrupted in which case it is called again or the read()/write() operation + * might not complete the full buffer size in one call in which case we go back + * to poll() again as well. Passing the same timeout as before would increase + * the total timeout and if repeated interruptions occur (e.g. by a timer + * signal) the operation might never time out or with significant delay. Hence + * before each poll() invocation the timeout is recalculated relative to the + * start of refill() or push() operation. + */ +static int get_rel_timeout_ms(struct timespec *start, unsigned int timeout_rel) +{ + struct timespec now; + int diff_ms; + + if (timeout_rel == 0) /* No timeout */ + return -1; + + clock_gettime(CLOCK_MONOTONIC, &now); + + diff_ms = (now.tv_sec - start->tv_sec) * 1000; + diff_ms += (now.tv_nsec - start->tv_nsec) / 1000000; + + if (diff_ms >= timeout_rel) /* Expired */ + return 0; + if (diff_ms > 0) /* Should never be false, but lets be safe */ + timeout_rel -= diff_ms; + if (timeout_rel > INT_MAX) + return INT_MAX; + + return (int) timeout_rel; +} + +static int device_check_ready(const struct iio_device *dev, short events, + struct timespec *start) +{ + struct pollfd pollfd[2] = { + { + .fd = dev->pdata->fd, + .events = events, + }, { + .fd = dev->pdata->cancel_fd, + .events = POLLIN, + } + }; + unsigned int rw_timeout_ms = dev->ctx->pdata->rw_timeout_ms; + int timeout_rel; + int ret; + + if (!dev->pdata->blocking) + return 0; + + do { + timeout_rel = get_rel_timeout_ms(start, rw_timeout_ms); + ret = poll(pollfd, 2, timeout_rel); + } while (ret == -1 && errno == EINTR); + + if ((pollfd[1].revents & POLLIN)) + return -EBADF; + + if (ret < 0) + return -errno; + if (!ret) + return -ETIMEDOUT; + if (pollfd[0].revents & POLLNVAL) + return -EBADF; + if (!(pollfd[0].revents & events)) + return -EIO; + return 0; +} + +static ssize_t local_read(const struct iio_device *dev, + void *dst, size_t len, uint32_t *mask, size_t words) +{ + struct iio_device_pdata *pdata = dev->pdata; + uintptr_t ptr = (uintptr_t) dst; + struct timespec start; + ssize_t readsize; + ssize_t ret; + + if (pdata->fd == -1) + return -EBADF; + if (words != dev->words) + return -EINVAL; + + memcpy(mask, dev->mask, words); + + if (len == 0) + return 0; + + clock_gettime(CLOCK_MONOTONIC, &start); + + while (len > 0) { + ret = device_check_ready(dev, POLLIN, &start); + if (ret < 0) + break; + + do { + ret = read(pdata->fd, (void *) ptr, len); + } while (ret == -1 && errno == EINTR); + + if (ret == -1) { + if (pdata->blocking && errno == EAGAIN) + continue; + ret = -errno; + break; + } else if (ret == 0) { + ret = -EIO; + break; + } + + ptr += ret; + len -= ret; + } + + readsize = (ssize_t)(ptr - (uintptr_t) dst); + if ((ret > 0 || ret == -EAGAIN) && (readsize > 0)) + return readsize; + else + return ret; +} + +static ssize_t local_write(const struct iio_device *dev, + const void *src, size_t len) +{ + struct iio_device_pdata *pdata = dev->pdata; + uintptr_t ptr = (uintptr_t) src; + struct timespec start; + ssize_t writtensize; + ssize_t ret; + + if (pdata->fd == -1) + return -EBADF; + + if (len == 0) + return 0; + + clock_gettime(CLOCK_MONOTONIC, &start); + + while (len > 0) { + ret = device_check_ready(dev, POLLOUT, &start); + if (ret < 0) + break; + + do { + ret = write(pdata->fd, (void *) ptr, len); + } while (ret == -1 && errno == EINTR); + + if (ret == -1) { + if (pdata->blocking && errno == EAGAIN) + continue; + + ret = -errno; + break; + } else if (ret == 0) { + ret = -EIO; + break; + } + + ptr += ret; + len -= ret; + } + + writtensize = (ssize_t)(ptr - (uintptr_t) src); + if ((ret > 0 || ret == -EAGAIN) && (writtensize > 0)) + return writtensize; + else + return ret; +} + +static ssize_t local_enable_buffer(const struct iio_device *dev) +{ + struct iio_device_pdata *pdata = dev->pdata; + ssize_t ret = 0; + + if (!pdata->buffer_enabled) { + ret = local_write_dev_attr(dev, + "buffer/enable", "1", 2, false); + if (ret >= 0) + pdata->buffer_enabled = true; + } + + return 0; +} + +static int local_set_kernel_buffers_count(const struct iio_device *dev, + unsigned int nb_blocks) +{ + struct iio_device_pdata *pdata = dev->pdata; + + if (pdata->fd != -1) + return -EBUSY; + + pdata->max_nb_blocks = nb_blocks; + + return 0; +} + +static ssize_t local_get_buffer(const struct iio_device *dev, + void **addr_ptr, size_t bytes_used, + uint32_t *mask, size_t words) +{ + struct block block; + struct iio_device_pdata *pdata = dev->pdata; + struct timespec start; + char err_str[1024]; + int f = pdata->fd; + ssize_t ret; + + if (!pdata->is_high_speed) + return -ENOSYS; + if (f == -1) + return -EBADF; + if (!addr_ptr) + return -EINVAL; + + if (pdata->last_dequeued >= 0) { + struct block *last_block = &pdata->blocks[pdata->last_dequeued]; + + if (pdata->cyclic) { + if (pdata->cyclic_buffer_enqueued) + return -EBUSY; + pdata->blocks[0].flags |= BLOCK_FLAG_CYCLIC; + pdata->cyclic_buffer_enqueued = true; + } + + last_block->bytes_used = bytes_used; + ret = (ssize_t) ioctl_nointr(f, + BLOCK_ENQUEUE_IOCTL, last_block); + if (ret) { + ret = (ssize_t) -errno; + iio_strerror(errno, err_str, sizeof(err_str)); + ERROR("Unable to enqueue block: %s\n", err_str); + return ret; + } + + if (pdata->cyclic) { + *addr_ptr = pdata->addrs[pdata->last_dequeued]; + return (ssize_t) last_block->bytes_used; + } + + pdata->last_dequeued = -1; + } + + clock_gettime(CLOCK_MONOTONIC, &start); + + do { + ret = (ssize_t) device_check_ready(dev, POLLIN | POLLOUT, &start); + if (ret < 0) + return ret; + + memset(&block, 0, sizeof(block)); + ret = (ssize_t) ioctl_nointr(f, BLOCK_DEQUEUE_IOCTL, &block); + } while (pdata->blocking && ret == -1 && errno == EAGAIN); + + if (ret) { + ret = (ssize_t) -errno; + if ((!pdata->blocking && ret != -EAGAIN) || + (pdata->blocking && ret != -ETIMEDOUT)) { + iio_strerror(errno, err_str, sizeof(err_str)); + ERROR("Unable to dequeue block: %s\n", err_str); + } + return ret; + } + + /* Requested buffer size is too big! */ + if (pdata->last_dequeued < 0 && bytes_used != block.size) + return -EFBIG; + + pdata->last_dequeued = block.id; + *addr_ptr = pdata->addrs[block.id]; + return (ssize_t) block.bytes_used; +} + +static ssize_t local_read_all_dev_attrs(const struct iio_device *dev, + char *dst, size_t len, enum iio_attr_type type) +{ + unsigned int i, nb; + char **attrs; + char *ptr = dst; + + switch (type) { + case IIO_ATTR_TYPE_DEVICE: + nb = dev->nb_attrs; + attrs = dev->attrs; + break; + case IIO_ATTR_TYPE_DEBUG: + nb = dev->nb_debug_attrs; + attrs = dev->debug_attrs; + break; + case IIO_ATTR_TYPE_BUFFER: + nb = dev->nb_buffer_attrs; + attrs = dev->buffer_attrs; + break; + default: + return -EINVAL; + break; + } + + for (i = 0; len >= 4 && i < nb; i++) { + /* Recursive! */ + ssize_t ret = local_read_dev_attr(dev, attrs[i], + ptr + 4, len - 4, type); + *(uint32_t *) ptr = iio_htobe32(ret); + + /* Align the length to 4 bytes */ + if (ret > 0 && ret & 3) + ret = ((ret >> 2) + 1) << 2; + ptr += 4 + (ret < 0 ? 0 : ret); + len -= 4 + (ret < 0 ? 0 : ret); + } + + return ptr - dst; +} + +static ssize_t local_read_all_chn_attrs(const struct iio_channel *chn, + char *dst, size_t len) +{ + unsigned int i; + char *ptr = dst; + + for (i = 0; len >= 4 && i < chn->nb_attrs; i++) { + /* Recursive! */ + ssize_t ret = local_read_chn_attr(chn, + chn->attrs[i].name, ptr + 4, len - 4); + *(uint32_t *) ptr = iio_htobe32(ret); + + /* Align the length to 4 bytes */ + if (ret > 0 && ret & 3) + ret = ((ret >> 2) + 1) << 2; + ptr += 4 + (ret < 0 ? 0 : ret); + len -= 4 + (ret < 0 ? 0 : ret); + } + + return ptr - dst; +} + +static int local_buffer_analyze(unsigned int nb, const char *src, size_t len) +{ + while (nb--) { + int32_t val; + + if (len < 4) + return -EINVAL; + + val = (int32_t) iio_be32toh(*(uint32_t *) src); + src += 4; + len -= 4; + + if (val > 0) { + if ((uint32_t) val > len) + return -EINVAL; + + /* Align the length to 4 bytes */ + if (val & 3) + val = ((val >> 2) + 1) << 2; + len -= val; + src += val; + } + } + + /* We should have analyzed the whole buffer by now */ + return !len ? 0 : -EINVAL; +} + +static ssize_t local_write_all_dev_attrs(const struct iio_device *dev, + const char *src, size_t len, enum iio_attr_type type) +{ + unsigned int i, nb; + char **attrs; + const char *ptr = src; + + switch (type) { + case IIO_ATTR_TYPE_DEVICE: + nb = dev->nb_attrs; + attrs = dev->attrs; + break; + case IIO_ATTR_TYPE_DEBUG: + nb = dev->nb_debug_attrs; + attrs = dev->debug_attrs; + break; + case IIO_ATTR_TYPE_BUFFER: + nb = dev->nb_buffer_attrs; + attrs = dev->buffer_attrs; + break; + default: + return -EINVAL; + break; + } + + /* First step: Verify that the buffer is in the correct format */ + if (local_buffer_analyze(nb, src, len)) + return -EINVAL; + + /* Second step: write the attributes */ + for (i = 0; i < nb; i++) { + int32_t val = (int32_t) iio_be32toh(*(uint32_t *) ptr); + ptr += 4; + + if (val > 0) { + local_write_dev_attr(dev, attrs[i], ptr, val, type); + + /* Align the length to 4 bytes */ + if (val & 3) + val = ((val >> 2) + 1) << 2; + ptr += val; + } + } + + return ptr - src; +} + +static ssize_t local_write_all_chn_attrs(const struct iio_channel *chn, + const char *src, size_t len) +{ + unsigned int i, nb = chn->nb_attrs; + const char *ptr = src; + + /* First step: Verify that the buffer is in the correct format */ + if (local_buffer_analyze(nb, src, len)) + return -EINVAL; + + /* Second step: write the attributes */ + for (i = 0; i < nb; i++) { + int32_t val = (int32_t) iio_be32toh(*(uint32_t *) ptr); + ptr += 4; + + if (val > 0) { + local_write_chn_attr(chn, chn->attrs[i].name, ptr, val); + + /* Align the length to 4 bytes */ + if (val & 3) + val = ((val >> 2) + 1) << 2; + ptr += val; + } + } + + return ptr - src; +} + +static ssize_t local_read_dev_attr(const struct iio_device *dev, + const char *attr, char *dst, size_t len, enum iio_attr_type type) +{ + FILE *f; + char buf[1024]; + ssize_t ret; + + if (!attr) + return local_read_all_dev_attrs(dev, dst, len, type); + + switch (type) { + case IIO_ATTR_TYPE_DEVICE: + iio_snprintf(buf, sizeof(buf), "/sys/bus/iio/devices/%s/%s", + dev->id, attr); + break; + case IIO_ATTR_TYPE_DEBUG: + iio_snprintf(buf, sizeof(buf), "/sys/kernel/debug/iio/%s/%s", + dev->id, attr); + break; + case IIO_ATTR_TYPE_BUFFER: + iio_snprintf(buf, sizeof(buf), "/sys/bus/iio/devices/%s/buffer/%s", + dev->id, attr); + break; + default: + return -EINVAL; + } + + f = fopen(buf, "re"); + if (!f) + return -errno; + + ret = fread(dst, 1, len, f); + if (ret > 0) + dst[ret - 1] = '\0'; + fflush(f); + if (ferror(f)) + ret = -errno; + fclose(f); + return ret ? ret : -EIO; +} + +static ssize_t local_write_dev_attr(const struct iio_device *dev, + const char *attr, const char *src, size_t len, enum iio_attr_type type) +{ + FILE *f; + char buf[1024]; + ssize_t ret; + + if (!attr) + return local_write_all_dev_attrs(dev, src, len, type); + + switch (type) { + case IIO_ATTR_TYPE_DEVICE: + iio_snprintf(buf, sizeof(buf), "/sys/bus/iio/devices/%s/%s", + dev->id, attr); + break; + case IIO_ATTR_TYPE_DEBUG: + iio_snprintf(buf, sizeof(buf), "/sys/kernel/debug/iio/%s/%s", + dev->id, attr); + break; + case IIO_ATTR_TYPE_BUFFER: + iio_snprintf(buf, sizeof(buf), "/sys/bus/iio/devices/%s/buffer/%s", + dev->id, attr); + break; + default: + return -EINVAL; + } + + f = fopen(buf, "we"); + if (!f) + return -errno; + + ret = fwrite(src, 1, len, f); + fflush(f); + if (ferror(f)) + ret = -errno; + fclose(f); + return ret ? ret : -EIO; +} + +static const char * get_filename(const struct iio_channel *chn, + const char *attr) +{ + unsigned int i; + for (i = 0; i < chn->nb_attrs; i++) + if (!strcmp(attr, chn->attrs[i].name)) + return chn->attrs[i].filename; + return attr; +} + +static ssize_t local_read_chn_attr(const struct iio_channel *chn, + const char *attr, char *dst, size_t len) +{ + if (!attr) + return local_read_all_chn_attrs(chn, dst, len); + + attr = get_filename(chn, attr); + return local_read_dev_attr(chn->dev, attr, dst, len, false); +} + +static ssize_t local_write_chn_attr(const struct iio_channel *chn, + const char *attr, const char *src, size_t len) +{ + if (!attr) + return local_write_all_chn_attrs(chn, src, len); + + attr = get_filename(chn, attr); + return local_write_dev_attr(chn->dev, attr, src, len, false); +} + +static int channel_write_state(const struct iio_channel *chn, bool en) +{ + ssize_t ret; + + if (!chn->pdata->enable_fn) { + ERROR("Libiio bug: No \"en\" attribute parsed\n"); + return -EINVAL; + } + + ret = local_write_chn_attr(chn, chn->pdata->enable_fn, en ? "1" : "0", 2); + if (ret < 0) + return (int) ret; + else + return 0; +} + +static int enable_high_speed(const struct iio_device *dev) +{ + struct block_alloc_req req; + struct iio_device_pdata *pdata = dev->pdata; + unsigned int nb_blocks; + unsigned int i; + int ret, fd = pdata->fd; + + /* + * For the BLOCK_ALLOC_IOCTL ioctl it is not possible to distingush + * between an error during the allocation (e.g. incorrect size) or + * whether the high-speed interface is not supported. BLOCK_FREE_IOCTL does + * never fail if the device supports the high-speed interface, so we use it + * here. Calling it when no blocks are allocated the ioctl has no effect. + */ + ret = ioctl_nointr(fd, BLOCK_FREE_IOCTL, NULL); + if (ret < 0) + return -ENOSYS; + + if (pdata->cyclic) { + nb_blocks = 1; + DEBUG("Enabling cyclic mode\n"); + } else { + nb_blocks = pdata->max_nb_blocks; + DEBUG("Cyclic mode not enabled\n"); + } + + pdata->blocks = calloc(nb_blocks, sizeof(*pdata->blocks)); + if (!pdata->blocks) + return -ENOMEM; + + pdata->addrs = calloc(nb_blocks, sizeof(*pdata->addrs)); + if (!pdata->addrs) { + free(pdata->blocks); + pdata->blocks = NULL; + return -ENOMEM; + } + + req.id = 0; + req.type = 0; + req.size = pdata->samples_count * + iio_device_get_sample_size_mask(dev, dev->mask, dev->words); + req.count = nb_blocks; + + ret = ioctl_nointr(fd, BLOCK_ALLOC_IOCTL, &req); + if (ret < 0) { + ret = -errno; + goto err_freemem; + } + + if (req.count == 0) { + ret = -ENOMEM; + goto err_block_free; + } + + /* We might get less blocks than what we asked for */ + pdata->allocated_nb_blocks = req.count; + + /* mmap all the blocks */ + for (i = 0; i < pdata->allocated_nb_blocks; i++) { + pdata->blocks[i].id = i; + ret = ioctl_nointr(fd, BLOCK_QUERY_IOCTL, &pdata->blocks[i]); + if (ret) { + ret = -errno; + goto err_munmap; + } + + ret = ioctl_nointr(fd, BLOCK_ENQUEUE_IOCTL, &pdata->blocks[i]); + if (ret) { + ret = -errno; + goto err_munmap; + } + + pdata->addrs[i] = mmap(0, pdata->blocks[i].size, + PROT_READ | PROT_WRITE, + MAP_SHARED, fd, pdata->blocks[i].offset); + if (pdata->addrs[i] == MAP_FAILED) { + ret = -errno; + goto err_munmap; + } + } + + pdata->last_dequeued = -1; + return 0; + +err_munmap: + for (; i > 0; i--) + munmap(pdata->addrs[i - 1], pdata->blocks[i - 1].size); +err_block_free: + ioctl_nointr(fd, BLOCK_FREE_IOCTL, 0); + pdata->allocated_nb_blocks = 0; +err_freemem: + free(pdata->addrs); + pdata->addrs = NULL; + free(pdata->blocks); + pdata->blocks = NULL; + return ret; +} + +static int local_open(const struct iio_device *dev, + size_t samples_count, bool cyclic) +{ + unsigned int i; + int ret; + char buf[1024]; + struct iio_device_pdata *pdata = dev->pdata; + + if (pdata->fd != -1) + return -EBUSY; + + ret = local_write_dev_attr(dev, "buffer/enable", "0", 2, false); + if (ret < 0) + return ret; + + iio_snprintf(buf, sizeof(buf), "%lu", (unsigned long) samples_count); + ret = local_write_dev_attr(dev, "buffer/length", + buf, strlen(buf) + 1, false); + if (ret < 0) + return ret; + + pdata->cancel_fd = eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK); + if (pdata->cancel_fd == -1) + return -errno; + + iio_snprintf(buf, sizeof(buf), "/dev/%s", dev->id); + pdata->fd = open(buf, O_RDWR | O_CLOEXEC | O_NONBLOCK); + if (pdata->fd == -1) { + ret = -errno; + goto err_close_cancel_fd; + } + + /* Disable channels */ + for (i = 0; i < dev->nb_channels; i++) { + struct iio_channel *chn = dev->channels[i]; + if (chn->index >= 0 && !iio_channel_is_enabled(chn)) { + ret = channel_write_state(chn, false); + if (ret < 0) + goto err_close; + } + } + /* Enable channels */ + for (i = 0; i < dev->nb_channels; i++) { + struct iio_channel *chn = dev->channels[i]; + if (chn->index >= 0 && iio_channel_is_enabled(chn)) { + ret = channel_write_state(chn, true); + if (ret < 0) + goto err_close; + } + } + + pdata->cyclic = cyclic; + pdata->cyclic_buffer_enqueued = false; + pdata->buffer_enabled = false; + pdata->samples_count = samples_count; + + ret = enable_high_speed(dev); + if (ret < 0 && ret != -ENOSYS) + goto err_close; + + pdata->is_high_speed = !ret; + + if (!pdata->is_high_speed) { + unsigned long size = samples_count * pdata->max_nb_blocks; + WARNING("High-speed mode not enabled\n"); + + /* Cyclic mode is only supported in high-speed mode */ + if (cyclic) { + ret = -EPERM; + goto err_close; + } + + /* Increase the size of the kernel buffer, when using the + * low-speed interface. This avoids losing samples when + * refilling the iio_buffer. */ + iio_snprintf(buf, sizeof(buf), "%lu", size); + ret = local_write_dev_attr(dev, "buffer/length", + buf, strlen(buf) + 1, false); + if (ret < 0) + goto err_close; + } + + ret = local_enable_buffer(dev); + if (ret < 0) + goto err_close; + + return 0; +err_close: + close(pdata->fd); + pdata->fd = -1; +err_close_cancel_fd: + close(pdata->cancel_fd); + pdata->cancel_fd = -1; + return ret; +} + +static int local_close(const struct iio_device *dev) +{ + struct iio_device_pdata *pdata = dev->pdata; + unsigned int i; + int ret; + + if (pdata->fd == -1) + return -EBADF; + + if (pdata->is_high_speed) { + unsigned int i; + for (i = 0; i < pdata->allocated_nb_blocks; i++) + munmap(pdata->addrs[i], pdata->blocks[i].size); + ioctl_nointr(pdata->fd, BLOCK_FREE_IOCTL, 0); + pdata->allocated_nb_blocks = 0; + free(pdata->addrs); + pdata->addrs = NULL; + free(pdata->blocks); + pdata->blocks = NULL; + } + + ret = close(pdata->fd); + if (ret) + return ret; + + close(pdata->cancel_fd); + + pdata->fd = -1; + pdata->cancel_fd = -1; + + ret = local_write_dev_attr(dev, "buffer/enable", "0", 2, false); + + for (i = 0; i < dev->nb_channels; i++) { + struct iio_channel *chn = dev->channels[i]; + + if (chn->pdata->enable_fn) + channel_write_state(chn, false); + } + + return (ret < 0) ? ret : 0; +} + +static int local_get_fd(const struct iio_device *dev) +{ + if (dev->pdata->fd == -1) + return -EBADF; + else + return dev->pdata->fd; +} + +static int local_set_blocking_mode(const struct iio_device *dev, bool blocking) +{ + if (dev->pdata->fd == -1) + return -EBADF; + + if (dev->pdata->cyclic) + return -EPERM; + + dev->pdata->blocking = blocking; + + return 0; +} + +static int local_get_trigger(const struct iio_device *dev, + const struct iio_device **trigger) +{ + char buf[1024]; + unsigned int i; + ssize_t nb = local_read_dev_attr(dev, "trigger/current_trigger", + buf, sizeof(buf), false); + if (nb < 0) { + *trigger = NULL; + return (int) nb; + } + + if (buf[0] == '\0') { + *trigger = NULL; + return 0; + } + + nb = dev->ctx->nb_devices; + for (i = 0; i < (size_t) nb; i++) { + const struct iio_device *cur = dev->ctx->devices[i]; + if (cur->name && !strcmp(cur->name, buf)) { + *trigger = cur; + return 0; + } + } + return -ENXIO; +} + +static int local_set_trigger(const struct iio_device *dev, + const struct iio_device *trigger) +{ + ssize_t nb; + const char *value = trigger ? trigger->name : ""; + nb = local_write_dev_attr(dev, "trigger/current_trigger", + value, strlen(value) + 1, false); + if (nb < 0) + return (int) nb; + else + return 0; +} + +static bool is_channel(const char *attr, bool strict) +{ + char *ptr = NULL; + if (!strncmp(attr, "in_timestamp_", sizeof("in_timestamp_") - 1)) + return true; + if (!strncmp(attr, "in_", 3)) + ptr = strchr(attr + 3, '_'); + else if (!strncmp(attr, "out_", 4)) + ptr = strchr(attr + 4, '_'); + if (!ptr) + return false; + if (!strict) + return true; + if (*(ptr - 1) >= '0' && *(ptr - 1) <= '9') + return true; + + if (find_channel_modifier(ptr + 1, NULL) != IIO_NO_MOD) + return true; + return false; +} + +static char * get_channel_id(const char *attr) +{ + char *res, *ptr; + size_t len; + + attr = strchr(attr, '_') + 1; + ptr = strchr(attr, '_'); + if (find_channel_modifier(ptr + 1, &len) != IIO_NO_MOD) + ptr += len + 1; + + res = malloc(ptr - attr + 1); + if (!res) + return NULL; + + memcpy(res, attr, ptr - attr); + res[ptr - attr] = 0; + return res; +} + +static char * get_short_attr_name(struct iio_channel *chn, const char *attr) +{ + char *ptr = strchr(attr, '_') + 1; + size_t len; + + ptr = strchr(ptr, '_') + 1; + if (find_channel_modifier(ptr, &len) != IIO_NO_MOD) + ptr += len + 1; + + if (chn->name) { + size_t len = strlen(chn->name); + if (strncmp(chn->name, ptr, len) == 0 && ptr[len] == '_') + ptr += len + 1; + } + + return iio_strdup(ptr); +} + +static int read_device_name(struct iio_device *dev) +{ + char buf[1024]; + ssize_t ret = iio_device_attr_read(dev, "name", buf, sizeof(buf)); + if (ret < 0) + return ret; + else if (ret == 0) + return -EIO; + + dev->name = iio_strdup(buf); + if (!dev->name) + return -ENOMEM; + else + return 0; +} + +static int add_attr_to_device(struct iio_device *dev, const char *attr) +{ + char **attrs, *name; + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(device_attrs_blacklist); i++) + if (!strcmp(device_attrs_blacklist[i], attr)) + return 0; + + if (!strcmp(attr, "name")) + return read_device_name(dev); + + name = iio_strdup(attr); + if (!name) + return -ENOMEM; + + attrs = realloc(dev->attrs, (1 + dev->nb_attrs) * sizeof(char *)); + if (!attrs) { + free(name); + return -ENOMEM; + } + + attrs[dev->nb_attrs++] = name; + dev->attrs = attrs; + DEBUG("Added attr \'%s\' to device \'%s\'\n", attr, dev->id); + return 0; +} + +static int handle_protected_scan_element_attr(struct iio_channel *chn, + const char *name, const char *path) +{ + struct iio_device *dev = chn->dev; + char buf[1024]; + int ret; + + if (!strcmp(name, "index")) { + ret = local_read_dev_attr(dev, path, buf, sizeof(buf), false); + if (ret > 0) + chn->index = atol(buf); + + } else if (!strcmp(name, "type")) { + ret = local_read_dev_attr(dev, path, buf, sizeof(buf), false); + if (ret > 0) { + char endian, sign; + + if (strchr(buf, 'X')) { + sscanf(buf, "%ce:%c%u/%uX%u>>%u", &endian, &sign, + &chn->format.bits, &chn->format.length, + &chn->format.repeat, &chn->format.shift); + } else { + chn->format.repeat = 1; + sscanf(buf, "%ce:%c%u/%u>>%u", &endian, &sign, + &chn->format.bits, &chn->format.length, + &chn->format.shift); + } + chn->format.is_signed = (sign == 's' || sign == 'S'); + chn->format.is_fully_defined = + (sign == 'S' || sign == 'U'|| + chn->format.bits == chn->format.length); + chn->format.is_be = endian == 'b'; + } + + } else if (!strcmp(name, "en")) { + if (chn->pdata->enable_fn) { + ERROR("Libiio bug: \"en\" attribute already parsed for channel %s!\n", + chn->id); + return -EINVAL; + } + + chn->pdata->enable_fn = iio_strdup(path); + if (!chn->pdata->enable_fn) + return -ENOMEM; + + } else { + return -EINVAL; + } + + return 0; +} + +static int handle_scan_elements(struct iio_channel *chn) +{ + struct iio_channel_pdata *pdata = chn->pdata; + unsigned int i; + + for (i = 0; i < pdata->nb_protected_attrs; i++) { + int ret = handle_protected_scan_element_attr(chn, + pdata->protected_attrs[i].name, + pdata->protected_attrs[i].filename); + if (ret < 0) + return ret; + } + + return 0; +} + +static int add_protected_attr(struct iio_channel *chn, char *name, char *fn) +{ + struct iio_channel_pdata *pdata = chn->pdata; + struct iio_channel_attr *attrs; + + attrs = realloc(pdata->protected_attrs, + (1 + pdata->nb_protected_attrs) * sizeof(*attrs)); + if (!attrs) + return -ENOMEM; + + attrs[pdata->nb_protected_attrs].name = name; + attrs[pdata->nb_protected_attrs++].filename = fn; + pdata->protected_attrs = attrs; + + DEBUG("Add protected attr \'%s\' to channel \'%s\'\n", name, chn->id); + return 0; +} + +static void free_protected_attrs(struct iio_channel *chn) +{ + struct iio_channel_pdata *pdata = chn->pdata; + unsigned int i; + + for (i = 0; i < pdata->nb_protected_attrs; i++) { + free(pdata->protected_attrs[i].name); + free(pdata->protected_attrs[i].filename); + } + + free(pdata->protected_attrs); + pdata->nb_protected_attrs = 0; + pdata->protected_attrs = NULL; +} + +static int add_attr_to_channel(struct iio_channel *chn, + const char *attr, const char *path, bool is_scan_element) +{ + struct iio_channel_attr *attrs; + char *fn, *name = get_short_attr_name(chn, attr); + if (!name) + return -ENOMEM; + + fn = iio_strdup(path); + if (!fn) + goto err_free_name; + + if (is_scan_element) { + int ret = add_protected_attr(chn, name, fn); + + if (ret < 0) + goto err_free_fn; + + return 0; + } + + attrs = realloc(chn->attrs, (1 + chn->nb_attrs) * + sizeof(struct iio_channel_attr)); + if (!attrs) + goto err_free_fn; + + attrs[chn->nb_attrs].filename = fn; + attrs[chn->nb_attrs++].name = name; + chn->attrs = attrs; + DEBUG("Added attr \'%s\' to channel \'%s\'\n", name, chn->id); + return 0; + +err_free_fn: + free(fn); +err_free_name: + free(name); + return -ENOMEM; +} + +static int add_channel_to_device(struct iio_device *dev, + struct iio_channel *chn) +{ + struct iio_channel **channels = realloc(dev->channels, + (dev->nb_channels + 1) * sizeof(struct iio_channel *)); + if (!channels) + return -ENOMEM; + + channels[dev->nb_channels++] = chn; + dev->channels = channels; + DEBUG("Added channel \'%s\' to device \'%s\'\n", chn->id, dev->id); + return 0; +} + +static int add_device_to_context(struct iio_context *ctx, + struct iio_device *dev) +{ + struct iio_device **devices = realloc(ctx->devices, + (ctx->nb_devices + 1) * sizeof(struct iio_device *)); + if (!devices) + return -ENOMEM; + + devices[ctx->nb_devices++] = dev; + ctx->devices = devices; + DEBUG("Added device \'%s\' to context \'%s\'\n", dev->id, ctx->name); + return 0; +} + +static struct iio_channel *create_channel(struct iio_device *dev, + char *id, const char *attr, const char *path, + bool is_scan_element) +{ + struct iio_channel *chn = zalloc(sizeof(*chn)); + if (!chn) + return NULL; + + chn->pdata = zalloc(sizeof(*chn->pdata)); + if (!chn->pdata) + goto err_free_chn; + + if (!strncmp(attr, "out_", 4)) + chn->is_output = true; + else if (strncmp(attr, "in_", 3)) + goto err_free_chn_pdata; + + chn->dev = dev; + chn->id = id; + chn->is_scan_element = is_scan_element; + chn->index = -ENOENT; + + if (!add_attr_to_channel(chn, attr, path, is_scan_element)) + return chn; + +err_free_chn_pdata: + free(chn->pdata->enable_fn); + free(chn->pdata); +err_free_chn: + free(chn); + return NULL; +} + +static int add_channel(struct iio_device *dev, const char *name, + const char *path, bool dir_is_scan_elements) +{ + struct iio_channel *chn; + char *channel_id; + unsigned int i; + int ret; + + channel_id = get_channel_id(name); + if (!channel_id) + return -ENOMEM; + + for (i = 0; i < dev->nb_channels; i++) { + chn = dev->channels[i]; + if (!strcmp(chn->id, channel_id) + && chn->is_output == (name[0] == 'o')) { + free(channel_id); + ret = add_attr_to_channel(chn, name, path, + dir_is_scan_elements); + chn->is_scan_element = dir_is_scan_elements && !ret; + return ret; + } + } + + chn = create_channel(dev, channel_id, name, path, dir_is_scan_elements); + if (!chn) { + free(channel_id); + return -ENXIO; + } + + iio_channel_init_finalize(chn); + + ret = add_channel_to_device(dev, chn); + if (ret) { + free(chn->pdata->enable_fn); + free(chn->pdata); + free_channel(chn); + } + return ret; +} + +/* + * Possible return values: + * 0 = Attribute should not be moved to the channel + * 1 = Attribute should be moved to the channel and it is a shared attribute + * 2 = Attribute should be moved to the channel and it is a private attribute + */ +static unsigned int is_global_attr(struct iio_channel *chn, const char *attr) +{ + unsigned int len; + char *ptr; + + if (!chn->is_output && !strncmp(attr, "in_", 3)) + attr += 3; + else if (chn->is_output && !strncmp(attr, "out_", 4)) + attr += 4; + else + return 0; + + ptr = strchr(attr, '_'); + if (!ptr) + return 0; + + len = ptr - attr; + + if (strncmp(chn->id, attr, len)) + return 0; + + DEBUG("Found match: %s and %s\n", chn->id, attr); + if (chn->id[len] >= '0' && chn->id[len] <= '9') { + if (chn->name) { + size_t name_len = strlen(chn->name); + if (strncmp(chn->name, attr + len + 1, name_len) == 0 && + attr[len + 1 + name_len] == '_') + return 2; + } + return 1; + } else if (chn->id[len] != '_') { + return 0; + } + + if (find_channel_modifier(chn->id + len + 1, NULL) != IIO_NO_MOD) + return 1; + + return 0; +} + +static int detect_global_attr(struct iio_device *dev, const char *attr, + unsigned int level, bool *match) +{ + unsigned int i; + + *match = false; + for (i = 0; i < dev->nb_channels; i++) { + struct iio_channel *chn = dev->channels[i]; + if (is_global_attr(chn, attr) == level) { + int ret; + *match = true; + ret = add_attr_to_channel(chn, attr, attr, false); + if (ret) + return ret; + } + } + + return 0; +} + +static int detect_and_move_global_attrs(struct iio_device *dev) +{ + unsigned int i; + char **ptr = dev->attrs; + + for (i = 0; i < dev->nb_attrs; i++) { + const char *attr = dev->attrs[i]; + bool match; + int ret; + + ret = detect_global_attr(dev, attr, 2, &match); + if (ret) + return ret; + + if (!match) { + ret = detect_global_attr(dev, attr, 1, &match); + if (ret) + return ret; + } + + if (match) { + free(dev->attrs[i]); + dev->attrs[i] = NULL; + } + } + + /* Find channels without an index */ + for (i = 0; i < dev->nb_attrs; i++) { + const char *attr = dev->attrs[i]; + int ret; + + if (!dev->attrs[i]) + continue; + + if (is_channel(attr, false)) { + ret = add_channel(dev, attr, attr, false); + if (ret) + return ret; + + free(dev->attrs[i]); + dev->attrs[i] = NULL; + } + } + + for (i = 0; i < dev->nb_attrs; i++) { + if (dev->attrs[i]) + *ptr++ = dev->attrs[i]; + } + + dev->nb_attrs = ptr - dev->attrs; + if (!dev->nb_attrs) { + free(dev->attrs); + dev->attrs = NULL; + } + + return 0; +} + +static int add_buffer_attr(void *d, const char *path) +{ + struct iio_device *dev = (struct iio_device *) d; + const char *name = strrchr(path, '/') + 1; + char **attrs, *attr; + int i; + + for (i = 0; i < ARRAY_SIZE(buffer_attrs_reserved); i++) + if (!strcmp(buffer_attrs_reserved[i], name)) + return 0; + + attr = iio_strdup(name); + if (!attr) + return -ENOMEM; + + attrs = realloc(dev->buffer_attrs, (1 + dev->nb_buffer_attrs) * sizeof(char *)); + if (!attrs) { + free(attr); + return -ENOMEM; + } + + attrs[dev->nb_buffer_attrs++] = attr; + dev->buffer_attrs = attrs; + DEBUG("Added buffer attr \'%s\' to device \'%s\'\n", attr, dev->id); + return 0; +} + +static int add_attr_or_channel_helper(struct iio_device *dev, + const char *path, 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(name, true)) + return add_attr_to_device(dev, name); + path = name; + } + + return add_channel(dev, name, path, 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); +} + +static int add_scan_element(void *d, const char *path) +{ + return add_attr_or_channel_helper((struct iio_device *) d, path, true); +} + +static int foreach_in_dir(void *d, const char *path, bool is_dir, + int (*callback)(void *, const char *)) +{ + struct dirent *entry; + DIR *dir; + int ret = 0; + + dir = opendir(path); + if (!dir) + return -errno; + + while (true) { + struct stat st; + char buf[1024]; + + errno = 0; + entry = readdir(dir); + if (!entry) { + if (!errno) + break; + + ret = -errno; + iio_strerror(errno, buf, sizeof(buf)); + ERROR("Unable to open directory %s: %s\n", path, buf); + goto out_close_dir; + } + + iio_snprintf(buf, sizeof(buf), "%s/%s", path, entry->d_name); + if (stat(buf, &st) < 0) { + ret = -errno; + iio_strerror(errno, buf, sizeof(buf)); + ERROR("Unable to stat file: %s\n", buf); + goto out_close_dir; + } + + if (is_dir && S_ISDIR(st.st_mode) && entry->d_name[0] != '.') + ret = callback(d, buf); + else if (!is_dir && S_ISREG(st.st_mode)) + ret = callback(d, buf); + else + continue; + + if (ret < 0) + goto out_close_dir; + } + +out_close_dir: + closedir(dir); + return ret; +} + +static int add_scan_elements(struct iio_device *dev, const char *devpath) +{ + struct stat st; + char buf[1024]; + + iio_snprintf(buf, sizeof(buf), "%s/scan_elements", devpath); + + if (!stat(buf, &st) && S_ISDIR(st.st_mode)) { + int ret = foreach_in_dir(dev, buf, false, add_scan_element); + if (ret < 0) + return ret; + } + + return 0; +} + +static int add_buffer_attributes(struct iio_device *dev, const char *devpath) +{ + struct stat st; + char buf[1024]; + + iio_snprintf(buf, sizeof(buf), "%s/buffer", devpath); + + if (!stat(buf, &st) && S_ISDIR(st.st_mode)) { + int ret = foreach_in_dir(dev, buf, false, add_buffer_attr); + if (ret < 0) + return ret; + } + + return 0; +} + +static int create_device(void *d, const char *path) +{ + uint32_t *mask = NULL; + unsigned int i; + int ret; + struct iio_context *ctx = d; + struct iio_device *dev = zalloc(sizeof(*dev)); + if (!dev) + return -ENOMEM; + + dev->pdata = zalloc(sizeof(*dev->pdata)); + if (!dev->pdata) { + free(dev); + return -ENOMEM; + } + + dev->pdata->fd = -1; + dev->pdata->blocking = true; + dev->pdata->max_nb_blocks = NB_BLOCKS; + + dev->ctx = ctx; + dev->id = iio_strdup(strrchr(path, '/') + 1); + if (!dev->id) { + local_free_pdata(dev); + free(dev); + return -ENOMEM; + } + + ret = foreach_in_dir(dev, path, false, add_attr_or_channel); + if (ret < 0) + goto err_free_device; + + ret = add_buffer_attributes(dev, path); + if (ret < 0) + goto err_free_device; + + ret = add_scan_elements(dev, path); + if (ret < 0) + goto err_free_scan_elements; + + for (i = 0; i < dev->nb_channels; i++) { + struct iio_channel *chn = dev->channels[i]; + + set_channel_name(chn); + ret = handle_scan_elements(chn); + free_protected_attrs(chn); + if (ret < 0) + goto err_free_scan_elements; + } + + ret = detect_and_move_global_attrs(dev); + if (ret < 0) + goto err_free_device; + + dev->words = (dev->nb_channels + 31) / 32; + if (dev->words) { + mask = calloc(dev->words, sizeof(*mask)); + if (!mask) { + ret = -ENOMEM; + goto err_free_device; + } + } + + dev->mask = mask; + + ret = add_device_to_context(ctx, dev); + if (!ret) + return 0; + +err_free_scan_elements: + for (i = 0; i < dev->nb_channels; i++) + free_protected_attrs(dev->channels[i]); +err_free_device: + local_free_pdata(dev); + free_device(dev); + return ret; +} + +static int add_debug_attr(void *d, const char *path) +{ + struct iio_device *dev = d; + const char *attr = strrchr(path, '/') + 1; + char **attrs, *name = iio_strdup(attr); + if (!name) + return -ENOMEM; + + attrs = realloc(dev->debug_attrs, + (1 + dev->nb_debug_attrs) * sizeof(char *)); + if (!attrs) { + free(name); + return -ENOMEM; + } + + attrs[dev->nb_debug_attrs++] = name; + dev->debug_attrs = attrs; + DEBUG("Added debug attr \'%s\' to device \'%s\'\n", name, dev->id); + return 0; +} + +static int add_debug(void *d, const char *path) +{ + struct iio_context *ctx = d; + const char *name = strrchr(path, '/') + 1; + struct iio_device *dev = iio_context_find_device(ctx, name); + if (!dev) + return -ENODEV; + else + return foreach_in_dir(dev, path, false, add_debug_attr); +} + +static int local_set_timeout(struct iio_context *ctx, unsigned int timeout) +{ + ctx->pdata->rw_timeout_ms = timeout; + return 0; +} + +static void local_cancel(const struct iio_device *dev) +{ + struct iio_device_pdata *pdata = dev->pdata; + uint64_t event = 1; + int ret; + + ret = write(pdata->cancel_fd, &event, sizeof(event)); + if (ret == -1) { + /* If this happens something went very seriously wrong */ + char err_str[1024]; + iio_strerror(errno, err_str, sizeof(err_str)); + ERROR("Unable to signal cancellation event: %s\n", err_str); + } +} + +static struct iio_context * local_clone( + const struct iio_context *ctx __attribute__((unused))) +{ + return local_create_context(); +} + +static const struct iio_backend_ops local_ops = { + .clone = local_clone, + .open = local_open, + .close = local_close, + .get_fd = local_get_fd, + .set_blocking_mode = local_set_blocking_mode, + .read = local_read, + .write = local_write, + .set_kernel_buffers_count = local_set_kernel_buffers_count, + .get_buffer = local_get_buffer, + .read_device_attr = local_read_dev_attr, + .write_device_attr = local_write_dev_attr, + .read_channel_attr = local_read_chn_attr, + .write_channel_attr = local_write_chn_attr, + .get_trigger = local_get_trigger, + .set_trigger = local_set_trigger, + .shutdown = local_shutdown, + .set_timeout = local_set_timeout, + .cancel = local_cancel, +}; + +static void init_data_scale(struct iio_channel *chn) +{ + char buf[1024]; + ssize_t ret; + + ret = iio_channel_attr_read(chn, "scale", buf, sizeof(buf)); + if (ret < 0) { + chn->format.with_scale = false; + } else { + chn->format.with_scale = true; + chn->format.scale = atof(buf); + } +} + +static void init_scan_elements(struct iio_context *ctx) +{ + unsigned int i, j; + + for (i = 0; i < ctx->nb_devices; i++) { + struct iio_device *dev = ctx->devices[i]; + + for (j = 0; j < dev->nb_channels; j++) + init_data_scale(dev->channels[j]); + } +} + +#ifdef WITH_LOCAL_CONFIG +static int populate_context_attrs(struct iio_context *ctx, const char *file) +{ + struct INI *ini; + int ret; + + ini = ini_open(file); + if (!ini) { + /* INI file not present -> not an error */ + if (errno == ENOENT) + return 0; + else + return -errno; + } + + while (true) { + const char *section; + size_t len; + + ret = ini_next_section(ini, §ion, &len); + if (ret <= 0) + goto out_close_ini; + + if (!strncmp(section, "Context Attributes", len)) + break; + } + + do { + const char *key, *value; + char *new_key, *new_val; + size_t klen, vlen; + + ret = ini_read_pair(ini, &key, &klen, &value, &vlen); + if (ret <= 0) + break; + + /* Create a dup of the strings read from the INI, since they are + * not NULL-terminated. */ + new_key = strndup(key, klen); + new_val = strndup(value, vlen); + + if (!new_key || !new_val) + ret = -ENOMEM; + else + ret = iio_context_add_attr(ctx, new_key, new_val); + + free(new_key); + free(new_val); + } while (!ret); + +out_close_ini: + ini_close(ini); + return ret; +} +#endif + +struct iio_context * local_create_context(void) +{ + int ret = -ENOMEM; + unsigned int len; + struct utsname uts; + struct iio_context *ctx = zalloc(sizeof(*ctx)); + if (!ctx) + goto err_set_errno; + + ctx->ops = &local_ops; + ctx->name = "local"; + + ctx->pdata = zalloc(sizeof(*ctx->pdata)); + if (!ctx->pdata) { + free(ctx); + goto err_set_errno; + } + + local_set_timeout(ctx, DEFAULT_TIMEOUT_MS); + + uname(&uts); + len = strlen(uts.sysname) + strlen(uts.nodename) + strlen(uts.release) + + strlen(uts.version) + strlen(uts.machine); + ctx->description = malloc(len + 5); /* 4 spaces + EOF */ + if (!ctx->description) { + free(ctx->pdata); + free(ctx); + goto err_set_errno; + } + + iio_snprintf(ctx->description, len + 5, "%s %s %s %s %s", uts.sysname, + uts.nodename, uts.release, uts.version, uts.machine); + + ret = foreach_in_dir(ctx, "/sys/bus/iio/devices", true, create_device); + if (ret < 0) + goto err_context_destroy; + + foreach_in_dir(ctx, "/sys/kernel/debug/iio", true, add_debug); + + init_scan_elements(ctx); + +#ifdef WITH_LOCAL_CONFIG + ret = populate_context_attrs(ctx, "/etc/libiio.ini"); + if (ret < 0) + goto err_context_destroy; +#endif + + ret = iio_context_add_attr(ctx, "local,kernel", uts.release); + if (ret < 0) + goto err_context_destroy; + + ret = iio_context_init(ctx); + if (ret < 0) + goto err_context_destroy; + + return ctx; + +err_context_destroy: + iio_context_destroy(ctx); +err_set_errno: + errno = -ret; + return NULL; +} + +static int check_device(void *d, const char *path) +{ + *(bool *)d = true; + return 0; +} + +int local_context_scan(struct iio_scan_result *scan_result) +{ + struct iio_context_info **info; + bool exists = false; + char *desc, *uri; + int ret; + + ret = foreach_in_dir(&exists, "/sys/bus/iio", true, check_device); + if (ret < 0 || !exists) + return 0; + + desc = iio_strdup("Local devices"); + if (!desc) + return -ENOMEM; + + uri = iio_strdup("local:"); + if (!uri) + goto err_free_desc; + + info = iio_scan_result_add(scan_result, 1); + if (!info) + goto err_free_uri; + + info[0]->description = desc; + info[0]->uri = uri; + return 0; + +err_free_uri: + free(uri); +err_free_desc: + free(desc); + return -ENOMEM; +} |