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.
1001 lines
24 KiB
C
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;
|
|
}
|