| /* |
| * libiio - Library for interfacing industrial I/O (IIO) devices |
| * |
| * Copyright (C) 2014-2016 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 "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; |
| } |