]> sigrok.org Git - libserialport.git/blobdiff - serialport.c
Implement non-blocking I/O on Windows.
[libserialport.git] / serialport.c
index f426fca79efef55d4c2d8b304e22aca869048d37..a57f1773b856d6d569b337cce8722f604db2c437 100644 (file)
 
 struct sp_port {
        char *name;
+       int nonblocking;
 #ifdef _WIN32
        HANDLE hdl;
+       OVERLAPPED write_ovl;
+       BYTE *write_buf;
+       BOOL writing;
 #else
        int fd;
 #endif
@@ -153,7 +157,7 @@ void (*sp_debug_handler)(const char *format, ...) = sp_default_debug_handler;
 #define RETURN_FAIL(msg) do { DEBUG_FAIL(msg); return SP_ERR_FAIL; } while (0)
 #define RETURN_VALUE(fmt, x) do { DEBUG("%s returning " fmt, __func__, x); return x; } while (0)
 #define SET_ERROR(val, err, msg) do { DEBUG_ERROR(err, msg); val = err; } while (0)
-#define SET_FAIL(val, msg) do { DEBUG_FAIL(msg); val = err; } while (0)
+#define SET_FAIL(val, msg) do { DEBUG_FAIL(msg); val = SP_ERR_FAIL; } while (0)
 #define TRACE(fmt, ...) DEBUG("%s(" fmt ") called", __func__, ##__VA_ARGS__)
 
 #define TRY(x) do { int ret = x; if (ret != SP_OK) RETURN_CODEVAL(ret); } while (0)
@@ -578,8 +582,11 @@ enum sp_return sp_open(struct sp_port *port, enum sp_mode flags)
 
        DEBUG("Opening port %s", port->name);
 
+       port->nonblocking = (flags & SP_MODE_NONBLOCK) ? 1 : 0;
+
 #ifdef _WIN32
        DWORD desired_access = 0, flags_and_attributes = 0;
+       COMMTIMEOUTS timeouts;
        char *escaped_port_name;
 
        /* Prefix port name with '\\.\' to work with ports above COM9. */
@@ -603,6 +610,31 @@ enum sp_return sp_open(struct sp_port *port, enum sp_mode flags)
 
        if (port->hdl == INVALID_HANDLE_VALUE)
                RETURN_FAIL("CreateFile() failed");
+
+       /* All timeouts disabled. */
+       timeouts.ReadIntervalTimeout = 0;
+       timeouts.ReadTotalTimeoutMultiplier = 0;
+       timeouts.ReadTotalTimeoutConstant = 0;
+       timeouts.WriteTotalTimeoutMultiplier = 0;
+       timeouts.WriteTotalTimeoutConstant = 0;
+
+       if (port->nonblocking) {
+               /* 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->write_buf = NULL;
+               port->writing = FALSE;
+       }
+
+       if (SetCommTimeouts(port->hdl, &timeouts) == 0) {
+               sp_close(port);
+               RETURN_FAIL("SetCommTimeouts() failed");
+       }
 #else
        int flags_local = 0;
        struct port_data data;
@@ -665,6 +697,14 @@ enum sp_return sp_close(struct sp_port *port)
        if (CloseHandle(port->hdl) == 0)
                RETURN_FAIL("CloseHandle() failed");
        port->hdl = INVALID_HANDLE_VALUE;
+       if (port->nonblocking) {
+               if (port->writing)
+                       /* Write should have been stopped by closing the port, so safe to free buffer. */
+                       free(port->write_buf);
+               /* Close event handle created for overlapped writes. */
+               if (CloseHandle(port->write_ovl.hEvent) == 0)
+                       RETURN_FAIL("CloseHandle() failed");
+       }
 #else
        /* Returns 0 upon success, -1 upon failure. */
        if (close(port->fd) == -1)
@@ -684,7 +724,7 @@ enum sp_return sp_flush(struct sp_port *port, enum sp_buffer buffers)
        if (buffers > SP_BUF_BOTH)
                RETURN_ERROR(SP_ERR_ARG, "Invalid buffer selection");
 
-       const char *buffer_names[] = {"input", "output", "both"};
+       const char *buffer_names[] = {"no", "input", "output", "both"};
 
        DEBUG("Flushing %s buffers on port %s", buffer_names[buffers], port->name);
 
@@ -746,12 +786,59 @@ enum sp_return sp_write(struct sp_port *port, const void *buf, size_t count)
 
        DEBUG("Writing up to %d bytes to port %s", count, port->name);
 
+       if (count == 0)
+               RETURN_VALUE("0", 0);
+
 #ifdef _WIN32
        DWORD written = 0;
 
-       /* Returns non-zero upon success, 0 upon failure. */
-       if (WriteFile(port->hdl, buf, count, &written, NULL) == 0)
-               RETURN_FAIL("WriteFile() failed");
+       if (port->nonblocking) {
+               /* Non-blocking write. */
+
+               /* Check whether previous write is complete. */
+               if (port->writing) {
+                       if (HasOverlappedIoCompleted(&port->write_ovl)) {
+                               DEBUG("Previous write completed");
+                               port->writing = 0;
+                               free(port->write_buf);
+                               port->write_buf = NULL;
+                       } else {
+                               DEBUG("Previous write not complete");
+                               /* Can't take a new write until the previous one finishes. */
+                               RETURN_VALUE("0", 0);
+                       }
+               }
+
+               /* Copy user buffer. */
+               if (!(port->write_buf = malloc(count)))
+                       RETURN_ERROR(SP_ERR_MEM, "buffer copy malloc failed");
+               memcpy(port->write_buf, buf, count);
+
+               /* Start asynchronous write. */
+               if (WriteFile(port->hdl, buf, count, NULL, &port->write_ovl) == 0) {
+                       if (GetLastError() == ERROR_IO_PENDING) {
+                               DEBUG("Asynchronous write started");
+                               port->writing = 1;
+                               RETURN_VALUE("%d", count);
+                       } else {
+                               free(port->write_buf);
+                               port->write_buf = NULL;
+                               /* Actual failure of some kind. */
+                               RETURN_FAIL("WriteFile() failed");
+                       }
+               } else {
+                       DEBUG("Write completed immediately");
+                       free(port->write_buf);
+                       port->write_buf = NULL;
+                       RETURN_VALUE("%d", count);
+               }
+       } else {
+               /* Blocking write. */
+               if (WriteFile(port->hdl, buf, count, &written, NULL) == 0) {
+                       RETURN_FAIL("WriteFile() failed");
+               }
+       }
+
        RETURN_VALUE("%d", written);
 #else
        /* Returns the number of bytes written, or -1 upon failure. */
@@ -786,8 +873,14 @@ enum sp_return sp_read(struct sp_port *port, void *buf, size_t count)
        ssize_t bytes_read;
 
        /* Returns the number of bytes read, or -1 upon failure. */
-       if ((bytes_read = read(port->fd, buf, count)) < 0)
-               RETURN_FAIL("read() failed");
+       if ((bytes_read = read(port->fd, buf, count)) < 0) {
+               if (port->nonblocking && errno == EAGAIN)
+                       /* Port is opened in nonblocking mode and there are no bytes available. */
+                       bytes_read = 0;
+               else
+                       /* This is an actual failure. */
+                       RETURN_FAIL("read() failed");
+       }
        RETURN_VALUE("%d", bytes_read);
 #endif
 }
@@ -1467,13 +1560,27 @@ static enum sp_return set_config(struct sp_port *port, struct port_data *data,
 enum sp_return sp_new_config(struct sp_port_config **config_ptr)
 {
        TRACE("%p", config_ptr);
+       struct sp_port_config *config;
 
        if (!config_ptr)
                RETURN_ERROR(SP_ERR_ARG, "Null result pointer");
 
-       if (!(*config_ptr = malloc(sizeof(struct sp_port_config))))
+       *config_ptr = NULL;
+
+       if (!(config = malloc(sizeof(struct sp_port_config))))
                RETURN_ERROR(SP_ERR_MEM, "config malloc failed");
 
+       config->baudrate = -1;
+       config->bits = -1;
+       config->parity = -1;
+       config->stopbits = -1;
+       config->rts = -1;
+       config->cts = -1;
+       config->dtr = -1;
+       config->dsr = -1;
+
+       *config_ptr = config;
+
        RETURN_OK();
 }