summaryrefslogtreecommitdiff
path: root/src/serial.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/serial.c')
-rw-r--r--src/serial.c535
1 files changed, 535 insertions, 0 deletions
diff --git a/src/serial.c b/src/serial.c
new file mode 100644
index 0000000..af424a2
--- /dev/null
+++ b/src/serial.c
@@ -0,0 +1,535 @@
+/*
+ * libiio - Library for interfacing industrial I/O (IIO) devices
+ *
+ * Copyright (C) 2014-2016 Analog Devices, Inc.
+ * Author: Paul Cercueil <paul.cercueil@analog.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * */
+
+#include "debug.h"
+#include "iio-private.h"
+#include "iio-lock.h"
+#include "iiod-client.h"
+
+#include <errno.h>
+#include <libserialport.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#define DEFAULT_TIMEOUT_MS 1000
+
+struct iio_context_pdata {
+ struct sp_port *port;
+ struct iio_mutex *lock;
+ struct iiod_client *iiod_client;
+
+ unsigned int timeout_ms;
+};
+
+struct iio_device_pdata {
+ bool opened;
+};
+
+static inline int libserialport_to_errno(enum sp_return ret)
+{
+ switch (ret) {
+ case SP_ERR_ARG:
+ return -EINVAL;
+ case SP_ERR_FAIL:
+ return -sp_last_error_code();
+ case SP_ERR_MEM:
+ return -ENOMEM;
+ case SP_ERR_SUPP:
+ return -ENOSYS;
+ default:
+ return (int) ret;
+ }
+}
+
+static int serial_get_version(const struct iio_context *ctx,
+ unsigned int *major, unsigned int *minor, char git_tag[8])
+{
+ struct iio_context_pdata *pdata = ctx->pdata;
+
+ return iiod_client_get_version(pdata->iiod_client, NULL,
+ major, minor, git_tag);
+}
+
+static int serial_open(const struct iio_device *dev,
+ size_t samples_count, bool cyclic)
+{
+ const struct iio_context *ctx = iio_device_get_context(dev);
+ struct iio_context_pdata *ctx_pdata = ctx->pdata;
+ struct iio_device_pdata *pdata = dev->pdata;
+ int ret = -EBUSY;
+
+ iio_mutex_lock(ctx_pdata->lock);
+ if (pdata->opened)
+ goto out_unlock;
+
+ ret = iiod_client_open_unlocked(ctx_pdata->iiod_client, NULL,
+ dev, samples_count, cyclic);
+
+ pdata->opened = !ret;
+
+out_unlock:
+ iio_mutex_unlock(ctx_pdata->lock);
+ return ret;
+}
+
+static int serial_close(const struct iio_device *dev)
+{
+ const struct iio_context *ctx = iio_device_get_context(dev);
+ struct iio_context_pdata *ctx_pdata = ctx->pdata;
+ struct iio_device_pdata *pdata = dev->pdata;
+ int ret = -EBADF;
+
+ iio_mutex_lock(ctx_pdata->lock);
+ if (!pdata->opened)
+ goto out_unlock;
+
+ ret = iiod_client_close_unlocked(ctx_pdata->iiod_client, NULL, dev);
+ pdata->opened = false;
+
+out_unlock:
+ iio_mutex_unlock(ctx_pdata->lock);
+ return ret;
+}
+
+static ssize_t serial_read(const struct iio_device *dev, void *dst, size_t len,
+ uint32_t *mask, size_t words)
+{
+ const struct iio_context *ctx = iio_device_get_context(dev);
+ struct iio_context_pdata *pdata = ctx->pdata;
+ ssize_t ret;
+
+ iio_mutex_lock(pdata->lock);
+ ret = iiod_client_read_unlocked(pdata->iiod_client, NULL,
+ dev, dst, len, mask, words);
+ iio_mutex_unlock(pdata->lock);
+
+ return ret;
+}
+
+static ssize_t serial_write(const struct iio_device *dev,
+ const void *src, size_t len)
+{
+ const struct iio_context *ctx = iio_device_get_context(dev);
+ struct iio_context_pdata *pdata = ctx->pdata;
+ ssize_t ret;
+
+ iio_mutex_lock(pdata->lock);
+ ret = iiod_client_write_unlocked(pdata->iiod_client, NULL, dev, src, len);
+ iio_mutex_unlock(pdata->lock);
+
+ return ret;
+}
+
+static ssize_t serial_read_dev_attr(const struct iio_device *dev,
+ const char *attr, char *dst, size_t len, enum iio_attr_type type)
+{
+ const struct iio_context *ctx = iio_device_get_context(dev);
+ struct iio_context_pdata *pdata = ctx->pdata;
+
+ return iiod_client_read_attr(pdata->iiod_client, NULL,
+ dev, NULL, attr, dst, len, type);
+}
+
+static ssize_t serial_write_dev_attr(const struct iio_device *dev,
+ const char *attr, const char *src, size_t len, enum iio_attr_type type)
+{
+ const struct iio_context *ctx = iio_device_get_context(dev);
+ struct iio_context_pdata *pdata = ctx->pdata;
+
+ return iiod_client_write_attr(pdata->iiod_client, NULL,
+ dev, NULL, attr, src, len, type);
+}
+
+static ssize_t serial_read_chn_attr(const struct iio_channel *chn,
+ const char *attr, char *dst, size_t len)
+{
+ const struct iio_device *dev = iio_channel_get_device(chn);
+ const struct iio_context *ctx = iio_device_get_context(dev);
+ struct iio_context_pdata *pdata = ctx->pdata;
+
+ return iiod_client_read_attr(pdata->iiod_client, NULL,
+ chn->dev, chn, attr, dst, len, false);
+}
+
+static ssize_t serial_write_chn_attr(const struct iio_channel *chn,
+ const char *attr, const char *src, size_t len)
+{
+ const struct iio_device *dev = iio_channel_get_device(chn);
+ const struct iio_context *ctx = iio_device_get_context(dev);
+ struct iio_context_pdata *pdata = ctx->pdata;
+
+ return iiod_client_write_attr(pdata->iiod_client, NULL,
+ dev, chn, attr, src, len, false);
+}
+
+static int serial_set_kernel_buffers_count(const struct iio_device *dev,
+ unsigned int nb_blocks)
+{
+ const struct iio_context *ctx = iio_device_get_context(dev);
+ struct iio_context_pdata *pdata = ctx->pdata;
+
+ return iiod_client_set_kernel_buffers_count(pdata->iiod_client, NULL,
+ dev, nb_blocks);
+}
+
+static ssize_t serial_write_data(struct iio_context_pdata *pdata,
+ void *io_data, const char *data, size_t len)
+{
+ ssize_t ret = (ssize_t) libserialport_to_errno(sp_blocking_write(
+ pdata->port, data, len, pdata->timeout_ms));
+
+ DEBUG("Write returned %li: %s\n", (long) ret, data);
+ return ret;
+}
+
+static ssize_t serial_read_data(struct iio_context_pdata *pdata,
+ void *io_data, char *buf, size_t len)
+{
+ ssize_t ret = (ssize_t) libserialport_to_errno(sp_blocking_read_next(
+ pdata->port, buf, len, pdata->timeout_ms));
+
+ DEBUG("Read returned %li: %.*s\n", (long) ret, (int) ret, buf);
+ return ret;
+}
+
+static ssize_t serial_read_line(struct iio_context_pdata *pdata,
+ void *io_data, char *buf, size_t len)
+{
+ size_t i;
+ bool found = false;
+ int ret;
+
+ DEBUG("Readline size 0x%lx\n", (unsigned long) len);
+
+ for (i = 0; i < len - 1; i++) {
+ ret = libserialport_to_errno(sp_blocking_read_next(
+ pdata->port, &buf[i], 1,
+ pdata->timeout_ms));
+ if (ret < 0) {
+ ERROR("sp_blocking_read_next returned %i\n", ret);
+ return (ssize_t) ret;
+ }
+
+ DEBUG("Character: %c\n", buf[i]);
+
+ if (buf[i] != '\n')
+ found = true;
+ else if (found)
+ break;
+ }
+
+ /* No \n found? Just garbage data */
+ if (!found || i == len - 1)
+ return -EIO;
+
+ return (ssize_t) i + 1;
+}
+
+static void serial_shutdown(struct iio_context *ctx)
+{
+ struct iio_context_pdata *ctx_pdata = ctx->pdata;
+ unsigned int i;
+
+ iiod_client_destroy(ctx_pdata->iiod_client);
+ iio_mutex_destroy(ctx_pdata->lock);
+ sp_close(ctx_pdata->port);
+ sp_free_port(ctx_pdata->port);
+
+ for (i = 0; i < iio_context_get_devices_count(ctx); i++) {
+ const struct iio_device *dev = iio_context_get_device(ctx, i);
+ struct iio_device_pdata *pdata = dev->pdata;
+
+ free(pdata);
+ }
+
+ free(ctx_pdata);
+}
+
+static int serial_set_timeout(struct iio_context *ctx, unsigned int timeout)
+{
+ ctx->pdata->timeout_ms = timeout;
+ return 0;
+}
+
+static const struct iio_backend_ops serial_ops = {
+ .get_version = serial_get_version,
+ .open = serial_open,
+ .close = serial_close,
+ .read = serial_read,
+ .write = serial_write,
+ .read_device_attr = serial_read_dev_attr,
+ .write_device_attr = serial_write_dev_attr,
+ .read_channel_attr = serial_read_chn_attr,
+ .write_channel_attr = serial_write_chn_attr,
+ .set_kernel_buffers_count = serial_set_kernel_buffers_count,
+ .shutdown = serial_shutdown,
+ .set_timeout = serial_set_timeout,
+};
+
+static const struct iiod_client_ops serial_iiod_client_ops = {
+ .write = serial_write_data,
+ .read = serial_read_data,
+ .read_line = serial_read_line,
+};
+
+static int apply_settings(struct sp_port *port, unsigned int baud_rate,
+ unsigned int bits, unsigned int stop_bits,
+ enum sp_parity parity, enum sp_flowcontrol flow)
+{
+ int ret;
+
+ ret = libserialport_to_errno(sp_set_baudrate(port, (int) baud_rate));
+ if (ret)
+ return ret;
+
+ ret = libserialport_to_errno(sp_set_bits(port, (int) bits));
+ if (ret)
+ return ret;
+
+ ret = libserialport_to_errno(sp_set_stopbits(port, (int) stop_bits));
+ if (ret)
+ return ret;
+
+ ret = libserialport_to_errno(sp_set_parity(port, parity));
+ if (ret)
+ return ret;
+
+ return libserialport_to_errno(sp_set_flowcontrol(port, flow));
+}
+
+static struct iio_context * serial_create_context(const char *port_name,
+ unsigned int baud_rate, unsigned int bits,
+ enum sp_parity parity, enum sp_flowcontrol flow)
+{
+ struct sp_port *port;
+ struct iio_context_pdata *pdata;
+ struct iio_context *ctx;
+ char *name, *desc, *description;
+ size_t desc_len;
+ unsigned int i;
+ int ret;
+
+ ret = libserialport_to_errno(sp_get_port_by_name(port_name, &port));
+ if (ret) {
+ errno = -ret;
+ return NULL;
+ }
+
+ ret = libserialport_to_errno(sp_open(port, SP_MODE_READ_WRITE));
+ if (ret) {
+ errno = -ret;
+ goto err_free_port;
+ }
+
+ ret = apply_settings(port, baud_rate, bits, 1, parity, flow);
+ if (ret) {
+ errno = -ret;
+ goto err_close_port;
+ }
+
+ /* Empty the buffers */
+ sp_flush(port, SP_BUF_BOTH);
+
+ name = sp_get_port_name(port);
+ desc = sp_get_port_description(port);
+
+ desc_len = sizeof(": \0") + strlen(name) + strlen(desc);
+ description = malloc(desc_len);
+ if (!description) {
+ errno = ENOMEM;
+ goto err_close_port;
+ }
+
+ iio_snprintf(description, desc_len, "%s: %s", name, desc);
+
+ pdata = zalloc(sizeof(*pdata));
+ if (!pdata) {
+ errno = ENOMEM;
+ goto err_free_description;
+ }
+
+ pdata->port = port;
+ pdata->timeout_ms = DEFAULT_TIMEOUT_MS;
+
+ pdata->lock = iio_mutex_create();
+ if (!pdata->lock) {
+ errno = ENOMEM;
+ goto err_free_pdata;
+ }
+
+ pdata->iiod_client = iiod_client_new(pdata, pdata->lock,
+ &serial_iiod_client_ops);
+ if (!pdata->iiod_client)
+ goto err_destroy_mutex;
+
+ ctx = iiod_client_create_context(pdata->iiod_client, NULL);
+ if (!ctx)
+ goto err_destroy_iiod_client;
+
+ ctx->name = "serial";
+ ctx->ops = &serial_ops;
+ ctx->pdata = pdata;
+ ctx->description = description;
+
+ for (i = 0; i < iio_context_get_devices_count(ctx); i++) {
+ struct iio_device *dev = iio_context_get_device(ctx, i);
+
+ dev->pdata = zalloc(sizeof(*dev->pdata));
+ if (!dev->pdata) {
+ ret = -ENOMEM;
+ goto err_context_destroy;
+ }
+ }
+
+ return ctx;
+
+err_context_destroy:
+ iio_context_destroy(ctx);
+ errno = -ret;
+ return NULL;
+
+err_destroy_iiod_client:
+ iiod_client_destroy(pdata->iiod_client);
+err_destroy_mutex:
+ iio_mutex_destroy(pdata->lock);
+err_free_pdata:
+ free(pdata);
+err_free_description:
+ free(description);
+err_close_port:
+ sp_close(port);
+err_free_port:
+ sp_free_port(port);
+ return NULL;
+}
+
+static int serial_parse_params(const char *params,
+ unsigned int *baud_rate, unsigned int *bits,
+ enum sp_parity *parity, enum sp_flowcontrol *flow)
+{
+ char *end;
+
+ *baud_rate = strtoul(params, &end, 10);
+ if (params == end)
+ return -EINVAL;
+
+ switch (*end) {
+ case '\0':
+ /* Default settings */
+ *bits = 8;
+ *parity = SP_PARITY_NONE;
+ *flow = SP_FLOWCONTROL_NONE;
+ return 0;
+ case 'n':
+ *parity = SP_PARITY_NONE;
+ break;
+ case 'o':
+ *parity = SP_PARITY_ODD;
+ break;
+ case 'e':
+ *parity = SP_PARITY_EVEN;
+ break;
+ case 'm':
+ *parity = SP_PARITY_MARK;
+ break;
+ case 's':
+ *parity = SP_PARITY_SPACE;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ params = (const char *)((uintptr_t) end + 1);
+
+ if (!*params) {
+ *bits = 8;
+ *flow = SP_FLOWCONTROL_NONE;
+ return 0;
+ }
+
+ *bits = strtoul(params, &end, 10);
+ if (params == end)
+ return -EINVAL;
+
+ switch (*end) {
+ case '\0':
+ *flow = SP_FLOWCONTROL_NONE;
+ return 0;
+ case 'x':
+ *flow = SP_FLOWCONTROL_XONXOFF;
+ break;
+ case 'r':
+ *flow = SP_FLOWCONTROL_RTSCTS;
+ break;
+ case 'd':
+ *flow = SP_FLOWCONTROL_DTRDSR;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* We should have a '\0' after the flow character */
+ if (end[1])
+ return -EINVAL;
+ else
+ return 0;
+}
+
+struct iio_context * serial_create_context_from_uri(const char *uri)
+{
+ struct iio_context *ctx = NULL;
+ char *comma, *uri_dup;
+ unsigned int baud_rate, bits;
+ enum sp_parity parity;
+ enum sp_flowcontrol flow;
+ int ret;
+
+ if (strncmp(uri, "serial:", sizeof("serial:") - 1) != 0)
+ goto err_bad_uri;
+
+ uri_dup = iio_strdup((const char *)
+ ((uintptr_t) uri + sizeof("serial:") - 1));
+ if (!uri_dup) {
+ errno = ENOMEM;
+ return NULL;
+ }
+
+ comma = strchr(uri_dup, ',');
+ if (!comma)
+ goto err_free_dup;
+
+ *comma = '\0';
+
+ ret = serial_parse_params((char *)((uintptr_t) comma + 1),
+ &baud_rate, &bits, &parity, &flow);
+ if (ret)
+ goto err_free_dup;
+
+ ctx = serial_create_context(uri_dup, baud_rate, bits, parity, flow);
+
+ free(uri_dup);
+ return ctx;
+
+err_free_dup:
+ free(uri_dup);
+err_bad_uri:
+ ERROR("Bad URI: \'%s\'\n", uri);
+ errno = EINVAL;
+ return NULL;
+}