libdivecomputer/src/serial_posix.c
Jef Driesen f6fa2b84bc Add a poll function to the I/O interface
The Linux implementation is very straighforward and just a lightweight
wrapper around the select function. But the Windows implementation is
much more complex, because the Windows event notification mechanism
behaves very different:

The WaitCommEvent function does not support a timeout and is always a
blocking call. The only way to implement a timeout is to use
asynchronous I/O (or overlapped I/O as it's called in the Windows API),
to run the operation in the background. This requires some additional
book keeping to keep track of the pending background operation.

The event mechanism is also edge triggered instead of level triggered,
and reading the event with the WaitCommEvent function clears the pending
event. Therefore, the state of the input buffer needs to be checked with
the ClearCommError function before and after the WaitCommEvent call.

The check before is necessary in case the event is already cleared by a
previous WaitCommEvent call, while there is still data present in the
input buffer. In this case, WaitCommEvent should not be called at all,
because it would wait until more data arrives.

The check afterwards is necessary in case WaitCommEvent reports a
pending event, while the data in the input buffer has already been
consumed. In this case, the current event must be ignored and
WaitCommEvent needs to be called again, to wait for the next event.
2020-01-06 13:44:07 +01:00

1001 lines
24 KiB
C

/*
* libdivecomputer
*
* Copyright (C) 2008 Jef Driesen
*
* 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.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301 USA
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <stdlib.h> // malloc, free
#include <string.h> // strerror
#include <errno.h> // errno
#include <unistd.h> // open, close, read, write
#include <fcntl.h> // fcntl
#include <termios.h> // tcgetattr, tcsetattr, cfsetispeed, cfsetospeed, tcflush, tcsendbreak
#include <sys/ioctl.h> // ioctl
#include <time.h> // nanosleep
#ifdef HAVE_LINUX_SERIAL_H
#include <linux/serial.h>
#endif
#ifdef HAVE_IOKIT_SERIAL_IOSS_H
#include <IOKit/serial/ioss.h>
#endif
#include <stdio.h>
#include <sys/types.h>
#include <dirent.h>
#include <fnmatch.h>
#ifndef TIOCINQ
#define TIOCINQ FIONREAD
#endif
#ifdef ENABLE_PTY
#define NOPTY (errno != EINVAL && errno != ENOTTY)
#else
#define NOPTY 1
#endif
#include <libdivecomputer/serial.h>
#include "common-private.h"
#include "context-private.h"
#include "iostream-private.h"
#include "iterator-private.h"
#include "descriptor-private.h"
#include "timer.h"
#define DIRNAME "/dev"
static dc_status_t dc_serial_iterator_next (dc_iterator_t *iterator, void *item);
static dc_status_t dc_serial_iterator_free (dc_iterator_t *iterator);
static dc_status_t dc_serial_set_timeout (dc_iostream_t *iostream, int timeout);
static dc_status_t dc_serial_set_latency (dc_iostream_t *iostream, unsigned int value);
static dc_status_t dc_serial_set_break (dc_iostream_t *iostream, unsigned int value);
static dc_status_t dc_serial_set_dtr (dc_iostream_t *iostream, unsigned int value);
static dc_status_t dc_serial_set_rts (dc_iostream_t *iostream, unsigned int value);
static dc_status_t dc_serial_get_lines (dc_iostream_t *iostream, unsigned int *value);
static dc_status_t dc_serial_get_available (dc_iostream_t *iostream, size_t *value);
static dc_status_t dc_serial_configure (dc_iostream_t *iostream, unsigned int baudrate, unsigned int databits, dc_parity_t parity, dc_stopbits_t stopbits, dc_flowcontrol_t flowcontrol);
static dc_status_t dc_serial_poll (dc_iostream_t *iostream, int timeout);
static dc_status_t dc_serial_read (dc_iostream_t *iostream, void *data, size_t size, size_t *actual);
static dc_status_t dc_serial_write (dc_iostream_t *iostream, const void *data, size_t size, size_t *actual);
static dc_status_t dc_serial_flush (dc_iostream_t *iostream);
static dc_status_t dc_serial_purge (dc_iostream_t *iostream, dc_direction_t direction);
static dc_status_t dc_serial_sleep (dc_iostream_t *iostream, unsigned int milliseconds);
static dc_status_t dc_serial_close (dc_iostream_t *iostream);
struct dc_serial_device_t {
char name[256];
};
typedef struct dc_serial_iterator_t {
dc_iterator_t base;
dc_filter_t filter;
DIR *dp;
} dc_serial_iterator_t;
typedef struct dc_serial_t {
dc_iostream_t base;
/*
* The file descriptor corresponding to the serial port.
*/
int fd;
int timeout;
dc_timer_t *timer;
/*
* Serial port settings are saved into this variable immediately
* after the port is opened. These settings are restored when the
* serial port is closed.
*/
struct termios tty;
} dc_serial_t;
static const dc_iterator_vtable_t dc_serial_iterator_vtable = {
sizeof(dc_serial_iterator_t),
dc_serial_iterator_next,
dc_serial_iterator_free,
};
static const dc_iostream_vtable_t dc_serial_vtable = {
sizeof(dc_serial_t),
dc_serial_set_timeout, /* set_timeout */
dc_serial_set_latency, /* set_latency */
dc_serial_set_break, /* set_break */
dc_serial_set_dtr, /* set_dtr */
dc_serial_set_rts, /* set_rts */
dc_serial_get_lines, /* get_lines */
dc_serial_get_available, /* get_available */
dc_serial_configure, /* configure */
dc_serial_poll, /* poll */
dc_serial_read, /* read */
dc_serial_write, /* write */
dc_serial_flush, /* flush */
dc_serial_purge, /* purge */
dc_serial_sleep, /* sleep */
dc_serial_close, /* close */
};
static dc_status_t
syserror(int errcode)
{
switch (errcode) {
case EINVAL:
return DC_STATUS_INVALIDARGS;
case ENOMEM:
return DC_STATUS_NOMEMORY;
case ENOENT:
return DC_STATUS_NODEVICE;
case EACCES:
case EBUSY:
return DC_STATUS_NOACCESS;
default:
return DC_STATUS_IO;
}
}
const char *
dc_serial_device_get_name (dc_serial_device_t *device)
{
if (device == NULL || device->name[0] == '\0')
return NULL;
return device->name;
}
void
dc_serial_device_free (dc_serial_device_t *device)
{
free (device);
}
dc_status_t
dc_serial_iterator_new (dc_iterator_t **out, dc_context_t *context, dc_descriptor_t *descriptor)
{
dc_status_t status = DC_STATUS_SUCCESS;
dc_serial_iterator_t *iterator = NULL;
if (out == NULL)
return DC_STATUS_INVALIDARGS;
iterator = (dc_serial_iterator_t *) dc_iterator_allocate (context, &dc_serial_iterator_vtable);
if (iterator == NULL) {
SYSERROR (context, ENOMEM);
return DC_STATUS_NOMEMORY;
}
iterator->dp = opendir (DIRNAME);
if (iterator->dp == NULL) {
int errcode = errno;
SYSERROR (context, errcode);
status = syserror (errcode);
goto error_free;
}
iterator->filter = dc_descriptor_get_filter (descriptor);
*out = (dc_iterator_t *) iterator;
return DC_STATUS_SUCCESS;
error_free:
dc_iterator_deallocate ((dc_iterator_t *) iterator);
return status;
}
static dc_status_t
dc_serial_iterator_next (dc_iterator_t *abstract, void *out)
{
dc_serial_iterator_t *iterator = (dc_serial_iterator_t *) abstract;
dc_serial_device_t *device = NULL;
struct dirent *ep = NULL;
const char *patterns[] = {
#if defined (__APPLE__)
"tty.*",
#else
"ttyS*",
"ttyUSB*",
"ttyACM*",
"rfcomm*",
#endif
NULL
};
while ((ep = readdir (iterator->dp)) != NULL) {
for (size_t i = 0; patterns[i] != NULL; ++i) {
if (fnmatch (patterns[i], ep->d_name, 0) != 0)
continue;
char filename[sizeof(device->name)];
int n = snprintf (filename, sizeof (filename), "%s/%s", DIRNAME, ep->d_name);
if (n < 0 || (size_t) n >= sizeof (filename)) {
return DC_STATUS_NOMEMORY;
}
if (iterator->filter && !iterator->filter (DC_TRANSPORT_SERIAL, filename)) {
continue;
}
device = (dc_serial_device_t *) malloc (sizeof(dc_serial_device_t));
if (device == NULL) {
SYSERROR (abstract->context, ENOMEM);
return DC_STATUS_NOMEMORY;
}
strncpy(device->name, filename, sizeof(device->name));
*(dc_serial_device_t **) out = device;
return DC_STATUS_SUCCESS;
}
}
return DC_STATUS_DONE;
}
static dc_status_t
dc_serial_iterator_free (dc_iterator_t *abstract)
{
dc_serial_iterator_t *iterator = (dc_serial_iterator_t *) abstract;
closedir (iterator->dp);
return DC_STATUS_SUCCESS;
}
dc_status_t
dc_serial_open (dc_iostream_t **out, dc_context_t *context, const char *name)
{
dc_status_t status = DC_STATUS_SUCCESS;
dc_serial_t *device = NULL;
if (out == NULL || name == NULL)
return DC_STATUS_INVALIDARGS;
INFO (context, "Open: name=%s", name);
// Allocate memory.
device = (dc_serial_t *) dc_iostream_allocate (context, &dc_serial_vtable, DC_TRANSPORT_SERIAL);
if (device == NULL) {
SYSERROR (context, ENOMEM);
return DC_STATUS_NOMEMORY;
}
// Default to blocking reads.
device->timeout = -1;
// Create a high resolution timer.
status = dc_timer_new (&device->timer);
if (status != DC_STATUS_SUCCESS) {
ERROR (context, "Failed to create a high resolution timer.");
goto error_free;
}
// Open the device in non-blocking mode, to return immediately
// without waiting for the modem connection to complete.
device->fd = open (name, O_RDWR | O_NOCTTY | O_NONBLOCK);
if (device->fd == -1) {
int errcode = errno;
SYSERROR (context, errcode);
status = syserror (errcode);
goto error_timer_free;
}
#ifndef ENABLE_PTY
// Enable exclusive access mode.
if (ioctl (device->fd, TIOCEXCL, NULL) != 0) {
int errcode = errno;
SYSERROR (context, errcode);
status = syserror (errcode);
goto error_close;
}
#endif
// Retrieve the current terminal attributes, to
// be able to restore them when closing the device.
// It is also used to check if the obtained
// file descriptor represents a terminal device.
if (tcgetattr (device->fd, &device->tty) != 0) {
int errcode = errno;
SYSERROR (context, errcode);
status = syserror (errcode);
goto error_close;
}
*out = (dc_iostream_t *) device;
return DC_STATUS_SUCCESS;
error_close:
close (device->fd);
error_timer_free:
dc_timer_free (device->timer);
error_free:
dc_iostream_deallocate ((dc_iostream_t *) device);
return status;
}
static dc_status_t
dc_serial_close (dc_iostream_t *abstract)
{
dc_status_t status = DC_STATUS_SUCCESS;
dc_serial_t *device = (dc_serial_t *) abstract;
// Restore the initial terminal attributes.
if (tcsetattr (device->fd, TCSANOW, &device->tty) != 0) {
int errcode = errno;
SYSERROR (abstract->context, errcode);
dc_status_set_error(&status, syserror (errcode));
}
#ifndef ENABLE_PTY
// Disable exclusive access mode.
if (ioctl (device->fd, TIOCNXCL, NULL)) {
int errcode = errno;
SYSERROR (abstract->context, errcode);
dc_status_set_error(&status, syserror (errcode));
}
#endif
// Close the device.
if (close (device->fd) != 0) {
int errcode = errno;
SYSERROR (abstract->context, errcode);
dc_status_set_error(&status, syserror (errcode));
}
dc_timer_free (device->timer);
return status;
}
static dc_status_t
dc_serial_configure (dc_iostream_t *abstract, unsigned int baudrate, unsigned int databits, dc_parity_t parity, dc_stopbits_t stopbits, dc_flowcontrol_t flowcontrol)
{
dc_serial_t *device = (dc_serial_t *) abstract;
// Retrieve the current settings.
struct termios tty;
memset (&tty, 0, sizeof (tty));
if (tcgetattr (device->fd, &tty) != 0) {
int errcode = errno;
SYSERROR (abstract->context, errcode);
return syserror (errcode);
}
// Setup raw input/output mode without echo.
tty.c_iflag &= ~(IGNBRK | BRKINT | ISTRIP | INLCR | IGNCR | ICRNL);
tty.c_oflag &= ~(OPOST);
tty.c_lflag &= ~(ICANON | ECHO | ISIG | IEXTEN);
// Enable the receiver (CREAD) and ignore modem control lines (CLOCAL).
tty.c_cflag |= (CLOCAL | CREAD);
// VMIN is the minimum number of characters for non-canonical read
// and VTIME is the timeout in deciseconds for non-canonical read.
// Setting both of these parameters to zero implies that a read
// will return immediately, only giving the currently available
// characters (non-blocking read behaviour). However, a non-blocking
// read (or write) can also be achieved by using O_NONBLOCK.
// But together with VMIN = 1, it becomes possible to recognize
// the difference between a timeout and modem disconnect (EOF)
// when read() returns zero.
tty.c_cc[VMIN] = 1;
tty.c_cc[VTIME] = 0;
// Set the baud rate.
int custom = 0;
speed_t baud = 0;
switch (baudrate) {
case 0: baud = B0; break;
case 50: baud = B50; break;
case 75: baud = B75; break;
case 110: baud = B110; break;
case 134: baud = B134; break;
case 150: baud = B150; break;
case 200: baud = B200; break;
case 300: baud = B300; break;
case 600: baud = B600; break;
case 1200: baud = B1200; break;
case 1800: baud = B1800; break;
case 2400: baud = B2400; break;
case 4800: baud = B4800; break;
case 9600: baud = B9600; break;
case 19200: baud = B19200; break;
case 38400: baud = B38400; break;
#ifdef B57600
case 57600: baud = B57600; break;
#endif
#ifdef B115200
case 115200: baud = B115200; break;
#endif
#ifdef B230400
case 230400: baud = B230400; break;
#endif
#ifdef B460800
case 460800: baud = B460800; break;
#endif
#ifdef B500000
case 500000: baud = B500000; break;
#endif
#ifdef B576000
case 576000: baud = B576000; break;
#endif
#ifdef B921600
case 921600: baud = B921600; break;
#endif
#ifdef B1000000
case 1000000: baud = B1000000; break;
#endif
#ifdef B1152000
case 1152000: baud = B1152000; break;
#endif
#ifdef B1500000
case 1500000: baud = B1500000; break;
#endif
#ifdef B2000000
case 2000000: baud = B2000000; break;
#endif
#ifdef B2500000
case 2500000: baud = B2500000; break;
#endif
#ifdef B3000000
case 3000000: baud = B3000000; break;
#endif
#ifdef B3500000
case 3500000: baud = B3500000; break;
#endif
#ifdef B4000000
case 4000000: baud = B4000000; break;
#endif
default:
baud = B38400; /* Required for custom baudrates on linux. */
custom = 1;
break;
}
if (cfsetispeed (&tty, baud) != 0 ||
cfsetospeed (&tty, baud) != 0) {
int errcode = errno;
SYSERROR (abstract->context, errcode);
return syserror (errcode);
}
// Set the character size.
tty.c_cflag &= ~CSIZE;
switch (databits) {
case 5:
tty.c_cflag |= CS5;
break;
case 6:
tty.c_cflag |= CS6;
break;
case 7:
tty.c_cflag |= CS7;
break;
case 8:
tty.c_cflag |= CS8;
break;
default:
return DC_STATUS_INVALIDARGS;
}
// Set the parity type.
#ifdef CMSPAR
tty.c_cflag &= ~(PARENB | PARODD | CMSPAR);
#else
tty.c_cflag &= ~(PARENB | PARODD);
#endif
tty.c_iflag &= ~(IGNPAR | PARMRK | INPCK);
switch (parity) {
case DC_PARITY_NONE:
tty.c_iflag |= IGNPAR;
break;
case DC_PARITY_EVEN:
tty.c_cflag |= PARENB;
tty.c_iflag |= INPCK;
break;
case DC_PARITY_ODD:
tty.c_cflag |= (PARENB | PARODD);
tty.c_iflag |= INPCK;
break;
#ifdef CMSPAR
case DC_PARITY_MARK:
tty.c_cflag |= (PARENB | PARODD | CMSPAR);
tty.c_iflag |= INPCK;
break;
case DC_PARITY_SPACE:
tty.c_cflag |= (PARENB | CMSPAR);
tty.c_iflag |= INPCK;
break;
#endif
default:
return DC_STATUS_INVALIDARGS;
}
// Set the number of stop bits.
switch (stopbits) {
case DC_STOPBITS_ONE:
tty.c_cflag &= ~CSTOPB;
break;
case DC_STOPBITS_TWO:
tty.c_cflag |= CSTOPB;
break;
default:
return DC_STATUS_INVALIDARGS;
}
// Set the flow control.
switch (flowcontrol) {
case DC_FLOWCONTROL_NONE:
#ifdef CRTSCTS
tty.c_cflag &= ~CRTSCTS;
#endif
tty.c_iflag &= ~(IXON | IXOFF | IXANY);
break;
case DC_FLOWCONTROL_HARDWARE:
#ifdef CRTSCTS
tty.c_cflag |= CRTSCTS;
tty.c_iflag &= ~(IXON | IXOFF | IXANY);
break;
#else
return DC_STATUS_UNSUPPORTED;
#endif
case DC_FLOWCONTROL_SOFTWARE:
#ifdef CRTSCTS
tty.c_cflag &= ~CRTSCTS;
#endif
tty.c_iflag |= (IXON | IXOFF);
break;
default:
return DC_STATUS_INVALIDARGS;
}
// Apply the new settings.
if (tcsetattr (device->fd, TCSANOW, &tty) != 0 && NOPTY) {
int errcode = errno;
SYSERROR (abstract->context, errcode);
return syserror (errcode);
}
// Configure a custom baudrate if necessary.
if (custom) {
#if defined(TIOCGSERIAL) && defined(TIOCSSERIAL) && !defined(__ANDROID__)
// Get the current settings.
struct serial_struct ss;
if (ioctl (device->fd, TIOCGSERIAL, &ss) != 0 && NOPTY) {
int errcode = errno;
SYSERROR (abstract->context, errcode);
return syserror (errcode);
}
// Set the custom divisor.
ss.custom_divisor = ss.baud_base / baudrate;
ss.flags &= ~ASYNC_SPD_MASK;
ss.flags |= ASYNC_SPD_CUST;
// Apply the new settings.
if (ioctl (device->fd, TIOCSSERIAL, &ss) != 0 && NOPTY) {
int errcode = errno;
SYSERROR (abstract->context, errcode);
return syserror (errcode);
}
#elif defined(IOSSIOSPEED)
speed_t speed = baudrate;
if (ioctl (device->fd, IOSSIOSPEED, &speed) != 0 && NOPTY) {
int errcode = errno;
SYSERROR (abstract->context, errcode);
return syserror (errcode);
}
#else
// Custom baudrates are not supported.
return DC_STATUS_UNSUPPORTED;
#endif
}
return DC_STATUS_SUCCESS;
}
static dc_status_t
dc_serial_set_timeout (dc_iostream_t *abstract, int timeout)
{
dc_serial_t *device = (dc_serial_t *) abstract;
device->timeout = timeout;
return DC_STATUS_SUCCESS;
}
static dc_status_t
dc_serial_set_latency (dc_iostream_t *abstract, unsigned int milliseconds)
{
dc_serial_t *device = (dc_serial_t *) abstract;
#if defined(TIOCGSERIAL) && defined(TIOCSSERIAL) && !defined(__ANDROID__)
// Get the current settings.
struct serial_struct ss;
if (ioctl (device->fd, TIOCGSERIAL, &ss) != 0 && NOPTY) {
int errcode = errno;
SYSERROR (abstract->context, errcode);
return syserror (errcode);
}
// Set or clear the low latency flag.
if (milliseconds == 0) {
ss.flags |= ASYNC_LOW_LATENCY;
} else {
ss.flags &= ~ASYNC_LOW_LATENCY;
}
// Apply the new settings.
if (ioctl (device->fd, TIOCSSERIAL, &ss) != 0 && NOPTY) {
int errcode = errno;
SYSERROR (abstract->context, errcode);
return syserror (errcode);
}
#elif defined(IOSSDATALAT)
// Set the receive latency in microseconds. Serial drivers use this
// value to determine how often to dequeue characters received by
// the hardware. A value of zero restores the default value.
unsigned long usec = (milliseconds == 0 ? 1 : milliseconds * 1000);
if (ioctl (device->fd, IOSSDATALAT, &usec) != 0 && NOPTY) {
int errcode = errno;
SYSERROR (abstract->context, errcode);
return syserror (errcode);
}
#endif
return DC_STATUS_SUCCESS;
}
static dc_status_t
dc_serial_poll (dc_iostream_t *abstract, int timeout)
{
dc_serial_t *device = (dc_serial_t *) abstract;
int rc = 0;
do {
fd_set fds;
FD_ZERO (&fds);
FD_SET (device->fd, &fds);
struct timeval tv, *ptv = NULL;
if (timeout > 0) {
tv.tv_sec = (timeout / 1000);
tv.tv_usec = (timeout % 1000) * 1000;
ptv = &tv;
} else if (timeout == 0) {
tv.tv_sec = 0;
tv.tv_usec = 0;
ptv = &tv;
}
rc = select (device->fd + 1, &fds, NULL, NULL, ptv);
} while (rc < 0 && errno == EINTR);
if (rc < 0) {
int errcode = errno;
SYSERROR (abstract->context, errcode);
return syserror (errcode);
} else if (rc == 0) {
return DC_STATUS_TIMEOUT;
} else {
return DC_STATUS_SUCCESS;
}
}
static dc_status_t
dc_serial_read (dc_iostream_t *abstract, void *data, size_t size, size_t *actual)
{
dc_status_t status = DC_STATUS_SUCCESS;
dc_serial_t *device = (dc_serial_t *) abstract;
size_t nbytes = 0;
// The absolute target time.
dc_usecs_t target = 0;
int init = 1;
while (nbytes < size) {
fd_set fds;
FD_ZERO (&fds);
FD_SET (device->fd, &fds);
struct timeval tv, *ptv = NULL;
if (device->timeout > 0) {
dc_usecs_t timeout = 0;
dc_usecs_t now = 0;
status = dc_timer_now (device->timer, &now);
if (status != DC_STATUS_SUCCESS) {
goto out;
}
if (init) {
// Calculate the initial timeout.
timeout = (dc_usecs_t) device->timeout * 1000;
// Calculate the target time.
target = now + timeout;
init = 0;
} else {
// Calculate the remaining timeout.
if (now < target) {
timeout = target - now;
} else {
timeout = 0;
}
}
tv.tv_sec = timeout / 1000000;
tv.tv_usec = timeout % 1000000;
ptv = &tv;
} else if (device->timeout == 0) {
tv.tv_sec = 0;
tv.tv_usec = 0;
ptv = &tv;
}
int rc = select (device->fd + 1, &fds, NULL, NULL, ptv);
if (rc < 0) {
int errcode = errno;
if (errcode == EINTR)
continue; // Retry.
SYSERROR (abstract->context, errcode);
status = syserror (errcode);
goto out;
} else if (rc == 0) {
break; // Timeout.
}
ssize_t n = read (device->fd, (char *) data + nbytes, size - nbytes);
if (n < 0) {
int errcode = errno;
if (errcode == EINTR || errcode == EAGAIN)
continue; // Retry.
SYSERROR (abstract->context, errcode);
status = syserror (errcode);
goto out;
} else if (n == 0) {
break; // EOF.
}
nbytes += n;
}
if (nbytes != size) {
status = DC_STATUS_TIMEOUT;
}
out:
if (actual)
*actual = nbytes;
return status;
}
static dc_status_t
dc_serial_write (dc_iostream_t *abstract, const void *data, size_t size, size_t *actual)
{
dc_status_t status = DC_STATUS_SUCCESS;
dc_serial_t *device = (dc_serial_t *) abstract;
size_t nbytes = 0;
while (nbytes < size) {
fd_set fds;
FD_ZERO (&fds);
FD_SET (device->fd, &fds);
int rc = select (device->fd + 1, NULL, &fds, NULL, NULL);
if (rc < 0) {
int errcode = errno;
if (errcode == EINTR)
continue; // Retry.
SYSERROR (abstract->context, errcode);
status = syserror (errcode);
goto out;
} else if (rc == 0) {
break; // Timeout.
}
ssize_t n = write (device->fd, (const char *) data + nbytes, size - nbytes);
if (n < 0) {
int errcode = errno;
if (errcode == EINTR || errcode == EAGAIN)
continue; // Retry.
SYSERROR (abstract->context, errcode);
status = syserror (errcode);
goto out;
} else if (n == 0) {
break; // EOF.
}
nbytes += n;
}
// Wait until all data has been transmitted.
#ifdef __ANDROID__
/* Android is missing tcdrain, so use ioctl version instead */
while (ioctl (device->fd, TCSBRK, 1) != 0) {
#else
while (tcdrain (device->fd) != 0) {
#endif
int errcode = errno;
if (errcode != EINTR ) {
SYSERROR (abstract->context, errcode);
status = syserror (errcode);
goto out;
}
}
out:
if (actual)
*actual = nbytes;
return status;
}
static dc_status_t
dc_serial_purge (dc_iostream_t *abstract, dc_direction_t direction)
{
dc_serial_t *device = (dc_serial_t *) abstract;
int flags = 0;
switch (direction) {
case DC_DIRECTION_INPUT:
flags = TCIFLUSH;
break;
case DC_DIRECTION_OUTPUT:
flags = TCOFLUSH;
break;
case DC_DIRECTION_ALL:
flags = TCIOFLUSH;
break;
default:
return DC_STATUS_INVALIDARGS;
}
if (tcflush (device->fd, flags) != 0) {
int errcode = errno;
SYSERROR (abstract->context, errcode);
return syserror (errcode);
}
return DC_STATUS_SUCCESS;
}
static dc_status_t
dc_serial_flush (dc_iostream_t *abstract)
{
return DC_STATUS_SUCCESS;
}
static dc_status_t
dc_serial_set_break (dc_iostream_t *abstract, unsigned int level)
{
dc_serial_t *device = (dc_serial_t *) abstract;
unsigned long action = (level ? TIOCSBRK : TIOCCBRK);
if (ioctl (device->fd, action, NULL) != 0 && NOPTY) {
int errcode = errno;
SYSERROR (abstract->context, errcode);
return syserror (errcode);
}
return DC_STATUS_SUCCESS;
}
static dc_status_t
dc_serial_set_dtr (dc_iostream_t *abstract, unsigned int level)
{
dc_serial_t *device = (dc_serial_t *) abstract;
unsigned long action = (level ? TIOCMBIS : TIOCMBIC);
int value = TIOCM_DTR;
if (ioctl (device->fd, action, &value) != 0 && NOPTY) {
int errcode = errno;
SYSERROR (abstract->context, errcode);
return syserror (errcode);
}
return DC_STATUS_SUCCESS;
}
static dc_status_t
dc_serial_set_rts (dc_iostream_t *abstract, unsigned int level)
{
dc_serial_t *device = (dc_serial_t *) abstract;
unsigned long action = (level ? TIOCMBIS : TIOCMBIC);
int value = TIOCM_RTS;
if (ioctl (device->fd, action, &value) != 0 && NOPTY) {
int errcode = errno;
SYSERROR (abstract->context, errcode);
return syserror (errcode);
}
return DC_STATUS_SUCCESS;
}
static dc_status_t
dc_serial_get_available (dc_iostream_t *abstract, size_t *value)
{
dc_serial_t *device = (dc_serial_t *) abstract;
int bytes = 0;
if (ioctl (device->fd, TIOCINQ, &bytes) != 0) {
int errcode = errno;
SYSERROR (abstract->context, errcode);
return syserror (errcode);
}
if (value)
*value = bytes;
return DC_STATUS_SUCCESS;
}
static dc_status_t
dc_serial_get_lines (dc_iostream_t *abstract, unsigned int *value)
{
dc_serial_t *device = (dc_serial_t *) abstract;
unsigned int lines = 0;
int status = 0;
if (ioctl (device->fd, TIOCMGET, &status) != 0) {
int errcode = errno;
SYSERROR (abstract->context, errcode);
return syserror (errcode);
}
if (status & TIOCM_CAR)
lines |= DC_LINE_DCD;
if (status & TIOCM_CTS)
lines |= DC_LINE_CTS;
if (status & TIOCM_DSR)
lines |= DC_LINE_DSR;
if (status & TIOCM_RNG)
lines |= DC_LINE_RNG;
if (value)
*value = lines;
return DC_STATUS_SUCCESS;
}
static dc_status_t
dc_serial_sleep (dc_iostream_t *abstract, unsigned int timeout)
{
struct timespec ts;
ts.tv_sec = (timeout / 1000);
ts.tv_nsec = (timeout % 1000) * 1000000;
while (nanosleep (&ts, &ts) != 0) {
int errcode = errno;
if (errcode != EINTR ) {
SYSERROR (abstract->context, errcode);
return syserror (errcode);
}
}
return DC_STATUS_SUCCESS;
}