blob: e61dfa3b0e6c0aaedb602b59d219431006259d56 [file] [log] [blame]
/*
* libiio - Library for interfacing industrial I/O (IIO) devices
*
* Copyright (C) 2014-2020 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 "iiod-client.h"
#include "iio-lock.h"
#include "iio-private.h"
#include <errno.h>
#include <inttypes.h>
#include <string.h>
#include <stdio.h>
#define REQUEST_CLIENT_ID "REQUEST_CLIENT_ID\r\n"
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) {
ERROR("READ LINE: %zu\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';
value = (int) strtol(ptr, &end, 10);
if (ptr == end)
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_request_client_id(struct iiod_client *client, void *desc)
{
struct iio_context_pdata *pdata = client->pdata;
const struct iiod_client_ops *ops = client->ops;
char buf[256], *ptr = buf, *end;
int ret, to_read;
iio_mutex_lock(client->lock);
ret = ops->write(pdata, desc, REQUEST_CLIENT_ID, strlen(REQUEST_CLIENT_ID));
if (ret < 0) {
iio_mutex_unlock(client->lock);
return ret;
}
ret = iiod_client_read_integer(client, desc, &to_read);
iio_mutex_unlock(client->lock);
if (ret < 0)
return ret;
return to_read;
}
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 = ops->write(pdata, desc, "VERSION\r\n", sizeof("VERSION\r\n") - 1);
if (ret < 0) {
iio_mutex_unlock(client->lock);
return ret;
}
ret = ops->read_line(pdata, desc, buf, sizeof(buf));
iio_mutex_unlock(client->lock);
if (ret < 0)
return ret;
maj = strtol(ptr, &end, 10);
if (ptr == end)
return -EIO;
ptr = end + 1;
min = strtol(ptr, &end, 10);
if (ptr == end)
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)
strncpy(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 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;
int client_id = iiod_client_request_client_id(client, desc);
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;
if (client_id >= 0)
ctx->client_id = client_id;
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;
iio_snprintf(buf, sizeof(buf), "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) {
iio_snprintf(ptr, (ptr - buf) + i * 8, "%08" PRIx32,
dev->mask[i - 1]);
}
strcpy(ptr, cyclic ? " CYCLIC\r\n" : "\r\n");
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) {
ERROR("READ ALL: %zu\n", ret);
goto out_buf_free;
} else
ret = 0;
buf[words*8] = '\0';
DEBUG("Reading mask\n");
for (i = words, ptr = buf; i > 0; i--) {
sscanf(ptr, "%08" PRIx32, &mask[i - 1]);
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;
}
int iiod_client_register_client_id(struct iiod_client *client, void *desc,
int client_id)
{
char buf[1024];
ssize_t ret;
int to_read;
iio_snprintf(buf, sizeof(buf), "REGISTER_CLIENT_ID %i\r\n", client_id);
ret = iiod_client_write_all(client, desc, buf, strlen(buf));
if (ret < 0)
return ret;
ret = iiod_client_read_integer(client, desc, &to_read);
if (ret < 0)
return ret;
return to_read;
}
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) {
ERROR("WRITE ALL: %zu\n", ret);
return ret;
}
do {
int to_read;
ret = iiod_client_read_integer(client, desc, &to_read);
if (ret < 0) {
ERROR("READ INTEGER: %zu\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) {
ERROR("READ ALL: %zu\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;
}