New API and implementation for blocking and non-blocking I/O.
This commit is contained in:
parent
8fbf876dfd
commit
e3dcf9068e
|
@ -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.
|
||||||
|
|
392
serialport.c
392
serialport.c
|
@ -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. */
|
||||||
/* Close event handle created for overlapped writes. */
|
if (port->read_ovl.hEvent != INVALID_HANDLE_VALUE && CloseHandle(port->read_ovl.hEvent) == 0)
|
||||||
if (CloseHandle(port->write_ovl.hEvent) == 0)
|
RETURN_FAIL("read event CloseHandle() failed");
|
||||||
RETURN_FAIL("CloseHandle() failed");
|
/* Close event handle created for overlapped writes. */
|
||||||
}
|
if (port->write_ovl.hEvent != INVALID_HANDLE_VALUE && CloseHandle(port->write_ovl.hEvent) == 0)
|
||||||
|
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,53 +935,48 @@ 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) {
|
/* Check whether previous write is complete. */
|
||||||
/* Non-blocking write. */
|
if (port->writing) {
|
||||||
|
if (HasOverlappedIoCompleted(&port->write_ovl)) {
|
||||||
/* Check whether previous write is complete. */
|
DEBUG("Previous write completed");
|
||||||
if (port->writing) {
|
port->writing = 0;
|
||||||
if (HasOverlappedIoCompleted(&port->write_ovl)) {
|
} else {
|
||||||
DEBUG("Previous write completed");
|
DEBUG("Previous write not complete");
|
||||||
port->writing = 0;
|
/* Can't take a new write until the previous one finishes. */
|
||||||
} else {
|
RETURN_VALUE("0", 0);
|
||||||
DEBUG("Previous write not complete");
|
|
||||||
/* Can't take a new write until the previous one finishes. */
|
|
||||||
RETURN_VALUE("0", 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Keep writing data until the OS has to actually start an async IO for it.
|
|
||||||
* At that point we know the buffer is full. */
|
|
||||||
while (written < count)
|
|
||||||
{
|
|
||||||
/* Copy first byte of user buffer. */
|
|
||||||
port->pending_byte = *ptr++;
|
|
||||||
|
|
||||||
/* Start asynchronous write. */
|
|
||||||
if (WriteFile(port->hdl, &port->pending_byte, 1, NULL, &port->write_ovl) == 0) {
|
|
||||||
if (GetLastError() == ERROR_IO_PENDING) {
|
|
||||||
DEBUG("Asynchronous write started");
|
|
||||||
port->writing = 1;
|
|
||||||
RETURN_VALUE("%d", ++written);
|
|
||||||
} else {
|
|
||||||
/* Actual failure of some kind. */
|
|
||||||
RETURN_FAIL("WriteFile() failed");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
DEBUG("Single byte written immediately.");
|
|
||||||
written++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
DEBUG("All bytes written immediately.");
|
|
||||||
|
|
||||||
} else {
|
|
||||||
/* Blocking write. */
|
|
||||||
if (WriteFile(port->hdl, buf, count, &written, NULL) == 0) {
|
|
||||||
RETURN_FAIL("WriteFile() failed");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 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.
|
||||||
|
* At that point we know the buffer is full. */
|
||||||
|
while (written < count)
|
||||||
|
{
|
||||||
|
/* Copy first byte of user buffer. */
|
||||||
|
port->pending_byte = *ptr++;
|
||||||
|
|
||||||
|
/* Start asynchronous write. */
|
||||||
|
if (WriteFile(port->hdl, &port->pending_byte, 1, NULL, &port->write_ovl) == 0) {
|
||||||
|
if (GetLastError() == ERROR_IO_PENDING) {
|
||||||
|
DEBUG("Asynchronous write started");
|
||||||
|
port->writing = 1;
|
||||||
|
RETURN_VALUE("%d", ++written);
|
||||||
|
} else {
|
||||||
|
/* Actual failure of some kind. */
|
||||||
|
RETURN_FAIL("WriteFile() failed");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
DEBUG("Single byte written immediately.");
|
||||||
|
written++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DEBUG("All bytes written immediately.");
|
||||||
|
|
||||||
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. */
|
||||||
|
|
Loading…
Reference in New Issue