| 1 | /*************************************************************************** |
| 2 | ftdi_stream.c - description |
| 3 | ------------------- |
| 4 | copyright : (C) 2009 Micah Dowty 2010 Uwe Bonnes |
| 5 | email : opensource@intra2net.com |
| 6 | SPDX-License-Identifier: (LGPL-2.1-only AND MIT) |
| 7 | ***************************************************************************/ |
| 8 | |
| 9 | /*************************************************************************** |
| 10 | * * |
| 11 | * This program is free software; you can redistribute it and/or modify * |
| 12 | * it under the terms of the GNU Lesser General Public License * |
| 13 | * version 2.1 as published by the Free Software Foundation; * |
| 14 | * * |
| 15 | ***************************************************************************/ |
| 16 | |
| 17 | /* Adapted from |
| 18 | * fastftdi.c - A minimal FTDI FT232H interface for which supports bit-bang |
| 19 | * mode, but focuses on very high-performance support for |
| 20 | * synchronous FIFO mode. Requires libusb-1.0 |
| 21 | * |
| 22 | * Copyright (C) 2009 Micah Dowty |
| 23 | * |
| 24 | * Permission is hereby granted, free of charge, to any person obtaining a copy |
| 25 | * of this software and associated documentation files (the "Software"), to deal |
| 26 | * in the Software without restriction, including without limitation the rights |
| 27 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
| 28 | * copies of the Software, and to permit persons to whom the Software is |
| 29 | * furnished to do so, subject to the following conditions: |
| 30 | * |
| 31 | * The above copyright notice and this permission notice shall be included in |
| 32 | * all copies or substantial portions of the Software. |
| 33 | * |
| 34 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
| 35 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
| 36 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
| 37 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
| 38 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
| 39 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |
| 40 | * THE SOFTWARE. |
| 41 | */ |
| 42 | |
| 43 | #include <stdlib.h> |
| 44 | #include <stdio.h> |
| 45 | #ifndef _WIN32 |
| 46 | #include <sys/time.h> |
| 47 | #endif |
| 48 | #include <libusb.h> |
| 49 | |
| 50 | #include "ftdi.h" |
| 51 | |
| 52 | typedef struct |
| 53 | { |
| 54 | FTDIStreamCallback *callback; |
| 55 | void *userdata; |
| 56 | int packetsize; |
| 57 | int activity; |
| 58 | int result; |
| 59 | FTDIProgressInfo progress; |
| 60 | } FTDIStreamState; |
| 61 | |
| 62 | /* Handle callbacks |
| 63 | * |
| 64 | * With Exit request, free memory and release the transfer |
| 65 | * |
| 66 | * state->result is only set when some error happens |
| 67 | */ |
| 68 | static void LIBUSB_CALL |
| 69 | ftdi_readstream_cb(struct libusb_transfer *transfer) |
| 70 | { |
| 71 | FTDIStreamState *state = transfer->user_data; |
| 72 | int packet_size = state->packetsize; |
| 73 | |
| 74 | state->activity++; |
| 75 | if (transfer->status == LIBUSB_TRANSFER_COMPLETED) |
| 76 | { |
| 77 | int i; |
| 78 | uint8_t *ptr = transfer->buffer; |
| 79 | int length = transfer->actual_length; |
| 80 | int numPackets = (length + packet_size - 1) / packet_size; |
| 81 | int res = 0; |
| 82 | |
| 83 | for (i = 0; i < numPackets; i++) |
| 84 | { |
| 85 | int payloadLen; |
| 86 | int packetLen = length; |
| 87 | |
| 88 | if (packetLen > packet_size) |
| 89 | packetLen = packet_size; |
| 90 | |
| 91 | payloadLen = packetLen - 2; |
| 92 | state->progress.current.totalBytes += payloadLen; |
| 93 | |
| 94 | res = state->callback(ptr + 2, payloadLen, |
| 95 | NULL, state->userdata); |
| 96 | |
| 97 | ptr += packetLen; |
| 98 | length -= packetLen; |
| 99 | } |
| 100 | if (res) |
| 101 | { |
| 102 | free(transfer->buffer); |
| 103 | libusb_free_transfer(transfer); |
| 104 | } |
| 105 | else |
| 106 | { |
| 107 | transfer->status = -1; |
| 108 | state->result = libusb_submit_transfer(transfer); |
| 109 | } |
| 110 | } |
| 111 | else |
| 112 | { |
| 113 | fprintf(stderr, "unknown status %d\n",transfer->status); |
| 114 | state->result = LIBUSB_ERROR_IO; |
| 115 | } |
| 116 | } |
| 117 | |
| 118 | /** |
| 119 | Helper function to calculate (unix) time differences |
| 120 | |
| 121 | \param a timeval |
| 122 | \param b timeval |
| 123 | */ |
| 124 | static double |
| 125 | TimevalDiff(const struct timeval *a, const struct timeval *b) |
| 126 | { |
| 127 | return (a->tv_sec - b->tv_sec) + 1e-6 * (a->tv_usec - b->tv_usec); |
| 128 | } |
| 129 | |
| 130 | /** |
| 131 | Streaming reading of data from the device |
| 132 | |
| 133 | Use asynchronous transfers in libusb-1.0 for high-performance |
| 134 | streaming of data from a device interface back to the PC. This |
| 135 | function continuously transfers data until either an error occurs |
| 136 | or the callback returns a nonzero value. This function returns |
| 137 | a libusb error code or the callback's return value. |
| 138 | |
| 139 | For every contiguous block of received data, the callback will |
| 140 | be invoked. |
| 141 | |
| 142 | \param ftdi pointer to ftdi_context |
| 143 | \param callback to user supplied function for one block of data |
| 144 | \param userdata |
| 145 | \param packetsPerTransfer number of packets per transfer |
| 146 | \param numTransfers Number of transfers per callback |
| 147 | |
| 148 | */ |
| 149 | |
| 150 | int |
| 151 | ftdi_readstream(struct ftdi_context *ftdi, |
| 152 | FTDIStreamCallback *callback, void *userdata, |
| 153 | int packetsPerTransfer, int numTransfers) |
| 154 | { |
| 155 | struct libusb_transfer **transfers; |
| 156 | FTDIStreamState state = { callback, userdata, ftdi->max_packet_size, 1 }; |
| 157 | int bufferSize = packetsPerTransfer * ftdi->max_packet_size; |
| 158 | int xferIndex; |
| 159 | int err = 0; |
| 160 | |
| 161 | /* Only FT2232H and FT232H know about the synchronous FIFO Mode*/ |
| 162 | if ((ftdi->type != TYPE_2232H) && (ftdi->type != TYPE_232H)) |
| 163 | { |
| 164 | fprintf(stderr,"Device doesn't support synchronous FIFO mode\n"); |
| 165 | return 1; |
| 166 | } |
| 167 | |
| 168 | /* We don't know in what state we are, switch to reset*/ |
| 169 | if (ftdi_set_bitmode(ftdi, 0xff, BITMODE_RESET) < 0) |
| 170 | { |
| 171 | fprintf(stderr,"Can't reset mode\n"); |
| 172 | return 1; |
| 173 | } |
| 174 | |
| 175 | /* Purge anything remaining in the buffers*/ |
| 176 | if (ftdi_tcioflush(ftdi) < 0) |
| 177 | { |
| 178 | fprintf(stderr,"Can't flush FIFOs & buffers\n"); |
| 179 | return 1; |
| 180 | } |
| 181 | |
| 182 | /* |
| 183 | * Set up all transfers |
| 184 | */ |
| 185 | |
| 186 | transfers = calloc(numTransfers, sizeof *transfers); |
| 187 | if (!transfers) |
| 188 | { |
| 189 | err = LIBUSB_ERROR_NO_MEM; |
| 190 | goto cleanup; |
| 191 | } |
| 192 | |
| 193 | for (xferIndex = 0; xferIndex < numTransfers; xferIndex++) |
| 194 | { |
| 195 | struct libusb_transfer *transfer; |
| 196 | |
| 197 | transfer = libusb_alloc_transfer(0); |
| 198 | transfers[xferIndex] = transfer; |
| 199 | if (!transfer) |
| 200 | { |
| 201 | err = LIBUSB_ERROR_NO_MEM; |
| 202 | goto cleanup; |
| 203 | } |
| 204 | |
| 205 | libusb_fill_bulk_transfer(transfer, ftdi->usb_dev, ftdi->out_ep, |
| 206 | malloc(bufferSize), bufferSize, |
| 207 | ftdi_readstream_cb, |
| 208 | &state, 0); |
| 209 | |
| 210 | if (!transfer->buffer) |
| 211 | { |
| 212 | err = LIBUSB_ERROR_NO_MEM; |
| 213 | goto cleanup; |
| 214 | } |
| 215 | |
| 216 | transfer->status = -1; |
| 217 | err = libusb_submit_transfer(transfer); |
| 218 | if (err) |
| 219 | goto cleanup; |
| 220 | } |
| 221 | |
| 222 | /* Start the transfers only when everything has been set up. |
| 223 | * Otherwise the transfers start stuttering and the PC not |
| 224 | * fetching data for several to several ten milliseconds |
| 225 | * and we skip blocks |
| 226 | */ |
| 227 | if (ftdi_set_bitmode(ftdi, 0xff, BITMODE_SYNCFF) < 0) |
| 228 | { |
| 229 | fprintf(stderr,"Can't set synchronous fifo mode: %s\n", |
| 230 | ftdi_get_error_string(ftdi)); |
| 231 | goto cleanup; |
| 232 | } |
| 233 | |
| 234 | /* |
| 235 | * Run the transfers, and periodically assess progress. |
| 236 | */ |
| 237 | |
| 238 | gettimeofday(&state.progress.first.time, NULL); |
| 239 | |
| 240 | do |
| 241 | { |
| 242 | FTDIProgressInfo *progress = &state.progress; |
| 243 | const double progressInterval = 1.0; |
| 244 | struct timeval timeout = { 0, ftdi->usb_read_timeout * 1000}; |
| 245 | struct timeval now; |
| 246 | |
| 247 | int err = libusb_handle_events_timeout(ftdi->usb_ctx, &timeout); |
| 248 | if (err == LIBUSB_ERROR_INTERRUPTED) |
| 249 | /* restart interrupted events */ |
| 250 | err = libusb_handle_events_timeout(ftdi->usb_ctx, &timeout); |
| 251 | if (!state.result) |
| 252 | { |
| 253 | state.result = err; |
| 254 | } |
| 255 | if (state.activity == 0) |
| 256 | state.result = 1; |
| 257 | else |
| 258 | state.activity = 0; |
| 259 | |
| 260 | // If enough time has elapsed, update the progress |
| 261 | gettimeofday(&now, NULL); |
| 262 | if (TimevalDiff(&now, &progress->current.time) >= progressInterval) |
| 263 | { |
| 264 | progress->current.time = now; |
| 265 | progress->totalTime = TimevalDiff(&progress->current.time, |
| 266 | &progress->first.time); |
| 267 | |
| 268 | if (progress->prev.totalBytes) |
| 269 | { |
| 270 | // We have enough information to calculate rates |
| 271 | |
| 272 | double currentTime; |
| 273 | |
| 274 | currentTime = TimevalDiff(&progress->current.time, |
| 275 | &progress->prev.time); |
| 276 | |
| 277 | progress->totalRate = |
| 278 | progress->current.totalBytes /progress->totalTime; |
| 279 | progress->currentRate = |
| 280 | (progress->current.totalBytes - |
| 281 | progress->prev.totalBytes) / currentTime; |
| 282 | } |
| 283 | |
| 284 | state.callback(NULL, 0, progress, state.userdata); |
| 285 | progress->prev = progress->current; |
| 286 | |
| 287 | } |
| 288 | } while (!state.result); |
| 289 | |
| 290 | /* |
| 291 | * Cancel any outstanding transfers, and free memory. |
| 292 | */ |
| 293 | |
| 294 | cleanup: |
| 295 | fprintf(stderr, "cleanup\n"); |
| 296 | if (transfers) |
| 297 | free(transfers); |
| 298 | if (err) |
| 299 | return err; |
| 300 | else |
| 301 | return state.result; |
| 302 | } |
| 303 | |