928 lines
23 KiB
C
928 lines
23 KiB
C
/***************************************************************************//**
|
|
* @file iiod.c
|
|
* @brief Nonblocking implementation of iiod.
|
|
* @author Mihail Chindris (mihail.chindris@analog.com)
|
|
********************************************************************************
|
|
* Copyright 2022(c) Analog Devices, Inc.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions are met:
|
|
*
|
|
* 1. Redistributions of source code must retain the above copyright notice,
|
|
* this list of conditions and the following disclaimer.
|
|
*
|
|
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
|
* this list of conditions and the following disclaimer in the documentation
|
|
* and/or other materials provided with the distribution.
|
|
*
|
|
* 3. Neither the name of Analog Devices, Inc. nor the names of its
|
|
* contributors may be used to endorse or promote products derived from this
|
|
* software without specific prior written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY ANALOG DEVICES, INC. “AS IS” AND ANY EXPRESS OR
|
|
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
|
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
|
|
* EVENT SHALL ANALOG DEVICES, INC. BE LIABLE FOR ANY DIRECT, INDIRECT,
|
|
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
|
|
* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
|
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
|
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
|
|
* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
*******************************************************************************/
|
|
|
|
#include <inttypes.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <stdio.h>
|
|
#include <assert.h>
|
|
|
|
#include "iiod.h"
|
|
#include "iiod_private.h"
|
|
|
|
#include "no_os_error.h"
|
|
#include "no_os_util.h"
|
|
|
|
#define SET_DUMMY_IF_NULL(func, dummy) ((func) ? (func) : (dummy))
|
|
|
|
static char delim[] = " \r\n";
|
|
|
|
|
|
static const char *attr_types_strs[] = {
|
|
[IIO_ATTR_TYPE_DEBUG] = "DEBUG",
|
|
[IIO_ATTR_TYPE_BUFFER] = "BUFFER",
|
|
[IIO_ATTR_TYPE_CH_OUT] = "OUTPUT",
|
|
[IIO_ATTR_TYPE_CH_IN] = "INPUT"
|
|
};
|
|
|
|
static struct iiod_str cmds[] = {
|
|
[IIOD_CMD_HELP] = IIOD_STR("HELP"),
|
|
[IIOD_CMD_EXIT] = IIOD_STR("EXIT"),
|
|
[IIOD_CMD_PRINT] = IIOD_STR("PRINT"),
|
|
[IIOD_CMD_VERSION] = IIOD_STR("VERSION"),
|
|
[IIOD_CMD_TIMEOUT] = IIOD_STR("TIMEOUT"),
|
|
[IIOD_CMD_OPEN] = IIOD_STR("OPEN"),
|
|
[IIOD_CMD_CLOSE] = IIOD_STR("CLOSE"),
|
|
[IIOD_CMD_READ] = IIOD_STR("READ"),
|
|
[IIOD_CMD_WRITE] = IIOD_STR("WRITE"),
|
|
[IIOD_CMD_READBUF] = IIOD_STR("READBUF"),
|
|
[IIOD_CMD_WRITEBUF] = IIOD_STR("WRITEBUF"),
|
|
[IIOD_CMD_GETTRIG] = IIOD_STR("GETTRIG"),
|
|
[IIOD_CMD_SETTRIG] = IIOD_STR("SETTRIG"),
|
|
[IIOD_CMD_SET] = IIOD_STR("SET")
|
|
};
|
|
static const uint32_t priority_array[] = {
|
|
/* Order not tested, just personal expectation. Function can
|
|
* be improved, this improvement is chosen for simplicity */
|
|
IIOD_CMD_READBUF,
|
|
IIOD_CMD_WRITEBUF,
|
|
IIOD_CMD_READ,
|
|
IIOD_CMD_WRITE,
|
|
IIOD_CMD_OPEN,
|
|
IIOD_CMD_CLOSE,
|
|
IIOD_CMD_PRINT,
|
|
IIOD_CMD_EXIT,
|
|
IIOD_CMD_TIMEOUT,
|
|
IIOD_CMD_VERSION,
|
|
IIOD_CMD_GETTRIG,
|
|
IIOD_CMD_SETTRIG,
|
|
IIOD_CMD_HELP,
|
|
IIOD_CMD_SET
|
|
};
|
|
|
|
static_assert(NO_OS_ARRAY_SIZE(cmds) == NO_OS_ARRAY_SIZE(priority_array),
|
|
"Arrays must have the same size");
|
|
|
|
/* Set res->cmd to corresponding cmd and return the processed length of buf */
|
|
static int32_t parse_cmd(const char *token, struct comand_desc *res)
|
|
{
|
|
uint32_t i;
|
|
struct iiod_str *cmd;
|
|
|
|
if (!token)
|
|
return -EINVAL;
|
|
|
|
for (i = 0; i < NO_OS_ARRAY_SIZE(cmds); ++i) {
|
|
cmd = &cmds[priority_array[i]];
|
|
if (strcmp(token, cmd->str) == 0) {
|
|
res->cmd = priority_array[i];
|
|
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
return -EINVAL;
|
|
}
|
|
|
|
static int32_t parse_num(const char *token, uint32_t *res, uint32_t base)
|
|
{
|
|
char *end_ptr;
|
|
|
|
*res = strtoul(token, &end_ptr, base);
|
|
if (*res == 0 && *end_ptr != '\0')
|
|
return -EINVAL;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int32_t iiod_parse_open(const char *token, struct comand_desc *res,
|
|
char **ctx)
|
|
{
|
|
int32_t ret;
|
|
|
|
if (!token)
|
|
return -EINVAL;
|
|
|
|
ret = parse_num(token, &res->sample_count, 10);
|
|
if (NO_OS_IS_ERR_VALUE(ret))
|
|
return ret;
|
|
|
|
token = strtok_r(NULL, delim, ctx);
|
|
if (!token)
|
|
return -EINVAL;
|
|
|
|
ret = parse_num(token, &res->mask, 16);
|
|
if (NO_OS_IS_ERR_VALUE(ret))
|
|
return ret;
|
|
|
|
res->cyclic = 0;
|
|
token = strtok_r(NULL, delim, ctx);
|
|
if (token) {
|
|
if (strcmp(token, "CYCLIC") == 0)
|
|
res->cyclic = 1;
|
|
else
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int32_t iiod_parse_set(const char *token, struct comand_desc *res,
|
|
char **ctx)
|
|
{
|
|
if (!token)
|
|
return -EINVAL;
|
|
|
|
if (strcmp(token, "BUFFERS_COUNT"))
|
|
return -EINVAL;
|
|
|
|
token = strtok_r(NULL, delim, ctx);
|
|
if (!token)
|
|
return -EINVAL;
|
|
|
|
return parse_num(token, &res->count, 10);
|
|
}
|
|
|
|
static int32_t iiod_parse_rw_attr(const char *token, struct comand_desc *res,
|
|
char **ctx)
|
|
{
|
|
int32_t i;
|
|
|
|
res->type = IIO_ATTR_TYPE_DEVICE;
|
|
if (token) {
|
|
for (i = 0; i < IIO_ATTR_TYPE_DEVICE; ++i) {
|
|
if (strcmp(token, attr_types_strs[i]) == 0) {
|
|
token = strtok_r(NULL, delim, ctx);
|
|
res->type = i;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (res->type == IIO_ATTR_TYPE_CH_IN ||
|
|
res->type == IIO_ATTR_TYPE_CH_OUT) {
|
|
if (!token)
|
|
return -EINVAL;
|
|
strncpy(res->channel, token, sizeof(res->channel));
|
|
token = strtok_r(NULL, delim, ctx);
|
|
}
|
|
|
|
if (res->cmd == IIOD_CMD_WRITE) {
|
|
if (!token)
|
|
return -EINVAL;
|
|
|
|
if (*token >= '0' && *token <= '9') {
|
|
memset(res->attr, 0, sizeof(res->attr));
|
|
|
|
return parse_num(token, &res->bytes_count, 10);
|
|
}
|
|
|
|
strncpy(res->attr, token, sizeof(res->attr));
|
|
token = strtok_r(NULL, delim, ctx);
|
|
if (!token)
|
|
return -EINVAL;
|
|
|
|
return parse_num(token, &res->bytes_count, 10);
|
|
}
|
|
|
|
if (token)
|
|
strncpy(res->attr, token, sizeof(res->attr));
|
|
else
|
|
memset(res->attr, 0, sizeof(res->attr));
|
|
|
|
return 0;
|
|
}
|
|
|
|
int32_t iiod_parse_line(char *buf, struct comand_desc *res, char **ctx)
|
|
{
|
|
int32_t ret;
|
|
char *token;
|
|
|
|
token = strtok_r(buf, delim, ctx);
|
|
ret = parse_cmd(token, res);
|
|
if (NO_OS_IS_ERR_VALUE(ret))
|
|
return ret;
|
|
|
|
token = strtok_r(NULL, delim, ctx);
|
|
/* Commands without device */
|
|
switch (res->cmd) {
|
|
case IIOD_CMD_HELP:
|
|
case IIOD_CMD_EXIT:
|
|
case IIOD_CMD_PRINT:
|
|
case IIOD_CMD_VERSION:
|
|
return 0;
|
|
case IIOD_CMD_TIMEOUT:
|
|
return parse_num(token, &res->timeout, 10);
|
|
default:
|
|
break;
|
|
}
|
|
|
|
strncpy(res->device, token, sizeof(res->device));
|
|
token = strtok_r(NULL, delim, ctx);
|
|
switch (res->cmd) {
|
|
case IIOD_CMD_CLOSE:
|
|
case IIOD_CMD_GETTRIG:
|
|
return 0;
|
|
case IIOD_CMD_OPEN:
|
|
return iiod_parse_open(token, res, ctx);
|
|
case IIOD_CMD_READ:
|
|
case IIOD_CMD_WRITE:
|
|
return iiod_parse_rw_attr(token, res, ctx);
|
|
case IIOD_CMD_READBUF:
|
|
case IIOD_CMD_WRITEBUF:
|
|
return parse_num(token, &res->bytes_count, 10);
|
|
case IIOD_CMD_SETTRIG:
|
|
if (token)
|
|
strncpy(res->trigger, token, sizeof(res->trigger));
|
|
else
|
|
memset(res->trigger, 0, sizeof(res->trigger));
|
|
|
|
return 0;
|
|
case IIOD_CMD_SET:
|
|
return iiod_parse_set(token, res, ctx);
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return -EINVAL;
|
|
}
|
|
|
|
static int dummy_open(struct iiod_ctx *ctx, const char *device,
|
|
uint32_t samples, uint32_t mask, bool cyclic)
|
|
{
|
|
return -EINVAL;
|
|
}
|
|
|
|
static int dummy_close(struct iiod_ctx *ctx, const char *device)
|
|
{
|
|
return -EINVAL;
|
|
}
|
|
|
|
static int dummy_rw_attr(struct iiod_ctx *ctx, const char *device,
|
|
struct iiod_attr *attr, char *buf, uint32_t len)
|
|
{
|
|
return -EINVAL;
|
|
}
|
|
|
|
static int dummy_rd_data(struct iiod_ctx *ctx, const char *device, char *buf,
|
|
uint32_t bytes)
|
|
{
|
|
return -EINVAL;
|
|
}
|
|
|
|
static int dummy_wr_data(struct iiod_ctx *ctx, const char *device,
|
|
const char *trig, uint32_t bytes)
|
|
{
|
|
return -EINVAL;
|
|
}
|
|
|
|
static int dummy_set_timeout(struct iiod_ctx *ctx, uint32_t timeout)
|
|
{
|
|
return -EINVAL;
|
|
}
|
|
|
|
static int dummy_set_buffers_count(struct iiod_ctx *ctx, const char *device,
|
|
uint32_t buffers_count)
|
|
{
|
|
return -EINVAL;
|
|
}
|
|
|
|
int32_t iiod_copy_ops(struct iiod_ops *ops, struct iiod_ops *new_ops)
|
|
{
|
|
if (!new_ops->recv || !new_ops->send)
|
|
return -EINVAL;
|
|
|
|
ops->recv = new_ops->recv;
|
|
ops->send = new_ops->send;
|
|
|
|
ops->open = SET_DUMMY_IF_NULL(new_ops->open, dummy_open);
|
|
ops->close = SET_DUMMY_IF_NULL(new_ops->close, dummy_close);
|
|
ops->read_buffer = SET_DUMMY_IF_NULL(new_ops->read_buffer, dummy_rd_data);
|
|
ops->write_buffer = SET_DUMMY_IF_NULL(new_ops->write_buffer, dummy_wr_data);
|
|
ops->read_attr = SET_DUMMY_IF_NULL(new_ops->read_attr, dummy_rw_attr);
|
|
ops->write_attr = SET_DUMMY_IF_NULL(new_ops->write_attr, dummy_rw_attr);
|
|
ops->get_trigger = SET_DUMMY_IF_NULL(new_ops->get_trigger, dummy_rd_data);
|
|
ops->set_trigger = SET_DUMMY_IF_NULL(new_ops->set_trigger, dummy_wr_data);
|
|
ops->set_timeout = SET_DUMMY_IF_NULL(new_ops->set_timeout, dummy_set_timeout);
|
|
ops->set_buffers_count = SET_DUMMY_IF_NULL(new_ops->set_buffers_count,
|
|
dummy_set_buffers_count);
|
|
ops->refill_buffer = SET_DUMMY_IF_NULL(new_ops->refill_buffer,
|
|
dummy_close);
|
|
ops->push_buffer = SET_DUMMY_IF_NULL(new_ops->push_buffer,
|
|
dummy_close);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int32_t iiod_init(struct iiod_desc **desc, struct iiod_init_param *param)
|
|
{
|
|
struct iiod_desc *ldesc;
|
|
int32_t ret;
|
|
|
|
if (!desc || !param || !param->ops)
|
|
return -EINVAL;
|
|
|
|
ldesc = (struct iiod_desc *)calloc(1, sizeof(*ldesc));
|
|
if (!ldesc)
|
|
return -ENOMEM;
|
|
|
|
ret = iiod_copy_ops(&ldesc->ops, param->ops);
|
|
if (NO_OS_IS_ERR_VALUE(ret)) {
|
|
free(ldesc);
|
|
|
|
return ret;
|
|
}
|
|
|
|
ldesc->xml = param->xml;
|
|
ldesc->xml_len = param->xml_len;
|
|
ldesc->app_instance = param->instance;
|
|
ldesc->phy_type = param->phy_type;
|
|
|
|
*desc = ldesc;
|
|
|
|
return 0;
|
|
}
|
|
|
|
void iiod_remove(struct iiod_desc *desc)
|
|
{
|
|
free(desc);
|
|
}
|
|
|
|
static void conn_clean_state(struct iiod_conn_priv *conn)
|
|
{
|
|
memset(&conn->cmd_data, 0, sizeof(conn->cmd_data));
|
|
memset(&conn->res, 0, sizeof(conn->res));
|
|
memset(&conn->nb_buf, 0, sizeof(conn->nb_buf));
|
|
|
|
conn->res.buf.buf = NULL;
|
|
conn->res.buf.idx = 0;
|
|
conn->parser_idx = 0;
|
|
conn->state = IIOD_READING_LINE;
|
|
}
|
|
|
|
int32_t iiod_conn_add(struct iiod_desc *desc, struct iiod_conn_data *data,
|
|
uint32_t *new_conn_id)
|
|
{
|
|
uint32_t i;
|
|
struct iiod_conn_priv *conn;
|
|
|
|
if (!desc || !new_conn_id)
|
|
return -EINVAL;
|
|
|
|
for (i = 0; i < IIOD_MAX_CONNECTIONS; ++i)
|
|
if (!desc->conns[i].used) {
|
|
conn = &desc->conns[i];
|
|
memset(conn, 0, sizeof(*conn));
|
|
conn->used = 1;
|
|
conn->conn = data->conn;
|
|
/*
|
|
* TODO in future:
|
|
* think of using other buffer (e.g. ciruclar_buffer)
|
|
* to somehow implement zero copy
|
|
*/
|
|
conn->payload_buf = data->buf;
|
|
conn->payload_buf_len = data->len;
|
|
*new_conn_id = i;
|
|
|
|
return 0;
|
|
}
|
|
|
|
return -EBUSY;
|
|
}
|
|
|
|
int32_t iiod_conn_remove(struct iiod_desc *desc, uint32_t conn_id,
|
|
struct iiod_conn_data *data)
|
|
{
|
|
if (!desc || conn_id > IIOD_MAX_CONNECTIONS ||
|
|
!desc->conns[conn_id].used)
|
|
return -EINVAL;
|
|
struct iiod_conn_priv *conn;
|
|
conn = &desc->conns[conn_id];
|
|
data->conn = conn->conn;
|
|
data->len = conn->payload_buf_len;
|
|
data->buf = conn->payload_buf;
|
|
conn->used = 0;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int32_t call_op(struct iiod_ops *ops, struct comand_desc *data,
|
|
struct iiod_ctx *ctx)
|
|
{
|
|
switch (data->cmd) {
|
|
case IIOD_CMD_HELP:
|
|
return -EINVAL;
|
|
case IIOD_CMD_TIMEOUT:
|
|
return ops->set_timeout(ctx, data->timeout);
|
|
case IIOD_CMD_OPEN:
|
|
return ops->open(ctx, data->device, data->sample_count,
|
|
data->mask, data->cyclic);
|
|
case IIOD_CMD_CLOSE:
|
|
return ops->close(ctx, data->device);
|
|
case IIOD_CMD_SETTRIG:
|
|
return ops->set_trigger(ctx, data->device, data->trigger,
|
|
strlen(data->trigger));
|
|
case IIOD_CMD_SET:
|
|
return ops->set_buffers_count(ctx, data->device, data->count);
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return -EINVAL;
|
|
}
|
|
|
|
/*
|
|
* Unload data from buf without blocking.
|
|
* When done will return 0, if there is still data to be sent it will return
|
|
* -EAGIAN. On error, an negative error code is returned
|
|
*/
|
|
static int32_t rw_iiod_buff(struct iiod_desc *desc, struct iiod_conn_priv *conn,
|
|
struct iiod_buff *buf, uint8_t flags)
|
|
{
|
|
struct iiod_ctx ctx = IIOD_CTX(desc, conn);
|
|
uint8_t *tmp_buf;
|
|
int32_t ret;
|
|
int32_t len;
|
|
|
|
len = buf->len - buf->idx;
|
|
if (len) {
|
|
tmp_buf = (uint8_t *)buf->buf + buf->idx;
|
|
if (flags & IIOD_WR)
|
|
ret = desc->ops.send(&ctx, tmp_buf, len);
|
|
else
|
|
ret = desc->ops.recv(&ctx, tmp_buf, len);
|
|
if (NO_OS_IS_ERR_VALUE(ret))
|
|
return ret;
|
|
|
|
buf->idx += ret;
|
|
if (ret < len)
|
|
return -EAGAIN;
|
|
}
|
|
|
|
if (flags & IIOD_ENDL) {
|
|
ret = desc->ops.send(&ctx, (uint8_t *)"\n", 1);
|
|
if (NO_OS_IS_ERR_VALUE(ret))
|
|
return ret;
|
|
|
|
if (ret != 1)
|
|
return -EAGAIN;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int32_t do_read_buff_delayed(struct iiod_desc *desc,
|
|
struct iiod_conn_priv *conn)
|
|
{
|
|
struct iiod_ctx ctx = IIOD_CTX(desc, conn);
|
|
uint32_t max_to_read;
|
|
int32_t ret, len;
|
|
|
|
conn->nb_buf.buf = conn->payload_buf;
|
|
len = no_os_min(conn->payload_buf_len, conn->cmd_data.bytes_count);
|
|
max_to_read = len - conn->nb_buf.len;
|
|
ret = desc->ops.read_buffer(&ctx, conn->cmd_data.device,
|
|
conn->nb_buf.buf + conn->nb_buf.len, max_to_read);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
conn->nb_buf.len += ret;
|
|
|
|
if (conn->nb_buf.len < conn->cmd_data.bytes_count)
|
|
return -EAGAIN;
|
|
|
|
ret = rw_iiod_buff(desc, conn, &conn->nb_buf, IIOD_WR);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
conn->nb_buf.len = 0;
|
|
conn->nb_buf.idx = 0;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int32_t do_read_buff(struct iiod_desc *desc, struct iiod_conn_priv *conn)
|
|
{
|
|
struct iiod_ctx ctx;
|
|
int32_t ret, len;
|
|
|
|
/*
|
|
* When using the network backend wait for a whole buffer to be filled
|
|
* before sending in order to reduce the ammount of network traffic.
|
|
*/
|
|
if (desc->phy_type == USE_NETWORK)
|
|
return do_read_buff_delayed(desc, conn);
|
|
|
|
ctx = (struct iiod_ctx)IIOD_CTX(desc, conn);
|
|
if (conn->nb_buf.len == 0) {
|
|
conn->nb_buf.buf = conn->payload_buf;
|
|
len = no_os_min(conn->payload_buf_len,
|
|
conn->cmd_data.bytes_count);
|
|
/* Read from dev */
|
|
ret = desc->ops.read_buffer(&ctx, conn->cmd_data.device,
|
|
conn->nb_buf.buf, len);
|
|
if (NO_OS_IS_ERR_VALUE(ret))
|
|
return ret;
|
|
len = ret;
|
|
conn->nb_buf.len = len;
|
|
conn->nb_buf.idx = 0;
|
|
}
|
|
if (conn->nb_buf.idx < conn->nb_buf.len) {
|
|
/* Write on conn */
|
|
ret = rw_iiod_buff(desc, conn, &conn->nb_buf, IIOD_WR);
|
|
if (NO_OS_IS_ERR_VALUE(ret))
|
|
return ret;
|
|
|
|
conn->cmd_data.bytes_count -= conn->nb_buf.len;
|
|
conn->nb_buf.len = 0;
|
|
if (conn->cmd_data.bytes_count)
|
|
return -EAGAIN;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int32_t do_write_buff(struct iiod_desc *desc,
|
|
struct iiod_conn_priv *conn)
|
|
{
|
|
struct iiod_ctx ctx = IIOD_CTX(desc, conn);
|
|
int32_t ret, len;
|
|
|
|
if (conn->nb_buf.len == 0) {
|
|
conn->nb_buf.buf = conn->payload_buf;
|
|
len = no_os_min(conn->payload_buf_len,
|
|
conn->cmd_data.bytes_count);
|
|
conn->nb_buf.len = len;
|
|
conn->nb_buf.idx = 0;
|
|
}
|
|
if (conn->nb_buf.idx < conn->nb_buf.len) {
|
|
/* Read from conn */
|
|
ret = rw_iiod_buff(desc, conn, &conn->nb_buf, IIOD_RD);
|
|
if (NO_OS_IS_ERR_VALUE(ret))
|
|
return ret;
|
|
}
|
|
/* Write to dev */
|
|
ret = desc->ops.write_buffer(&ctx, conn->cmd_data.device,
|
|
conn->nb_buf.buf, conn->nb_buf.len);
|
|
if (NO_OS_IS_ERR_VALUE(ret))
|
|
return ret;
|
|
|
|
conn->cmd_data.bytes_count -= conn->nb_buf.len;
|
|
conn->nb_buf.len = 0;
|
|
if (conn->cmd_data.bytes_count)
|
|
return -EAGAIN;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int32_t iiod_run_cmd(struct iiod_desc *desc,
|
|
struct iiod_conn_priv *conn)
|
|
{
|
|
struct iiod_ctx ctx = IIOD_CTX(desc, conn);
|
|
struct comand_desc *data = &conn->cmd_data;
|
|
struct iiod_attr attr = {
|
|
.type = data->type,
|
|
.name = data->attr,
|
|
.channel = data->channel
|
|
};
|
|
int32_t ret;
|
|
|
|
switch (data->cmd) {
|
|
case IIOD_CMD_HELP:
|
|
case IIOD_CMD_TIMEOUT:
|
|
case IIOD_CMD_OPEN:
|
|
case IIOD_CMD_CLOSE:
|
|
case IIOD_CMD_SETTRIG:
|
|
case IIOD_CMD_SET:
|
|
if (data->cmd == IIOD_CMD_OPEN) {
|
|
conn->mask = data->mask;
|
|
if (data->cyclic)
|
|
conn->is_cyclic_buffer = true;
|
|
}
|
|
if (data->cmd == IIOD_CMD_CLOSE)
|
|
/* Set is_cyclic_buffer to false every time the device is closed */
|
|
conn->is_cyclic_buffer = false;
|
|
conn->res.val = call_op(&desc->ops, data, &ctx);
|
|
conn->res.write_val = 1;
|
|
break;
|
|
case IIOD_CMD_EXIT:
|
|
conn->res.val = 0;
|
|
conn->res.write_val = 1;
|
|
|
|
return -ENOTCONN;
|
|
case IIOD_CMD_PRINT:
|
|
conn->res.val = desc->xml_len;
|
|
conn->res.write_val = 1;
|
|
conn->res.buf.buf = desc->xml;
|
|
conn->res.buf.len = desc->xml_len;
|
|
break;
|
|
case IIOD_CMD_VERSION:
|
|
conn->res.buf.buf = IIOD_VERSION;
|
|
conn->res.buf.len = IIOD_VERSION_LEN;
|
|
break;
|
|
case IIOD_CMD_READ:
|
|
case IIOD_CMD_GETTRIG:
|
|
if (data->cmd == IIOD_CMD_READ)
|
|
ret = desc->ops.read_attr(&ctx, data->device, &attr,
|
|
conn->payload_buf,
|
|
conn->payload_buf_len);
|
|
else
|
|
ret = desc->ops.get_trigger(&ctx, data->device,
|
|
conn->payload_buf,
|
|
conn->payload_buf_len);
|
|
conn->res.val = ret;
|
|
conn->res.write_val = 1;
|
|
if (!NO_OS_IS_ERR_VALUE(ret)) {
|
|
conn->res.buf.buf = conn->payload_buf;
|
|
conn->res.buf.len = ret;
|
|
}
|
|
break;
|
|
case IIOD_CMD_WRITE:
|
|
conn->payload_buf[data->bytes_count] = '\0';
|
|
ret = desc->ops.write_attr(&ctx, data->device, &attr,
|
|
conn->payload_buf,
|
|
data->bytes_count);
|
|
conn->nb_buf.len = 0;
|
|
conn->res.val = ret;
|
|
conn->res.write_val = 1;
|
|
break;
|
|
case IIOD_CMD_READBUF:
|
|
conn->res.write_val = 1;
|
|
ret = desc->ops.refill_buffer(&ctx, data->device);
|
|
if (NO_OS_IS_ERR_VALUE(ret)) {
|
|
conn->res.val = ret;
|
|
break;
|
|
}
|
|
conn->res.val = data->bytes_count;
|
|
ret = snprintf(conn->buf_mask, 10, "%08"PRIx32, conn->mask);
|
|
conn->res.buf.buf = conn->buf_mask;
|
|
conn->res.buf.len = ret;
|
|
break;
|
|
case IIOD_CMD_WRITEBUF:
|
|
conn->res.val = data->bytes_count;
|
|
conn->res.write_val = 1;
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int32_t iiod_read_line(struct iiod_desc *desc,
|
|
struct iiod_conn_priv *conn)
|
|
{
|
|
struct iiod_ctx ctx = {
|
|
.instance = desc->app_instance,
|
|
.conn = conn->conn
|
|
};
|
|
int32_t ret;
|
|
char *ch;
|
|
|
|
while (conn->parser_idx < IIOD_PARSER_MAX_BUF_SIZE - 1) {
|
|
ch = conn->parser_buf + conn->parser_idx;
|
|
ret = desc->ops.recv(&ctx, (uint8_t *)ch, 1);
|
|
if (ret == -EAGAIN || ret == 0)
|
|
return -EAGAIN;
|
|
|
|
if (NO_OS_IS_ERR_VALUE(ret))
|
|
goto end;
|
|
|
|
if (conn->parser_idx == 0 && (*ch == '\n' || *ch == '\r'))
|
|
continue ;
|
|
|
|
++conn->parser_idx;
|
|
if (*ch == '\n') {
|
|
conn->parser_buf[conn->parser_idx] = '\0';
|
|
ret = 0;
|
|
goto end;
|
|
}
|
|
}
|
|
|
|
ret = -EIO;
|
|
end:
|
|
conn->parser_idx = 0;
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* Function will return SUCCESS when a state was processed.
|
|
* If a state is still in processing state, it will return -EAGAIN.
|
|
* If other error occur. E.g. Connection errors, they are returned and
|
|
* the connection must be cleaned up.
|
|
*/
|
|
static int32_t iiod_run_state(struct iiod_desc *desc,
|
|
struct iiod_conn_priv *conn)
|
|
{
|
|
struct iiod_ctx ctx = {
|
|
.instance = desc->app_instance,
|
|
.conn = conn->conn
|
|
};
|
|
int32_t ret;
|
|
|
|
switch (conn->state) {
|
|
case IIOD_READING_LINE:
|
|
/* Read input data until \n. I/O Calls */
|
|
ret = iiod_read_line(desc, conn);
|
|
if (NO_OS_IS_ERR_VALUE(ret))
|
|
return ret;
|
|
|
|
/* Fill struct comand_desc with data from line. No I/O */
|
|
ret = iiod_parse_line(conn->parser_buf, &conn->cmd_data,
|
|
&conn->strtok_ctx);
|
|
if (NO_OS_IS_ERR_VALUE(ret)) {
|
|
/* Parsing line failed */
|
|
conn->res.write_val = 1;
|
|
conn->res.val = ret;
|
|
conn->state = IIOD_WRITING_CMD_RESULT;
|
|
} else if (conn->cmd_data.cmd == IIOD_CMD_WRITE) {
|
|
/* Special case. Attribute needs to be read */
|
|
conn->nb_buf.buf = conn->payload_buf;
|
|
conn->nb_buf.len = conn->cmd_data.bytes_count;
|
|
conn->nb_buf.idx = 0;
|
|
conn->state = IIOD_READING_WRITE_DATA;
|
|
} else {
|
|
conn->state = IIOD_RUNNING_CMD;
|
|
}
|
|
|
|
return 0;
|
|
case IIOD_RUNNING_CMD:
|
|
/* Execute or call necessary ops depending on cmd. No I/O */
|
|
ret = iiod_run_cmd(desc, conn);
|
|
if (NO_OS_IS_ERR_VALUE(ret))
|
|
return ret;
|
|
|
|
conn->state = IIOD_WRITING_CMD_RESULT;
|
|
|
|
return 0;
|
|
case IIOD_WRITING_CMD_RESULT:
|
|
/* Write result or the length of data to be sent*/
|
|
if (conn->res.write_val) {
|
|
if (conn->nb_buf.len == 0) {
|
|
conn->nb_buf.buf = conn->parser_buf;
|
|
ret = sprintf(conn->nb_buf.buf, "%"PRIi32,
|
|
conn->res.val);
|
|
conn->nb_buf.len = ret;
|
|
conn->nb_buf.idx = 0;
|
|
}
|
|
/* Non-blocking. Will enter here until val is sent */
|
|
if (conn->nb_buf.idx < conn->nb_buf.len) {
|
|
ret = rw_iiod_buff(desc, conn, &conn->nb_buf,
|
|
IIOD_WR | IIOD_ENDL);
|
|
if (NO_OS_IS_ERR_VALUE(ret))
|
|
return ret;
|
|
}
|
|
}
|
|
/* Send buf from result. Non blocking */
|
|
if (conn->res.buf.buf &&
|
|
conn->res.buf.idx < conn->res.buf.len) {
|
|
ret = rw_iiod_buff(desc, conn, &conn->res.buf,
|
|
IIOD_WR | IIOD_ENDL);
|
|
if (NO_OS_IS_ERR_VALUE(ret))
|
|
return ret;
|
|
}
|
|
|
|
if (conn->cmd_data.cmd != IIOD_CMD_READBUF &&
|
|
conn->cmd_data.cmd != IIOD_CMD_WRITEBUF) {
|
|
if (conn->is_cyclic_buffer && conn->cmd_data.cmd != IIOD_CMD_OPEN)
|
|
conn->state = IIOD_PUSH_CYCLIC_BUFFER;
|
|
else
|
|
conn->state = IIOD_LINE_DONE;
|
|
} else {
|
|
/* Preapre for IIOD_RW_BUF state */
|
|
memset(&conn->nb_buf, 0, sizeof(conn->nb_buf));
|
|
conn->state = IIOD_RW_BUF;
|
|
}
|
|
|
|
return 0;
|
|
case IIOD_RW_BUF:
|
|
/* IIOD_CMD_READBUF and IIOD_CMD_WRITEBUF special case */
|
|
/* Non blocking read/write until all data is processed */
|
|
if (conn->cmd_data.cmd == IIOD_CMD_READBUF)
|
|
ret = do_read_buff(desc, conn);
|
|
else {
|
|
ret = do_write_buff(desc, conn);
|
|
if (ret == 0) {
|
|
conn->res.write_val = 1;
|
|
ret = desc->ops.push_buffer(&ctx,
|
|
conn->cmd_data.device);
|
|
if (NO_OS_IS_ERR_VALUE(ret)) {
|
|
conn->res.val = ret;
|
|
conn->state = IIOD_LINE_DONE;
|
|
|
|
return 0;
|
|
}
|
|
memset(&conn->res.buf, 0, sizeof(conn->res.buf));
|
|
conn->res.val = conn->cmd_data.bytes_count;
|
|
conn->cmd_data.cmd = IIOD_CMD_PRINT;
|
|
conn->state = IIOD_WRITING_CMD_RESULT;
|
|
|
|
return 0;
|
|
}
|
|
}
|
|
if (NO_OS_IS_ERR_VALUE(ret))
|
|
return ret;
|
|
|
|
conn->state = IIOD_LINE_DONE;
|
|
|
|
return 0;
|
|
case IIOD_READING_WRITE_DATA:
|
|
/* Read attribute */
|
|
ret = rw_iiod_buff(desc, conn, &conn->nb_buf, IIOD_RD);
|
|
if (NO_OS_IS_ERR_VALUE(ret))
|
|
return ret;
|
|
|
|
conn->state = IIOD_RUNNING_CMD;
|
|
|
|
return 0;
|
|
case IIOD_PUSH_CYCLIC_BUFFER:
|
|
/* Push puffer to IIO application */
|
|
ret = desc->ops.push_buffer(&ctx,
|
|
conn->cmd_data.device);
|
|
/* If an error was encountered, close connection */
|
|
if (NO_OS_IS_ERR_VALUE(ret)) {
|
|
conn->res.val = ret;
|
|
desc->ops.close(&ctx, conn->cmd_data.device);
|
|
conn->state = IIOD_LINE_DONE;
|
|
conn->is_cyclic_buffer = false;
|
|
return 0;
|
|
}
|
|
|
|
/* Read data from the client to verify whether a close command has been sent */
|
|
ret = iiod_read_line(desc, conn);
|
|
if (NO_OS_IS_ERR_VALUE(ret))
|
|
return 0;
|
|
|
|
/* Fill struct comand_desc with data from line */
|
|
ret = iiod_parse_line(conn->parser_buf, &conn->cmd_data,
|
|
&conn->strtok_ctx);
|
|
if (!NO_OS_IS_ERR_VALUE(ret) && conn->cmd_data.cmd == IIOD_CMD_CLOSE) {
|
|
/* Exit this state only if a close command is received
|
|
All other commands will be ignored.
|
|
*/
|
|
conn->nb_buf.len = 0;
|
|
conn->state = IIOD_RUNNING_CMD;
|
|
conn->is_cyclic_buffer = false;
|
|
}
|
|
return 0;
|
|
|
|
default:
|
|
/* Should never get here */
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
|
|
int32_t iiod_conn_step(struct iiod_desc *desc, uint32_t conn_id)
|
|
{
|
|
struct iiod_conn_priv *conn;
|
|
int32_t ret;
|
|
|
|
if (!desc || conn_id > IIOD_MAX_CONNECTIONS ||
|
|
!desc->conns[conn_id].used)
|
|
return -EINVAL;
|
|
|
|
conn = &desc->conns[conn_id];
|
|
do {
|
|
ret = iiod_run_state(desc, conn);
|
|
if (ret == -EAGAIN)
|
|
return ret;
|
|
if (NO_OS_IS_ERR_VALUE(ret) || conn->state == IIOD_LINE_DONE)
|
|
break;
|
|
//The loop will continue because the state was changed.
|
|
} while (true);
|
|
|
|
conn_clean_state(conn);
|
|
|
|
return ret;
|
|
}
|