296 lines
9.3 KiB
C
296 lines
9.3 KiB
C
|
/***************************************************************************
|
||
|
ftdi_stream.c - description
|
||
|
-------------------
|
||
|
copyright : (C) 2009 Micah Dowty 2010 Uwe Bonnes
|
||
|
email : opensource@intra2net.com
|
||
|
***************************************************************************/
|
||
|
|
||
|
/***************************************************************************
|
||
|
* *
|
||
|
* This program is free software; you can redistribute it and/or modify *
|
||
|
* it under the terms of the GNU Lesser General Public License *
|
||
|
* version 2.1 as published by the Free Software Foundation; *
|
||
|
* *
|
||
|
***************************************************************************/
|
||
|
|
||
|
/* Adapted from
|
||
|
* fastftdi.c - A minimal FTDI FT232H interface for which supports bit-bang
|
||
|
* mode, but focuses on very high-performance support for
|
||
|
* synchronous FIFO mode. Requires libusb-1.0
|
||
|
*
|
||
|
* Copyright (C) 2009 Micah Dowty
|
||
|
*
|
||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||
|
* of this software and associated documentation files (the "Software"), to deal
|
||
|
* in the Software without restriction, including without limitation the rights
|
||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||
|
* copies of the Software, and to permit persons to whom the Software is
|
||
|
* furnished to do so, subject to the following conditions:
|
||
|
*
|
||
|
* The above copyright notice and this permission notice shall be included in
|
||
|
* all copies or substantial portions of the Software.
|
||
|
*
|
||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||
|
* THE SOFTWARE.
|
||
|
*/
|
||
|
|
||
|
#include <stdlib.h>
|
||
|
#include <stdio.h>
|
||
|
|
||
|
#include "ftdi.h"
|
||
|
|
||
|
typedef struct
|
||
|
{
|
||
|
FTDIStreamCallback *callback;
|
||
|
void *userdata;
|
||
|
int packetsize;
|
||
|
int activity;
|
||
|
int result;
|
||
|
FTDIProgressInfo progress;
|
||
|
} FTDIStreamState;
|
||
|
|
||
|
/* Handle callbacks
|
||
|
*
|
||
|
* With Exit request, free memory and release the transfer
|
||
|
*
|
||
|
* state->result is only set when some error happens
|
||
|
*/
|
||
|
static void
|
||
|
ftdi_readstream_cb(struct libusb_transfer *transfer)
|
||
|
{
|
||
|
FTDIStreamState *state = transfer->user_data;
|
||
|
int packet_size = state->packetsize;
|
||
|
|
||
|
state->activity++;
|
||
|
if (transfer->status == LIBUSB_TRANSFER_COMPLETED)
|
||
|
{
|
||
|
int i;
|
||
|
uint8_t *ptr = transfer->buffer;
|
||
|
int length = transfer->actual_length;
|
||
|
int numPackets = (length + packet_size - 1) / packet_size;
|
||
|
int res = 0;
|
||
|
|
||
|
for (i = 0; i < numPackets; i++)
|
||
|
{
|
||
|
int payloadLen;
|
||
|
int packetLen = length;
|
||
|
|
||
|
if (packetLen > packet_size)
|
||
|
packetLen = packet_size;
|
||
|
|
||
|
payloadLen = packetLen - 2;
|
||
|
state->progress.current.totalBytes += payloadLen;
|
||
|
|
||
|
res = state->callback(ptr + 2, payloadLen,
|
||
|
NULL, state->userdata);
|
||
|
|
||
|
ptr += packetLen;
|
||
|
length -= packetLen;
|
||
|
}
|
||
|
if (res)
|
||
|
{
|
||
|
free(transfer->buffer);
|
||
|
libusb_free_transfer(transfer);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
transfer->status = -1;
|
||
|
state->result = libusb_submit_transfer(transfer);
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
fprintf(stderr, "unknown status %d\n",transfer->status);
|
||
|
state->result = LIBUSB_ERROR_IO;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
Helper function to calculate (unix) time differences
|
||
|
|
||
|
\param a timeval
|
||
|
\param b timeval
|
||
|
*/
|
||
|
static double
|
||
|
TimevalDiff(const struct timeval *a, const struct timeval *b)
|
||
|
{
|
||
|
return (a->tv_sec - b->tv_sec) + 1e-6 * (a->tv_usec - b->tv_usec);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
Streaming reading of data from the device
|
||
|
|
||
|
Use asynchronous transfers in libusb-1.0 for high-performance
|
||
|
streaming of data from a device interface back to the PC. This
|
||
|
function continuously transfers data until either an error occurs
|
||
|
or the callback returns a nonzero value. This function returns
|
||
|
a libusb error code or the callback's return value.
|
||
|
|
||
|
For every contiguous block of received data, the callback will
|
||
|
be invoked.
|
||
|
|
||
|
\param ftdi pointer to ftdi_context
|
||
|
\param callback to user supplied function for one block of data
|
||
|
\param userdata
|
||
|
\param packetsPerTransfer number of packets per transfer
|
||
|
\param numTransfers Number of transfers per callback
|
||
|
|
||
|
*/
|
||
|
|
||
|
int
|
||
|
ftdi_readstream(struct ftdi_context *ftdi,
|
||
|
FTDIStreamCallback *callback, void *userdata,
|
||
|
int packetsPerTransfer, int numTransfers)
|
||
|
{
|
||
|
struct libusb_transfer **transfers;
|
||
|
FTDIStreamState state = { callback, userdata, ftdi->max_packet_size, 1 };
|
||
|
int bufferSize = packetsPerTransfer * ftdi->max_packet_size;
|
||
|
int xferIndex;
|
||
|
int err = 0;
|
||
|
|
||
|
/* Only FT2232H and FT232H know about the synchronous FIFO Mode*/
|
||
|
if ((ftdi->type != TYPE_2232H) && (ftdi->type != TYPE_232H))
|
||
|
{
|
||
|
fprintf(stderr,"Device doesn't support synchronous FIFO mode\n");
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
/* We don't know in what state we are, switch to reset*/
|
||
|
if (ftdi_set_bitmode(ftdi, 0xff, BITMODE_RESET) < 0)
|
||
|
{
|
||
|
fprintf(stderr,"Can't reset mode\n");
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
/* Purge anything remaining in the buffers*/
|
||
|
if (ftdi_usb_purge_buffers(ftdi) < 0)
|
||
|
{
|
||
|
fprintf(stderr,"Can't Purge\n");
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Set up all transfers
|
||
|
*/
|
||
|
|
||
|
transfers = calloc(numTransfers, sizeof *transfers);
|
||
|
if (!transfers) {
|
||
|
err = LIBUSB_ERROR_NO_MEM;
|
||
|
goto cleanup;
|
||
|
}
|
||
|
|
||
|
for (xferIndex = 0; xferIndex < numTransfers; xferIndex++)
|
||
|
{
|
||
|
struct libusb_transfer *transfer;
|
||
|
|
||
|
transfer = libusb_alloc_transfer(0);
|
||
|
transfers[xferIndex] = transfer;
|
||
|
if (!transfer) {
|
||
|
err = LIBUSB_ERROR_NO_MEM;
|
||
|
goto cleanup;
|
||
|
}
|
||
|
|
||
|
libusb_fill_bulk_transfer(transfer, ftdi->usb_dev, ftdi->out_ep,
|
||
|
malloc(bufferSize), bufferSize,
|
||
|
ftdi_readstream_cb,
|
||
|
&state, 0);
|
||
|
|
||
|
if (!transfer->buffer) {
|
||
|
err = LIBUSB_ERROR_NO_MEM;
|
||
|
goto cleanup;
|
||
|
}
|
||
|
|
||
|
transfer->status = -1;
|
||
|
err = libusb_submit_transfer(transfer);
|
||
|
if (err)
|
||
|
goto cleanup;
|
||
|
}
|
||
|
|
||
|
/* Start the transfers only when everything has been set up.
|
||
|
* Otherwise the transfers start stuttering and the PC not
|
||
|
* fetching data for several to several ten milliseconds
|
||
|
* and we skip blocks
|
||
|
*/
|
||
|
if (ftdi_set_bitmode(ftdi, 0xff, BITMODE_SYNCFF) < 0)
|
||
|
{
|
||
|
fprintf(stderr,"Can't set synchronous fifo mode: %s\n",
|
||
|
ftdi_get_error_string(ftdi));
|
||
|
goto cleanup;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Run the transfers, and periodically assess progress.
|
||
|
*/
|
||
|
|
||
|
gettimeofday(&state.progress.first.time, NULL);
|
||
|
|
||
|
do
|
||
|
{
|
||
|
FTDIProgressInfo *progress = &state.progress;
|
||
|
const double progressInterval = 1.0;
|
||
|
struct timeval timeout = { 0, ftdi->usb_read_timeout };
|
||
|
struct timeval now;
|
||
|
|
||
|
int err = libusb_handle_events_timeout(ftdi->usb_ctx, &timeout);
|
||
|
if (err == LIBUSB_ERROR_INTERRUPTED)
|
||
|
/* restart interrupted events */
|
||
|
err = libusb_handle_events_timeout(ftdi->usb_ctx, &timeout);
|
||
|
if (!state.result)
|
||
|
{
|
||
|
state.result = err;
|
||
|
}
|
||
|
if (state.activity == 0)
|
||
|
state.result = 1;
|
||
|
else
|
||
|
state.activity = 0;
|
||
|
|
||
|
// If enough time has elapsed, update the progress
|
||
|
gettimeofday(&now, NULL);
|
||
|
if (TimevalDiff(&now, &progress->current.time) >= progressInterval)
|
||
|
{
|
||
|
progress->current.time = now;
|
||
|
progress->totalTime = TimevalDiff(&progress->current.time,
|
||
|
&progress->first.time);
|
||
|
|
||
|
if (progress->prev.totalBytes)
|
||
|
{
|
||
|
// We have enough information to calculate rates
|
||
|
|
||
|
double currentTime;
|
||
|
|
||
|
currentTime = TimevalDiff(&progress->current.time,
|
||
|
&progress->prev.time);
|
||
|
|
||
|
progress->totalRate =
|
||
|
progress->current.totalBytes /progress->totalTime;
|
||
|
progress->currentRate =
|
||
|
(progress->current.totalBytes -
|
||
|
progress->prev.totalBytes) / currentTime;
|
||
|
}
|
||
|
|
||
|
state.callback(NULL, 0, progress, state.userdata);
|
||
|
progress->prev = progress->current;
|
||
|
|
||
|
}
|
||
|
} while (!state.result);
|
||
|
|
||
|
/*
|
||
|
* Cancel any outstanding transfers, and free memory.
|
||
|
*/
|
||
|
|
||
|
cleanup:
|
||
|
fprintf(stderr, "cleanup\n");
|
||
|
if (transfers)
|
||
|
free(transfers);
|
||
|
if (err)
|
||
|
return err;
|
||
|
else
|
||
|
return state.result;
|
||
|
}
|
||
|
|