mirror of
https://github.com/rtlsdrblog/rtl-sdr-blog.git
synced 2024-12-28 11:58:30 +01:00
lib: Fix race condition after cancellation of USB transfers
This commit is contained in:
parent
031a044e31
commit
c16a86b269
405
src/librtlsdr.c
405
src/librtlsdr.c
@ -92,18 +92,56 @@ static const int fir_default[FIR_LEN] = {
|
||||
101, 156, 215, 273, 327, 372, 404, 421 /* 12 bit signed */
|
||||
};
|
||||
|
||||
struct rtlsdr_dev;
|
||||
|
||||
/*
|
||||
* RTL-SDR transfer context.
|
||||
*
|
||||
* Proper transfer cancellation is a stateful operation that involves
|
||||
* delivering an asynchronous cancellation request and waiting for its
|
||||
* completion. This is necessary to prevent a race condition during
|
||||
* cleanup when the device is closed (especially in Win32 systems).
|
||||
*
|
||||
* The rtlsdr_xfer_ctx structure encapsulates a transfer state, including
|
||||
* its buffer, its completion status and a pointer to the device it
|
||||
* belongs to.
|
||||
*/
|
||||
|
||||
struct rtlsdr_xfer_ctx {
|
||||
struct rtlsdr_dev *dev;
|
||||
struct libusb_transfer *xfer;
|
||||
unsigned char *buffer;
|
||||
int done;
|
||||
int zero_copy;
|
||||
};
|
||||
|
||||
typedef struct rtlsdr_xfer_ctx rtlsdr_xfer_ctx_t;
|
||||
|
||||
static rtlsdr_xfer_ctx_t *rtlsdr_xfer_ctx_new(struct rtlsdr_dev *dev);
|
||||
static int rtlsdr_xfer_ctx_alloc_buffer(rtlsdr_xfer_ctx_t *self);
|
||||
static void rtlsdr_xfer_ctx_free_buffer(rtlsdr_xfer_ctx_t *self);
|
||||
static int rtlsdr_xfer_ctx_issue_transfer(rtlsdr_xfer_ctx_t *self);
|
||||
static void LIBUSB_CALL _rtlsdr_xfer_libusb_callback(
|
||||
struct libusb_transfer *xfer);
|
||||
static void rtlsdr_xfer_ctx_destroy(rtlsdr_xfer_ctx_t *self);
|
||||
|
||||
#define rtlsdr_xfer_ctx_status(self) \
|
||||
((self)->xfer->status)
|
||||
|
||||
/* Friend of rtlsdr_xfer_ctx */
|
||||
struct rtlsdr_dev {
|
||||
libusb_context *ctx;
|
||||
struct libusb_device_handle *devh;
|
||||
uint32_t xfer_buf_num;
|
||||
|
||||
rtlsdr_xfer_ctx_t **xfer_ctx_list;
|
||||
uint32_t xfer_ctx_num;
|
||||
uint32_t xfer_buf_len;
|
||||
struct libusb_transfer **xfer;
|
||||
unsigned char **xfer_buf;
|
||||
|
||||
rtlsdr_read_async_cb_t cb;
|
||||
void *cb_ctx;
|
||||
enum rtlsdr_async_status async_status;
|
||||
int async_cancel;
|
||||
int use_zerocopy;
|
||||
|
||||
/* rtl demod context */
|
||||
uint32_t rate; /* Hz */
|
||||
uint32_t rtl_xtal; /* Hz */
|
||||
@ -1718,157 +1756,259 @@ int rtlsdr_read_sync(rtlsdr_dev_t *dev, void *buf, int len, int *n_read)
|
||||
return libusb_bulk_transfer(dev->devh, 0x81, buf, len, n_read, BULK_TIMEOUT);
|
||||
}
|
||||
|
||||
static void LIBUSB_CALL _libusb_callback(struct libusb_transfer *xfer)
|
||||
{
|
||||
rtlsdr_dev_t *dev = (rtlsdr_dev_t *)xfer->user_data;
|
||||
|
||||
if (LIBUSB_TRANSFER_COMPLETED == xfer->status) {
|
||||
if (dev->cb)
|
||||
dev->cb(xfer->buffer, xfer->actual_length, dev->cb_ctx);
|
||||
|
||||
libusb_submit_transfer(xfer); /* resubmit transfer */
|
||||
dev->xfer_errors = 0;
|
||||
} else if (LIBUSB_TRANSFER_CANCELLED != xfer->status) {
|
||||
#ifndef _WIN32
|
||||
if (LIBUSB_TRANSFER_ERROR == xfer->status)
|
||||
dev->xfer_errors++;
|
||||
|
||||
if (dev->xfer_errors >= dev->xfer_buf_num ||
|
||||
LIBUSB_TRANSFER_NO_DEVICE == xfer->status) {
|
||||
#endif
|
||||
dev->dev_lost = 1;
|
||||
rtlsdr_cancel_async(dev);
|
||||
fprintf(stderr, "cb transfer status: %d, "
|
||||
"canceling...\n", xfer->status);
|
||||
#ifndef _WIN32
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
int rtlsdr_wait_async(rtlsdr_dev_t *dev, rtlsdr_read_async_cb_t cb, void *ctx)
|
||||
{
|
||||
return rtlsdr_read_async(dev, cb, ctx, 0, 0);
|
||||
}
|
||||
|
||||
static int _rtlsdr_alloc_async_buffers(rtlsdr_dev_t *dev)
|
||||
static void
|
||||
rtlsdr_xfer_ctx_free_buffer(rtlsdr_xfer_ctx_t *self)
|
||||
{
|
||||
unsigned int i;
|
||||
/*
|
||||
* This path is excersed only if we managed to
|
||||
* allocate a buffer, which in turn only happens if
|
||||
* self->dev->xfer_buf_len is set to something meaningful
|
||||
*/
|
||||
|
||||
if (!dev)
|
||||
return -1;
|
||||
if (self->buffer != NULL) {
|
||||
if (self->zero_copy) {
|
||||
#if defined (__linux__) && LIBUSB_API_VERSION >= 0x01000105
|
||||
libusb_dev_mem_free(
|
||||
self->dev->devh,
|
||||
self->buffer,
|
||||
self->dev->xfer_buf_len);
|
||||
#endif /* __linux__ */
|
||||
} else {
|
||||
free(self->buffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!dev->xfer) {
|
||||
dev->xfer = malloc(dev->xfer_buf_num *
|
||||
sizeof(struct libusb_transfer *));
|
||||
static int
|
||||
rtlsdr_xfer_ctx_cancel(rtlsdr_xfer_ctx_t *self)
|
||||
{
|
||||
int r = 0;
|
||||
|
||||
for(i = 0; i < dev->xfer_buf_num; ++i)
|
||||
dev->xfer[i] = libusb_alloc_transfer(0);
|
||||
struct timeval zero_tv = {0, 0};
|
||||
|
||||
self->done = 0;
|
||||
|
||||
r = libusb_cancel_transfer(self->xfer);
|
||||
|
||||
while (!self->done) {
|
||||
libusb_handle_events_timeout_completed(
|
||||
self->dev->ctx,
|
||||
&zero_tv,
|
||||
&self->done);
|
||||
}
|
||||
|
||||
if (dev->xfer_buf)
|
||||
return -2;
|
||||
return r;
|
||||
}
|
||||
|
||||
dev->xfer_buf = malloc(dev->xfer_buf_num * sizeof(unsigned char *));
|
||||
memset(dev->xfer_buf, 0, dev->xfer_buf_num * sizeof(unsigned char *));
|
||||
static int
|
||||
rtlsdr_xfer_ctx_issue_transfer(rtlsdr_xfer_ctx_t *self)
|
||||
{
|
||||
self->done = 0;
|
||||
|
||||
libusb_fill_bulk_transfer(
|
||||
self->xfer,
|
||||
self->dev->devh,
|
||||
0x81,
|
||||
self->buffer,
|
||||
self->dev->xfer_buf_len,
|
||||
_rtlsdr_xfer_libusb_callback,
|
||||
self,
|
||||
BULK_TIMEOUT);
|
||||
|
||||
return libusb_submit_transfer(self->xfer);
|
||||
}
|
||||
|
||||
static int
|
||||
rtlsdr_xfer_ctx_alloc_buffer(rtlsdr_xfer_ctx_t *self)
|
||||
{
|
||||
int ok = 0;
|
||||
uint32_t buf_len = self->dev->xfer_buf_len;
|
||||
|
||||
self->buffer = NULL;
|
||||
self->zero_copy = 0;
|
||||
|
||||
#if defined(ENABLE_ZEROCOPY) && defined (__linux__) && LIBUSB_API_VERSION >= 0x01000105
|
||||
fprintf(stderr, "Allocating %d zero-copy buffers\n", dev->xfer_buf_num);
|
||||
self->buffer = libusb_dev_mem_alloc(self->dev->devh, buf_len);
|
||||
|
||||
dev->use_zerocopy = 1;
|
||||
for (i = 0; i < dev->xfer_buf_num; ++i) {
|
||||
dev->xfer_buf[i] = libusb_dev_mem_alloc(dev->devh, dev->xfer_buf_len);
|
||||
|
||||
if (dev->xfer_buf[i]) {
|
||||
/* Check if Kernel usbfs mmap() bug is present: if the
|
||||
* mapping is correct, the buffers point to memory that
|
||||
* was memset to 0 by the Kernel, otherwise, they point
|
||||
* to random memory. We check if the buffers are zeroed
|
||||
* and otherwise fall back to buffers in userspace.
|
||||
*/
|
||||
if (dev->xfer_buf[i][0] || memcmp(dev->xfer_buf[i],
|
||||
dev->xfer_buf[i] + 1,
|
||||
dev->xfer_buf_len - 1)) {
|
||||
fprintf(stderr, "Detected Kernel usbfs mmap() "
|
||||
"bug, falling back to buffers "
|
||||
"in userspace\n");
|
||||
dev->use_zerocopy = 0;
|
||||
break;
|
||||
}
|
||||
if (self->buffer != NULL) {
|
||||
/* Check if Kernel usbfs mmap() bug is present: if the
|
||||
* mapping is correct, the buffers point to memory that
|
||||
* was memset to 0 by the Kernel, otherwise, they point
|
||||
* to random memory. We check if the buffers are zeroed
|
||||
* and otherwise fall back to buffers in userspace.
|
||||
*/
|
||||
if (self->buffer[0] || memcmp(self->buffer,
|
||||
self->buffer + 1,
|
||||
buf_len - 1)) {
|
||||
fprintf(stderr, "Detected Kernel usbfs mmap() "
|
||||
"bug, falling back to buffers "
|
||||
"in userspace\n");
|
||||
} else {
|
||||
fprintf(stderr, "Failed to allocate zero-copy "
|
||||
"buffer for transfer %d\nFalling "
|
||||
"back to buffers in userspace\n", i);
|
||||
dev->use_zerocopy = 0;
|
||||
break;
|
||||
/* All looks good! Green light to zerocopy */
|
||||
self->zero_copy = 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* zero-copy buffer allocation failed (partially or completely)
|
||||
* we need to free the buffers again if already allocated */
|
||||
if (!dev->use_zerocopy) {
|
||||
for (i = 0; i < dev->xfer_buf_num; ++i) {
|
||||
if (dev->xfer_buf[i])
|
||||
libusb_dev_mem_free(dev->devh,
|
||||
dev->xfer_buf[i],
|
||||
dev->xfer_buf_len);
|
||||
}
|
||||
/*
|
||||
* Zero copy buffer allocation failed. We just need to free the
|
||||
* failed ones, as the current implementation allows mixed
|
||||
* userspace / zero-copy buffers.
|
||||
*/
|
||||
|
||||
if (!self->zero_copy && self->buffer != NULL) {
|
||||
libusb_dev_mem_free(
|
||||
self->dev->devh,
|
||||
self->buffer,
|
||||
buf_len);
|
||||
self->buffer = NULL;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* no zero-copy available, allocate buffers in userspace */
|
||||
if (!dev->use_zerocopy) {
|
||||
for (i = 0; i < dev->xfer_buf_num; ++i) {
|
||||
dev->xfer_buf[i] = malloc(dev->xfer_buf_len);
|
||||
/* No zero copy, allocate in userspace */
|
||||
if (!self->zero_copy)
|
||||
if ((self->buffer = malloc(buf_len)) == NULL)
|
||||
goto done;
|
||||
|
||||
if (!dev->xfer_buf[i])
|
||||
return -ENOMEM;
|
||||
}
|
||||
ok = 1;
|
||||
|
||||
done:
|
||||
return ok;
|
||||
}
|
||||
|
||||
rtlsdr_xfer_ctx_t *rtlsdr_xfer_ctx_new(struct rtlsdr_dev *dev)
|
||||
{
|
||||
rtlsdr_xfer_ctx_t *new = NULL;
|
||||
|
||||
if (dev == NULL)
|
||||
return NULL;
|
||||
|
||||
if ((new = calloc(1, sizeof(rtlsdr_xfer_ctx_t))) == NULL)
|
||||
goto fail;
|
||||
|
||||
new->dev = dev;
|
||||
|
||||
if ((new->xfer = libusb_alloc_transfer(0)) == NULL)
|
||||
goto fail;
|
||||
|
||||
if (!rtlsdr_xfer_ctx_alloc_buffer(new))
|
||||
goto fail;
|
||||
|
||||
return new;
|
||||
|
||||
fail:
|
||||
if (new != NULL)
|
||||
rtlsdr_xfer_ctx_destroy(new);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void LIBUSB_CALL _rtlsdr_xfer_libusb_callback(
|
||||
struct libusb_transfer *xfer)
|
||||
{
|
||||
rtlsdr_xfer_ctx_t *ctx = (rtlsdr_xfer_ctx_t *) xfer->user_data;
|
||||
rtlsdr_dev_t *dev = ctx->dev;
|
||||
|
||||
switch (xfer->status) {
|
||||
case LIBUSB_TRANSFER_COMPLETED:
|
||||
if (dev->cb)
|
||||
dev->cb(xfer->buffer, xfer->actual_length, dev->cb_ctx);
|
||||
|
||||
libusb_submit_transfer(xfer); /* resubmit transfer */
|
||||
dev->xfer_errors = 0;
|
||||
ctx->done = 1;
|
||||
break;
|
||||
|
||||
case LIBUSB_TRANSFER_CANCELLED:
|
||||
ctx->done = 1;
|
||||
break;
|
||||
|
||||
default:
|
||||
/* Maybe an error */
|
||||
#ifndef _WIN32
|
||||
if (LIBUSB_TRANSFER_ERROR == xfer->status)
|
||||
dev->xfer_errors++;
|
||||
|
||||
if (dev->xfer_errors >= dev->xfer_ctx_num ||
|
||||
LIBUSB_TRANSFER_NO_DEVICE == xfer->status) {
|
||||
#endif
|
||||
dev->dev_lost = 1;
|
||||
rtlsdr_cancel_async(dev);
|
||||
fprintf(stderr, "cb transfer status: %d, "
|
||||
"canceling...\n", xfer->status);
|
||||
#ifndef _WIN32
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
void
|
||||
rtlsdr_xfer_ctx_destroy(rtlsdr_xfer_ctx_t *self)
|
||||
{
|
||||
if (self->xfer != NULL)
|
||||
libusb_free_transfer(self->xfer);
|
||||
|
||||
rtlsdr_xfer_ctx_free_buffer(self);
|
||||
|
||||
free(self);
|
||||
}
|
||||
|
||||
static int _rtlsdr_free_async_buffers(rtlsdr_dev_t *dev)
|
||||
{
|
||||
unsigned int i;
|
||||
|
||||
if (!dev)
|
||||
if (dev == NULL)
|
||||
return -1;
|
||||
|
||||
if (dev->xfer) {
|
||||
for(i = 0; i < dev->xfer_buf_num; ++i) {
|
||||
if (dev->xfer[i]) {
|
||||
libusb_free_transfer(dev->xfer[i]);
|
||||
}
|
||||
}
|
||||
if (dev->xfer_ctx_list != NULL) {
|
||||
for (i = 0; i < dev->xfer_ctx_num; ++i)
|
||||
if (dev->xfer_ctx_list[i] != NULL)
|
||||
rtlsdr_xfer_ctx_destroy(dev->xfer_ctx_list[i]);
|
||||
|
||||
free(dev->xfer);
|
||||
dev->xfer = NULL;
|
||||
}
|
||||
|
||||
if (dev->xfer_buf) {
|
||||
for (i = 0; i < dev->xfer_buf_num; ++i) {
|
||||
if (dev->xfer_buf[i]) {
|
||||
if (dev->use_zerocopy) {
|
||||
#if defined (__linux__) && LIBUSB_API_VERSION >= 0x01000105
|
||||
libusb_dev_mem_free(dev->devh,
|
||||
dev->xfer_buf[i],
|
||||
dev->xfer_buf_len);
|
||||
#endif
|
||||
} else {
|
||||
free(dev->xfer_buf[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
free(dev->xfer_buf);
|
||||
dev->xfer_buf = NULL;
|
||||
free(dev->xfer_ctx_list);
|
||||
dev->xfer_ctx_list = NULL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int _rtlsdr_alloc_async_buffers(rtlsdr_dev_t *dev)
|
||||
{
|
||||
unsigned int i;
|
||||
int ret = -1;
|
||||
|
||||
if (dev == NULL)
|
||||
goto done;
|
||||
|
||||
if (dev->xfer_ctx_list == NULL) {
|
||||
/* Lazy allocation of async buffers */
|
||||
|
||||
if ((dev->xfer_ctx_list = calloc(
|
||||
dev->xfer_ctx_num,
|
||||
sizeof(rtlsdr_xfer_ctx_t *))) == NULL)
|
||||
goto done;
|
||||
|
||||
for (i = 0; i < dev->xfer_ctx_num; ++i) {
|
||||
dev->xfer_ctx_list[i] = rtlsdr_xfer_ctx_new(dev);
|
||||
if (dev->xfer_ctx_list[i] == NULL)
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
|
||||
ret = 0;
|
||||
|
||||
done:
|
||||
if (ret != 0)
|
||||
_rtlsdr_free_async_buffers(dev);
|
||||
|
||||
return ret;
|
||||
|
||||
}
|
||||
|
||||
int rtlsdr_read_async(rtlsdr_dev_t *dev, rtlsdr_read_async_cb_t cb, void *ctx,
|
||||
uint32_t buf_num, uint32_t buf_len)
|
||||
{
|
||||
@ -1891,9 +2031,9 @@ int rtlsdr_read_async(rtlsdr_dev_t *dev, rtlsdr_read_async_cb_t cb, void *ctx,
|
||||
dev->cb_ctx = ctx;
|
||||
|
||||
if (buf_num > 0)
|
||||
dev->xfer_buf_num = buf_num;
|
||||
dev->xfer_ctx_num = buf_num;
|
||||
else
|
||||
dev->xfer_buf_num = DEFAULT_BUF_NUMBER;
|
||||
dev->xfer_ctx_num = DEFAULT_BUF_NUMBER;
|
||||
|
||||
if (buf_len > 0 && buf_len % 512 == 0) /* len must be multiple of 512 */
|
||||
dev->xfer_buf_len = buf_len;
|
||||
@ -1902,17 +2042,9 @@ int rtlsdr_read_async(rtlsdr_dev_t *dev, rtlsdr_read_async_cb_t cb, void *ctx,
|
||||
|
||||
_rtlsdr_alloc_async_buffers(dev);
|
||||
|
||||
for(i = 0; i < dev->xfer_buf_num; ++i) {
|
||||
libusb_fill_bulk_transfer(dev->xfer[i],
|
||||
dev->devh,
|
||||
0x81,
|
||||
dev->xfer_buf[i],
|
||||
dev->xfer_buf_len,
|
||||
_libusb_callback,
|
||||
(void *)dev,
|
||||
BULK_TIMEOUT);
|
||||
for(i = 0; i < dev->xfer_ctx_num; ++i) {
|
||||
r = rtlsdr_xfer_ctx_issue_transfer(dev->xfer_ctx_list[i]);
|
||||
|
||||
r = libusb_submit_transfer(dev->xfer[i]);
|
||||
if (r < 0) {
|
||||
fprintf(stderr, "Failed to submit transfer %i\n"
|
||||
"Please increase your allowed "
|
||||
@ -1938,21 +2070,16 @@ int rtlsdr_read_async(rtlsdr_dev_t *dev, rtlsdr_read_async_cb_t cb, void *ctx,
|
||||
if (RTLSDR_CANCELING == dev->async_status) {
|
||||
next_status = RTLSDR_INACTIVE;
|
||||
|
||||
if (!dev->xfer)
|
||||
if (dev->xfer_ctx_list == NULL)
|
||||
break;
|
||||
|
||||
for(i = 0; i < dev->xfer_buf_num; ++i) {
|
||||
if (!dev->xfer[i])
|
||||
for(i = 0; i < dev->xfer_ctx_num; ++i) {
|
||||
if (dev->xfer_ctx_list[i] == NULL)
|
||||
continue;
|
||||
|
||||
if (LIBUSB_TRANSFER_CANCELLED !=
|
||||
dev->xfer[i]->status) {
|
||||
r = libusb_cancel_transfer(dev->xfer[i]);
|
||||
/* handle events after canceling
|
||||
* to allow transfer status to
|
||||
* propagate */
|
||||
libusb_handle_events_timeout_completed(dev->ctx,
|
||||
&zerotv, NULL);
|
||||
rtlsdr_xfer_ctx_status(dev->xfer_ctx_list[i])) {
|
||||
r = rtlsdr_xfer_ctx_cancel(dev->xfer_ctx_list[i]);
|
||||
if (r < 0)
|
||||
continue;
|
||||
|
||||
@ -2054,4 +2181,4 @@ int rtlsdr_set_gpio(rtlsdr_dev_t *dev, int gpio_pin, int on)
|
||||
rtlsdr_set_gpio_bit(dev, gpio_pin, on);
|
||||
|
||||
return 1;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user