blob: ab6fe383a34d617a8e8234846c58ddecdb0af7f0 [file] [log] [blame]
/*
* libiio - Library for interfacing industrial I/O (IIO) devices
*
* Copyright (C) 2014-2015 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 "iio-config.h"
#include "iio-private.h"
#include "iio-lock.h"
#include "iiod-client.h"
#include <errno.h>
#include <fcntl.h>
#include <stdbool.h>
#include <string.h>
#include <sys/types.h>
#include <time.h>
#ifdef _WIN32
#include <winsock2.h>
#include <ws2tcpip.h>
#define close(s) closesocket(s)
/* winsock2.h defines ERROR, we don't want that */
#undef ERROR
#else /* _WIN32 */
#include <arpa/inet.h>
#include <netdb.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <net/if.h>
#include <sys/mman.h>
#include <poll.h>
#include <sys/socket.h>
#include <unistd.h>
#endif /* _WIN32 */
#ifdef HAVE_AVAHI
#include <avahi-client/client.h>
#include <avahi-common/error.h>
#include <avahi-client/lookup.h>
#include <avahi-common/simple-watch.h>
#endif
#include "debug.h"
#define DEFAULT_TIMEOUT_MS 5000
#define _STRINGIFY(x) #x
#define STRINGIFY(x) _STRINGIFY(x)
#define IIOD_PORT 30431
#define IIOD_PORT_STR STRINGIFY(IIOD_PORT)
struct iio_network_io_context {
int fd;
/* Only buffer IO contexts can be cancelled. */
bool cancellable;
bool cancelled;
#if defined(_WIN32)
WSAEVENT events[2];
#elif defined(WITH_NETWORK_EVENTFD)
int cancel_fd[1]; /* eventfd */
#else
int cancel_fd[2]; /* pipe */
#endif
unsigned int timeout_ms;
};
struct iio_context_pdata {
struct iio_network_io_context io_ctx;
struct addrinfo *addrinfo;
struct iio_mutex *lock;
struct iiod_client *iiod_client;
bool msg_trunc_supported;
};
struct iio_device_pdata {
struct iio_network_io_context io_ctx;
#ifdef WITH_NETWORK_GET_BUFFER
int memfd;
void *mmap_addr;
size_t mmap_len;
#endif
bool wait_for_err_code, is_cyclic, is_tx;
struct iio_mutex *lock;
};
#ifdef _WIN32
static int set_blocking_mode(int s, bool blocking)
{
unsigned long nonblock;
int ret;
nonblock = blocking ? 0 : 1;
ret = ioctlsocket(s, FIONBIO, &nonblock);
if (ret == SOCKET_ERROR) {
ret = -WSAGetLastError();
return ret;
}
return 0;
}
static int setup_cancel(struct iio_network_io_context *io_ctx)
{
io_ctx->events[0] = WSACreateEvent();
if (io_ctx->events[0] == WSA_INVALID_EVENT)
return -ENOMEM; /* Pretty much the only error that can happen */
io_ctx->events[1] = WSACreateEvent();
if (io_ctx->events[1] == WSA_INVALID_EVENT) {
WSACloseEvent(io_ctx->events[0]);
return -ENOMEM;
}
return 0;
}
static void cleanup_cancel(struct iio_network_io_context *io_ctx)
{
WSACloseEvent(io_ctx->events[0]);
WSACloseEvent(io_ctx->events[1]);
}
static void do_cancel(struct iio_network_io_context *io_ctx)
{
WSASetEvent(io_ctx->events[1]);
}
static int wait_cancellable(struct iio_network_io_context *io_ctx, bool read)
{
long wsa_events = FD_CLOSE;
DWORD ret;
if (!io_ctx->cancellable)
return 0;
if (read)
wsa_events |= FD_READ;
else
wsa_events |= FD_WRITE;
WSAEventSelect(io_ctx->fd, NULL, 0);
WSAResetEvent(io_ctx->events[0]);
WSAEventSelect(io_ctx->fd, io_ctx->events[0], wsa_events);
ret = WSAWaitForMultipleEvents(2, io_ctx->events, FALSE,
WSA_INFINITE, FALSE);
if (ret == WSA_WAIT_EVENT_0 + 1)
return -EBADF;
return 0;
}
static int network_get_error(void)
{
return -WSAGetLastError();
}
static bool network_should_retry(int err)
{
return err == -WSAEWOULDBLOCK || err == -WSAETIMEDOUT;
}
static bool network_is_interrupted(int err)
{
return false;
}
static bool network_connect_in_progress(int err)
{
return err == -WSAEWOULDBLOCK;
}
#define NETWORK_ERR_TIMEOUT WSAETIMEDOUT
#else
static int set_blocking_mode(int fd, bool blocking)
{
int ret = fcntl(fd, F_GETFL, 0);
if (ret < 0)
return -errno;
if (blocking)
ret &= ~O_NONBLOCK;
else
ret |= O_NONBLOCK;
ret = fcntl(fd, F_SETFL, ret);
return ret < 0 ? -errno : 0;
}
#include <poll.h>
#if defined(WITH_NETWORK_EVENTFD)
#include <sys/eventfd.h>
static int create_cancel_fd(struct iio_network_io_context *io_ctx)
{
io_ctx->cancel_fd[0] = eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK);
if (io_ctx->cancel_fd[0] < 0)
return -errno;
return 0;
}
static void cleanup_cancel(struct iio_network_io_context *io_ctx)
{
close(io_ctx->cancel_fd[0]);
}
#define CANCEL_WR_FD 0
#else
static int create_cancel_fd(struct iio_network_io_context *io_ctx)
{
int ret;
#ifdef HAS_PIPE2
ret = pipe2(io_ctx->cancel_fd, O_CLOEXEC | O_NONBLOCK);
if (ret < 0 && errno != ENOSYS) /* If ENOSYS try pipe() */
return -errno;
#endif
ret = pipe(io_ctx->cancel_fd);
if (ret < 0)
return -errno;
ret = set_blocking_mode(io_ctx->cancel_fd[0], false);
if (ret < 0)
goto err_close;
ret = set_blocking_mode(io_ctx->cancel_fd[1], false);
if (ret < 0)
goto err_close;
return 0;
err_close:
close(io_ctx->cancel_fd[0]);
close(io_ctx->cancel_fd[1]);
return ret;
}
static void cleanup_cancel(struct iio_network_io_context *io_ctx)
{
close(io_ctx->cancel_fd[0]);
close(io_ctx->cancel_fd[1]);
}
#define CANCEL_WR_FD 1
#endif
static int setup_cancel(struct iio_network_io_context *io_ctx)
{
int ret;
ret = set_blocking_mode(io_ctx->fd, false);
if (ret)
return ret;
return create_cancel_fd(io_ctx);
}
static void do_cancel(struct iio_network_io_context *io_ctx)
{
uint64_t event = 1;
int ret;
ret = write(io_ctx->cancel_fd[CANCEL_WR_FD], &event, sizeof(event));
if (ret == -1) {
/* If this happens something went very seriously wrong */
char err_str[1024];
iio_strerror(errno, err_str, sizeof(err_str));
ERROR("Unable to signal cancellation event: %s\n", err_str);
}
}
static int wait_cancellable(struct iio_network_io_context *io_ctx, bool read)
{
struct pollfd pfd[2];
int ret;
if (!io_ctx->cancellable)
return 0;
memset(pfd, 0, sizeof(pfd));
pfd[0].fd = io_ctx->fd;
if (read)
pfd[0].events = POLLIN;
else
pfd[0].events = POLLOUT;
pfd[1].fd = io_ctx->cancel_fd[0];
pfd[1].events = POLLIN;
do {
int timeout_ms;
if (io_ctx->timeout_ms > 0)
timeout_ms = (int) io_ctx->timeout_ms;
else
timeout_ms = -1;
do {
ret = poll(pfd, 2, timeout_ms);
} while (ret == -1 && errno == EINTR);
if (ret == -1)
return -errno;
if (!ret)
return -EPIPE;
if (pfd[1].revents & POLLIN)
return -EBADF;
} while (!(pfd[0].revents & (pfd[0].events | POLLERR | POLLHUP)));
return 0;
}
static int network_get_error(void)
{
return -errno;
}
static bool network_should_retry(int err)
{
return err == -EAGAIN;
}
static bool network_is_interrupted(int err)
{
return err == -EINTR;
}
static bool network_connect_in_progress(int err)
{
return err == -EINPROGRESS;
}
#define NETWORK_ERR_TIMEOUT ETIMEDOUT
#endif
#ifdef HAVE_AVAHI
struct avahi_discovery_data {
AvahiSimplePoll *poll;
AvahiAddress *address;
uint16_t *port;
bool found, resolved;
};
static void __avahi_resolver_cb(AvahiServiceResolver *resolver,
__notused AvahiIfIndex iface, __notused AvahiProtocol proto,
__notused AvahiResolverEvent event, __notused const char *name,
__notused const char *type, __notused const char *domain,
__notused const char *host_name, const AvahiAddress *address,
uint16_t port, __notused AvahiStringList *txt,
__notused AvahiLookupResultFlags flags, void *d)
{
struct avahi_discovery_data *ddata = (struct avahi_discovery_data *) d;
memcpy(ddata->address, address, sizeof(*address));
*ddata->port = port;
ddata->resolved = true;
avahi_service_resolver_free(resolver);
}
static void __avahi_browser_cb(AvahiServiceBrowser *browser,
AvahiIfIndex iface, AvahiProtocol proto,
AvahiBrowserEvent event, const char *name,
const char *type, const char *domain,
__notused AvahiLookupResultFlags flags, void *d)
{
struct avahi_discovery_data *ddata = (struct avahi_discovery_data *) d;
struct AvahiClient *client = avahi_service_browser_get_client(browser);
switch (event) {
default:
case AVAHI_BROWSER_NEW:
ddata->found = !!avahi_service_resolver_new(client, iface,
proto, name, type, domain,
AVAHI_PROTO_UNSPEC, 0,
__avahi_resolver_cb, d);
break;
case AVAHI_BROWSER_ALL_FOR_NOW:
if (ddata->found) {
while (!ddata->resolved) {
struct timespec ts;
ts.tv_sec = 0;
ts.tv_nsec = 4000000;
nanosleep(&ts, NULL);
}
}
/* fall-through */
case AVAHI_BROWSER_FAILURE:
avahi_simple_poll_quit(ddata->poll);
/* fall-through */
case AVAHI_BROWSER_CACHE_EXHAUSTED:
break;
}
}
static int discover_host(AvahiAddress *addr, uint16_t *port)
{
struct avahi_discovery_data ddata;
int ret = 0;
AvahiClient *client;
AvahiServiceBrowser *browser;
AvahiSimplePoll *poll = avahi_simple_poll_new();
if (!poll)
return -ENOMEM;
client = avahi_client_new(avahi_simple_poll_get(poll),
0, NULL, NULL, &ret);
if (!client) {
ERROR("Unable to start ZeroConf client :%s\n",
avahi_strerror(ret));
goto err_free_poll;
}
memset(&ddata, 0, sizeof(ddata));
ddata.poll = poll;
ddata.address = addr;
ddata.port = port;
browser = avahi_service_browser_new(client,
AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC,
"_iio._tcp", NULL, 0, __avahi_browser_cb, &ddata);
if (!browser) {
ret = avahi_client_errno(client);
ERROR("Unable to create ZeroConf browser: %s\n",
avahi_strerror(ret));
goto err_free_client;
}
DEBUG("Trying to discover host\n");
avahi_simple_poll_loop(poll);
if (!ddata.found)
ret = ENXIO;
avahi_service_browser_free(browser);
err_free_client:
avahi_client_free(client);
err_free_poll:
avahi_simple_poll_free(poll);
return -ret; /* we want a negative error code */
}
#endif /* HAVE_AVAHI */
static ssize_t network_recv(struct iio_network_io_context *io_ctx,
void *data, size_t len, int flags)
{
ssize_t ret;
int err;
while (1) {
ret = wait_cancellable(io_ctx, true);
if (ret < 0)
return ret;
ret = recv(io_ctx->fd, data, (int) len, flags);
if (ret == 0)
return -EPIPE;
else if (ret > 0)
break;
err = network_get_error();
if (network_should_retry(err)) {
if (io_ctx->cancellable)
continue;
else
return -EPIPE;
} else if (!network_is_interrupted(err)) {
return (ssize_t) err;
}
}
return ret;
}
static ssize_t network_send(struct iio_network_io_context *io_ctx,
const void *data, size_t len, int flags)
{
ssize_t ret;
int err;
while (1) {
ret = wait_cancellable(io_ctx, false);
if (ret < 0)
return ret;
ret = send(io_ctx->fd, data, (int) len, flags);
if (ret == 0)
return -EPIPE;
else if (ret > 0)
break;
err = network_get_error();
if (network_should_retry(err)) {
if (io_ctx->cancellable)
continue;
else
return -EPIPE;
} else if (!network_is_interrupted(err)) {
return (ssize_t) err;
}
}
return ret;
}
static ssize_t write_all(struct iio_network_io_context *io_ctx,
const void *src, size_t len)
{
uintptr_t ptr = (uintptr_t) src;
while (len) {
ssize_t ret = network_send(io_ctx, (const void *) ptr, len, 0);
if (ret < 0)
return ret;
ptr += ret;
len -= ret;
}
return (ssize_t)(ptr - (uintptr_t) src);
}
static ssize_t write_command(struct iio_network_io_context *io_ctx,
const char *cmd)
{
ssize_t ret;
DEBUG("Writing command: %s\n", cmd);
ret = write_all(io_ctx, cmd, strlen(cmd));
if (ret < 0) {
char buf[1024];
iio_strerror(-ret, buf, sizeof(buf));
ERROR("Unable to send command: %s\n", buf);
}
return ret;
}
static void network_cancel(const struct iio_device *dev)
{
struct iio_device_pdata *ppdata = dev->pdata;
do_cancel(&ppdata->io_ctx);
ppdata->io_ctx.cancelled = true;
}
#ifndef _WIN32
/* Use it if available */
#ifndef SOCK_CLOEXEC
#define SOCK_CLOEXEC 0
#endif
static int do_create_socket(const struct addrinfo *addrinfo)
{
int fd;
fd = socket(addrinfo->ai_family, addrinfo->ai_socktype | SOCK_CLOEXEC, 0);
if (fd < 0)
return -errno;
return fd;
}
static int set_socket_timeout(int fd, unsigned int timeout)
{
struct timeval tv;
tv.tv_sec = timeout / 1000;
tv.tv_usec = (timeout % 1000) * 1000;
if (setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv)) < 0 ||
setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO,
&tv, sizeof(tv)) < 0)
return -errno;
else
return 0;
}
#else
/* Use it if available */
#ifndef WSA_FLAG_NO_HANDLE_INHERIT
#define WSA_FLAG_NO_HANDLE_INHERIT 0
#endif
static int do_create_socket(const struct addrinfo *addrinfo)
{
SOCKET s;
s = WSASocketW(addrinfo->ai_family, addrinfo->ai_socktype, 0, NULL, 0,
WSA_FLAG_NO_HANDLE_INHERIT | WSA_FLAG_OVERLAPPED);
if (s == INVALID_SOCKET)
return -WSAGetLastError();
return (int) s;
}
static int set_socket_timeout(int fd, unsigned int timeout)
{
if (setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO,
(const char *) &timeout, sizeof(timeout)) < 0 ||
setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO,
(const char *) &timeout, sizeof(timeout)) < 0)
return -WSAGetLastError();
else
return 0;
}
#endif /* !_WIN32 */
/* The purpose of this function is to provide a version of connect()
* that does not ignore timeouts... */
static int do_connect(int fd, const struct addrinfo *addrinfo,
unsigned int timeout)
{
int ret, error;
socklen_t len;
#ifdef _WIN32
struct timeval tv;
struct timeval *ptv;
fd_set set;
#else
struct pollfd pfd;
#endif
ret = set_blocking_mode(fd, false);
if (ret < 0)
return ret;
ret = connect(fd, addrinfo->ai_addr, (int) addrinfo->ai_addrlen);
if (ret < 0) {
ret = network_get_error();
if (!network_connect_in_progress(ret))
return ret;
}
#ifdef _WIN32
FD_ZERO(&set);
FD_SET(fd, &set);
if (timeout != 0) {
tv.tv_sec = timeout / 1000;
tv.tv_usec = (timeout % 1000) * 1000;
ptv = &tv;
} else {
ptv = NULL;
}
ret = select(fd + 1, NULL, &set, &set, ptv);
#else
pfd.fd = fd;
pfd.events = POLLOUT | POLLERR;
pfd.revents = 0;
do {
ret = poll(&pfd, 1, timeout);
} while (ret == -1 && errno == EINTR);
#endif
if (ret < 0)
return network_get_error();
if (ret == 0)
return -NETWORK_ERR_TIMEOUT;
/* Verify that we don't have an error */
len = sizeof(error);
ret = getsockopt(fd, SOL_SOCKET, SO_ERROR, (char *)&error, &len);
if(ret < 0)
return network_get_error();
if (error)
return -error;
ret = set_blocking_mode(fd, true);
if (ret < 0)
return ret;
return 0;
}
static int create_socket(const struct addrinfo *addrinfo, unsigned int timeout)
{
int ret, fd, yes = 1;
fd = do_create_socket(addrinfo);
if (fd < 0)
return fd;
ret = do_connect(fd, addrinfo, timeout);
if (ret < 0) {
close(fd);
return ret;
}
set_socket_timeout(fd, timeout);
if (setsockopt(fd, IPPROTO_TCP, TCP_NODELAY,
(const char *) &yes, sizeof(yes)) < 0) {
ret = -errno;
close(fd);
return ret;
}
return fd;
}
static int network_open(const struct iio_device *dev,
size_t samples_count, bool cyclic)
{
struct iio_context_pdata *pdata = dev->ctx->pdata;
struct iio_device_pdata *ppdata = dev->pdata;
int ret = -EBUSY;
iio_mutex_lock(ppdata->lock);
if (ppdata->io_ctx.fd >= 0)
goto out_mutex_unlock;
ret = create_socket(pdata->addrinfo, DEFAULT_TIMEOUT_MS);
if (ret < 0)
goto out_mutex_unlock;
ppdata->io_ctx.fd = ret;
ppdata->io_ctx.cancelled = false;
ppdata->io_ctx.cancellable = false;
ppdata->io_ctx.timeout_ms = DEFAULT_TIMEOUT_MS;
ret = iiod_client_open_unlocked(pdata->iiod_client,
&ppdata->io_ctx, dev, samples_count, cyclic);
if (ret < 0)
goto err_close_socket;
ret = setup_cancel(&ppdata->io_ctx);
if (ret < 0)
goto err_close_socket;
set_socket_timeout(ppdata->io_ctx.fd, pdata->io_ctx.timeout_ms);
ppdata->io_ctx.timeout_ms = pdata->io_ctx.timeout_ms;
ppdata->io_ctx.cancellable = true;
ppdata->is_tx = iio_device_is_tx(dev);
ppdata->is_cyclic = cyclic;
ppdata->wait_for_err_code = false;
#ifdef WITH_NETWORK_GET_BUFFER
ppdata->mmap_len = samples_count * iio_device_get_sample_size(dev);
#endif
iio_mutex_unlock(ppdata->lock);
return 0;
err_close_socket:
close(ppdata->io_ctx.fd);
ppdata->io_ctx.fd = -1;
out_mutex_unlock:
iio_mutex_unlock(ppdata->lock);
return ret;
}
static int network_close(const struct iio_device *dev)
{
struct iio_device_pdata *pdata = dev->pdata;
int ret = -EBADF;
iio_mutex_lock(pdata->lock);
if (pdata->io_ctx.fd >= 0) {
if (!pdata->io_ctx.cancelled) {
ret = iiod_client_close_unlocked(
dev->ctx->pdata->iiod_client,
&pdata->io_ctx, dev);
write_command(&pdata->io_ctx, "\r\nEXIT\r\n");
} else {
ret = 0;
}
cleanup_cancel(&pdata->io_ctx);
close(pdata->io_ctx.fd);
pdata->io_ctx.fd = -1;
}
#ifdef WITH_NETWORK_GET_BUFFER
if (pdata->memfd >= 0)
close(pdata->memfd);
pdata->memfd = -1;
if (pdata->mmap_addr) {
munmap(pdata->mmap_addr, pdata->mmap_len);
pdata->mmap_addr = NULL;
}
#endif
iio_mutex_unlock(pdata->lock);
return ret;
}
static ssize_t network_read(const struct iio_device *dev, void *dst, size_t len,
uint32_t *mask, size_t words)
{
struct iio_device_pdata *pdata = dev->pdata;
ssize_t ret;
iio_mutex_lock(pdata->lock);
ret = iiod_client_read_unlocked(dev->ctx->pdata->iiod_client,
&pdata->io_ctx, dev, dst, len, mask, words);
iio_mutex_unlock(pdata->lock);
return ret;
}
static ssize_t network_write(const struct iio_device *dev,
const void *src, size_t len)
{
struct iio_device_pdata *pdata = dev->pdata;
ssize_t ret;
iio_mutex_lock(pdata->lock);
ret = iiod_client_write_unlocked(dev->ctx->pdata->iiod_client,
&pdata->io_ctx, dev, src, len);
iio_mutex_unlock(pdata->lock);
return ret;
}
#ifdef WITH_NETWORK_GET_BUFFER
static ssize_t read_all(struct iio_network_io_context *io_ctx,
void *dst, size_t len)
{
uintptr_t ptr = (uintptr_t) dst;
while (len) {
ssize_t ret = network_recv(io_ctx, (void *) ptr, len, 0);
if (ret < 0)
return ret;
ptr += ret;
len -= ret;
}
return (ssize_t)(ptr - (uintptr_t) dst);
}
static int read_integer(struct iio_network_io_context *io_ctx, long *val)
{
unsigned int i;
char buf[1024], *ptr;
ssize_t ret;
bool found = false;
for (i = 0; i < sizeof(buf) - 1; i++) {
ret = read_all(io_ctx, buf + i, 1);
if (ret < 0)
return (int) ret;
/* Skip the eventual first few carriage returns.
* Also stop when a dot is found (for parsing floats) */
if (buf[i] != '\n' && buf[i] != '.')
found = true;
else if (found)
break;
}
buf[i] = '\0';
ret = (ssize_t) strtol(buf, &ptr, 10);
if (ptr == buf)
return -EINVAL;
*val = (long) ret;
return 0;
}
static ssize_t network_read_mask(struct iio_network_io_context *io_ctx,
uint32_t *mask, size_t words)
{
long read_len;
ssize_t ret;
ret = read_integer(io_ctx, &read_len);
if (ret < 0)
return ret;
if (read_len > 0 && mask) {
size_t i;
char buf[9];
buf[8] = '\0';
DEBUG("Reading mask\n");
for (i = words; i > 0; i--) {
ret = read_all(io_ctx, buf, 8);
if (ret < 0)
return ret;
sscanf(buf, "%08x", &mask[i - 1]);
DEBUG("mask[%lu] = 0x%x\n",
(unsigned long)(i - 1), mask[i - 1]);
}
}
if (read_len > 0) {
char c;
ssize_t nb = read_all(io_ctx, &c, 1);
if (nb > 0 && c != '\n')
read_len = -EIO;
}
return (ssize_t) read_len;
}
static ssize_t read_error_code(struct iio_network_io_context *io_ctx)
{
/*
* The server returns two integer codes.
* The first one is returned right after the WRITEBUF command is issued,
* and corresponds to the error code returned when the server attempted
* to open the device.
* If zero, a second error code is returned, that corresponds (if positive)
* to the number of bytes written.
*
* To speed up things, we delay error reporting. We just send out the
* data without reading the error code that the server gives us, because
* the answer will take too much time. If an error occured, it will be
* reported by the next call to iio_buffer_push().
*/
unsigned int i;
long resp = 0;
for (i = 0; i < 2; i++) {
ssize_t ret = read_integer(io_ctx, &resp);
if (ret < 0)
return ret;
if (resp < 0)
return (ssize_t) resp;
}
return (ssize_t) resp;
}
static ssize_t write_rwbuf_command(const struct iio_device *dev,
const char *cmd)
{
struct iio_device_pdata *pdata = dev->pdata;
if (pdata->wait_for_err_code) {
ssize_t ret = read_error_code(&pdata->io_ctx);
pdata->wait_for_err_code = false;
if (ret < 0)
return ret;
}
return write_command(&pdata->io_ctx, cmd);
}
static ssize_t network_do_splice(struct iio_device_pdata *pdata, size_t len,
bool read)
{
int pipefd[2];
int fd_in, fd_out;
ssize_t ret, read_len = len, write_len = 0;
ret = (ssize_t) pipe2(pipefd, O_CLOEXEC);
if (ret < 0)
return -errno;
if (read) {
fd_in = pdata->io_ctx.fd;
fd_out = pdata->memfd;
} else {
fd_in = pdata->memfd;
fd_out = pdata->io_ctx.fd;
}
do {
ret = wait_cancellable(&pdata->io_ctx, read);
if (ret < 0)
goto err_close_pipe;
if (read_len) {
/*
* SPLICE_F_NONBLOCK is just here to avoid a deadlock when
* splicing from a socket. As the socket is not in
* non-blocking mode, it should never return -EAGAIN.
* TODO(pcercuei): Find why it locks...
* */
ret = splice(fd_in, NULL, pipefd[1], NULL, read_len,
SPLICE_F_MOVE | SPLICE_F_NONBLOCK);
if (!ret)
ret = -EIO;
if (ret < 0 && errno != EAGAIN) {
ret = -errno;
goto err_close_pipe;
} else if (ret > 0) {
write_len += ret;
read_len -= ret;
}
}
if (write_len) {
ret = splice(pipefd[0], NULL, fd_out, NULL, write_len,
SPLICE_F_MOVE | SPLICE_F_NONBLOCK);
if (!ret)
ret = -EIO;
if (ret < 0 && errno != EAGAIN) {
ret = -errno;
goto err_close_pipe;
} else if (ret > 0) {
write_len -= ret;
}
}
} while (write_len || read_len);
err_close_pipe:
close(pipefd[0]);
close(pipefd[1]);
return ret < 0 ? ret : len;
}
static ssize_t network_get_buffer(const struct iio_device *dev,
void **addr_ptr, size_t bytes_used,
uint32_t *mask, size_t words)
{
struct iio_device_pdata *pdata = dev->pdata;
ssize_t ret, read = 0;
int memfd;
if (pdata->is_cyclic)
return -ENOSYS;
/* We check early that the temporary file can be created, so that we can
* return -ENOSYS in case it fails, which will indicate that the
* high-speed interface is not available.
*
* O_TMPFILE -> Linux 3.11.
* TODO: use memfd_create (Linux 3.17) */
memfd = open(P_tmpdir, O_RDWR | O_TMPFILE | O_EXCL | O_CLOEXEC, S_IRWXU);
if (memfd < 0)
return -ENOSYS;
if (!addr_ptr || words != (dev->nb_channels + 31) / 32) {
close(memfd);
return -EINVAL;
}
if (pdata->mmap_addr)
munmap(pdata->mmap_addr, pdata->mmap_len);
if (pdata->mmap_addr && pdata->is_tx) {
char buf[1024];
iio_snprintf(buf, sizeof(buf), "WRITEBUF %s %lu\r\n",
dev->id, (unsigned long) bytes_used);
iio_mutex_lock(pdata->lock);
ret = write_rwbuf_command(dev, buf);
if (ret < 0)
goto err_close_memfd;
ret = network_do_splice(pdata, bytes_used, false);
if (ret < 0)
goto err_close_memfd;
pdata->wait_for_err_code = true;
iio_mutex_unlock(pdata->lock);
}
if (pdata->memfd >= 0)
close(pdata->memfd);
pdata->memfd = memfd;
ret = (ssize_t) ftruncate(pdata->memfd, pdata->mmap_len);
if (ret < 0) {
ret = -errno;
ERROR("Unable to truncate temp file: %zi\n", -ret);
return ret;
}
if (!pdata->is_tx) {
char buf[1024];
size_t len = pdata->mmap_len;
iio_snprintf(buf, sizeof(buf), "READBUF %s %lu\r\n",
dev->id, (unsigned long) len);
iio_mutex_lock(pdata->lock);
ret = write_rwbuf_command(dev, buf);
if (ret < 0)
goto err_unlock;
do {
ret = network_read_mask(&pdata->io_ctx, mask, words);
if (!ret)
break;
if (ret < 0)
goto err_unlock;
mask = NULL; /* We read the mask only once */
ret = network_do_splice(pdata, ret, true);
if (ret < 0)
goto err_unlock;
read += ret;
len -= ret;
} while (len);
iio_mutex_unlock(pdata->lock);
}
pdata->mmap_addr = mmap(NULL, pdata->mmap_len,
PROT_READ | PROT_WRITE, MAP_SHARED, pdata->memfd, 0);
if (pdata->mmap_addr == MAP_FAILED) {
pdata->mmap_addr = NULL;
ret = -errno;
ERROR("Unable to mmap: %zi\n", -ret);
return ret;
}
*addr_ptr = pdata->mmap_addr;
return read ? read : (ssize_t) bytes_used;
err_close_memfd:
close(memfd);
err_unlock:
iio_mutex_unlock(pdata->lock);
return ret;
}
#endif
static ssize_t network_read_dev_attr(const struct iio_device *dev,
const char *attr, char *dst, size_t len, enum iio_attr_type type)
{
struct iio_context_pdata *pdata = dev->ctx->pdata;
return iiod_client_read_attr(pdata->iiod_client,
&pdata->io_ctx, dev, NULL, attr, dst, len, type);
}
static ssize_t network_write_dev_attr(const struct iio_device *dev,
const char *attr, const char *src, size_t len, enum iio_attr_type type)
{
struct iio_context_pdata *pdata = dev->ctx->pdata;
return iiod_client_write_attr(pdata->iiod_client,
&pdata->io_ctx, dev, NULL, attr, src, len, type);
}
static ssize_t network_read_chn_attr(const struct iio_channel *chn,
const char *attr, char *dst, size_t len)
{
struct iio_context_pdata *pdata = chn->dev->ctx->pdata;
return iiod_client_read_attr(pdata->iiod_client,
&pdata->io_ctx, chn->dev, chn, attr, dst, len, false);
}
static ssize_t network_write_chn_attr(const struct iio_channel *chn,
const char *attr, const char *src, size_t len)
{
struct iio_context_pdata *pdata = chn->dev->ctx->pdata;
return iiod_client_write_attr(pdata->iiod_client,
&pdata->io_ctx, chn->dev, chn, attr, src, len, false);
}
static int network_get_trigger(const struct iio_device *dev,
const struct iio_device **trigger)
{
struct iio_context_pdata *pdata = dev->ctx->pdata;
return iiod_client_get_trigger(pdata->iiod_client,
&pdata->io_ctx, dev, trigger);
}
static int network_set_trigger(const struct iio_device *dev,
const struct iio_device *trigger)
{
struct iio_context_pdata *pdata = dev->ctx->pdata;
return iiod_client_set_trigger(pdata->iiod_client,
&pdata->io_ctx, dev, trigger);
}
static void network_shutdown(struct iio_context *ctx)
{
struct iio_context_pdata *pdata = ctx->pdata;
unsigned int i;
iio_mutex_lock(pdata->lock);
write_command(&pdata->io_ctx, "\r\nEXIT\r\n");
close(pdata->io_ctx.fd);
iio_mutex_unlock(pdata->lock);
for (i = 0; i < ctx->nb_devices; i++) {
struct iio_device *dev = ctx->devices[i];
struct iio_device_pdata *dpdata = dev->pdata;
if (dpdata) {
network_close(dev);
iio_mutex_destroy(dpdata->lock);
free(dpdata);
}
}
iiod_client_destroy(pdata->iiod_client);
iio_mutex_destroy(pdata->lock);
freeaddrinfo(pdata->addrinfo);
free(pdata);
}
static int network_get_version(const struct iio_context *ctx,
unsigned int *major, unsigned int *minor, char git_tag[8])
{
return iiod_client_get_version(ctx->pdata->iiod_client,
&ctx->pdata->io_ctx, major, minor, git_tag);
}
static unsigned int calculate_remote_timeout(unsigned int timeout)
{
/* XXX(pcercuei): We currently hardcode timeout / 2 for the backend used
* by the remote. Is there something better to do here? */
return timeout / 2;
}
static int network_set_timeout(struct iio_context *ctx, unsigned int timeout)
{
struct iio_context_pdata *pdata = ctx->pdata;
int ret, fd = pdata->io_ctx.fd;
ret = set_socket_timeout(fd, timeout);
if (!ret) {
unsigned int remote_timeout = calculate_remote_timeout(timeout);
ret = iiod_client_set_timeout(pdata->iiod_client,
&pdata->io_ctx, remote_timeout);
if (!ret)
pdata->io_ctx.timeout_ms = timeout;
}
if (ret < 0) {
char buf[1024];
iio_strerror(-ret, buf, sizeof(buf));
WARNING("Unable to set R/W timeout: %s\n", buf);
}
return ret;
}
static int network_set_kernel_buffers_count(const struct iio_device *dev,
unsigned int nb_blocks)
{
struct iio_context_pdata *pdata = dev->ctx->pdata;
return iiod_client_set_kernel_buffers_count(pdata->iiod_client,
&pdata->io_ctx, dev, nb_blocks);
}
static struct iio_context * network_clone(const struct iio_context *ctx)
{
const char *addr = iio_context_get_attr_value(ctx, "ip,ip-addr");
return iio_create_network_context(addr);
}
static const struct iio_backend_ops network_ops = {
.clone = network_clone,
.open = network_open,
.close = network_close,
.read = network_read,
.write = network_write,
#ifdef WITH_NETWORK_GET_BUFFER
.get_buffer = network_get_buffer,
#endif
.read_device_attr = network_read_dev_attr,
.write_device_attr = network_write_dev_attr,
.read_channel_attr = network_read_chn_attr,
.write_channel_attr = network_write_chn_attr,
.get_trigger = network_get_trigger,
.set_trigger = network_set_trigger,
.shutdown = network_shutdown,
.get_version = network_get_version,
.set_timeout = network_set_timeout,
.set_kernel_buffers_count = network_set_kernel_buffers_count,
.cancel = network_cancel,
};
static ssize_t network_write_data(struct iio_context_pdata *pdata,
void *io_data, const char *src, size_t len)
{
struct iio_network_io_context *io_ctx = io_data;
return network_send(io_ctx, src, len, 0);
}
static ssize_t network_read_data(struct iio_context_pdata *pdata,
void *io_data, char *dst, size_t len)
{
struct iio_network_io_context *io_ctx = io_data;
return network_recv(io_ctx, dst, len, 0);
}
static ssize_t network_read_line(struct iio_context_pdata *pdata,
void *io_data, char *dst, size_t len)
{
bool found = false;
size_t i;
#ifdef __linux__
struct iio_network_io_context *io_ctx = io_data;
ssize_t ret;
size_t bytes_read = 0;
do {
size_t to_trunc;
ret = network_recv(io_ctx, dst, len, MSG_PEEK);
if (ret < 0)
return ret;
/* Lookup for the trailing \n */
for (i = 0; i < (size_t) ret && dst[i] != '\n'; i++);
found = i < (size_t) ret;
len -= ret;
dst += ret;
if (found)
to_trunc = i + 1;
else
to_trunc = (size_t) ret;
/* Advance the read offset to the byte following the \n if
* found, or after the last charater read otherwise */
if (pdata->msg_trunc_supported)
ret = network_recv(io_ctx, NULL, to_trunc, MSG_TRUNC);
else
ret = network_recv(io_ctx, dst - ret, to_trunc, 0);
if (ret < 0)
return ret;
bytes_read += to_trunc;
} while (!found && len);
if (!found)
return -EIO;
else
return bytes_read;
#else
for (i = 0; i < len - 1; i++) {
ssize_t ret = network_read_data(pdata, io_data, dst + i, 1);
if (ret < 0)
return ret;
if (dst[i] != '\n')
found = true;
else if (found)
break;
}
if (!found || i == len - 1)
return -EIO;
return (ssize_t) i + 1;
#endif
}
static const struct iiod_client_ops network_iiod_client_ops = {
.write = network_write_data,
.read = network_read_data,
.read_line = network_read_line,
};
#ifdef __linux__
/*
* As of build 16299, Windows Subsystem for Linux presents a Linux API but
* without support for MSG_TRUNC. Since WSL allows running native Linux
* applications this is not something that can be detected at compile time. If
* we want to support WSL we have to have a runtime workaround.
*/
static bool msg_trunc_supported(struct iio_network_io_context *io_ctx)
{
int ret;
ret = network_recv(io_ctx, NULL, 0, MSG_TRUNC | MSG_DONTWAIT);
return ret != -EFAULT && ret != -EINVAL;
}
#else
static bool msg_trunc_supported(struct iio_network_io_context *io_ctx)
{
return false;
}
#endif
struct iio_context * network_create_context(const char *host)
{
struct addrinfo hints, *res;
struct iio_context *ctx;
struct iio_context_pdata *pdata;
size_t i, len;
int fd, ret;
char *description;
#ifdef _WIN32
WSADATA wsaData;
ret = WSAStartup(MAKEWORD(2, 0), &wsaData);
if (ret < 0) {
ERROR("WSAStartup failed with error %i\n", ret);
errno = -ret;
return NULL;
}
#endif
memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
#ifdef HAVE_AVAHI
if (!host) {
char addr_str[AVAHI_ADDRESS_STR_MAX];
char port_str[6];
AvahiAddress address;
uint16_t port = IIOD_PORT;
memset(&address, 0, sizeof(address));
ret = discover_host(&address, &port);
if (ret < 0) {
char buf[1024];
iio_strerror(-ret, buf, sizeof(buf));
DEBUG("Unable to find host: %s\n", buf);
errno = -ret;
return NULL;
}
avahi_address_snprint(addr_str, sizeof(addr_str), &address);
iio_snprintf(port_str, sizeof(port_str), "%hu", port);
ret = getaddrinfo(addr_str, port_str, &hints, &res);
} else
#endif
{
ret = getaddrinfo(host, IIOD_PORT_STR, &hints, &res);
}
if (ret) {
ERROR("Unable to find host: %s\n", gai_strerror(ret));
#ifndef _WIN32
if (ret != EAI_SYSTEM)
errno = -ret;
#endif
return NULL;
}
fd = create_socket(res, DEFAULT_TIMEOUT_MS);
if (fd < 0) {
errno = -fd;
goto err_free_addrinfo;
}
pdata = zalloc(sizeof(*pdata));
if (!pdata) {
errno = ENOMEM;
goto err_close_socket;
}
pdata->io_ctx.fd = fd;
pdata->addrinfo = res;
pdata->io_ctx.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,
&network_iiod_client_ops);
pdata->msg_trunc_supported = msg_trunc_supported(&pdata->io_ctx);
if (pdata->msg_trunc_supported)
DEBUG("MSG_TRUNC is supported\n");
else
DEBUG("MSG_TRUNC is NOT supported\n");
if (!pdata->iiod_client)
goto err_destroy_mutex;
DEBUG("Creating context...\n");
ctx = iiod_client_create_context(pdata->iiod_client, &pdata->io_ctx);
if (!ctx)
goto err_destroy_iiod_client;
/* Override the name and low-level functions of the XML context
* with those corresponding to the network context */
ctx->name = "network";
ctx->ops = &network_ops;
ctx->pdata = pdata;
#ifdef HAVE_IPV6
len = INET6_ADDRSTRLEN + IF_NAMESIZE + 2;
#else
len = INET_ADDRSTRLEN + 1;
#endif
description = malloc(len);
if (!description) {
ret = -ENOMEM;
goto err_network_shutdown;
}
description[0] = '\0';
#ifdef HAVE_IPV6
if (res->ai_family == AF_INET6) {
struct sockaddr_in6 *in = (struct sockaddr_in6 *) res->ai_addr;
char *ptr;
inet_ntop(AF_INET6, &in->sin6_addr,
description, INET6_ADDRSTRLEN);
ptr = if_indextoname(in->sin6_scope_id, description +
strlen(description) + 1);
if (!ptr) {
ret = -errno;
ERROR("Unable to lookup interface of IPv6 address\n");
goto err_free_description;
}
*(ptr - 1) = '%';
}
#endif
if (res->ai_family == AF_INET) {
struct sockaddr_in *in = (struct sockaddr_in *) res->ai_addr;
#if (!_WIN32 || _WIN32_WINNT >= 0x600)
inet_ntop(AF_INET, &in->sin_addr, description, INET_ADDRSTRLEN);
#else
char *tmp = inet_ntoa(in->sin_addr);
strncpy(description, tmp, len);
#endif
}
ret = iio_context_add_attr(ctx, "ip,ip-addr", description);
if (ret < 0)
goto err_free_description;
for (i = 0; i < ctx->nb_devices; i++) {
struct iio_device *dev = ctx->devices[i];
dev->pdata = zalloc(sizeof(*dev->pdata));
if (!dev->pdata) {
ret = -ENOMEM;
goto err_free_description;
}
dev->pdata->io_ctx.fd = -1;
dev->pdata->io_ctx.timeout_ms = DEFAULT_TIMEOUT_MS;
#ifdef WITH_NETWORK_GET_BUFFER
dev->pdata->memfd = -1;
#endif
dev->pdata->lock = iio_mutex_create();
if (!dev->pdata->lock) {
ret = -ENOMEM;
goto err_free_description;
}
}
if (ctx->description) {
size_t desc_len = strlen(description);
size_t new_size = desc_len + strlen(ctx->description) + 2;
char *ptr, *new_description = realloc(description, new_size);
if (!new_description) {
ret = -ENOMEM;
goto err_free_description;
}
ptr = strrchr(new_description, '\0');
iio_snprintf(ptr, new_size - desc_len, " %s", ctx->description);
free(ctx->description);
ctx->description = new_description;
} else {
ctx->description = description;
}
iiod_client_set_timeout(pdata->iiod_client, &pdata->io_ctx,
calculate_remote_timeout(DEFAULT_TIMEOUT_MS));
return ctx;
err_free_description:
free(description);
err_network_shutdown:
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_close_socket:
close(fd);
err_free_addrinfo:
freeaddrinfo(res);
return NULL;
}