/* * libiio - Library for interfacing industrial I/O (IIO) devices * * Copyright (C) 2014-2020 Analog Devices, Inc. * Author: Paul Cercueil * * 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 "iiod-client.h" #include "iio-lock.h" #include "iio-private.h" #include #include #include #include struct iiod_client { struct iio_context_pdata *pdata; const struct iiod_client_ops *ops; struct iio_mutex *lock; }; static ssize_t iiod_client_read_integer(struct iiod_client *client, void *desc, int *val) { unsigned int i; char buf[1024], *ptr = NULL, *end; ssize_t ret; int value; do { ret = client->ops->read_line(client->pdata, desc, buf, sizeof(buf)); if (ret < 0) { IIO_ERROR("READ LINE: %zd\n", ret); return ret; } for (i = 0; i < (unsigned int) ret; i++) { if (buf[i] != '\n') { if (!ptr) ptr = &buf[i]; } else if (!!ptr) { break; } } } while (!ptr); buf[i] = '\0'; errno = 0; value = (int) strtol(ptr, &end, 10); if (ptr == end || errno == ERANGE) return -EINVAL; *val = value; return 0; } static int iiod_client_exec_command(struct iiod_client *client, void *desc, const char *cmd) { int resp; ssize_t ret; ret = client->ops->write(client->pdata, desc, cmd, strlen(cmd)); if (ret < 0) return (int) ret; ret = iiod_client_read_integer(client, desc, &resp); return ret < 0 ? (int) ret : resp; } static ssize_t iiod_client_write_all(struct iiod_client *client, void *desc, const void *src, size_t len) { struct iio_context_pdata *pdata = client->pdata; const struct iiod_client_ops *ops = client->ops; uintptr_t ptr = (uintptr_t) src; while (len) { ssize_t ret = ops->write(pdata, desc, (const void *) ptr, len); if (ret < 0) { if (ret == -EINTR) continue; else return ret; } if (ret == 0) return -EPIPE; ptr += ret; len -= ret; } return (ssize_t) (ptr - (uintptr_t) src); } static ssize_t iiod_client_read_all(struct iiod_client *client, void *desc, void *dst, size_t len) { struct iio_context_pdata *pdata = client->pdata; const struct iiod_client_ops *ops = client->ops; uintptr_t ptr = (uintptr_t) dst; while (len) { ssize_t ret = ops->read(pdata, desc, (void *) ptr, len); if (ret < 0) { if (ret == -EINTR) continue; else return ret; } if (ret == 0) return -EPIPE; ptr += ret; len -= ret; } return (ssize_t) (ptr - (uintptr_t) dst); } struct iiod_client * iiod_client_new(struct iio_context_pdata *pdata, struct iio_mutex *lock, const struct iiod_client_ops *ops) { struct iiod_client *client; client = malloc(sizeof(*client)); if (!client) { errno = ENOMEM; return NULL; } client->lock = lock; client->pdata = pdata; client->ops = ops; return client; } void iiod_client_destroy(struct iiod_client *client) { free(client); } int iiod_client_get_version(struct iiod_client *client, void *desc, unsigned int *major, unsigned int *minor, char *git_tag) { struct iio_context_pdata *pdata = client->pdata; const struct iiod_client_ops *ops = client->ops; char buf[256], *ptr = buf, *end; long maj, min; int ret; iio_mutex_lock(client->lock); ret = (int) ops->write(pdata, desc, "VERSION\r\n", sizeof("VERSION\r\n") - 1); if (ret < 0) { iio_mutex_unlock(client->lock); return ret; } ret = (int) ops->read_line(pdata, desc, buf, sizeof(buf)); iio_mutex_unlock(client->lock); if (ret < 0) return ret; errno = 0; maj = strtol(ptr, &end, 10); if (ptr == end || errno == ERANGE) return -EIO; ptr = end + 1; errno = 0; min = strtol(ptr, &end, 10); if (ptr == end || errno == ERANGE) return -EIO; ptr = end + 1; if (buf + ret < ptr + 8) return -EIO; /* Strip the \n */ ptr[buf + ret - ptr - 1] = '\0'; if (major) *major = (unsigned int) maj; if (minor) *minor = (unsigned int) min; if (git_tag) iio_strlcpy(git_tag, ptr, 8); return 0; } int iiod_client_get_trigger(struct iiod_client *client, void *desc, const struct iio_device *dev, const struct iio_device **trigger) { const struct iio_context *ctx = iio_device_get_context(dev); unsigned int i, nb_devices = iio_context_get_devices_count(ctx); char buf[1024]; unsigned int name_len; int ret; iio_snprintf(buf, sizeof(buf), "GETTRIG %s\r\n", iio_device_get_id(dev)); iio_mutex_lock(client->lock); ret = iiod_client_exec_command(client, desc, buf); if (ret == 0) *trigger = NULL; if (ret <= 0) goto out_unlock; if ((unsigned int) ret > sizeof(buf) - 1) { ret = -EIO; goto out_unlock; } name_len = ret; ret = (int) iiod_client_read_all(client, desc, buf, name_len + 1); if (ret < 0) goto out_unlock; ret = -ENXIO; for (i = 0; i < nb_devices; i++) { struct iio_device *cur = iio_context_get_device(ctx, i); if (iio_device_is_trigger(cur)) { const char *name = iio_device_get_name(cur); if (!name) continue; if (!strncmp(name, buf, name_len)) { *trigger = cur; ret = 0; goto out_unlock; } } } out_unlock: iio_mutex_unlock(client->lock); return ret; } int iiod_client_set_trigger(struct iiod_client *client, void *desc, const struct iio_device *dev, const struct iio_device *trigger) { char buf[1024]; int ret; if (trigger) { iio_snprintf(buf, sizeof(buf), "SETTRIG %s %s\r\n", iio_device_get_id(dev), iio_device_get_id(trigger)); } else { iio_snprintf(buf, sizeof(buf), "SETTRIG %s\r\n", iio_device_get_id(dev)); } iio_mutex_lock(client->lock); ret = iiod_client_exec_command(client, desc, buf); iio_mutex_unlock(client->lock); return ret; } int iiod_client_set_kernel_buffers_count(struct iiod_client *client, void *desc, const struct iio_device *dev, unsigned int nb_blocks) { int ret; char buf[1024]; iio_snprintf(buf, sizeof(buf), "SET %s BUFFERS_COUNT %u\r\n", iio_device_get_id(dev), nb_blocks); iio_mutex_lock(client->lock); ret = iiod_client_exec_command(client, desc, buf); iio_mutex_unlock(client->lock); return ret; } int iiod_client_set_timeout(struct iiod_client *client, void *desc, unsigned int timeout) { int ret; char buf[1024]; iio_snprintf(buf, sizeof(buf), "TIMEOUT %u\r\n", timeout); iio_mutex_lock(client->lock); ret = iiod_client_exec_command(client, desc, buf); iio_mutex_unlock(client->lock); return ret; } static int iiod_client_discard(struct iiod_client *client, void *desc, char *buf, size_t buf_len, size_t to_discard) { do { size_t read_len; ssize_t ret; if (to_discard > buf_len) read_len = buf_len; else read_len = to_discard; ret = iiod_client_read_all(client, desc, buf, read_len); if (ret < 0) return (int) ret; to_discard -= (size_t) ret; } while (to_discard); return 0; } ssize_t iiod_client_read_attr(struct iiod_client *client, void *desc, const struct iio_device *dev, const struct iio_channel *chn, const char *attr, char *dest, size_t len, enum iio_attr_type type) { const char *id = iio_device_get_id(dev); char buf[1024]; ssize_t ret; if (attr) { if (chn) { if (!iio_channel_find_attr(chn, attr)) return -ENOENT; } else { switch (type) { case IIO_ATTR_TYPE_DEVICE: if (!iio_device_find_attr(dev, attr)) return -ENOENT; break; case IIO_ATTR_TYPE_DEBUG: if (!iio_device_find_debug_attr(dev, attr)) return -ENOENT; break; case IIO_ATTR_TYPE_BUFFER: if (!iio_device_find_buffer_attr(dev, attr)) return -ENOENT; break; default: return -EINVAL; } } } if (chn) { iio_snprintf(buf, sizeof(buf), "READ %s %s %s %s\r\n", id, iio_channel_is_output(chn) ? "OUTPUT" : "INPUT", iio_channel_get_id(chn), attr ? attr : ""); } else { switch (type) { case IIO_ATTR_TYPE_DEVICE: iio_snprintf(buf, sizeof(buf), "READ %s %s\r\n", id, attr ? attr : ""); break; case IIO_ATTR_TYPE_DEBUG: iio_snprintf(buf, sizeof(buf), "READ %s DEBUG %s\r\n", id, attr ? attr : ""); break; case IIO_ATTR_TYPE_BUFFER: iio_snprintf(buf, sizeof(buf), "READ %s BUFFER %s\r\n", id, attr ? attr : ""); break; } } iio_mutex_lock(client->lock); ret = (ssize_t) iiod_client_exec_command(client, desc, buf); if (ret < 0) goto out_unlock; if ((size_t) ret + 1 > len) { iiod_client_discard(client, desc, dest, len, ret + 1); ret = -EIO; goto out_unlock; } /* +1: Also read the trailing \n */ ret = iiod_client_read_all(client, desc, dest, ret + 1); if (ret > 0) { /* Discard the trailing \n */ ret--; /* Replace it with a \0 just in case */ dest[ret] = '\0'; } out_unlock: iio_mutex_unlock(client->lock); return ret; } ssize_t iiod_client_write_attr(struct iiod_client *client, void *desc, const struct iio_device *dev, const struct iio_channel *chn, const char *attr, const char *src, size_t len, enum iio_attr_type type) { struct iio_context_pdata *pdata = client->pdata; const struct iiod_client_ops *ops = client->ops; const char *id = iio_device_get_id(dev); char buf[1024]; ssize_t ret; int resp; if (attr) { if (chn) { if (!iio_channel_find_attr(chn, attr)) return -ENOENT; } else { switch (type) { case IIO_ATTR_TYPE_DEVICE: if (!iio_device_find_attr(dev, attr)) return -ENOENT; break; case IIO_ATTR_TYPE_DEBUG: if (!iio_device_find_debug_attr(dev, attr)) return -ENOENT; break; case IIO_ATTR_TYPE_BUFFER: if (!iio_device_find_buffer_attr(dev, attr)) return -ENOENT; break; default: return -EINVAL; } } } if (chn) { iio_snprintf(buf, sizeof(buf), "WRITE %s %s %s %s %lu\r\n", id, iio_channel_is_output(chn) ? "OUTPUT" : "INPUT", iio_channel_get_id(chn), attr ? attr : "", (unsigned long) len); } else { switch (type) { case IIO_ATTR_TYPE_DEVICE: iio_snprintf(buf, sizeof(buf), "WRITE %s %s %lu\r\n", id, attr ? attr : "", (unsigned long) len); break; case IIO_ATTR_TYPE_DEBUG: iio_snprintf(buf, sizeof(buf), "WRITE %s DEBUG %s %lu\r\n", id, attr ? attr : "", (unsigned long) len); break; case IIO_ATTR_TYPE_BUFFER: iio_snprintf(buf, sizeof(buf), "WRITE %s BUFFER %s %lu\r\n", id, attr ? attr : "", (unsigned long) len); break; } } iio_mutex_lock(client->lock); ret = ops->write(pdata, desc, buf, strlen(buf)); if (ret < 0) goto out_unlock; ret = iiod_client_write_all(client, desc, src, len); if (ret < 0) goto out_unlock; ret = iiod_client_read_integer(client, desc, &resp); if (ret < 0) goto out_unlock; ret = (ssize_t) resp; out_unlock: iio_mutex_unlock(client->lock); return ret; } struct iio_context * iiod_client_create_context( struct iiod_client *client, void *desc) { struct iio_context *ctx = NULL; size_t xml_len; char *xml; int ret; iio_mutex_lock(client->lock); ret = iiod_client_exec_command(client, desc, "PRINT\r\n"); if (ret < 0) goto out_unlock; xml_len = (size_t) ret; xml = malloc(xml_len + 1); if (!xml) { ret = -ENOMEM; goto out_unlock; } /* +1: Also read the trailing \n */ ret = (int) iiod_client_read_all(client, desc, xml, xml_len + 1); if (ret < 0) goto out_free_xml; ctx = iio_create_xml_context_mem(xml, xml_len); if (!ctx) ret = -errno; out_free_xml: free(xml); out_unlock: iio_mutex_unlock(client->lock); if (!ctx) errno = -ret; return ctx; } int iiod_client_open_unlocked(struct iiod_client *client, void *desc, const struct iio_device *dev, size_t samples_count, bool cyclic) { char buf[1024], *ptr; size_t i; ssize_t len; len = sizeof(buf); len -= iio_snprintf(buf, len, "OPEN %s %lu ", iio_device_get_id(dev), (unsigned long) samples_count); ptr = buf + strlen(buf); for (i = dev->words; i > 0; i--, ptr += 8) { len -= iio_snprintf(ptr, len, "%08" PRIx32, dev->mask[i - 1]); } len -= iio_strlcpy(ptr, cyclic ? " CYCLIC\r\n" : "\r\n", len); if (len < 0) { IIO_ERROR("strlength problem in iiod_client_open_unlocked\n"); return -ENOMEM; } return iiod_client_exec_command(client, desc, buf); } int iiod_client_close_unlocked(struct iiod_client *client, void *desc, const struct iio_device *dev) { char buf[1024]; iio_snprintf(buf, sizeof(buf), "CLOSE %s\r\n", iio_device_get_id(dev)); return iiod_client_exec_command(client, desc, buf); } static int iiod_client_read_mask(struct iiod_client *client, void *desc, uint32_t *mask, size_t words) { size_t i; ssize_t ret; char *buf, *ptr; buf = malloc(words * 8 + 1); if (!buf) return -ENOMEM; ret = iiod_client_read_all(client, desc, buf, words * 8 + 1); if (ret < 0) { IIO_ERROR("READ ALL: %zd\n", ret); goto out_buf_free; } else ret = 0; buf[words*8] = '\0'; IIO_DEBUG("Reading mask\n"); for (i = words, ptr = buf; i > 0; i--) { iio_sscanf(ptr, "%08" PRIx32, &mask[i - 1]); IIO_DEBUG("mask[%lu] = 0x%08" PRIx32 "\n", (unsigned long)(i - 1), mask[i - 1]); ptr = (char *) ((uintptr_t) ptr + 8); } out_buf_free: free(buf); return (int) ret; } ssize_t iiod_client_read_unlocked(struct iiod_client *client, void *desc, const struct iio_device *dev, void *dst, size_t len, uint32_t *mask, size_t words) { unsigned int nb_channels = iio_device_get_channels_count(dev); uintptr_t ptr = (uintptr_t) dst; char buf[1024]; ssize_t ret, read = 0; if (!len || words != (nb_channels + 31) / 32) return -EINVAL; iio_snprintf(buf, sizeof(buf), "READBUF %s %lu\r\n", iio_device_get_id(dev), (unsigned long) len); ret = iiod_client_write_all(client, desc, buf, strlen(buf)); if (ret < 0) { IIO_ERROR("WRITE ALL: %zd\n", ret); return ret; } do { int to_read; ret = iiod_client_read_integer(client, desc, &to_read); if (ret < 0) { IIO_ERROR("READ INTEGER: %zd\n", ret); return ret; } if (to_read < 0) return (ssize_t) to_read; if (!to_read) break; if (mask) { ret = iiod_client_read_mask(client, desc, mask, words); if (ret < 0) { IIO_ERROR("READ ALL: %zd\n", ret); return ret; } mask = NULL; /* We read the mask only once */ } ret = iiod_client_read_all(client, desc, (char *) ptr, to_read); if (ret < 0) return ret; ptr += ret; read += ret; len -= ret; } while (len); return read; } ssize_t iiod_client_write_unlocked(struct iiod_client *client, void *desc, const struct iio_device *dev, const void *src, size_t len) { ssize_t ret; char buf[1024]; int val; iio_snprintf(buf, sizeof(buf), "WRITEBUF %s %lu\r\n", dev->id, (unsigned long) len); ret = iiod_client_write_all(client, desc, buf, strlen(buf)); if (ret < 0) return ret; ret = iiod_client_read_integer(client, desc, &val); if (ret < 0) return ret; if (val < 0) return (ssize_t) val; ret = iiod_client_write_all(client, desc, src, len); if (ret < 0) return ret; ret = iiod_client_read_integer(client, desc, &val); if (ret < 0) return ret; if (val < 0) return (ssize_t) val; return (ssize_t) len; }