int hs_poll(hs_poll_source *sources, unsigned int count, int timeout) { assert(sources); assert(count); assert(count <= HS_POLL_MAX_SOURCES); fd_set fds; uint64_t start; int maxfd, r; FD_ZERO(&fds); maxfd = 0; for (unsigned int i = 0; i < count; i++) { if (sources[i].desc >= FD_SETSIZE) { for (unsigned int j = i; j < count; j++) sources[j].ready = 0; return hs_error(HS_ERROR_SYSTEM, "Cannot select() on descriptor %d (too big)", sources[i].desc); } FD_SET(sources[i].desc, &fds); sources[i].ready = 0; if (sources[i].desc > maxfd) maxfd = sources[i].desc; } start = hs_millis(); restart: if (timeout >= 0) { int adjusted_timeout; struct timeval tv; adjusted_timeout = hs_adjust_timeout(timeout, start); tv.tv_sec = adjusted_timeout / 1000; tv.tv_usec = (adjusted_timeout % 1000) * 1000; r = select(maxfd + 1, &fds, NULL, NULL, &tv); } else { r = select(maxfd + 1, &fds, NULL, NULL, NULL); } if (r < 0) { if (errno == EINTR) goto restart; return hs_error(HS_ERROR_SYSTEM, "poll() failed: %s", strerror(errno)); } if (!r) return 0; for (unsigned int i = 0; i < count; i++) sources[i].ready = !!FD_ISSET(sources[i].desc, &fds); return r; }
int hs_device_open(hs_device *dev, hs_handle **rh) { assert(dev); assert(rh); if (dev->state != HS_DEVICE_STATUS_ONLINE) return hs_error(HS_ERROR_NOT_FOUND, "Device '%s' is not connected", dev->path); return (*dev->vtable->open)(dev, rh); }
ssize_t hs_hid_send_feature_report(hs_handle *h, const uint8_t *buf, size_t size) { assert(h); assert(h->dev->type == HS_DEVICE_TYPE_HID); assert(buf); if (size < 2) return 0; // Timeout behavior? BOOL success = HidD_SetFeature(h->handle, (char *)buf, (DWORD)size); if (!success) return hs_error(HS_ERROR_IO, "I/O error while writing to '%s'", h->dev->path); return (ssize_t)size; }
int hs_poll(hs_poll_source *sources, unsigned int count, int timeout) { assert(sources); assert(count); assert(count <= HS_POLL_MAX_SOURCES); HANDLE handles[HS_POLL_MAX_SOURCES]; for (unsigned int i = 0; i < count; i++) { handles[i] = sources[i].desc; sources[i].ready = 0; } DWORD ret = WaitForMultipleObjects((DWORD)count, handles, FALSE, timeout < 0 ? INFINITE : (DWORD)timeout); if (ret == WAIT_FAILED) return hs_error(HS_ERROR_SYSTEM, "WaitForMultipleObjects() failed: %s", hs_win32_strerror(0)); for (unsigned int i = 0; i < count; i++) sources[i].ready = (i == ret); return ret < count; }
ssize_t hs_hid_read(hs_handle *h, uint8_t *buf, size_t size, int timeout) { assert(h); assert(h->dev->type == HS_DEVICE_TYPE_HID); assert(buf); assert(size); ssize_t r; if (timeout) { struct pollfd pfd; uint64_t start; pfd.events = POLLIN; pfd.fd = h->fd; start = hs_millis(); restart: r = poll(&pfd, 1, hs_adjust_timeout(timeout, start)); if (r < 0) { if (errno == EINTR) goto restart; return hs_error(HS_ERROR_SYSTEM, "poll('%s') failed: %s", h->dev->path, strerror(errno)); } if (!r) return 0; } if (h->numbered_reports) { /* Work around a hidraw bug introduced in Linux 2.6.28 and fixed in Linux 2.6.34, see https://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/commit/?id=5a38f2c7c4dd53d5be097930902c108e362584a3 */ if (detect_kernel26_byte_bug()) { if (size + 1 > h->buf_size) { free(h->buf); h->buf_size = 0; h->buf = malloc(size + 1); if (!h->buf) return hs_error(HS_ERROR_MEMORY, NULL); h->buf_size = size + 1; } r = read(h->fd, h->buf, size + 1); if (r > 0) memcpy(buf, h->buf + 1, (size_t)--r); } else { r = read(h->fd, buf, size); } } else { r = read(h->fd, buf + 1, size - 1); if (r > 0) { buf[0] = 0; r++; } } if (r < 0) { switch (errno) { case EAGAIN: #if defined(EWOULDBLOCK) && EWOULDBLOCK != EAGAIN case EWOULDBLOCK: #endif return 0; case EIO: case ENXIO: return hs_error(HS_ERROR_IO, "I/O error while reading from '%s'", h->dev->path); } return hs_error(HS_ERROR_SYSTEM, "read('%s') failed: %s", h->dev->path, strerror(errno)); } return r; }
static int open_hidraw_device(hs_device *dev, hs_handle **rh) { hs_handle *h; struct hidraw_report_descriptor report; int size, r; h = calloc(1, sizeof(*h)); if (!h) { r = hs_error(HS_ERROR_MEMORY, NULL); goto error; } h->dev = hs_device_ref(dev); restart: h->fd = open(dev->path, O_RDWR | O_CLOEXEC | O_NONBLOCK); if (h->fd < 0) { switch (errno) { case EINTR: goto restart; case EACCES: r = hs_error(HS_ERROR_ACCESS, "Permission denied for device '%s'", dev->path); break; case EIO: case ENXIO: case ENODEV: r = hs_error(HS_ERROR_IO, "I/O error while opening device '%s'", dev->path); break; case ENOENT: case ENOTDIR: r = hs_error(HS_ERROR_NOT_FOUND, "Device '%s' not found", dev->path); break; default: r = hs_error(HS_ERROR_SYSTEM, "open('%s') failed: %s", dev->path, strerror(errno)); break; } goto error; } r = ioctl(h->fd, HIDIOCGRDESCSIZE, &size); if (r < 0) { r = hs_error(HS_ERROR_SYSTEM, "ioctl('%s', HIDIOCGRDESCSIZE) failed: %s", h->dev->path, strerror(errno)); goto error; } report.size = (uint32_t)size; r = ioctl(h->fd, HIDIOCGRDESC, &report); if (r < 0) { r = hs_error(HS_ERROR_SYSTEM, "ioctl('%s', HIDIOCGRDESC) failed: %s", h->dev->path, strerror(errno)); goto error; } parse_descriptor(h, &report); *rh = h; return 0; error: hs_handle_close(h); return r; }
int hs_serial_set_config(hs_handle *h, const hs_serial_config *config) { assert(h); assert(config); DCB dcb; BOOL success; dcb.DCBlength = sizeof(dcb); success = GetCommState(h->handle, &dcb); if (!success) return hs_error(HS_ERROR_SYSTEM, "GetCommState() failed on '%s': %s", h->dev->path, hs_win32_strerror(0)); switch (config->baudrate) { case 0: break; case 110: case 134: case 150: case 200: case 300: case 600: case 1200: case 1800: case 2400: case 4800: case 9600: case 19200: case 38400: case 57600: case 115200: case 230400: dcb.BaudRate = config->baudrate; break; default: return hs_error(HS_ERROR_SYSTEM, "Unsupported baud rate value: %u", config->baudrate); } switch (config->databits) { case 0: break; case 5: case 6: case 7: case 8: dcb.ByteSize = (BYTE)config->databits; break; default: return hs_error(HS_ERROR_SYSTEM, "Invalid data bits setting: %u", config->databits); } switch (config->stopbits) { case 0: break; case 1: dcb.StopBits = ONESTOPBIT; break; case 2: dcb.StopBits = TWOSTOPBITS; break; default: return hs_error(HS_ERROR_SYSTEM, "Invalid stop bits setting: %u", config->stopbits); } switch (config->parity) { case 0: break; case HS_SERIAL_CONFIG_PARITY_OFF: dcb.fParity = FALSE; dcb.Parity = NOPARITY; break; case HS_SERIAL_CONFIG_PARITY_EVEN: dcb.fParity = TRUE; dcb.Parity = EVENPARITY; break; case HS_SERIAL_CONFIG_PARITY_ODD: dcb.fParity = TRUE; dcb.Parity = ODDPARITY; break; case HS_SERIAL_CONFIG_PARITY_MARK: dcb.fParity = TRUE; dcb.Parity = MARKPARITY; break; case HS_SERIAL_CONFIG_PARITY_SPACE: dcb.fParity = TRUE; dcb.Parity = SPACEPARITY; break; default: return hs_error(HS_ERROR_SYSTEM, "Invalid parity setting: %d", config->parity); } switch (config->rts) { case 0: break; case HS_SERIAL_CONFIG_RTS_OFF: dcb.fRtsControl = RTS_CONTROL_DISABLE; dcb.fOutxCtsFlow = FALSE; break; case HS_SERIAL_CONFIG_RTS_ON: dcb.fRtsControl = RTS_CONTROL_ENABLE; dcb.fOutxCtsFlow = FALSE; break; case HS_SERIAL_CONFIG_RTS_FLOW: dcb.fRtsControl = RTS_CONTROL_HANDSHAKE; dcb.fOutxCtsFlow = TRUE; break; default: return hs_error(HS_ERROR_SYSTEM, "Invalid RTS setting: %d", config->rts); } switch (config->dtr) { case 0: break; case HS_SERIAL_CONFIG_DTR_OFF: dcb.fDtrControl = DTR_CONTROL_DISABLE; dcb.fOutxDsrFlow = FALSE; break; case HS_SERIAL_CONFIG_DTR_ON: dcb.fDtrControl = DTR_CONTROL_ENABLE; dcb.fOutxDsrFlow = FALSE; break; default: return hs_error(HS_ERROR_SYSTEM, "Invalid DTR setting: %d", config->dtr); } switch (config->xonxoff) { case 0: break; case HS_SERIAL_CONFIG_XONXOFF_OFF: dcb.fOutX = FALSE; dcb.fInX = FALSE; break; case HS_SERIAL_CONFIG_XONXOFF_IN: dcb.fOutX = FALSE; dcb.fInX = TRUE; break; case HS_SERIAL_CONFIG_XONXOFF_OUT: dcb.fOutX = TRUE; dcb.fInX = FALSE; break; case HS_SERIAL_CONFIG_XONXOFF_INOUT: dcb.fOutX = TRUE; dcb.fInX = TRUE; break; default: return hs_error(HS_ERROR_SYSTEM, "Invalid XON/XOFF setting: %d", config->xonxoff); } success = SetCommState(h->handle, &dcb); if (!success) return hs_error(HS_ERROR_SYSTEM, "SetCommState() failed on '%s': %s", h->dev->path, hs_win32_strerror(0)); return 0; }
int hs_serial_get_config(hs_handle *h, hs_serial_config *config) { assert(h); assert(config); DCB dcb; BOOL success; dcb.DCBlength = sizeof(dcb); success = GetCommState(h->handle, &dcb); if (!success) return hs_error(HS_ERROR_SYSTEM, "GetCommState() failed on '%s': %s", h->dev->path, hs_win32_strerror(0)); /* 0 is the INVALID value for all parameters, we keep that value if we can't interpret a DCB setting (only a cross-platform subset of it is exposed in hs_serial_config). */ memset(config, 0, sizeof(*config)); config->baudrate = dcb.BaudRate; config->databits = dcb.ByteSize; // There is also ONE5STOPBITS, ignore it for now (and ever, probably) switch (dcb.StopBits) { case ONESTOPBIT: config->stopbits = 1; break; case TWOSTOPBITS: config->stopbits = 2; break; } if (dcb.fParity) { switch (dcb.Parity) { case NOPARITY: config->parity = HS_SERIAL_CONFIG_PARITY_OFF; break; case EVENPARITY: config->parity = HS_SERIAL_CONFIG_PARITY_EVEN; break; case ODDPARITY: config->parity = HS_SERIAL_CONFIG_PARITY_ODD; break; case MARKPARITY: config->parity = HS_SERIAL_CONFIG_PARITY_MARK; break; case SPACEPARITY: config->parity = HS_SERIAL_CONFIG_PARITY_SPACE; break; } } else { config->parity = HS_SERIAL_CONFIG_PARITY_OFF; } switch (dcb.fRtsControl) { case RTS_CONTROL_DISABLE: config->rts = HS_SERIAL_CONFIG_RTS_OFF; break; case RTS_CONTROL_ENABLE: config->rts = HS_SERIAL_CONFIG_RTS_ON; break; case RTS_CONTROL_HANDSHAKE: config->rts = HS_SERIAL_CONFIG_RTS_FLOW; break; } switch (dcb.fDtrControl) { case DTR_CONTROL_DISABLE: config->dtr = HS_SERIAL_CONFIG_DTR_OFF; break; case DTR_CONTROL_ENABLE: config->dtr = HS_SERIAL_CONFIG_DTR_ON; break; } if (dcb.fInX && dcb.fOutX) { config->xonxoff = HS_SERIAL_CONFIG_XONXOFF_INOUT; } else if (dcb.fInX) { config->xonxoff = HS_SERIAL_CONFIG_XONXOFF_IN; } else if (dcb.fOutX) { config->xonxoff = HS_SERIAL_CONFIG_XONXOFF_OUT; } else { config->xonxoff = HS_SERIAL_CONFIG_XONXOFF_OFF; } return 0; }