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