| /* |
| * libiio - Library for interfacing industrial I/O (IIO) devices |
| * |
| * Copyright (C) 2014 Analog Devices, Inc. |
| * Author: Paul Cercueil <[email protected]> |
| * |
| * 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 "sort.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 ret; |
| } |
| |
| 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 %s channel \'%s\' to device \'%s\'\n", |
| chn->is_output ? "output" : "input", 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; |
| |
| qsort(dev->buffer_attrs, dev->nb_buffer_attrs, sizeof(char *), |
| iio_buffer_attr_compare); |
| } |
| |
| 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; |
| |
| /* sorting is done after global attrs are added */ |
| for (i = 0; i < dev->nb_channels; i++) { |
| struct iio_channel *chn = dev->channels[i]; |
| qsort(chn->attrs, chn->nb_attrs, sizeof(struct iio_channel_attr), |
| iio_channel_attr_compare); |
| } |
| qsort(dev->attrs, dev->nb_attrs, sizeof(char *), |
| iio_device_attr_compare); |
| |
| 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; |
| |
| qsort(ctx->devices, ctx->nb_devices, sizeof(struct iio_device *), |
| iio_device_compare); |
| |
| 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; |
| } |