+ return ret;
+#endif
+}
+
+/**
+ * Read a number of bytes from the specified serial port.
+ *
+ * @param fd File descriptor of the serial port.
+ * @param buf Buffer where to store the bytes that are read.
+ * @param count The number of bytes to read.
+ *
+ * @return The number of bytes read, or -1 upon failure.
+ */
+SR_PRIV int serial_read(int fd, void *buf, size_t count)
+{
+ sr_spew("FD %d: Reading %d bytes.", fd, count);
+
+#ifdef _WIN32
+ DWORD tmp = 0;
+
+ /* FIXME */
+ /* Returns non-zero upon success, 0 upon failure. */
+ return ReadFile(hdl, buf, count, &tmp, NULL);
+#else
+ ssize_t ret;
+
+ /* Returns the number of bytes read, or -1 upon failure. */
+ ret = read(fd, buf, count);
+ if (ret < 0) {
+ /*
+ * Should be sr_err(), but that would yield lots of
+ * "Resource temporarily unavailable" messages.
+ */
+ sr_spew("FD %d: Read error: %s.", fd, strerror(errno));
+ } else if ((size_t)ret != count) {
+ sr_spew("FD %d: Only read %d/%d bytes.", fd, ret, count);
+ }
+
+ return ret;
+#endif
+}
+
+/**
+ * Create a backup of the current parameters of the specified serial port.
+ *
+ * @param fd File descriptor of the serial port.
+ *
+ * @return Pointer to a struct termios upon success, NULL upon errors.
+ * It is the caller's responsibility to g_free() the pointer if no
+ * longer needed.
+ */
+SR_PRIV void *serial_backup_params(int fd)
+{
+ sr_dbg("FD %d: Creating serial parameters backup.", fd);
+
+#ifdef _WIN32
+ /* TODO */
+#else
+ struct termios *term;
+
+ if (!(term = g_try_malloc(sizeof(struct termios)))) {
+ sr_err("termios struct malloc failed.");
+ return NULL;
+ }
+
+ /* Returns 0 upon success, -1 upon failure. */
+ if (tcgetattr(fd, term) < 0) {
+ sr_err("FD %d: Error getting serial parameters: %s.",
+ fd, strerror(errno));
+ g_free(term);
+ return NULL;
+ }
+
+ return term;
+#endif
+}
+
+/**
+ * Restore serial port settings from a previously created backup.
+ *
+ * @param fd File descriptor of the serial port.
+ * @param backup Pointer to a struct termios which contains the settings
+ * to restore.
+ *
+ * @return 0 upon success, -1 upon failure.
+ */
+SR_PRIV int serial_restore_params(int fd, void *backup)
+{
+ sr_dbg("FD %d: Restoring serial parameters from backup.", fd);
+
+ if (!backup) {
+ sr_err("FD %d: Cannot restore serial params (NULL).", fd);
+ return -1;
+ }
+
+#ifdef _WIN32
+ /* TODO */
+#else
+ int ret;
+
+ /* Returns 0 upon success, -1 upon failure. */
+ if ((ret = tcsetattr(fd, TCSADRAIN, (struct termios *)backup)) < 0) {
+ sr_err("FD %d: Error restoring serial parameters: %s.",
+ fd, strerror(errno));
+ }
+
+ return ret;
+#endif
+}
+
+/**
+ * Set serial parameters for the specified serial port.
+ *
+ * @param baudrate The baudrate to set.
+ * @param bits The number of data bits to use.
+ * @param parity The parity setting to use (0 = none, 1 = even, 2 = odd).
+ * @param stopbits The number of stop bits to use (1 or 2).
+ * @param flowcontrol The flow control settings to use (0 = none, 1 = RTS/CTS,
+ * 2 = XON/XOFF).
+ *
+ * @return SR_OK upon success, SR_ERR upon failure.
+ */
+SR_PRIV int serial_set_params(int fd, int baudrate, int bits, int parity,
+ int stopbits, int flowcontrol)
+{
+ sr_dbg("FD %d: Setting serial parameters.", fd);
+
+#ifdef _WIN32
+ DCB dcb;
+
+ if (!GetCommState(hdl, &dcb)) {
+ /* TODO: Error handling. */
+ return SR_ERR;
+ }
+
+ switch (baudrate) {
+ /* TODO: Support for higher baud rates. */
+ case 115200:
+ dcb.BaudRate = CBR_115200;
+ break;
+ case 57600:
+ dcb.BaudRate = CBR_57600;
+ break;
+ case 38400:
+ dcb.BaudRate = CBR_38400;
+ break;
+ case 19200:
+ dcb.BaudRate = CBR_19200;
+ break;
+ case 9600:
+ dcb.BaudRate = CBR_9600;
+ break;
+ case 4800:
+ dcb.BaudRate = CBR_4800;
+ break;
+ case 2400:
+ dcb.BaudRate = CBR_2400;
+ break;
+ default:
+ sr_err("Unsupported baudrate: %d.", baudrate);
+ return SR_ERR;
+ }
+ dcb.ByteSize = bits;
+ dcb.Parity = NOPARITY; /* TODO: Don't hardcode. */
+ dcb.StopBits = ONESTOPBIT; /* TODO: Don't hardcode. */
+
+ if (!SetCommState(hdl, &dcb)) {
+ /* TODO: Error handling. */
+ return SR_ERR;
+ }
+#else
+ struct termios term;
+ speed_t baud;
+
+ sr_dbg("FD %d: Getting terminal settings.", fd);
+ if (tcgetattr(fd, &term) < 0) {
+ sr_err("tcgetattr() error: %s.", strerror(errno));
+ return SR_ERR;
+ }
+
+ switch (baudrate) {
+ case 50:
+ baud = B50;
+ break;
+ case 75:
+ baud = B75;
+ break;
+ case 110:
+ baud = B110;
+ break;
+ case 134:
+ baud = B134;
+ break;
+ case 150:
+ baud = B150;
+ break;
+ case 200:
+ baud = B200;
+ break;
+ case 300:
+ baud = B300;
+ break;
+ case 600:
+ baud = B600;
+ break;
+ case 1200:
+ baud = B1200;
+ break;
+ case 1800:
+ baud = B1800;
+ break;
+ case 2400:
+ baud = B2400;
+ break;
+ case 4800:
+ baud = B4800;
+ break;
+ case 9600:
+ baud = B9600;
+ break;
+ case 19200:
+ baud = B19200;
+ break;
+ case 38400:
+ baud = B38400;
+ break;
+ case 57600:
+ baud = B57600;
+ break;
+ case 115200:
+ baud = B115200;
+ break;
+ case 230400:
+ baud = B230400;
+ break;
+#ifndef __APPLE__
+ case 460800:
+ baud = B460800;
+ break;
+#endif
+ default:
+ sr_err("Unsupported baudrate: %d.", baudrate);
+ return SR_ERR;
+ }
+
+ sr_dbg("FD %d: Configuring output baudrate to %d (%d).",
+ fd, baudrate, baud);
+ if (cfsetospeed(&term, baud) < 0) {
+ sr_err("cfsetospeed() error: %ѕ.", strerror(errno));
+ return SR_ERR;
+ }
+
+ sr_dbg("FD %d: Configuring input baudrate to %d (%d).",
+ fd, baudrate, baud);
+ if (cfsetispeed(&term, baud) < 0) {
+ sr_err("cfsetispeed() error: %ѕ.", strerror(errno));
+ return SR_ERR;
+ }
+
+ sr_dbg("FD %d: Configuring %d data bits.", fd, bits);
+ term.c_cflag &= ~CSIZE;
+ switch (bits) {
+ case 8:
+ term.c_cflag |= CS8;
+ break;
+ case 7:
+ term.c_cflag |= CS7;
+ break;
+ default:
+ sr_err("Unsupported data bits number: %d.", bits);
+ return SR_ERR;
+ }
+
+ sr_dbg("FD %d: Configuring %d stop bits.", fd, stopbits);
+ term.c_cflag &= ~CSTOPB;
+ switch (stopbits) {
+ case 1:
+ /* Do nothing, a cleared CSTOPB entry means "1 stop bit". */
+ break;
+ case 2:
+ term.c_cflag |= CSTOPB;
+ break;
+ default:
+ sr_err("Unsupported stopbits number: %d.", stopbits);
+ return SR_ERR;
+ }
+
+ term.c_iflag &= ~(IXON | IXOFF);
+ term.c_cflag &= ~CRTSCTS;
+ switch (flowcontrol) {
+ case 0:
+ /* No flow control. */
+ sr_dbg("FD %d: Configuring no flow control.", fd);
+ break;
+ case 1:
+ sr_dbg("FD %d: Configuring RTS/CTS flow control.", fd);
+ term.c_cflag |= CRTSCTS;
+ break;
+ case 2:
+ sr_dbg("FD %d: Configuring XON/XOFF flow control.", fd);
+ term.c_iflag |= IXON | IXOFF;
+ break;
+ default:
+ sr_err("Unsupported flow control setting: %d.", flowcontrol);
+ return SR_ERR;
+ }
+
+ term.c_iflag &= ~IGNPAR;
+ term.c_cflag &= ~(PARODD | PARENB);
+ switch (parity) {
+ case SERIAL_PARITY_NONE:
+ sr_dbg("FD %d: Configuring no parity.", fd);
+ term.c_iflag |= IGNPAR;
+ break;
+ case SERIAL_PARITY_EVEN:
+ sr_dbg("FD %d: Configuring even parity.", fd);
+ term.c_cflag |= PARENB;
+ break;
+ case SERIAL_PARITY_ODD:
+ sr_dbg("FD %d: Configuring odd parity.", fd);
+ term.c_cflag |= PARENB | PARODD;
+ break;
+ default:
+ sr_err("Unsupported parity setting: %d.", parity);
+ return SR_ERR;
+ }
+
+ /* Do NOT translate carriage return to newline on input. */
+ term.c_iflag &= ~(ICRNL);
+
+ /* Disable canonical mode, and don't echo input characters. */
+ term.c_lflag &= ~(ICANON | ECHO);
+
+ /* Write the configured settings. */
+ if (tcsetattr(fd, TCSADRAIN, &term) < 0) {
+ sr_err("tcsetattr() error: %ѕ.", strerror(errno));
+ return SR_ERR;
+ }
+#endif
+
+ return SR_OK;
+}
+
+#define SERIAL_COMM_SPEC "^(\\d+)/([78])([neo])([12])$"
+SR_PRIV int serial_set_paramstr(int fd, const char *paramstr)
+{
+ GRegex *reg;
+ GMatchInfo *match;
+ int speed, databits, parity, stopbits;
+ char *mstr;
+
+ speed = databits = parity = stopbits = 0;
+ reg = g_regex_new(SERIAL_COMM_SPEC, 0, 0, NULL);
+ if (g_regex_match(reg, paramstr, 0, &match)) {
+ if ((mstr = g_match_info_fetch(match, 1)))
+ speed = strtoul(mstr, NULL, 10);
+ g_free(mstr);
+ if ((mstr = g_match_info_fetch(match, 2)))
+ databits = strtoul(mstr, NULL, 10);
+ g_free(mstr);
+ if ((mstr = g_match_info_fetch(match, 3))) {
+ switch (mstr[0]) {
+ case 'n':
+ parity = SERIAL_PARITY_NONE;
+ break;
+ case 'e':
+ parity = SERIAL_PARITY_EVEN;
+ break;
+ case 'o':
+ parity = SERIAL_PARITY_ODD;
+ break;
+ }
+ }
+ g_free(mstr);
+ if ((mstr = g_match_info_fetch(match, 4)))
+ stopbits = strtoul(mstr, NULL, 10);
+ g_free(mstr);
+ }
+ g_match_info_unref(match);
+ g_regex_unref(reg);
+
+ if (speed)
+ return serial_set_params(fd, speed, databits, parity, stopbits, 0);
+ else
+ return SR_ERR_ARG;
+}
+
+SR_PRIV int serial_readline(int fd, char **buf, int *buflen,
+ uint64_t timeout_ms)
+{
+ uint64_t start;
+ int maxlen, len;
+
+ timeout_ms *= 1000;
+ start = g_get_monotonic_time();
+
+ maxlen = *buflen;
+ *buflen = len = 0;
+ while(1) {
+ len = maxlen - *buflen - 1;
+ if (len < 1)
+ break;
+ len = serial_read(fd, *buf + *buflen, 1);
+ if (len > 0) {
+ *buflen += len;
+ *(*buf + *buflen) = '\0';
+ if (*buflen > 0 && *(*buf + *buflen - 1) == '\r') {
+ /* Strip LF and terminate. */
+ *(*buf + --*buflen) = '\0';
+ break;
+ }
+ }
+ if (g_get_monotonic_time() - start > timeout_ms)
+ /* Timeout */
+ break;
+ g_usleep(2000);
+ }
+ sr_dbg("Received %d: '%s'.", *buflen, *buf);
+
+ return SR_OK;
+}