From: Martin Ling Date: Thu, 7 May 2015 09:07:55 +0000 (+0100) Subject: Add new sp_blocking_read_next() function. X-Git-Tag: libserialport-0.1.1~27 X-Git-Url: https://sigrok.org/gitaction?a=commitdiff_plain;h=e5c2630eeed1825889dfe7cf0aaeb734b047ea74;p=libserialport.git Add new sp_blocking_read_next() function. --- diff --git a/libserialport.h.in b/libserialport.h.in index 9c6842a..a4bd00e 100644 --- a/libserialport.h.in +++ b/libserialport.h.in @@ -1049,6 +1049,34 @@ enum sp_return sp_set_flowcontrol(struct sp_port *port, enum sp_flowcontrol flow */ enum sp_return sp_blocking_read(struct sp_port *port, void *buf, size_t count, unsigned int timeout_ms); +/** + * Read bytes from the specified serial port, returning as soon as any data is + * available. + * + * @warning If your program runs on Unix, defines its own signal handlers, and + * needs to abort blocking reads when these are called, then you + * should not use this function. It repeats system calls that return + * with EINTR. To be able to abort a read from a signal handler, you + * should implement your own blocking read using sp_nonblocking_read() + * together with a blocking method that makes sense for your program. + * E.g. you can obtain the file descriptor for an open port using + * sp_get_port_handle() and use this to call select() or pselect(), + * with appropriate arrangements to return if a signal is received. + * + * @param[in] port Pointer to a port structure. Must not be NULL. + * @param[out] buf Buffer in which to store the bytes read. Must not be NULL. + * @param[in] count Maximum number of bytes to read. Must not be zero. + * @param[in] timeout_ms Timeout in milliseconds, or zero to wait indefinitely. + * + * @return The number of bytes read on success, or a negative error code. If + * the result is zero, the timeout was reached before any bytes were + * available. If timeout_ms is zero, the function will always return + * either at least one byte, or a negative error code. + * + * @since 0.1.1 + */ +enum sp_return sp_blocking_read_next(struct sp_port *port, void *buf, size_t count, unsigned int timeout_ms); + /** * Read bytes from the specified serial port, without blocking. * diff --git a/serialport.c b/serialport.c index fb7adf6..da292ac 100644 --- a/serialport.c +++ b/serialport.c @@ -1071,6 +1071,140 @@ SP_API enum sp_return sp_blocking_read(struct sp_port *port, void *buf, #endif } +SP_API enum sp_return sp_blocking_read_next(struct sp_port *port, void *buf, + size_t count, unsigned int timeout_ms) +{ + TRACE("%p, %p, %d, %d", port, buf, count, timeout_ms); + + CHECK_OPEN_PORT(); + + if (!buf) + RETURN_ERROR(SP_ERR_ARG, "Null buffer"); + + if (count == 0) + RETURN_ERROR(SP_ERR_ARG, "Zero count"); + + if (timeout_ms) + DEBUG_FMT("Reading next max %d bytes from port %s, timeout %d ms", + count, port->name, timeout_ms); + else + DEBUG_FMT("Reading next max %d bytes from port %s, no timeout", + count, port->name); + +#ifdef _WIN32 + DWORD bytes_read = 0; + + /* If timeout_ms == 0, set maximum timeout. */ + DWORD timeout_val = (timeout_ms == 0 ? MAXDWORD - 1 : timeout_ms); + + /* Set timeout. */ + if (port->timeouts.ReadIntervalTimeout != MAXDWORD || + port->timeouts.ReadTotalTimeoutMultiplier != MAXDWORD || + port->timeouts.ReadTotalTimeoutConstant != timeout_val) { + port->timeouts.ReadIntervalTimeout = MAXDWORD; + port->timeouts.ReadTotalTimeoutMultiplier = MAXDWORD; + port->timeouts.ReadTotalTimeoutConstant = timeout_val; + if (SetCommTimeouts(port->hdl, &port->timeouts) == 0) + RETURN_FAIL("SetCommTimeouts() failed"); + } + + /* Loop until we have at least one byte, or timeout is reached. */ + while (bytes_read == 0) { + /* Start read. */ + if (ReadFile(port->hdl, buf, count, NULL, &port->read_ovl)) { + DEBUG("Read completed immediately"); + bytes_read = count; + } else if (GetLastError() == ERROR_IO_PENDING) { + DEBUG("Waiting for read to complete"); + if (GetOverlappedResult(port->hdl, &port->read_ovl, &bytes_read, TRUE) == 0) + RETURN_FAIL("GetOverlappedResult() failed"); + if (bytes_read > 0) { + DEBUG("Read completed"); + } else if (timeout_ms > 0) { + DEBUG("Read timed out"); + break; + } else { + DEBUG("Restarting read"); + } + } else { + RETURN_FAIL("ReadFile() failed"); + } + } + + TRY(restart_wait_if_needed(port, bytes_read)); + + RETURN_INT(bytes_read); + +#else + size_t bytes_read = 0; + struct timeval start, delta, now, end = {0, 0}; + int started = 0; + fd_set fds; + int result; + + if (timeout_ms) { + /* Get time at start of operation. */ + gettimeofday(&start, NULL); + /* Define duration of timeout. */ + delta.tv_sec = timeout_ms / 1000; + delta.tv_usec = (timeout_ms % 1000) * 1000; + /* Calculate time at which we should give up. */ + timeradd(&start, &delta, &end); + } + + FD_ZERO(&fds); + FD_SET(port->fd, &fds); + + /* Loop until we have at least one byte, or timeout is reached. */ + while (bytes_read == 0) { + /* + * Check timeout only if we have run select() at least once, + * to avoid any issues if a short timeout is reached before + * select() is even run. + */ + if (timeout_ms && started) { + gettimeofday(&now, NULL); + if (timercmp(&now, &end, >)) + /* Timeout has expired. */ + break; + timersub(&end, &now, &delta); + } + result = select(port->fd + 1, &fds, NULL, NULL, timeout_ms ? &delta : NULL); + started = 1; + if (result < 0) { + if (errno == EINTR) { + DEBUG("select() call was interrupted, repeating"); + continue; + } else { + RETURN_FAIL("select() failed"); + } + } else if (result == 0) { + /* Timeout has expired. */ + break; + } + + /* Do read. */ + result = read(port->fd, buf, count); + + 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; + } + + if (bytes_read == 0) + DEBUG("Read timed out"); + + RETURN_INT(bytes_read); +#endif +} + SP_API enum sp_return sp_nonblocking_read(struct sp_port *port, void *buf, size_t count) {