New API and implementation for blocking and non-blocking I/O.

This commit is contained in:
Martin Ling 2013-11-25 22:12:10 +00:00
parent 8fbf876dfd
commit e3dcf9068e
2 changed files with 356 additions and 102 deletions

View File

@ -120,8 +120,6 @@ enum sp_mode {
SP_MODE_READ = 1, SP_MODE_READ = 1,
/** Open port for write access. */ /** Open port for write access. */
SP_MODE_WRITE = 2, SP_MODE_WRITE = 2,
/** Open port in non-blocking mode. */
SP_MODE_NONBLOCK = 4,
}; };
/** Buffer selection. */ /** Buffer selection. */
@ -758,34 +756,76 @@ enum sp_return sp_set_flowcontrol(struct sp_port *port, enum sp_flowcontrol flow
*/ */
/** /**
* Read bytes from the specified serial port. * Read bytes from the specified serial port, blocking until available.
* *
* Note that this function may return after reading less than the specified * @param port Pointer to port structure.
* number of bytes; it is the user's responsibility to iterate as necessary * @param buf Buffer in which to store the bytes read.
* in this case. * @param count Requested number of bytes to read.
* @param timeout Timeout in milliseconds, or zero to wait indefinitely.
*
* @return The number of bytes read on success, or a negative error code. If
* the number of bytes returned is less than that requested, the
* timeout was reached before the requested number of bytes was
* available. If timeout is zero, the function will always return
* either the requested number of bytes or a negative error code.
*/
enum sp_return sp_blocking_read(struct sp_port *port, void *buf, size_t count, unsigned int timeout);
/**
* Read bytes from the specified serial port, without blocking.
* *
* @param port Pointer to port structure. * @param port Pointer to port structure.
* @param buf Buffer in which to store the bytes read. * @param buf Buffer in which to store the bytes read.
* @param count Maximum number of bytes to read. * @param count Maximum number of bytes to read.
* *
* @return The number of bytes read on success, or a negative error code. * @return The number of bytes read on success, or a negative error code. The
* number of bytes returned may be any number from zero to the maximum
* that was requested.
*/ */
enum sp_return sp_read(struct sp_port *port, void *buf, size_t count); enum sp_return sp_nonblocking_read(struct sp_port *port, void *buf, size_t count);
/** /**
* Write bytes to the specified serial port. * Write bytes to the specified serial port, blocking until complete.
* *
* Note that this function may return after writing less than the specified * Note that this function only ensures that the accepted bytes have been
* number of bytes; it is the user's responsibility to iterate as necessary * written to the OS; they may be held in driver or hardware buffers and not
* in this case. * yet physically transmitted. To check whether all written bytes have actually
* been transmitted, use the sp_output_waiting() function. To wait until all
* written bytes have actually been transmitted, use the sp_drain() function.
*
* @param port Pointer to port structure.
* @param buf Buffer containing the bytes to write.
* @param count Requested number of bytes to write.
* @param timeout Timeout in milliseconds, or zero to wait indefinitely.
*
* @return The number of bytes read on success, or a negative error code. If
* the number of bytes returned is less than that requested, the
* timeout was reached before the requested number of bytes was
* sent. If timeout is zero, the function will always return
* either the requested number of bytes or a negative error code. In
* the event of an error there is no way to determine how many bytes
* were sent before the error occured.
*/
enum sp_return sp_blocking_write(struct sp_port *port, const void *buf, size_t count, unsigned int timeout);
/**
* Write bytes to the specified serial port, without blocking.
*
* Note that this function only ensures that the accepted bytes have been
* written to the OS; they may be held in driver or hardware buffers and not
* yet physically transmitted. To check whether all written bytes have actually
* been transmitted, use the sp_output_waiting() function. To wait until all
* written bytes have actually been transmitted, use the sp_drain() function.
* *
* @param port Pointer to port structure. * @param port Pointer to port structure.
* @param buf Buffer containing the bytes to write. * @param buf Buffer containing the bytes to write.
* @param count Maximum number of bytes to write. * @param count Maximum number of bytes to write.
* *
* @return The number of bytes written on success, or a negative error code. * @return The number of bytes written on success, or a negative error code.
* The number of bytes returned may be any number from zero to the
* maximum that was requested.
*/ */
enum sp_return sp_write(struct sp_port *port, const void *buf, size_t count); enum sp_return sp_nonblocking_write(struct sp_port *port, const void *buf, size_t count);
/** /**
* Flush serial port buffers. Data in the selected buffer(s) is discarded. * Flush serial port buffers. Data in the selected buffer(s) is discarded.

View File

@ -37,6 +37,8 @@
#include <limits.h> #include <limits.h>
#include <termios.h> #include <termios.h>
#include <sys/ioctl.h> #include <sys/ioctl.h>
#include <sys/time.h>
#include <limits.h>
#endif #endif
#ifdef __APPLE__ #ifdef __APPLE__
#include <IOKit/IOKitLib.h> #include <IOKit/IOKitLib.h>
@ -61,10 +63,11 @@
struct sp_port { struct sp_port {
char *name; char *name;
int nonblocking;
#ifdef _WIN32 #ifdef _WIN32
HANDLE hdl; HANDLE hdl;
COMMTIMEOUTS timeouts;
OVERLAPPED write_ovl; OVERLAPPED write_ovl;
OVERLAPPED read_ovl;
BYTE pending_byte; BYTE pending_byte;
BOOL writing; BOOL writing;
#else #else
@ -581,16 +584,13 @@ enum sp_return sp_open(struct sp_port *port, enum sp_mode flags)
CHECK_PORT(); CHECK_PORT();
if (flags > (SP_MODE_READ | SP_MODE_WRITE | SP_MODE_NONBLOCK)) if (flags > (SP_MODE_READ | SP_MODE_WRITE))
RETURN_ERROR(SP_ERR_ARG, "Invalid flags"); RETURN_ERROR(SP_ERR_ARG, "Invalid flags");
DEBUG("Opening port %s", port->name); DEBUG("Opening port %s", port->name);
port->nonblocking = (flags & SP_MODE_NONBLOCK) ? 1 : 0;
#ifdef _WIN32 #ifdef _WIN32
DWORD desired_access = 0, flags_and_attributes = 0; DWORD desired_access = 0, flags_and_attributes = 0;
COMMTIMEOUTS timeouts;
char *escaped_port_name; char *escaped_port_name;
/* Prefix port name with '\\.\' to work with ports above COM9. */ /* Prefix port name with '\\.\' to work with ports above COM9. */
@ -599,13 +599,11 @@ enum sp_return sp_open(struct sp_port *port, enum sp_mode flags)
sprintf(escaped_port_name, "\\\\.\\%s", port->name); sprintf(escaped_port_name, "\\\\.\\%s", port->name);
/* Map 'flags' to the OS-specific settings. */ /* Map 'flags' to the OS-specific settings. */
flags_and_attributes = FILE_ATTRIBUTE_NORMAL; flags_and_attributes = FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED;
if (flags & SP_MODE_READ) if (flags & SP_MODE_READ)
desired_access |= GENERIC_READ; desired_access |= GENERIC_READ;
if (flags & SP_MODE_WRITE) if (flags & SP_MODE_WRITE)
desired_access |= GENERIC_WRITE; desired_access |= GENERIC_WRITE;
if (flags & SP_MODE_NONBLOCK)
flags_and_attributes |= FILE_FLAG_OVERLAPPED;
port->hdl = CreateFile(escaped_port_name, desired_access, 0, 0, port->hdl = CreateFile(escaped_port_name, desired_access, 0, 0,
OPEN_EXISTING, flags_and_attributes, 0); OPEN_EXISTING, flags_and_attributes, 0);
@ -613,33 +611,38 @@ enum sp_return sp_open(struct sp_port *port, enum sp_mode flags)
free(escaped_port_name); free(escaped_port_name);
if (port->hdl == INVALID_HANDLE_VALUE) if (port->hdl == INVALID_HANDLE_VALUE)
RETURN_FAIL("CreateFile() failed"); RETURN_FAIL("port CreateFile() failed");
/* All timeouts disabled. */ /* All timeouts initially disabled. */
timeouts.ReadIntervalTimeout = 0; port->timeouts.ReadIntervalTimeout = 0;
timeouts.ReadTotalTimeoutMultiplier = 0; port->timeouts.ReadTotalTimeoutMultiplier = 0;
timeouts.ReadTotalTimeoutConstant = 0; port->timeouts.ReadTotalTimeoutConstant = 0;
timeouts.WriteTotalTimeoutMultiplier = 0; port->timeouts.WriteTotalTimeoutMultiplier = 0;
timeouts.WriteTotalTimeoutConstant = 0; port->timeouts.WriteTotalTimeoutConstant = 0;
if (port->nonblocking) { if (SetCommTimeouts(port->hdl, &port->timeouts) == 0) {
/* Set read timeout such that all reads return immediately. */
timeouts.ReadIntervalTimeout = MAXDWORD;
/* Prepare OVERLAPPED structure for non-blocking writes. */
memset(&port->write_ovl, 0, sizeof(port->write_ovl));
if (!(port->write_ovl.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL))) {
sp_close(port);
RETURN_FAIL("CreateEvent() failed");
}
port->writing = FALSE;
}
if (SetCommTimeouts(port->hdl, &timeouts) == 0) {
sp_close(port); sp_close(port);
RETURN_FAIL("SetCommTimeouts() failed"); RETURN_FAIL("SetCommTimeouts() failed");
} }
/* Prepare OVERLAPPED structures. */
memset(&port->read_ovl, 0, sizeof(port->read_ovl));
memset(&port->write_ovl, 0, sizeof(port->write_ovl));
port->read_ovl.hEvent = INVALID_HANDLE_VALUE;
port->write_ovl.hEvent = INVALID_HANDLE_VALUE;
if ((port->read_ovl.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL)) == INVALID_HANDLE_VALUE) {
sp_close(port);
RETURN_FAIL("read event CreateEvent() failed");
}
if ((port->write_ovl.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL)) == INVALID_HANDLE_VALUE) {
sp_close(port);
RETURN_FAIL("write event CreateEvent() failed");
}
port->writing = FALSE;
#else #else
int flags_local = 0; int flags_local = O_NONBLOCK | O_NOCTTY;
/* Map 'flags' to the OS-specific settings. */ /* Map 'flags' to the OS-specific settings. */
if (flags & (SP_MODE_READ | SP_MODE_WRITE)) if (flags & (SP_MODE_READ | SP_MODE_WRITE))
@ -648,8 +651,6 @@ enum sp_return sp_open(struct sp_port *port, enum sp_mode flags)
flags_local |= O_RDONLY; flags_local |= O_RDONLY;
else if (flags & SP_MODE_WRITE) else if (flags & SP_MODE_WRITE)
flags_local |= O_WRONLY; flags_local |= O_WRONLY;
if (flags & SP_MODE_NONBLOCK)
flags_local |= O_NONBLOCK;
if ((port->fd = open(port->name, flags_local)) < 0) if ((port->fd = open(port->name, flags_local)) < 0)
RETURN_FAIL("open() failed"); RETURN_FAIL("open() failed");
@ -701,7 +702,7 @@ enum sp_return sp_open(struct sp_port *port, enum sp_mode flags)
data.term.c_oflag &= ~OFILL; data.term.c_oflag &= ~OFILL;
#endif #endif
data.term.c_lflag &= ~(ISIG | ICANON | ECHO | IEXTEN); data.term.c_lflag &= ~(ISIG | ICANON | ECHO | IEXTEN);
data.term.c_cc[VMIN] = 1; data.term.c_cc[VMIN] = 0;
data.term.c_cc[VTIME] = 0; data.term.c_cc[VTIME] = 0;
/* Ignore modem status lines; enable receiver; leave control lines alone on close. */ /* Ignore modem status lines; enable receiver; leave control lines alone on close. */
@ -729,13 +730,14 @@ enum sp_return sp_close(struct sp_port *port)
#ifdef _WIN32 #ifdef _WIN32
/* Returns non-zero upon success, 0 upon failure. */ /* Returns non-zero upon success, 0 upon failure. */
if (CloseHandle(port->hdl) == 0) if (CloseHandle(port->hdl) == 0)
RETURN_FAIL("CloseHandle() failed"); RETURN_FAIL("port CloseHandle() failed");
port->hdl = INVALID_HANDLE_VALUE; port->hdl = INVALID_HANDLE_VALUE;
if (port->nonblocking) { /* Close event handle created for overlapped reads. */
if (port->read_ovl.hEvent != INVALID_HANDLE_VALUE && CloseHandle(port->read_ovl.hEvent) == 0)
RETURN_FAIL("read event CloseHandle() failed");
/* Close event handle created for overlapped writes. */ /* Close event handle created for overlapped writes. */
if (CloseHandle(port->write_ovl.hEvent) == 0) if (port->write_ovl.hEvent != INVALID_HANDLE_VALUE && CloseHandle(port->write_ovl.hEvent) == 0)
RETURN_FAIL("CloseHandle() failed"); RETURN_FAIL("write event CloseHandle() failed");
}
#else #else
/* Returns 0 upon success, -1 upon failure. */ /* Returns 0 upon success, -1 upon failure. */
if (close(port->fd) == -1) if (close(port->fd) == -1)
@ -806,7 +808,116 @@ enum sp_return sp_drain(struct sp_port *port)
RETURN_OK(); RETURN_OK();
} }
enum sp_return sp_write(struct sp_port *port, const void *buf, size_t count) enum sp_return sp_blocking_write(struct sp_port *port, const void *buf, size_t count, unsigned int timeout)
{
TRACE("%p, %p, %d, %d", port, buf, count, timeout);
CHECK_OPEN_PORT();
if (!buf)
RETURN_ERROR(SP_ERR_ARG, "Null buffer");
if (timeout)
DEBUG("Writing %d bytes to port %s, timeout %d ms", count, port->name, timeout);
else
DEBUG("Writing %d bytes to port %s, no timeout", count, port->name);
if (count == 0)
RETURN_VALUE("0", 0);
#ifdef _WIN32
DWORD bytes_written = 0;
BOOL result;
/* Wait for previous non-blocking write to complete, if any. */
if (port->writing) {
DEBUG("Waiting for previous write to complete");
result = GetOverlappedResult(port->hdl, &port->write_ovl, &bytes_written, TRUE);
port->writing = 0;
if (!result)
RETURN_FAIL("Previous write failed to complete");
DEBUG("Previous write completed");
}
/* Set timeout. */
port->timeouts.WriteTotalTimeoutConstant = timeout;
if (SetCommTimeouts(port->hdl, &port->timeouts) == 0)
RETURN_FAIL("SetCommTimeouts() failed");
/* Start write. */
if (WriteFile(port->hdl, buf, count, NULL, &port->write_ovl) == 0) {
if (GetLastError() == ERROR_IO_PENDING) {
DEBUG("Waiting for write to complete");
GetOverlappedResult(port->hdl, &port->write_ovl, &bytes_written, TRUE);
DEBUG("Write completed, %d/%d bytes written", bytes_written, count);
RETURN_VALUE("%d", bytes_written);
} else {
RETURN_FAIL("WriteFile() failed");
}
} else {
DEBUG("Write completed immediately");
RETURN_VALUE("%d", count);
}
#else
size_t bytes_written = 0;
unsigned char *ptr = (unsigned char *) buf;
struct timeval start, delta, now, end = {0, 0};
fd_set fds;
int result;
if (timeout) {
/* Get time at start of operation. */
gettimeofday(&start, NULL);
/* Define duration of timeout. */
delta.tv_sec = timeout / 1000;
delta.tv_usec = timeout % 1000;
/* Calculate time at which we should give up. */
timeradd(&start, &delta, &end);
}
/* Loop until we have written the requested number of bytes. */
while (bytes_written < count)
{
/* Wait until space is available. */
FD_ZERO(&fds);
FD_SET(port->fd, &fds);
if (timeout) {
gettimeofday(&now, NULL);
if (timercmp(&now, &end, >)) {
DEBUG("write timed out");
RETURN_VALUE("%d", bytes_written);
}
timersub(&end, &now, &delta);
}
result = select(port->fd + 1, NULL, &fds, NULL, timeout ? &delta : NULL);
if (result < 0)
RETURN_FAIL("select() failed");
if (result == 0) {
DEBUG("write timed out");
RETURN_VALUE("%d", bytes_written);
}
/* Do write. */
result = write(port->fd, ptr, count - bytes_written);
if (result < 0) {
if (errno == EAGAIN)
/* This shouldn't happen because we did a select() first, but handle anyway. */
continue;
else
/* This is an actual failure. */
RETURN_FAIL("write() failed");
}
bytes_written += result;
ptr += result;
}
RETURN_VALUE("%d", bytes_written);
#endif
}
enum sp_return sp_nonblocking_write(struct sp_port *port, const void *buf, size_t count)
{ {
TRACE("%p, %p, %d", port, buf, count); TRACE("%p, %p, %d", port, buf, count);
@ -824,9 +935,6 @@ enum sp_return sp_write(struct sp_port *port, const void *buf, size_t count)
DWORD written = 0; DWORD written = 0;
BYTE *ptr = (BYTE *) buf; BYTE *ptr = (BYTE *) buf;
if (port->nonblocking) {
/* Non-blocking write. */
/* Check whether previous write is complete. */ /* Check whether previous write is complete. */
if (port->writing) { if (port->writing) {
if (HasOverlappedIoCompleted(&port->write_ovl)) { if (HasOverlappedIoCompleted(&port->write_ovl)) {
@ -839,6 +947,11 @@ enum sp_return sp_write(struct sp_port *port, const void *buf, size_t count)
} }
} }
/* Set timeout. */
port->timeouts.WriteTotalTimeoutConstant = 0;
if (SetCommTimeouts(port->hdl, &port->timeouts) == 0)
RETURN_FAIL("SetCommTimeouts() failed");
/* Keep writing data until the OS has to actually start an async IO for it. /* Keep writing data until the OS has to actually start an async IO for it.
* At that point we know the buffer is full. */ * At that point we know the buffer is full. */
while (written < count) while (written < count)
@ -864,13 +977,6 @@ enum sp_return sp_write(struct sp_port *port, const void *buf, size_t count)
DEBUG("All bytes written immediately."); DEBUG("All bytes written immediately.");
} else {
/* Blocking write. */
if (WriteFile(port->hdl, buf, count, &written, NULL) == 0) {
RETURN_FAIL("WriteFile() failed");
}
}
RETURN_VALUE("%d", written); RETURN_VALUE("%d", written);
#else #else
/* Returns the number of bytes written, or -1 upon failure. */ /* Returns the number of bytes written, or -1 upon failure. */
@ -883,7 +989,105 @@ enum sp_return sp_write(struct sp_port *port, const void *buf, size_t count)
#endif #endif
} }
enum sp_return sp_read(struct sp_port *port, void *buf, size_t count) enum sp_return sp_blocking_read(struct sp_port *port, void *buf, size_t count, unsigned int timeout)
{
TRACE("%p, %p, %d, %d", port, buf, count, timeout);
CHECK_OPEN_PORT();
if (!buf)
RETURN_ERROR(SP_ERR_ARG, "Null buffer");
if (timeout)
DEBUG("Reading %d bytes from port %s, timeout %d ms", count, port->name, timeout);
else
DEBUG("Reading %d bytes from port %s, no timeout", count, port->name);
if (count == 0)
RETURN_VALUE("0", 0);
#ifdef _WIN32
DWORD bytes_read = 0;
/* Set timeout. */
port->timeouts.ReadIntervalTimeout = 0;
port->timeouts.ReadTotalTimeoutConstant = timeout;
if (SetCommTimeouts(port->hdl, &port->timeouts) == 0)
RETURN_FAIL("SetCommTimeouts() failed");
/* Start read. */
if (ReadFile(port->hdl, buf, count, NULL, &port->read_ovl) == 0) {
if (GetLastError() == ERROR_IO_PENDING) {
DEBUG("Waiting for read to complete");
GetOverlappedResult(port->hdl, &port->read_ovl, &bytes_read, TRUE);
DEBUG("Read completed, %d/%d bytes read", bytes_read, count);
RETURN_VALUE("%d", bytes_read);
} else {
RETURN_FAIL("ReadFile() failed");
}
} else {
DEBUG("Read completed immediately");
RETURN_VALUE("%d", count);
}
#else
size_t bytes_read = 0;
unsigned char *ptr = (unsigned char *) buf;
struct timeval start, delta, now, end = {0, 0};
fd_set fds;
int result;
if (timeout) {
/* Get time at start of operation. */
gettimeofday(&start, NULL);
/* Define duration of timeout. */
delta.tv_sec = timeout / 1000;
delta.tv_usec = timeout % 1000;
/* Calculate time at which we should give up. */
timeradd(&start, &delta, &end);
}
/* Loop until we have the requested number of bytes. */
while (bytes_read < count)
{
/* Wait until data is available. */
FD_ZERO(&fds);
FD_SET(port->fd, &fds);
if (timeout) {
gettimeofday(&now, NULL);
if (timercmp(&now, &end, >))
/* Timeout has expired. */
RETURN_VALUE("%d", bytes_read);
timersub(&end, &now, &delta);
}
result = select(port->fd + 1, &fds, NULL, NULL, timeout ? &delta : NULL);
if (result < 0)
RETURN_FAIL("select() failed");
if (result == 0) {
DEBUG("read timed out");
RETURN_VALUE("%d", bytes_read);
}
/* Do read. */
result = read(port->fd, ptr, count - bytes_read);
if (result < 0) {
if (errno == EAGAIN)
/* This shouldn't happen because we did a select() first, but handle anyway. */
continue;
else
/* This is an actual failure. */
RETURN_FAIL("read() failed");
}
bytes_read += result;
ptr += result;
}
RETURN_VALUE("%d", bytes_read);
#endif
}
enum sp_return sp_nonblocking_read(struct sp_port *port, void *buf, size_t count)
{ {
TRACE("%p, %p, %d", port, buf, count); TRACE("%p, %p, %d", port, buf, count);
@ -895,19 +1099,29 @@ enum sp_return sp_read(struct sp_port *port, void *buf, size_t count)
DEBUG("Reading up to %d bytes from port %s", count, port->name); DEBUG("Reading up to %d bytes from port %s", count, port->name);
#ifdef _WIN32 #ifdef _WIN32
DWORD bytes_read = 0; DWORD bytes_read;
/* Returns non-zero upon success, 0 upon failure. */ /* Set timeout. */
if (ReadFile(port->hdl, buf, count, &bytes_read, NULL) == 0) port->timeouts.ReadIntervalTimeout = MAXDWORD;
port->timeouts.ReadTotalTimeoutConstant = 0;
if (SetCommTimeouts(port->hdl, &port->timeouts) == 0)
RETURN_FAIL("SetCommTimeouts() failed");
/* Do read. */
if (ReadFile(port->hdl, buf, count, NULL, &port->read_ovl) == 0)
RETURN_FAIL("ReadFile() failed"); RETURN_FAIL("ReadFile() failed");
/* Get number of bytes read. */
GetOverlappedResult(port->hdl, &port->read_ovl, &bytes_read, TRUE);
RETURN_VALUE("%d", bytes_read); RETURN_VALUE("%d", bytes_read);
#else #else
ssize_t bytes_read; ssize_t bytes_read;
/* Returns the number of bytes read, or -1 upon failure. */ /* Returns the number of bytes read, or -1 upon failure. */
if ((bytes_read = read(port->fd, buf, count)) < 0) { if ((bytes_read = read(port->fd, buf, count)) < 0) {
if (port->nonblocking && errno == EAGAIN) if (errno == EAGAIN)
/* Port is opened in nonblocking mode and there are no bytes available. */ /* No bytes available. */
bytes_read = 0; bytes_read = 0;
else else
/* This is an actual failure. */ /* This is an actual failure. */