/* * libdivecomputer * * Copyright (C) 2008 Jef Driesen * Copyright (C) 2014 Venkatesh Shukla * Copyright (C) 2015 Anton Lundin * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301 USA */ #include // malloc, free #include // strerror #include // errno #include // gettimeofday #include // nanosleep #include #include #include #ifndef __ANDROID__ #define INFO(context, fmt, ...) fprintf(stderr, "INFO: " fmt "\n", ##__VA_ARGS__) #define ERROR(context, fmt, ...) fprintf(stderr, "ERROR: " fmt "\n", ##__VA_ARGS__) #else #include #define INFO(context, fmt, ...) __android_log_print(ANDROID_LOG_DEBUG, __FILE__, "INFO: " fmt "\n", ##__VA_ARGS__) #define ERROR(context, fmt, ...) __android_log_print(ANDROID_LOG_DEBUG, __FILE__, "ERROR: " fmt "\n", ##__VA_ARGS__) #endif //#define SYSERROR(context, errcode) ERROR(__FILE__ ":" __LINE__ ": %s", strerror(errcode)) #define SYSERROR(context, errcode) ; #include /* Verbatim copied libdivecomputer enums to support configure */ typedef enum serial_parity_t { SERIAL_PARITY_NONE, SERIAL_PARITY_EVEN, SERIAL_PARITY_ODD } serial_parity_t; typedef enum serial_flowcontrol_t { SERIAL_FLOWCONTROL_NONE, SERIAL_FLOWCONTROL_HARDWARE, SERIAL_FLOWCONTROL_SOFTWARE } serial_flowcontrol_t; typedef enum serial_queue_t { SERIAL_QUEUE_INPUT = 0x01, SERIAL_QUEUE_OUTPUT = 0x02, SERIAL_QUEUE_BOTH = SERIAL_QUEUE_INPUT | SERIAL_QUEUE_OUTPUT } serial_queue_t; typedef enum serial_line_t { SERIAL_LINE_DCD, // Data carrier detect SERIAL_LINE_CTS, // Clear to send SERIAL_LINE_DSR, // Data set ready SERIAL_LINE_RNG, // Ring indicator } serial_line_t; #define VID 0x0403 // Vendor ID of FTDI #define MAX_BACKOFF 500 // Max milliseconds to wait before timing out. typedef struct serial_t { /* Library context. */ dc_context_t *context; /* * The file descriptor corresponding to the serial port. * Also a libftdi_ftdi_ctx could be used? */ struct ftdi_context *ftdi_ctx; long timeout; /* * Serial port settings are saved into this variable immediately * after the port is opened. These settings are restored when the * serial port is closed. * Saving this using libftdi context or libusb. Search further. * Custom implementation using libftdi functions could be done. */ /* Half-duplex settings */ int halfduplex; unsigned int baudrate; unsigned int nbits; } serial_t; static int serial_ftdi_get_received (serial_t *device) { if (device == NULL) return -1; // EINVAL (Invalid argument) // Direct access is not encouraged. But function implementation // is not available. The return quantity might be anything. // Find out further about its possible values and correct way of // access. int bytes = device->ftdi_ctx->readbuffer_remaining; return bytes; } static int serial_ftdi_get_transmitted (serial_t *device) { if (device == NULL) return -1; // EINVAL (Invalid argument) // This is not possible using libftdi. Look further into it. return -1; } static int serial_ftdi_sleep (serial_t *device, unsigned long timeout) { if (device == NULL) return -1; INFO (device->context, "Sleep: value=%lu", timeout); struct timespec ts; ts.tv_sec = (timeout / 1000); ts.tv_nsec = (timeout % 1000) * 1000000; while (nanosleep (&ts, &ts) != 0) { if (errno != EINTR ) { SYSERROR (device->context, errno); return -1; } } return 0; } // Used internally for opening ftdi devices static int serial_ftdi_open_device (struct ftdi_context *ftdi_ctx) { int accepted_pids[] = { 0x6001, 0x6010, 0x6011, // Suunto (Smart Interface), Heinrichs Weikamp 0xF460, // Oceanic 0xF680, // Suunto 0x87D0, // Cressi (Leonardo) }; int num_accepted_pids = 6; int i, pid, ret; for (i = 0; i < num_accepted_pids; i++) { pid = accepted_pids[i]; ret = ftdi_usb_open (ftdi_ctx, VID, pid); if (ret == -3) // Device not found continue; else return ret; } // No supported devices are attached. return ret; } // // Open the serial port. // Initialise ftdi_context and use it to open the device // //FIXME: ugly forward declaration of serial_ftdi_configure, util we support configure for real... static dc_status_t serial_ftdi_configure (serial_t *device, int baudrate, int databits, int parity, int stopbits, int flowcontrol); static dc_status_t serial_ftdi_open (serial_t **out, dc_context_t *context, const char* name) { if (out == NULL) return -1; // EINVAL (Invalid argument) INFO (context, "Open: name=%s", name ? name : ""); // Allocate memory. serial_t *device = (serial_t *) malloc (sizeof (serial_t)); if (device == NULL) { SYSERROR (context, errno); return DC_STATUS_NOMEMORY; } struct ftdi_context *ftdi_ctx = ftdi_new(); if (ftdi_ctx == NULL) { free(device); SYSERROR (context, errno); return DC_STATUS_NOMEMORY; } // Library context. device->context = context; // Default to blocking reads. device->timeout = -1; // Default to full-duplex. device->halfduplex = 0; device->baudrate = 0; device->nbits = 0; // Initialize device ftdi context ftdi_init(ftdi_ctx); if (ftdi_set_interface(ftdi_ctx,INTERFACE_ANY)) { free(device); ERROR (context, "%s", ftdi_get_error_string(ftdi_ctx)); return DC_STATUS_IO; } if (serial_ftdi_open_device(ftdi_ctx) < 0) { free(device); ERROR (context, "%s", ftdi_get_error_string(ftdi_ctx)); return DC_STATUS_IO; } if (ftdi_usb_reset(ftdi_ctx)) { ERROR (context, "%s", ftdi_get_error_string(ftdi_ctx)); return DC_STATUS_IO; } if (ftdi_usb_purge_buffers(ftdi_ctx)) { free(device); ERROR (context, "%s", ftdi_get_error_string(ftdi_ctx)); return DC_STATUS_IO; } device->ftdi_ctx = ftdi_ctx; //FIXME: remove this when custom-serial have support for configure calls serial_ftdi_configure (device, 115200, 8, 0, 1, 0); *out = device; return DC_STATUS_SUCCESS; } // // Close the serial port. // static int serial_ftdi_close (serial_t *device) { if (device == NULL) return 0; // Restore the initial terminal attributes. // See if it is possible using libusb or libftdi int ret = ftdi_usb_close(device->ftdi_ctx); if (ret < 0) { ERROR (device->context, "Unable to close the ftdi device : %d (%s)\n", ret, ftdi_get_error_string(device->ftdi_ctx)); return ret; } ftdi_free(device->ftdi_ctx); // Free memory. free (device); return 0; } // // Configure the serial port (baudrate, databits, parity, stopbits and flowcontrol). // static dc_status_t serial_ftdi_configure (serial_t *device, int baudrate, int databits, int parity, int stopbits, int flowcontrol) { if (device == NULL) return -1; // EINVAL (Invalid argument) INFO (device->context, "Configure: baudrate=%i, databits=%i, parity=%i, stopbits=%i, flowcontrol=%i", baudrate, databits, parity, stopbits, flowcontrol); enum ftdi_bits_type ft_bits; enum ftdi_stopbits_type ft_stopbits; enum ftdi_parity_type ft_parity; if (ftdi_set_baudrate(device->ftdi_ctx, baudrate) < 0) { ERROR (device->context, "%s", ftdi_get_error_string(device->ftdi_ctx)); return -1; } // Set the character size. switch (databits) { case 7: ft_bits = BITS_7; break; case 8: ft_bits = BITS_8; break; default: return DC_STATUS_INVALIDARGS; } // Set the parity type. switch (parity) { case SERIAL_PARITY_NONE: // No parity ft_parity = NONE; break; case SERIAL_PARITY_EVEN: // Even parity ft_parity = EVEN; break; case SERIAL_PARITY_ODD: // Odd parity ft_parity = ODD; break; default: return DC_STATUS_INVALIDARGS; } // Set the number of stop bits. switch (stopbits) { case 1: // One stopbit ft_stopbits = STOP_BIT_1; break; case 2: // Two stopbits ft_stopbits = STOP_BIT_2; break; default: return DC_STATUS_INVALIDARGS; } // Set the attributes if (ftdi_set_line_property(device->ftdi_ctx, ft_bits, ft_stopbits, ft_parity)) { ERROR (device->context, "%s", ftdi_get_error_string(device->ftdi_ctx)); return DC_STATUS_IO; } // Set the flow control. switch (flowcontrol) { case SERIAL_FLOWCONTROL_NONE: // No flow control. if (ftdi_setflowctrl(device->ftdi_ctx, SIO_DISABLE_FLOW_CTRL) < 0) { ERROR (device->context, "%s", ftdi_get_error_string(device->ftdi_ctx)); return DC_STATUS_IO; } break; case SERIAL_FLOWCONTROL_HARDWARE: // Hardware (RTS/CTS) flow control. if (ftdi_setflowctrl(device->ftdi_ctx, SIO_RTS_CTS_HS) < 0) { ERROR (device->context, "%s", ftdi_get_error_string(device->ftdi_ctx)); return DC_STATUS_IO; } break; case SERIAL_FLOWCONTROL_SOFTWARE: // Software (XON/XOFF) flow control. if (ftdi_setflowctrl(device->ftdi_ctx, SIO_XON_XOFF_HS) < 0) { ERROR (device->context, "%s", ftdi_get_error_string(device->ftdi_ctx)); return DC_STATUS_IO; } break; default: return DC_STATUS_INVALIDARGS; } device->baudrate = baudrate; device->nbits = 1 + databits + stopbits + (parity ? 1 : 0); return DC_STATUS_SUCCESS; } // // Configure the serial port (timeouts). // static int serial_ftdi_set_timeout (serial_t *device, long timeout) { if (device == NULL) return -1; // EINVAL (Invalid argument) INFO (device->context, "Timeout: value=%li", timeout); device->timeout = timeout; return 0; } static int serial_ftdi_set_halfduplex (serial_t *device, int value) { if (device == NULL) return -1; // EINVAL (Invalid argument) // Most ftdi chips support full duplex operation. ft232rl does. // Crosscheck other chips. device->halfduplex = value; return 0; } static int serial_ftdi_read (serial_t *device, void *data, unsigned int size) { if (device == NULL) return -1; // EINVAL (Invalid argument) // The total timeout. long timeout = device->timeout; // The absolute target time. struct timeval tve; static int backoff = 1; int init = 1; unsigned int nbytes = 0; while (nbytes < size) { struct timeval tvt; if (timeout > 0) { struct timeval now; if (gettimeofday (&now, NULL) != 0) { SYSERROR (device->context, errno); return -1; } if (init) { // Calculate the initial timeout. tvt.tv_sec = (timeout / 1000); tvt.tv_usec = (timeout % 1000) * 1000; // Calculate the target time. timeradd (&now, &tvt, &tve); } else { // Calculate the remaining timeout. if (timercmp (&now, &tve, <)) timersub (&tve, &now, &tvt); else timerclear (&tvt); } init = 0; } else if (timeout == 0) { timerclear (&tvt); } int n = ftdi_read_data (device->ftdi_ctx, (char *) data + nbytes, size - nbytes); if (n < 0) { if (n == LIBUSB_ERROR_INTERRUPTED) continue; //Retry. ERROR (device->context, "%s", ftdi_get_error_string(device->ftdi_ctx)); return -1; //Error during read call. } else if (n == 0) { // Exponential backoff. if (backoff > MAX_BACKOFF) { ERROR(device->context, "%s", "FTDI read timed out."); return -1; } serial_ftdi_sleep (device, backoff); backoff *= 2; } else { // Reset backoff to 1 on success. backoff = 1; } nbytes += n; } INFO (device->context, "Read %d bytes", nbytes); return nbytes; } static int serial_ftdi_write (serial_t *device, const void *data, unsigned int size) { if (device == NULL) return -1; // EINVAL (Invalid argument) struct timeval tve, tvb; if (device->halfduplex) { // Get the current time. if (gettimeofday (&tvb, NULL) != 0) { SYSERROR (device->context, errno); return -1; } } unsigned int nbytes = 0; while (nbytes < size) { int n = ftdi_write_data (device->ftdi_ctx, (char *) data + nbytes, size - nbytes); if (n < 0) { if (n == LIBUSB_ERROR_INTERRUPTED) continue; // Retry. ERROR (device->context, "%s", ftdi_get_error_string(device->ftdi_ctx)); return -1; // Error during write call. } else if (n == 0) { break; // EOF. } nbytes += n; } if (device->halfduplex) { // Get the current time. if (gettimeofday (&tve, NULL) != 0) { SYSERROR (device->context, errno); return -1; } // Calculate the elapsed time (microseconds). struct timeval tvt; timersub (&tve, &tvb, &tvt); unsigned long elapsed = tvt.tv_sec * 1000000 + tvt.tv_usec; // Calculate the expected duration (microseconds). A 2 millisecond fudge // factor is added because it improves the success rate significantly. unsigned long expected = 1000000.0 * device->nbits / device->baudrate * size + 0.5 + 2000; // Wait for the remaining time. if (elapsed < expected) { unsigned long remaining = expected - elapsed; // The remaining time is rounded up to the nearest millisecond to // match the Windows implementation. The higher resolution is // pointless anyway, since we already added a fudge factor above. serial_ftdi_sleep (device, (remaining + 999) / 1000); } } INFO (device->context, "Wrote %d bytes", nbytes); return nbytes; } static int serial_ftdi_flush (serial_t *device, int queue) { if (device == NULL) return -1; // EINVAL (Invalid argument) INFO (device->context, "Flush: queue=%u, input=%i, output=%i", queue, serial_ftdi_get_received (device), serial_ftdi_get_transmitted (device)); switch (queue) { case SERIAL_QUEUE_INPUT: if (ftdi_usb_purge_tx_buffer(device->ftdi_ctx)) { ERROR (device->context, "%s", ftdi_get_error_string(device->ftdi_ctx)); return -1; } break; case SERIAL_QUEUE_OUTPUT: if (ftdi_usb_purge_rx_buffer(device->ftdi_ctx)) { ERROR (device->context, "%s", ftdi_get_error_string(device->ftdi_ctx)); return -1; } break; default: if (ftdi_usb_purge_buffers(device->ftdi_ctx)) { ERROR (device->context, "%s", ftdi_get_error_string(device->ftdi_ctx)); return -1; } break; } return 0; } static int serial_ftdi_send_break (serial_t *device) { if (device == NULL) return -1; // EINVAL (Invalid argument)a INFO (device->context, "Break : One time period."); // no direct functions for sending break signals in libftdi. // there is a suggestion to lower the baudrate and sending NUL // and resetting the baudrate up again. But it has flaws. // Not implementing it before researching more. return -1; } static int serial_ftdi_set_break (serial_t *device, int level) { if (device == NULL) return -1; // EINVAL (Invalid argument) INFO (device->context, "Break: value=%i", level); // Not implemented in libftdi yet. Research it further. return -1; } static int serial_ftdi_set_dtr (serial_t *device, int level) { if (device == NULL) return -1; // EINVAL (Invalid argument) INFO (device->context, "DTR: value=%i", level); if (ftdi_setdtr(device->ftdi_ctx, level)) { ERROR (device->context, "%s", ftdi_get_error_string(device->ftdi_ctx)); return -1; } return 0; } static int serial_ftdi_set_rts (serial_t *device, int level) { if (device == NULL) return -1; // EINVAL (Invalid argument) INFO (device->context, "RTS: value=%i", level); if (ftdi_setrts(device->ftdi_ctx, level)) { ERROR (device->context, "%s", ftdi_get_error_string(device->ftdi_ctx)); return -1; } return 0; } const dc_serial_operations_t serial_ftdi_ops = { .open = serial_ftdi_open, .close = serial_ftdi_close, .read = serial_ftdi_read, .write = serial_ftdi_write, .flush = serial_ftdi_flush, .get_received = serial_ftdi_get_received, .get_transmitted = NULL, /*NOT USED ANYWHERE! serial_ftdi_get_transmitted */ .set_timeout = serial_ftdi_set_timeout #ifdef FIXED_SSRF_CUSTOM_SERIAL , .configure = serial_ftdi_configure, //static int serial_ftdi_configure (serial_t *device, int baudrate, int databits, int parity, int stopbits, int flowcontrol) .set_halfduplex = serial_ftdi_set_halfduplex, //static int serial_ftdi_set_halfduplex (serial_t *device, int value) .send_break = serial_ftdi_send_break, //static int serial_ftdi_send_break (serial_t *device) .set_break = serial_ftdi_set_break, //static int serial_ftdi_set_break (serial_t *device, int level) .set_dtr = serial_ftdi_set_dtr, //static int serial_ftdi_set_dtr (serial_t *device, int level) .set_rts = serial_ftdi_set_rts //static int serial_ftdi_set_rts (serial_t *device, int level) #endif }; dc_status_t dc_serial_ftdi_open(dc_serial_t **out, dc_context_t *context) { if (out == NULL) return DC_STATUS_INVALIDARGS; // Allocate memory. dc_serial_t *serial_device = (dc_serial_t *) malloc (sizeof (dc_serial_t)); if (serial_device == NULL) { return DC_STATUS_NOMEMORY; } // Initialize data and function pointers dc_serial_init(serial_device, NULL, &serial_ftdi_ops); // Open the serial device. dc_status_t rc = (dc_status_t) serial_ftdi_open (&serial_device->port, context, NULL); if (rc != DC_STATUS_SUCCESS) { free (serial_device); return rc; } // Set the type of the device serial_device->type = DC_TRANSPORT_USB;; *out = serial_device; return DC_STATUS_SUCCESS; }