On Mac OS X (and probably the other BSD's too), the ioctl() syscall takes an 'unsigned long' integer as the request parameter. On 64bit systems this is a 64bit type, while on 32bit systems it's a 32bit type. Some of the request constants are defined as 32 bit negative numbers. Casting it to a 64bit value will perform a sign extension operation to preserve the negative value. Because this results in a different request code when interpreted as an unsigned integer, the ioctl() call fails with ENOTTY. For example TIOCMBIS is defined as 0x8004746c and becomes 0xffffffff8004746 after the sign extension. Linux 64bit is unaffected by this problem. None of the request constants has the sign bit set, and thus the sign extension has no effect. For example TIOCMBIS is defined as 0x5416. By using an unsigned integer type, the sign extension can be avoided. We use the 'unsigned long' type in case one of the request constants happens to be defined as a 64bit number.
801 lines
16 KiB
C
801 lines
16 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 <sys/time.h> // gettimeofday
|
|
#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
|
|
|
|
#ifndef TIOCINQ
|
|
#define TIOCINQ FIONREAD
|
|
#endif
|
|
|
|
#include "serial.h"
|
|
#include "utils.h"
|
|
|
|
#define TRACE(expr) \
|
|
{ \
|
|
int error = errno; \
|
|
message ("TRACE (%s:%d, %s): %s (%d)\n", __FILE__, __LINE__, \
|
|
expr, serial_errmsg (), serial_errcode ()); \
|
|
errno = error; \
|
|
}
|
|
|
|
struct serial_t {
|
|
/*
|
|
* The file descriptor corresponding to the serial port.
|
|
*/
|
|
int fd;
|
|
long timeout;
|
|
/*
|
|
* 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;
|
|
/* Half-duplex settings */
|
|
int halfduplex;
|
|
unsigned int baudrate;
|
|
unsigned int nbits;
|
|
};
|
|
|
|
//
|
|
// Error reporting.
|
|
//
|
|
|
|
int serial_errcode (void)
|
|
{
|
|
return errno;
|
|
}
|
|
|
|
|
|
const char* serial_errmsg (void)
|
|
{
|
|
return strerror (errno);
|
|
}
|
|
|
|
//
|
|
// Open the serial port.
|
|
//
|
|
|
|
int
|
|
serial_open (serial_t **out, const char* name)
|
|
{
|
|
if (out == NULL)
|
|
return -1; // EINVAL (Invalid argument)
|
|
|
|
// Allocate memory.
|
|
serial_t *device = (serial_t *) malloc (sizeof (serial_t));
|
|
if (device == NULL) {
|
|
TRACE ("malloc");
|
|
return -1; // ENOMEM (Not enough space)
|
|
}
|
|
|
|
// Default to blocking reads.
|
|
device->timeout = -1;
|
|
|
|
// Default to full-duplex.
|
|
device->halfduplex = 0;
|
|
device->baudrate = 0;
|
|
device->nbits = 0;
|
|
|
|
// 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) {
|
|
TRACE ("open");
|
|
free (device);
|
|
return -1; // Error during open call.
|
|
}
|
|
|
|
// 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) {
|
|
TRACE ("tcgetattr");
|
|
close (device->fd);
|
|
free (device);
|
|
return -1;
|
|
}
|
|
|
|
*out = device;
|
|
|
|
return 0;
|
|
}
|
|
|
|
//
|
|
// Close the serial port.
|
|
//
|
|
|
|
int
|
|
serial_close (serial_t *device)
|
|
{
|
|
if (device == NULL)
|
|
return 0;
|
|
|
|
// Restore the initial terminal attributes.
|
|
if (tcsetattr (device->fd, TCSANOW, &device->tty) != 0) {
|
|
TRACE ("tcsetattr");
|
|
close (device->fd);
|
|
free (device);
|
|
return -1;
|
|
}
|
|
|
|
// Close the device.
|
|
if (close (device->fd) != 0) {
|
|
TRACE ("close");
|
|
free (device);
|
|
return -1;
|
|
}
|
|
|
|
// Free memory.
|
|
free (device);
|
|
|
|
return 0;
|
|
}
|
|
|
|
//
|
|
// Configure the serial port (baudrate, databits, parity, stopbits and flowcontrol).
|
|
//
|
|
|
|
int
|
|
serial_configure (serial_t *device, int baudrate, int databits, int parity, int stopbits, int flowcontrol)
|
|
{
|
|
if (device == NULL)
|
|
return -1; // EINVAL (Invalid argument)
|
|
|
|
// Retrieve the current settings.
|
|
struct termios tty;
|
|
if (tcgetattr (device->fd, &tty) != 0) {
|
|
TRACE ("tcgetattr");
|
|
return -1;
|
|
}
|
|
|
|
// 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) {
|
|
TRACE ("cfsetispeed/cfsetospeed");
|
|
return -1;
|
|
}
|
|
|
|
// 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 -1;
|
|
}
|
|
|
|
// Set the parity type.
|
|
tty.c_cflag &= ~(PARENB | PARODD);
|
|
tty.c_iflag &= ~(IGNPAR | PARMRK | INPCK);
|
|
switch (parity) {
|
|
case SERIAL_PARITY_NONE: // No parity
|
|
tty.c_iflag |= IGNPAR;
|
|
break;
|
|
case SERIAL_PARITY_EVEN: // Even parity
|
|
tty.c_cflag |= PARENB;
|
|
tty.c_iflag |= INPCK;
|
|
break;
|
|
case SERIAL_PARITY_ODD: // Odd parity
|
|
tty.c_cflag |= (PARENB | PARODD);
|
|
tty.c_iflag |= INPCK;
|
|
break;
|
|
default:
|
|
return -1;
|
|
}
|
|
|
|
// Set the number of stop bits.
|
|
switch (stopbits) {
|
|
case 1: // One stopbit
|
|
tty.c_cflag &= ~CSTOPB;
|
|
break;
|
|
case 2: // Two stopbits
|
|
tty.c_cflag |= CSTOPB;
|
|
break;
|
|
default:
|
|
return -1;
|
|
}
|
|
|
|
// Set the flow control.
|
|
switch (flowcontrol) {
|
|
case SERIAL_FLOWCONTROL_NONE: // No flow control.
|
|
#ifdef CRTSCTS
|
|
tty.c_cflag &= ~CRTSCTS;
|
|
#endif
|
|
tty.c_iflag &= ~(IXON | IXOFF | IXANY);
|
|
break;
|
|
case SERIAL_FLOWCONTROL_HARDWARE: // Hardware (RTS/CTS) flow control.
|
|
#ifdef CRTSCTS
|
|
tty.c_cflag |= CRTSCTS;
|
|
tty.c_iflag &= ~(IXON | IXOFF | IXANY);
|
|
break;
|
|
#else
|
|
return -1; // Hardware flow control is unsupported.
|
|
#endif
|
|
case SERIAL_FLOWCONTROL_SOFTWARE: // Software (XON/XOFF) flow control.
|
|
#ifdef CRTSCTS
|
|
tty.c_cflag &= ~CRTSCTS;
|
|
#endif
|
|
tty.c_iflag |= (IXON | IXOFF);
|
|
break;
|
|
default:
|
|
return -1;
|
|
}
|
|
|
|
// Apply the new settings.
|
|
if (tcsetattr (device->fd, TCSANOW, &tty) != 0) {
|
|
TRACE ("tcsetattr");
|
|
return -1;
|
|
}
|
|
|
|
// tcsetattr() returns success if any of the requested changes could be
|
|
// successfully carried out. Therefore, when making multiple changes
|
|
// it may be necessary to follow this call with a further call to
|
|
// tcgetattr() to check that all changes have been performed successfully.
|
|
|
|
struct termios active;
|
|
if (tcgetattr (device->fd, &active) != 0) {
|
|
TRACE ("tcgetattr");
|
|
return -1;
|
|
}
|
|
if (memcmp (&tty, &active, sizeof (struct termios) != 0)) {
|
|
TRACE ("memcmp");
|
|
return -1;
|
|
}
|
|
|
|
// Configure a custom baudrate if necessary.
|
|
if (custom) {
|
|
#if defined(TIOCGSERIAL) && defined(TIOCSSERIAL)
|
|
// Get the current settings.
|
|
struct serial_struct ss;
|
|
if (ioctl (device->fd, TIOCGSERIAL, &ss) != 0) {
|
|
TRACE ("ioctl");
|
|
return -1;
|
|
}
|
|
|
|
// 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) {
|
|
TRACE ("ioctl");
|
|
return -1;
|
|
}
|
|
#elif defined(IOSSIOSPEED)
|
|
speed_t speed = baudrate;
|
|
if (ioctl (device->fd, IOSSIOSPEED, &speed) != 0) {
|
|
TRACE ("ioctl");
|
|
return -1;
|
|
}
|
|
#else
|
|
// Custom baudrates are not supported.
|
|
return -1;
|
|
#endif
|
|
}
|
|
|
|
device->baudrate = baudrate;
|
|
device->nbits = 1 + databits + stopbits + (parity ? 1 : 0);
|
|
|
|
return 0;
|
|
}
|
|
|
|
//
|
|
// Configure the serial port (timeouts).
|
|
//
|
|
|
|
int
|
|
serial_set_timeout (serial_t *device, long timeout)
|
|
{
|
|
if (device == NULL)
|
|
return -1; // EINVAL (Invalid argument)
|
|
|
|
device->timeout = timeout;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
//
|
|
// Configure the serial port (recommended size of the input/output buffers).
|
|
//
|
|
|
|
int
|
|
serial_set_queue_size (serial_t *device, unsigned int input, unsigned int output)
|
|
{
|
|
if (device == NULL)
|
|
return -1; // ERROR_INVALID_PARAMETER (The parameter is incorrect)
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
int
|
|
serial_set_halfduplex (serial_t *device, int value)
|
|
{
|
|
if (device == NULL)
|
|
return -1; // EINVAL (Invalid argument)
|
|
|
|
device->halfduplex = value;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
int
|
|
serial_read (serial_t *device, void *data, unsigned int size)
|
|
{
|
|
if (device == NULL)
|
|
return -1; // EINVAL (Invalid argument)
|
|
|
|
// The total timeout.
|
|
long timeout = device->timeout;
|
|
|
|
// The absolute target time.
|
|
struct timeval tve;
|
|
|
|
int init = 1;
|
|
unsigned int nbytes = 0;
|
|
while (nbytes < size) {
|
|
fd_set fds;
|
|
FD_ZERO (&fds);
|
|
FD_SET (device->fd, &fds);
|
|
|
|
struct timeval tvt;
|
|
if (timeout > 0) {
|
|
struct timeval now;
|
|
if (gettimeofday (&now, NULL) != 0) {
|
|
TRACE ("gettimeofday");
|
|
return -1;
|
|
}
|
|
|
|
if (init) {
|
|
// Calculate the initial timeout.
|
|
tvt.tv_sec = (timeout / 1000);
|
|
tvt.tv_usec = (timeout % 1000) * 1000;
|
|
// Calculate the target time.
|
|
timeradd (&now, &tvt, &tve);
|
|
} else {
|
|
// Calculate the remaining timeout.
|
|
if (timercmp (&now, &tve, <))
|
|
timersub (&tve, &now, &tvt);
|
|
else
|
|
timerclear (&tvt);
|
|
}
|
|
init = 0;
|
|
} else if (timeout == 0) {
|
|
timerclear (&tvt);
|
|
}
|
|
|
|
int rc = select (device->fd + 1, &fds, NULL, NULL, timeout >= 0 ? &tvt : NULL);
|
|
if (rc < 0) {
|
|
if (errno == EINTR)
|
|
continue; // Retry.
|
|
TRACE ("select");
|
|
return -1; // Error during select call.
|
|
} else if (rc == 0) {
|
|
break; // Timeout.
|
|
}
|
|
|
|
int n = read (device->fd, (char *) data + nbytes, size - nbytes);
|
|
if (n < 0) {
|
|
if (errno == EINTR || errno == EAGAIN)
|
|
continue; // Retry.
|
|
TRACE ("read");
|
|
return -1; // Error during read call.
|
|
} else if (n == 0) {
|
|
break; // EOF.
|
|
}
|
|
|
|
nbytes += n;
|
|
}
|
|
|
|
return nbytes;
|
|
}
|
|
|
|
|
|
int
|
|
serial_write (serial_t *device, const void *data, unsigned int size)
|
|
{
|
|
if (device == NULL)
|
|
return -1; // EINVAL (Invalid argument)
|
|
|
|
struct timeval tve, tvb;
|
|
if (device->halfduplex) {
|
|
// Get the current time.
|
|
if (gettimeofday (&tvb, NULL) != 0) {
|
|
TRACE ("gettimeofday");
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
unsigned int 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) {
|
|
if (errno == EINTR)
|
|
continue; // Retry.
|
|
TRACE ("select");
|
|
return -1; // Error during select call.
|
|
} else if (rc == 0) {
|
|
break; // Timeout.
|
|
}
|
|
|
|
int n = write (device->fd, (char *) data + nbytes, size - nbytes);
|
|
if (n < 0) {
|
|
if (errno == EINTR || errno == EAGAIN)
|
|
continue; // Retry.
|
|
TRACE ("write");
|
|
return -1; // Error during write call.
|
|
} else if (n == 0) {
|
|
break; // EOF.
|
|
}
|
|
|
|
nbytes += n;
|
|
}
|
|
|
|
// Wait until all data has been transmitted.
|
|
while (tcdrain (device->fd) != 0) {
|
|
if (errno != EINTR ) {
|
|
TRACE ("tcdrain");
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
if (device->halfduplex) {
|
|
// Get the current time.
|
|
if (gettimeofday (&tve, NULL) != 0) {
|
|
TRACE ("gettimeofday");
|
|
return -1;
|
|
}
|
|
|
|
// Calculate the elapsed time (microseconds).
|
|
struct timeval tvt;
|
|
timersub (&tve, &tvb, &tvt);
|
|
unsigned long elapsed = tvt.tv_sec * 1000000 + tvt.tv_usec;
|
|
|
|
// Calculate the expected duration (microseconds). A 2 millisecond fudge
|
|
// factor is added because it improves the success rate significantly.
|
|
unsigned long expected = 1000000.0 * device->nbits / device->baudrate * size + 0.5 + 2000;
|
|
|
|
// Wait for the remaining time.
|
|
if (elapsed < expected) {
|
|
unsigned long remaining = expected - elapsed;
|
|
|
|
// The remaining time is rounded up to the nearest millisecond to
|
|
// match the Windows implementation. The higher resolution is
|
|
// pointless anyway, since we already added a fudge factor above.
|
|
serial_sleep ((remaining + 999) / 1000);
|
|
}
|
|
}
|
|
|
|
return nbytes;
|
|
}
|
|
|
|
|
|
int
|
|
serial_flush (serial_t *device, int queue)
|
|
{
|
|
if (device == NULL)
|
|
return -1; // EINVAL (Invalid argument)
|
|
|
|
int flags = 0;
|
|
|
|
switch (queue) {
|
|
case SERIAL_QUEUE_INPUT:
|
|
flags = TCIFLUSH;
|
|
break;
|
|
case SERIAL_QUEUE_OUTPUT:
|
|
flags = TCOFLUSH;
|
|
break;
|
|
default:
|
|
flags = TCIOFLUSH;
|
|
break;
|
|
}
|
|
|
|
if (tcflush (device->fd, flags) != 0) {
|
|
TRACE ("tcflush");
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
int
|
|
serial_send_break (serial_t *device)
|
|
{
|
|
if (device == NULL)
|
|
return -1; // EINVAL (Invalid argument)
|
|
|
|
if (tcsendbreak (device->fd, 0) != 0) {
|
|
TRACE ("tcsendbreak");
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
int
|
|
serial_set_break (serial_t *device, int level)
|
|
{
|
|
if (device == NULL)
|
|
return -1; // EINVAL (Invalid argument)
|
|
|
|
unsigned long action = (level ? TIOCSBRK : TIOCCBRK);
|
|
|
|
if (ioctl (device->fd, action, NULL) != 0) {
|
|
TRACE ("ioctl");
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
serial_set_status (int fd, int value, int level)
|
|
{
|
|
unsigned long action = (level ? TIOCMBIS : TIOCMBIC);
|
|
|
|
if (ioctl (fd, action, &value) != 0) {
|
|
TRACE ("ioctl");
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
int
|
|
serial_set_dtr (serial_t *device, int level)
|
|
{
|
|
if (device == NULL)
|
|
return -1; // EINVAL (Invalid argument)
|
|
|
|
return serial_set_status (device->fd, TIOCM_DTR, level);
|
|
}
|
|
|
|
|
|
int
|
|
serial_set_rts (serial_t *device, int level)
|
|
{
|
|
if (device == NULL)
|
|
return -1; // EINVAL (Invalid argument)
|
|
|
|
return serial_set_status (device->fd, TIOCM_RTS, level);
|
|
}
|
|
|
|
|
|
int
|
|
serial_get_received (serial_t *device)
|
|
{
|
|
if (device == NULL)
|
|
return -1; // EINVAL (Invalid argument)
|
|
|
|
int bytes = 0;
|
|
if (ioctl (device->fd, TIOCINQ, &bytes) != 0) {
|
|
TRACE ("ioctl");
|
|
return -1;
|
|
}
|
|
|
|
return bytes;
|
|
}
|
|
|
|
|
|
int
|
|
serial_get_transmitted (serial_t *device)
|
|
{
|
|
if (device == NULL)
|
|
return -1; // EINVAL (Invalid argument)
|
|
|
|
int bytes = 0;
|
|
if (ioctl (device->fd, TIOCOUTQ, &bytes) != 0) {
|
|
TRACE ("ioctl");
|
|
return -1;
|
|
}
|
|
|
|
return bytes;
|
|
}
|
|
|
|
|
|
int
|
|
serial_get_line (serial_t *device, int line)
|
|
{
|
|
if (device == NULL)
|
|
return -1; // EINVAL (Invalid argument)
|
|
|
|
int status = 0;
|
|
if (ioctl (device->fd, TIOCMGET, &status) != 0) {
|
|
TRACE ("ioctl");
|
|
return -1;
|
|
}
|
|
|
|
switch (line) {
|
|
case SERIAL_LINE_DCD:
|
|
return (status & TIOCM_CAR) == TIOCM_CAR;
|
|
case SERIAL_LINE_CTS:
|
|
return (status & TIOCM_CTS) == TIOCM_CTS;
|
|
case SERIAL_LINE_DSR:
|
|
return (status & TIOCM_DSR) == TIOCM_DSR;
|
|
case SERIAL_LINE_RNG:
|
|
return (status & TIOCM_RNG) == TIOCM_RNG;
|
|
default:
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
int
|
|
serial_sleep (unsigned long timeout)
|
|
{
|
|
struct timespec ts;
|
|
ts.tv_sec = (timeout / 1000);
|
|
ts.tv_nsec = (timeout % 1000) * 1000000;
|
|
|
|
while (nanosleep (&ts, &ts) != 0) {
|
|
if (errno != EINTR ) {
|
|
TRACE ("nanosleep");
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
int
|
|
serial_timer (void)
|
|
{
|
|
struct timeval tv;
|
|
if (gettimeofday (&tv, NULL) != 0) {
|
|
TRACE ("gettimeofday");
|
|
return 0;
|
|
}
|
|
|
|
return tv.tv_sec * 1000 + tv.tv_usec / 1000;
|
|
}
|