diff options
author | Arturs Artamonovs <arturs.artamonovs@protonmail.com> | 2024-11-01 08:38:25 +0000 |
---|---|---|
committer | Arturs Artamonovs <arturs.artamonovs@protonmail.com> | 2024-11-01 08:38:25 +0000 |
commit | ca50c0f64f1b2fce46b4cb83ed111854bac13852 (patch) | |
tree | 92713a6fb08559f6f7ddd4e03781572eba0a7f02 /Radio | |
parent | e3a7f5bafec6715cd11555c39925665c78029dd9 (diff) | |
download | PrySDR-ca50c0f64f1b2fce46b4cb83ed111854bac13852.tar.gz PrySDR-ca50c0f64f1b2fce46b4cb83ed111854bac13852.zip |
rtlsdr library example compiles
Diffstat (limited to 'Radio')
59 files changed, 31218 insertions, 19 deletions
diff --git a/Radio/HW/AirSpy/src/airspy.c b/Radio/HW/AirSpy/src/airspy.c new file mode 100644 index 0000000..02c1542 --- /dev/null +++ b/Radio/HW/AirSpy/src/airspy.c @@ -0,0 +1,2011 @@ +/* +Copyright (c) 2013, Michael Ossmann <mike@ossmann.com> +Copyright (c) 2012, Jared Boone <jared@sharebrained.com> +Copyright (c) 2014, Youssef Touil <youssef@airspy.com> +Copyright (c) 2014, Benjamin Vernoux <bvernoux@airspy.com> +Copyright (c) 2015, Ian Gilmour <ian@sdrsharp.com> + +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + + Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + Neither the name of AirSpy nor the names of its contributors may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include "libusb.h" + +#if _MSC_VER > 1700 // To avoid error with Visual Studio 2017/2019 or more define which define timespec as it is already defined in pthread.h +#define HAVE_STRUCT_TIMESPEC +#endif + +#include <pthread.h> + +#include "airspy.h" +#include "iqconverter_float.h" +#include "iqconverter_int16.h" +#include "filters.h" + +#ifndef bool +typedef int bool; +#define true 1 +#define false 0 +#endif + +#define PACKET_SIZE (12) +#define UNPACKED_SIZE (16) +#define RAW_BUFFER_COUNT (8) + +#ifdef AIRSPY_BIG_ENDIAN +#define TO_LE(x) __builtin_bswap32(x) +#else +#define TO_LE(x) x +#endif + +#define SAMPLE_RESOLUTION 12 +#define SAMPLE_ENCAPSULATION 16 + +#define SAMPLE_SHIFT (SAMPLE_ENCAPSULATION - SAMPLE_RESOLUTION) +#define SAMPLE_SCALE (1.0f / (1 << (15 - SAMPLE_SHIFT))) + +#define SERIAL_NUMBER_UNUSED (0ULL) +#define FILE_DESCRIPTOR_UNUSED (-1) + +#define USB_PRODUCT_ID (2) +#define STR_DESCRIPTOR_SIZE (250) + +#define MIN_SAMPLERATE_BY_VALUE (1000000) +#define SAMPLE_TYPE_IS_IQ(x) ((x) == AIRSPY_SAMPLE_FLOAT32_IQ || (x) == AIRSPY_SAMPLE_INT16_IQ) + +#define LIBUSB_CTRL_TIMEOUT_MS (500) + +typedef struct { + uint32_t freq_hz; +} set_freq_params_t; + +typedef struct airspy_device +{ + libusb_context* usb_context; + libusb_device_handle* usb_device; + struct libusb_transfer** transfers; + airspy_sample_block_cb_fn callback; + volatile bool streaming; + volatile bool stop_requested; + pthread_t transfer_thread; + pthread_t consumer_thread; + bool transfer_thread_running; + bool consumer_thread_running; + pthread_cond_t consumer_cv; + pthread_mutex_t consumer_mp; + uint32_t supported_samplerate_count; + uint32_t *supported_samplerates; + uint32_t transfer_count; + uint32_t buffer_size; + uint32_t dropped_buffers; + uint32_t dropped_buffers_queue[RAW_BUFFER_COUNT]; + uint16_t *received_samples_queue[RAW_BUFFER_COUNT]; + volatile int received_samples_queue_head; + volatile int received_samples_queue_tail; + volatile int received_buffer_count; + void *output_buffer; + uint16_t *unpacked_samples; + bool packing_enabled; + iqconverter_float_t *cnv_f; + iqconverter_int16_t *cnv_i; + void* ctx; + enum airspy_sample_type sample_type; +} airspy_device_t; + +static const uint16_t airspy_usb_vid = 0x1d50; +static const uint16_t airspy_usb_pid = 0x60a1; + +#define STR_PREFIX_SERIAL_AIRSPY_SIZE (10) + +#define SERIAL_AIRSPY_EXPECTED_SIZE (26) + +#define GAIN_COUNT (22) + +uint8_t airspy_linearity_vga_gains[GAIN_COUNT] = { 13, 12, 11, 11, 11, 11, 11, 10, 10, 10, 10, 10, 10, 10, 10, 10, 9, 8, 7, 6, 5, 4 }; +uint8_t airspy_linearity_mixer_gains[GAIN_COUNT] = { 12, 12, 11, 9, 8, 7, 6, 6, 5, 0, 0, 1, 0, 0, 2, 2, 1, 1, 1, 1, 0, 0 }; +uint8_t airspy_linearity_lna_gains[GAIN_COUNT] = { 14, 14, 14, 13, 12, 10, 9, 9, 8, 9, 8, 6, 5, 3, 1, 0, 0, 0, 0, 0, 0, 0 }; +uint8_t airspy_sensitivity_vga_gains[GAIN_COUNT] = { 13, 12, 11, 10, 9, 8, 7, 6, 5, 5, 5, 5, 5, 4, 4, 4, 4, 4, 4, 4, 4, 4 }; +uint8_t airspy_sensitivity_mixer_gains[GAIN_COUNT] = { 12, 12, 12, 12, 11, 10, 10, 9, 9, 8, 7, 4, 4, 4, 3, 2, 2, 1, 0, 0, 0, 0 }; +uint8_t airspy_sensitivity_lna_gains[GAIN_COUNT] = { 14, 14, 14, 14, 14, 14, 14, 14, 14, 13, 12, 12, 9, 9, 8, 7, 6, 5, 3, 2, 1, 0 }; + +static int cancel_transfers(airspy_device_t* device) +{ + uint32_t transfer_index; + + if (device->transfers != NULL) + { + for (transfer_index = 0; transfer_index<device->transfer_count; transfer_index++) + { + if (device->transfers[transfer_index] != NULL) + { + libusb_cancel_transfer(device->transfers[transfer_index]); + } + } + return AIRSPY_SUCCESS; + } + else { + return AIRSPY_ERROR_OTHER; + } +} + +static int free_transfers(airspy_device_t* device) +{ + int i; + uint32_t transfer_index; + + if (device->transfers != NULL) + { + // libusb_close() should free all transfers referenced from this array. + for (transfer_index = 0; transfer_index < device->transfer_count; transfer_index++) + { + if (device->transfers[transfer_index] != NULL) + { + free(device->transfers[transfer_index]->buffer); + libusb_free_transfer(device->transfers[transfer_index]); + device->transfers[transfer_index] = NULL; + } + } + free(device->transfers); + device->transfers = NULL; + + if (device->output_buffer != NULL) + { + free(device->output_buffer); + device->output_buffer = NULL; + } + + if (device->unpacked_samples != NULL) + { + free(device->unpacked_samples); + device->unpacked_samples = NULL; + } + + for (i = 0; i < RAW_BUFFER_COUNT; i++) + { + if (device->received_samples_queue[i] != NULL) + { + free(device->received_samples_queue[i]); + device->received_samples_queue[i] = NULL; + } + } + } + + return AIRSPY_SUCCESS; +} + +static int allocate_transfers(airspy_device_t* const device) +{ + int i; + size_t sample_count; + uint32_t transfer_index; + + if (device->transfers == NULL) + { + for (i = 0; i < RAW_BUFFER_COUNT; i++) + { + device->received_samples_queue[i] = (uint16_t *)malloc(device->buffer_size); + if (device->received_samples_queue[i] == NULL) + { + return AIRSPY_ERROR_NO_MEM; + } + + memset(device->received_samples_queue[i], 0, device->buffer_size); + } + + if (device->packing_enabled) + { + sample_count = ((device->buffer_size / 2) * 4) / 3; + } + else + { + sample_count = device->buffer_size / 2; + } + + device->output_buffer = (float *)malloc(sample_count * sizeof(float)); + if (device->output_buffer == NULL) + { + return AIRSPY_ERROR_NO_MEM; + } + + if (device->packing_enabled) + { + device->unpacked_samples = (uint16_t*)malloc(sample_count * sizeof(uint16_t)); + if (device->unpacked_samples == NULL) + { + return AIRSPY_ERROR_NO_MEM; + } + } + + device->transfers = (struct libusb_transfer**) calloc(device->transfer_count, sizeof(struct libusb_transfer)); + if (device->transfers == NULL) + { + return AIRSPY_ERROR_NO_MEM; + } + + for (transfer_index = 0; transfer_index<device->transfer_count; transfer_index++) + { + device->transfers[transfer_index] = libusb_alloc_transfer(0); + if (device->transfers[transfer_index] == NULL) + { + return AIRSPY_ERROR_LIBUSB; + } + + libusb_fill_bulk_transfer( + device->transfers[transfer_index], + device->usb_device, + 0, + (unsigned char*)malloc(device->buffer_size), + device->buffer_size, + NULL, + device, + 0 + ); + + if (device->transfers[transfer_index]->buffer == NULL) + { + return AIRSPY_ERROR_NO_MEM; + } + } + return AIRSPY_SUCCESS; + } + else + { + return AIRSPY_ERROR_BUSY; + } +} + +static int prepare_transfers(airspy_device_t* device, const uint_fast8_t endpoint_address, libusb_transfer_cb_fn callback) +{ + int error; + uint32_t transfer_index; + if (device->transfers != NULL) + { + for (transfer_index = 0; transfer_index<device->transfer_count; transfer_index++) + { + device->transfers[transfer_index]->endpoint = endpoint_address; + device->transfers[transfer_index]->callback = callback; + + error = libusb_submit_transfer(device->transfers[transfer_index]); + if (error != 0) + { + return AIRSPY_ERROR_LIBUSB; + } + } + return AIRSPY_SUCCESS; + } + else { + // This shouldn't happen. + return AIRSPY_ERROR_OTHER; + } +} + +static void convert_samples_int16(uint16_t *src, int16_t *dest, int count) +{ + int i; + for (i = 0; i < count; i += 4) + { + dest[i + 0] = (src[i + 0] - 2048) << SAMPLE_SHIFT; + dest[i + 1] = (src[i + 1] - 2048) << SAMPLE_SHIFT; + dest[i + 2] = (src[i + 2] - 2048) << SAMPLE_SHIFT; + dest[i + 3] = (src[i + 3] - 2048) << SAMPLE_SHIFT; + } +} + +static void convert_samples_float(uint16_t *src, float *dest, int count) +{ + int i; + for (i = 0; i < count; i += 4) + { + dest[i + 0] = (src[i + 0] - 2048) * SAMPLE_SCALE; + dest[i + 1] = (src[i + 1] - 2048) * SAMPLE_SCALE; + dest[i + 2] = (src[i + 2] - 2048) * SAMPLE_SCALE; + dest[i + 3] = (src[i + 3] - 2048) * SAMPLE_SCALE; + } +} + +static inline void unpack_samples(uint32_t *input, uint16_t *output, int length) +{ + int i, j; + + for (i = 0, j = 0; j < length; i += 3, j += 8) + { + output[j + 0] = (input[i] >> 20) & 0xfff; + output[j + 1] = (input[i] >> 8) & 0xfff; + output[j + 2] = ((input[i] & 0xff) << 4) | ((input[i + 1] >> 28) & 0xf); + output[j + 3] = ((input[i + 1] & 0xfff0000) >> 16); + output[j + 4] = ((input[i + 1] & 0xfff0) >> 4); + output[j + 5] = ((input[i + 1] & 0xf) << 8) | ((input[i + 2] & 0xff000000) >> 24); + output[j + 6] = ((input[i + 2] >> 12) & 0xfff); + output[j + 7] = ((input[i + 2] & 0xfff)); + } +} + +static void* consumer_threadproc(void *arg) +{ + int sample_count; + uint16_t* input_samples; + uint32_t dropped_buffers; + airspy_device_t* device = (airspy_device_t*)arg; + airspy_transfer_t transfer; + +#ifdef _WIN32 + + SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_HIGHEST); + +#endif + + pthread_mutex_lock(&device->consumer_mp); + + while (device->streaming && !device->stop_requested) + { + while (device->received_buffer_count == 0 && device->streaming && !device->stop_requested) + { + pthread_cond_wait(&device->consumer_cv, &device->consumer_mp); + } + if (!device->streaming || device->stop_requested) + { + break; + } + + input_samples = device->received_samples_queue[device->received_samples_queue_tail]; + dropped_buffers = device->dropped_buffers_queue[device->received_samples_queue_tail]; + device->received_samples_queue_tail = (device->received_samples_queue_tail + 1) & (RAW_BUFFER_COUNT - 1); + + pthread_mutex_unlock(&device->consumer_mp); + + if (device->packing_enabled) + { + sample_count = ((device->buffer_size / 2) * 4) / 3; + + if (device->sample_type != AIRSPY_SAMPLE_RAW) + { + unpack_samples((uint32_t*)input_samples, device->unpacked_samples, sample_count); + + input_samples = device->unpacked_samples; + } + } + else + { + sample_count = device->buffer_size / 2; + } + + switch (device->sample_type) + { + case AIRSPY_SAMPLE_FLOAT32_IQ: + convert_samples_float(input_samples, (float *)device->output_buffer, sample_count); + iqconverter_float_process(device->cnv_f, (float *) device->output_buffer, sample_count); + sample_count /= 2; + transfer.samples = device->output_buffer; + break; + + case AIRSPY_SAMPLE_FLOAT32_REAL: + convert_samples_float(input_samples, (float *)device->output_buffer, sample_count); + transfer.samples = device->output_buffer; + break; + + case AIRSPY_SAMPLE_INT16_IQ: + convert_samples_int16(input_samples, (int16_t *)device->output_buffer, sample_count); + iqconverter_int16_process(device->cnv_i, (int16_t *) device->output_buffer, sample_count); + sample_count /= 2; + transfer.samples = device->output_buffer; + break; + + case AIRSPY_SAMPLE_INT16_REAL: + convert_samples_int16(input_samples, (int16_t *)device->output_buffer, sample_count); + transfer.samples = device->output_buffer; + break; + + case AIRSPY_SAMPLE_UINT16_REAL: + case AIRSPY_SAMPLE_RAW: + transfer.samples = input_samples; + break; + + case AIRSPY_SAMPLE_END: + // Just to shut GCC's moaning + break; + } + + transfer.device = device; + transfer.ctx = device->ctx; + transfer.sample_count = sample_count; + transfer.sample_type = device->sample_type; + transfer.dropped_samples = (uint64_t) dropped_buffers * (uint64_t) sample_count; + + if (device->callback(&transfer) != 0) + { + device->streaming = false; + } + + pthread_mutex_lock(&device->consumer_mp); + device->received_buffer_count--; + } + + device->streaming = false; + + pthread_mutex_unlock(&device->consumer_mp); + + return NULL; +} + +static void airspy_libusb_transfer_callback(struct libusb_transfer* usb_transfer) +{ + uint16_t *temp; + airspy_device_t* device = (airspy_device_t*)usb_transfer->user_data; + + if (!device->streaming || device->stop_requested) + { + return; + } + + if (usb_transfer->status == LIBUSB_TRANSFER_COMPLETED && usb_transfer->actual_length == usb_transfer->length) + { + pthread_mutex_lock(&device->consumer_mp); + + if (device->received_buffer_count < RAW_BUFFER_COUNT) + { + temp = device->received_samples_queue[device->received_samples_queue_head]; + device->received_samples_queue[device->received_samples_queue_head] = (uint16_t *)usb_transfer->buffer; + usb_transfer->buffer = (uint8_t *)temp; + + device->dropped_buffers_queue[device->received_samples_queue_head] = device->dropped_buffers; + device->dropped_buffers = 0; + + device->received_samples_queue_head = (device->received_samples_queue_head + 1) & (RAW_BUFFER_COUNT - 1); + device->received_buffer_count++; + + pthread_cond_signal(&device->consumer_cv); + } + else + { + device->dropped_buffers++; + } + + pthread_mutex_unlock(&device->consumer_mp); + + if (libusb_submit_transfer(usb_transfer) != 0) + { + device->streaming = false; + } + } + else + { + device->streaming = false; + } +} + +static void* transfer_threadproc(void* arg) +{ + airspy_device_t* device = (airspy_device_t*)arg; + int error; + struct timeval timeout = { 0, 500000 }; + +#ifdef _WIN32 + + SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_HIGHEST); + +#endif + + while (device->streaming && !device->stop_requested) + { + error = libusb_handle_events_timeout_completed(device->usb_context, &timeout, NULL); + if (error < 0) + { + if (error != LIBUSB_ERROR_INTERRUPTED) + device->streaming = false; + } + } + + device->streaming = false; + + return NULL; +} + +static int kill_io_threads(airspy_device_t* device) +{ + struct timeval timeout = { 0, 0 }; + + if (device->stop_requested) + { + device->stop_requested = false; + device->streaming = false; + cancel_transfers(device); + + pthread_mutex_lock(&device->consumer_mp); + pthread_cond_signal(&device->consumer_cv); + pthread_mutex_unlock(&device->consumer_mp); + + if (device->transfer_thread_running) { + pthread_join(device->transfer_thread, NULL); + device->transfer_thread_running = false; + } + if (device->consumer_thread_running) { + pthread_join(device->consumer_thread, NULL); + device->consumer_thread_running = false; + } + + libusb_handle_events_timeout_completed(device->usb_context, &timeout, NULL); + } + + return AIRSPY_SUCCESS; +} + +static int create_io_threads(airspy_device_t* device, airspy_sample_block_cb_fn callback) +{ + int result; + pthread_attr_t attr; + + if (!device->streaming && !device->stop_requested) + { + device->callback = callback; + device->streaming = true; + + result = prepare_transfers(device, LIBUSB_ENDPOINT_IN | 1, (libusb_transfer_cb_fn)airspy_libusb_transfer_callback); + if (result != AIRSPY_SUCCESS) + { + return result; + } + + device->received_samples_queue_head = 0; + device->received_samples_queue_tail = 0; + device->received_buffer_count = 0; + + pthread_attr_init(&attr); + pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE); + + result = pthread_create(&device->consumer_thread, &attr, consumer_threadproc, device); + if (result != 0) + { + return AIRSPY_ERROR_THREAD; + } + device->consumer_thread_running = true; + + result = pthread_create(&device->transfer_thread, &attr, transfer_threadproc, device); + if (result != 0) + { + return AIRSPY_ERROR_THREAD; + } + device->transfer_thread_running = true; + + pthread_attr_destroy(&attr); + } + else { + return AIRSPY_ERROR_BUSY; + } + + return AIRSPY_SUCCESS; +} + +static void airspy_open_exit(airspy_device_t* device) +{ + if (device->usb_device != NULL) + { + libusb_release_interface(device->usb_device, 0); + libusb_close(device->usb_device); + device->usb_device = NULL; + } + libusb_exit(device->usb_context); + device->usb_context = NULL; +} + +static void airspy_open_device(airspy_device_t* device, + int* ret, + uint16_t vid, + uint16_t pid, + uint64_t serial_number_val) +{ + int i; + int result; + libusb_device_handle** libusb_dev_handle; + int serial_number_len; + libusb_device_handle* dev_handle; + libusb_device *dev; + libusb_device** devices = NULL; + + ssize_t cnt; + int serial_descriptor_index; + struct libusb_device_descriptor device_descriptor; + unsigned char serial_number[SERIAL_AIRSPY_EXPECTED_SIZE + 1]; + + libusb_dev_handle = &device->usb_device; + *libusb_dev_handle = NULL; + + cnt = libusb_get_device_list(device->usb_context, &devices); + if (cnt < 0) + { + *ret = AIRSPY_ERROR_NOT_FOUND; + return; + } + + i = 0; + while ((dev = devices[i++]) != NULL) + { + libusb_get_device_descriptor(dev, &device_descriptor); + + if ((device_descriptor.idVendor == vid) && + (device_descriptor.idProduct == pid)) + { + if (serial_number_val != SERIAL_NUMBER_UNUSED) + { + serial_descriptor_index = device_descriptor.iSerialNumber; + if (serial_descriptor_index > 0) + { + if (libusb_open(dev, libusb_dev_handle) != 0) + { + *libusb_dev_handle = NULL; + continue; + } + dev_handle = *libusb_dev_handle; + serial_number_len = libusb_get_string_descriptor_ascii(dev_handle, + serial_descriptor_index, + serial_number, + sizeof(serial_number)); + if (serial_number_len == SERIAL_AIRSPY_EXPECTED_SIZE) + { + uint64_t serial = 0; + // use same code to determine device's serial number as in airspy_list_devices() + { + char *start, *end; + + serial_number[SERIAL_AIRSPY_EXPECTED_SIZE] = 0; + start = (char*)(serial_number + STR_PREFIX_SERIAL_AIRSPY_SIZE); + end = NULL; + serial = strtoull(start, &end, 16); + } + + if (serial == serial_number_val) + { +#ifdef __linux__ + /* Check whether a kernel driver is attached to interface #0. If so, we'll + * need to detach it. + */ + if (libusb_kernel_driver_active(dev_handle, 0)) + { + libusb_detach_kernel_driver(dev_handle, 0); + } +#endif + result = libusb_set_configuration(dev_handle, 1); + if (result != 0) + { + libusb_close(dev_handle); + *libusb_dev_handle = NULL; + continue; + } + result = libusb_claim_interface(dev_handle, 0); + if (result != 0) + { + libusb_close(dev_handle); + *libusb_dev_handle = NULL; + continue; + } + break; + } + else + { + libusb_close(dev_handle); + *libusb_dev_handle = NULL; + continue; + } + } + else + { + libusb_close(dev_handle); + *libusb_dev_handle = NULL; + continue; + } + } + } + else + { + if (libusb_open(dev, libusb_dev_handle) == 0) + { + dev_handle = *libusb_dev_handle; +#ifdef __linux__ + /* Check whether a kernel driver is attached to interface #0. If so, we'll + * need to detach it. + */ + if (libusb_kernel_driver_active(dev_handle, 0)) + { + libusb_detach_kernel_driver(dev_handle, 0); + } +#endif + result = libusb_set_configuration(dev_handle, 1); + if (result != 0) + { + libusb_close(dev_handle); + *libusb_dev_handle = NULL; + continue; + } + result = libusb_claim_interface(dev_handle, 0); + if (result != 0) + { + libusb_close(dev_handle); + *libusb_dev_handle = NULL; + continue; + } + break; + } + } + } + } + libusb_free_device_list(devices, 1); + + dev_handle = device->usb_device; + if (dev_handle == NULL) + { + *ret = AIRSPY_ERROR_NOT_FOUND; + return; + } + + *ret = AIRSPY_SUCCESS; + return; +} + +static void airspy_open_device_fd(airspy_device_t* device, + int* ret, + int fd) +{ + int result = -1; + +#ifdef __ANDROID__ + result = libusb_wrap_sys_device(device->usb_context, (intptr_t)fd, &device->usb_device); +#else + device->usb_device = NULL; + *ret = AIRSPY_ERROR_UNSUPPORTED; + return; +#endif + + if (result != 0 || device->usb_device == NULL) + { + *ret = AIRSPY_ERROR_LIBUSB; + return; + } + +#ifdef __linux__ + /* Check whether a kernel driver is attached to interface #0. If so, we'll + * need to detach it. + */ + if (libusb_kernel_driver_active(device->usb_device, 0)) + { + libusb_detach_kernel_driver(device->usb_device, 0); + } +#endif + + result = libusb_set_configuration(device->usb_device, 1); + if (result != 0) + { + libusb_close(device->usb_device); + device->usb_device = NULL; + *ret = AIRSPY_ERROR_LIBUSB; + return; + } + + result = libusb_claim_interface(device->usb_device, 0); + if (result != 0) + { + libusb_close(device->usb_device); + device->usb_device = NULL; + *ret = AIRSPY_ERROR_LIBUSB; + return; + } + + *ret = AIRSPY_SUCCESS; + return; +} + +static int airspy_read_samplerates_from_fw(struct airspy_device* device, uint32_t* buffer, const uint32_t len) +{ + int result; + + result = libusb_control_transfer( + device->usb_device, + LIBUSB_ENDPOINT_IN | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE, + AIRSPY_GET_SAMPLERATES, + 0, + len, + (unsigned char*)buffer, + (len > 0 ? len : 1) * sizeof(uint32_t), + LIBUSB_CTRL_TIMEOUT_MS); + + if (result < 1) + { + return AIRSPY_ERROR_OTHER; + } + + return AIRSPY_SUCCESS; +} + +static int airspy_open_init(airspy_device_t** device, uint64_t serial_number, int fd) +{ + airspy_device_t* lib_device; + int libusb_error; + int result; + + *device = NULL; + + lib_device = (airspy_device_t*)calloc(1, sizeof(airspy_device_t)); + if (lib_device == NULL) + { + return AIRSPY_ERROR_NO_MEM; + } + +#ifdef __ANDROID__ + // LibUSB does not support device discovery on android + libusb_set_option(NULL, LIBUSB_OPTION_NO_DEVICE_DISCOVERY, NULL); +#endif + + libusb_error = libusb_init(&lib_device->usb_context); + if (libusb_error != 0) + { + free(lib_device); + return AIRSPY_ERROR_LIBUSB; + } + + if (fd == FILE_DESCRIPTOR_UNUSED) { + airspy_open_device(lib_device, + &result, + airspy_usb_vid, + airspy_usb_pid, + serial_number); + } + else { + airspy_open_device_fd(lib_device, + &result, + fd); + } + + if (lib_device->usb_device == NULL) + { + libusb_exit(lib_device->usb_context); + free(lib_device); + return result; + } + + lib_device->transfers = NULL; + lib_device->callback = NULL; + lib_device->transfer_count = 16; + lib_device->buffer_size = 262144; + lib_device->packing_enabled = false; + lib_device->streaming = false; + lib_device->stop_requested = false; + lib_device->sample_type = AIRSPY_SAMPLE_FLOAT32_IQ; + + result = airspy_read_samplerates_from_fw(lib_device, &lib_device->supported_samplerate_count, 0); + if (result == AIRSPY_SUCCESS) + { + lib_device->supported_samplerates = (uint32_t *) malloc(lib_device->supported_samplerate_count * sizeof(uint32_t)); + result = airspy_read_samplerates_from_fw(lib_device, lib_device->supported_samplerates, lib_device->supported_samplerate_count); + if (result != AIRSPY_SUCCESS) + { + free(lib_device->supported_samplerates); + } + } + + if (result != AIRSPY_SUCCESS) + { + lib_device->supported_samplerate_count = 2; + lib_device->supported_samplerates = (uint32_t *) malloc(lib_device->supported_samplerate_count * sizeof(uint32_t)); + lib_device->supported_samplerates[0] = 10000000; + lib_device->supported_samplerates[1] = 2500000; + } + + airspy_set_packing(lib_device, 0); + + result = allocate_transfers(lib_device); + if (result != 0) + { + airspy_open_exit(lib_device); + free(lib_device->supported_samplerates); + free(lib_device); + return AIRSPY_ERROR_NO_MEM; + } + + lib_device->cnv_f = iqconverter_float_create(HB_KERNEL_FLOAT, HB_KERNEL_FLOAT_LEN); + lib_device->cnv_i = iqconverter_int16_create(HB_KERNEL_INT16, HB_KERNEL_INT16_LEN); + + pthread_cond_init(&lib_device->consumer_cv, NULL); + pthread_mutex_init(&lib_device->consumer_mp, NULL); + + *device = lib_device; + + return AIRSPY_SUCCESS; +} + +#ifdef __cplusplus +extern "C" +{ +#endif + + void ADDCALL airspy_lib_version(airspy_lib_version_t* lib_version) + { + lib_version->major_version = AIRSPY_VER_MAJOR; + lib_version->minor_version = AIRSPY_VER_MINOR; + lib_version->revision = AIRSPY_VER_REVISION; + } + + /* airspy_init() deprecated */ + int ADDCALL airspy_init(void) + { + return AIRSPY_SUCCESS; + } + + /* airspy_exit() deprecated */ + int ADDCALL airspy_exit(void) + { + return AIRSPY_SUCCESS; + } + +int airspy_list_devices(uint64_t *serials, int count) +{ + libusb_device_handle* libusb_dev_handle; + struct libusb_context *context; + libusb_device** devices = NULL; + libusb_device *dev; + struct libusb_device_descriptor device_descriptor; + + int serial_descriptor_index; + int serial_number_len; + int output_count; + int i; + unsigned char serial_number[SERIAL_AIRSPY_EXPECTED_SIZE + 1]; + + if (serials) + { + memset(serials, 0, sizeof(uint64_t) * count); + } + +#ifdef __ANDROID__ + // LibUSB does not support device discovery on android + libusb_set_option(NULL, LIBUSB_OPTION_NO_DEVICE_DISCOVERY, NULL); +#endif + + if (libusb_init(&context) != 0) + { + return AIRSPY_ERROR_LIBUSB; + } + + if (libusb_get_device_list(context, &devices) < 0) + { + return AIRSPY_ERROR_NOT_FOUND; + } + + i = 0; + output_count = 0; + while ((dev = devices[i++]) != NULL && (!serials || output_count < count)) + { + libusb_get_device_descriptor(dev, &device_descriptor); + + if ((device_descriptor.idVendor == airspy_usb_vid) && + (device_descriptor.idProduct == airspy_usb_pid)) + { + serial_descriptor_index = device_descriptor.iSerialNumber; + if (serial_descriptor_index > 0) + { + if (libusb_open(dev, &libusb_dev_handle) != 0) + { + continue; + } + + serial_number_len = libusb_get_string_descriptor_ascii(libusb_dev_handle, + serial_descriptor_index, + serial_number, + sizeof(serial_number)); + + if (serial_number_len == SERIAL_AIRSPY_EXPECTED_SIZE) + { + char *start, *end; + uint64_t serial; + + serial_number[SERIAL_AIRSPY_EXPECTED_SIZE] = 0; + start = (char*)(serial_number + STR_PREFIX_SERIAL_AIRSPY_SIZE); + end = NULL; + serial = strtoull(start, &end, 16); + if (serial == 0 && start == end) + { + libusb_close(libusb_dev_handle); + continue; + } + + if (serials) + { + serials[output_count] = serial; + } + output_count++; + } + + libusb_close(libusb_dev_handle); + } + } + } + + libusb_free_device_list(devices, 1); + libusb_exit(context); + return output_count; +} + + int ADDCALL airspy_open_sn(airspy_device_t** device, uint64_t serial_number) + { + int result; + + result = airspy_open_init(device, serial_number, FILE_DESCRIPTOR_UNUSED); + return result; + } + + int ADDCALL airspy_open_fd(airspy_device_t** device, int fd) + { + int result; + + result = airspy_open_init(device, SERIAL_NUMBER_UNUSED, fd); + return result; + } + + int ADDCALL airspy_open(airspy_device_t** device) + { + int result; + + result = airspy_open_init(device, SERIAL_NUMBER_UNUSED, FILE_DESCRIPTOR_UNUSED); + return result; + } + + int ADDCALL airspy_close(airspy_device_t* device) + { + int result; + + result = AIRSPY_SUCCESS; + + if (device != NULL) + { + result = airspy_stop_rx(device); + + iqconverter_float_free(device->cnv_f); + iqconverter_int16_free(device->cnv_i); + + pthread_cond_destroy(&device->consumer_cv); + pthread_mutex_destroy(&device->consumer_mp); + + free_transfers(device); + airspy_open_exit(device); + free(device->supported_samplerates); + free(device); + } + + return result; + } + + int ADDCALL airspy_get_samplerates(struct airspy_device* device, uint32_t* buffer, const uint32_t len) + { + uint32_t i; + + if (len == 0) + { + *buffer = device->supported_samplerate_count; + } + else if (len <= device->supported_samplerate_count) + { + memcpy(buffer, device->supported_samplerates, len * sizeof(uint32_t)); + + if (!SAMPLE_TYPE_IS_IQ(device->sample_type)) + { + for (i = 0; i < len; i++) + { + buffer[i] *= 2; + } + } + } + else + { + return AIRSPY_ERROR_INVALID_PARAM; + } + + return AIRSPY_SUCCESS; + } + + int ADDCALL airspy_set_samplerate(airspy_device_t* device, uint32_t samplerate) + { + int result; + uint8_t retval; + uint8_t length; + uint32_t i; + + if (samplerate >= MIN_SAMPLERATE_BY_VALUE) + { + for (i = 0; i < device->supported_samplerate_count; i++) + { + if (samplerate == device->supported_samplerates[i]) + { + samplerate = i; + break; + } + } + + if (samplerate >= MIN_SAMPLERATE_BY_VALUE) + { + if (SAMPLE_TYPE_IS_IQ(device->sample_type)) + { + samplerate *= 2; + } + samplerate /= 1000; + } + } + + libusb_clear_halt(device->usb_device, LIBUSB_ENDPOINT_IN | 1); + + length = 1; + + result = libusb_control_transfer( + device->usb_device, + LIBUSB_ENDPOINT_IN | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE, + AIRSPY_SET_SAMPLERATE, + 0, + samplerate, + &retval, + length, + LIBUSB_CTRL_TIMEOUT_MS); + + if (result < length) + { + return AIRSPY_ERROR_LIBUSB; + } + else { + return AIRSPY_SUCCESS; + } + } + + int ADDCALL airspy_set_receiver_mode(airspy_device_t* device, receiver_mode_t value) + { + int result; + result = libusb_control_transfer( + device->usb_device, + LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE, + AIRSPY_RECEIVER_MODE, + value, + 0, + NULL, + 0, + LIBUSB_CTRL_TIMEOUT_MS); + + if (result != 0) + { + return AIRSPY_ERROR_LIBUSB; + } + else { + return AIRSPY_SUCCESS; + } + } + + int ADDCALL airspy_start_rx(airspy_device_t* device, airspy_sample_block_cb_fn callback, void* ctx) + { + int result; + + iqconverter_float_reset(device->cnv_f); + iqconverter_int16_reset(device->cnv_i); + + memset(device->dropped_buffers_queue, 0, RAW_BUFFER_COUNT * sizeof(uint32_t)); + device->dropped_buffers = 0; + + result = airspy_set_receiver_mode(device, RECEIVER_MODE_OFF); + if (result != AIRSPY_SUCCESS) + { + return result; + } + + libusb_clear_halt(device->usb_device, LIBUSB_ENDPOINT_IN | 1); + + result = airspy_set_receiver_mode(device, RECEIVER_MODE_RX); + if (result == AIRSPY_SUCCESS) + { + device->ctx = ctx; + result = create_io_threads(device, callback); + } + + return result; + } + + int ADDCALL airspy_stop_rx(airspy_device_t* device) + { + int result1, result2; + + device->stop_requested = true; + result1 = airspy_set_receiver_mode(device, RECEIVER_MODE_OFF); + result2 = kill_io_threads(device); + + if (result1 != AIRSPY_SUCCESS) + { + return result1; + } + return result2; + } + + int ADDCALL airspy_si5351c_read(airspy_device_t* device, uint8_t register_number, uint8_t* value) + { + uint8_t temp_value; + int result; + + temp_value = 0; + result = libusb_control_transfer( + device->usb_device, + LIBUSB_ENDPOINT_IN | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE, + AIRSPY_SI5351C_READ, + 0, + register_number, + (unsigned char*)&temp_value, + 1, + LIBUSB_CTRL_TIMEOUT_MS); + + if (result < 1) + { + return AIRSPY_ERROR_LIBUSB; + } + else { + *value = temp_value; + return AIRSPY_SUCCESS; + } + } + + int ADDCALL airspy_si5351c_write(airspy_device_t* device, uint8_t register_number, uint8_t value) + { + int result; + + result = libusb_control_transfer( + device->usb_device, + LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE, + AIRSPY_SI5351C_WRITE, + value, + register_number, + NULL, + 0, + LIBUSB_CTRL_TIMEOUT_MS); + + if (result != 0) + { + return AIRSPY_ERROR_LIBUSB; + } + else { + return AIRSPY_SUCCESS; + } + } + + int ADDCALL airspy_r820t_read(airspy_device_t* device, uint8_t register_number, uint8_t* value) + { + int result; + + result = libusb_control_transfer( + device->usb_device, + LIBUSB_ENDPOINT_IN | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE, + AIRSPY_R820T_READ, + 0, + register_number, + (unsigned char*)value, + 1, + LIBUSB_CTRL_TIMEOUT_MS); + + if (result < 1) + { + return AIRSPY_ERROR_LIBUSB; + } + else { + return AIRSPY_SUCCESS; + } + } + + int ADDCALL airspy_r820t_write(airspy_device_t* device, uint8_t register_number, uint8_t value) + { + int result; + + result = libusb_control_transfer( + device->usb_device, + LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE, + AIRSPY_R820T_WRITE, + value, + register_number, + NULL, + 0, + LIBUSB_CTRL_TIMEOUT_MS); + + if (result != 0) + { + return AIRSPY_ERROR_LIBUSB; + } + else { + return AIRSPY_SUCCESS; + } + } + + int ADDCALL airspy_gpio_read(airspy_device_t* device, airspy_gpio_port_t port, airspy_gpio_pin_t pin, uint8_t* value) + { + int result; + uint8_t port_pin; + + port_pin = ((uint8_t)port) << 5; + port_pin = port_pin | pin; + + result = libusb_control_transfer( + device->usb_device, + LIBUSB_ENDPOINT_IN | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE, + AIRSPY_GPIO_READ, + 0, + port_pin, + (unsigned char*)value, + 1, + LIBUSB_CTRL_TIMEOUT_MS); + + if (result < 1) + { + return AIRSPY_ERROR_LIBUSB; + } + else { + return AIRSPY_SUCCESS; + } + } + + int ADDCALL airspy_gpio_write(airspy_device_t* device, airspy_gpio_port_t port, airspy_gpio_pin_t pin, uint8_t value) + { + int result; + uint8_t port_pin; + + port_pin = ((uint8_t)port) << 5; + port_pin = port_pin | pin; + + result = libusb_control_transfer( + device->usb_device, + LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE, + AIRSPY_GPIO_WRITE, + value, + port_pin, + NULL, + 0, + LIBUSB_CTRL_TIMEOUT_MS); + + if (result != 0) + { + return AIRSPY_ERROR_LIBUSB; + } + else { + return AIRSPY_SUCCESS; + } + } + + + int ADDCALL airspy_gpiodir_read(airspy_device_t* device, airspy_gpio_port_t port, airspy_gpio_pin_t pin, uint8_t* value) + { + int result; + uint8_t port_pin; + + port_pin = ((uint8_t)port) << 5; + port_pin = port_pin | pin; + + result = libusb_control_transfer( + device->usb_device, + LIBUSB_ENDPOINT_IN | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE, + AIRSPY_GPIODIR_READ, + 0, + port_pin, + (unsigned char*)value, + 1, + LIBUSB_CTRL_TIMEOUT_MS); + + if (result < 1) + { + return AIRSPY_ERROR_LIBUSB; + } + else { + return AIRSPY_SUCCESS; + } + } + + int ADDCALL airspy_gpiodir_write(airspy_device_t* device, airspy_gpio_port_t port, airspy_gpio_pin_t pin, uint8_t value) + { + int result; + uint8_t port_pin; + + port_pin = ((uint8_t)port) << 5; + port_pin = port_pin | pin; + + result = libusb_control_transfer( + device->usb_device, + LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE, + AIRSPY_GPIODIR_WRITE, + value, + port_pin, + NULL, + 0, + LIBUSB_CTRL_TIMEOUT_MS); + + if (result != 0) + { + return AIRSPY_ERROR_LIBUSB; + } + else { + return AIRSPY_SUCCESS; + } + } + + int ADDCALL airspy_spiflash_erase(airspy_device_t* device) + { + int result; + result = libusb_control_transfer( + device->usb_device, + LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE, + AIRSPY_SPIFLASH_ERASE, + 0, + 0, + NULL, + 0, + LIBUSB_CTRL_TIMEOUT_MS); + + if (result != 0) + { + return AIRSPY_ERROR_LIBUSB; + } + else { + return AIRSPY_SUCCESS; + } + } + + int ADDCALL airspy_spiflash_erase_sector(airspy_device_t* device, const uint16_t sector_num) + { + int result; + result = libusb_control_transfer( + device->usb_device, + LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE, + AIRSPY_SPIFLASH_ERASE_SECTOR, + sector_num, + 0, + NULL, + 0, + LIBUSB_CTRL_TIMEOUT_MS); + + if (result != 0) + { + return AIRSPY_ERROR_LIBUSB; + } + else { + return AIRSPY_SUCCESS; + } + } + + int ADDCALL airspy_spiflash_write(airspy_device_t* device, const uint32_t address, const uint16_t length, unsigned char* const data) + { + int result; + + if (address > 0x0FFFFF) + { + return AIRSPY_ERROR_INVALID_PARAM; + } + + result = libusb_control_transfer( + device->usb_device, + LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE, + AIRSPY_SPIFLASH_WRITE, + address >> 16, + address & 0xFFFF, + data, + length, + LIBUSB_CTRL_TIMEOUT_MS); + + if (result < length) + { + return AIRSPY_ERROR_LIBUSB; + } + else { + return AIRSPY_SUCCESS; + } + } + + int ADDCALL airspy_spiflash_read(airspy_device_t* device, const uint32_t address, const uint16_t length, unsigned char* data) + { + int result; + + result = libusb_control_transfer( + device->usb_device, + LIBUSB_ENDPOINT_IN | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE, + AIRSPY_SPIFLASH_READ, + address >> 16, + address & 0xFFFF, + data, + length, + LIBUSB_CTRL_TIMEOUT_MS); + + if (result < length) + { + return AIRSPY_ERROR_LIBUSB; + } + else { + return AIRSPY_SUCCESS; + } + } + + int ADDCALL airspy_board_id_read(airspy_device_t* device, uint8_t* value) + { + int result; + result = libusb_control_transfer( + device->usb_device, + LIBUSB_ENDPOINT_IN | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE, + AIRSPY_BOARD_ID_READ, + 0, + 0, + value, + 1, + LIBUSB_CTRL_TIMEOUT_MS); + + if (result < 1) + { + return AIRSPY_ERROR_LIBUSB; + } + else { + return AIRSPY_SUCCESS; + } + } + + int ADDCALL airspy_version_string_read(airspy_device_t* device, char* version, uint8_t length) + { +#define VERSION_LOCAL_SIZE (128) + int result; + char version_local[VERSION_LOCAL_SIZE] = ""; + + result = libusb_control_transfer( + device->usb_device, + LIBUSB_ENDPOINT_IN | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE, + AIRSPY_VERSION_STRING_READ, + 0, + 0, + (unsigned char*)version_local, + (VERSION_LOCAL_SIZE - 1), + LIBUSB_CTRL_TIMEOUT_MS); + + if (result < 0) + { + return AIRSPY_ERROR_LIBUSB; + } + else + { + if (length > 0) + { + const int num_bytes_to_copy = (length > VERSION_LOCAL_SIZE ? VERSION_LOCAL_SIZE : length) - 1; + memcpy(version, version_local, num_bytes_to_copy); + version[num_bytes_to_copy] = 0; + return AIRSPY_SUCCESS; + } + else + { + return AIRSPY_ERROR_INVALID_PARAM; + } + } + } + + int ADDCALL airspy_board_partid_serialno_read(airspy_device_t* device, airspy_read_partid_serialno_t* read_partid_serialno) + { + uint8_t length; + int result; + + length = sizeof(airspy_read_partid_serialno_t); + result = libusb_control_transfer( + device->usb_device, + LIBUSB_ENDPOINT_IN | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE, + AIRSPY_BOARD_PARTID_SERIALNO_READ, + 0, + 0, + (unsigned char*)read_partid_serialno, + length, + LIBUSB_CTRL_TIMEOUT_MS); + + if (result < length) + { + return AIRSPY_ERROR_LIBUSB; + } + else { + + read_partid_serialno->part_id[0] = TO_LE(read_partid_serialno->part_id[0]); + read_partid_serialno->part_id[1] = TO_LE(read_partid_serialno->part_id[1]); + read_partid_serialno->serial_no[0] = TO_LE(read_partid_serialno->serial_no[0]); + read_partid_serialno->serial_no[1] = TO_LE(read_partid_serialno->serial_no[1]); + read_partid_serialno->serial_no[2] = TO_LE(read_partid_serialno->serial_no[2]); + read_partid_serialno->serial_no[3] = TO_LE(read_partid_serialno->serial_no[3]); + + return AIRSPY_SUCCESS; + } + } + + int ADDCALL airspy_set_sample_type(struct airspy_device* device, enum airspy_sample_type sample_type) + { + device->sample_type = sample_type; + return AIRSPY_SUCCESS; + } + + int ADDCALL airspy_set_freq(airspy_device_t* device, const uint32_t freq_hz) + { + set_freq_params_t set_freq_params; + uint8_t length; + int result; + + set_freq_params.freq_hz = TO_LE(freq_hz); + length = sizeof(set_freq_params_t); + + result = libusb_control_transfer( + device->usb_device, + LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE, + AIRSPY_SET_FREQ, + 0, + 0, + (unsigned char*)&set_freq_params, + length, + LIBUSB_CTRL_TIMEOUT_MS); + + if (result < length) + { + return AIRSPY_ERROR_LIBUSB; + } + else { + return AIRSPY_SUCCESS; + } + } + + int ADDCALL airspy_set_conversion_filter_float32(struct airspy_device* device, const float *kernel, const uint32_t len) + { + if (device->streaming) + { + return AIRSPY_ERROR_BUSY; + } + + iqconverter_float_free(device->cnv_f); + device->cnv_f = iqconverter_float_create(kernel, len); + + return AIRSPY_SUCCESS; + } + + int ADDCALL airspy_set_conversion_filter_int16(struct airspy_device* device, const int16_t *kernel, const uint32_t len) + { + if (device->streaming) + { + return AIRSPY_ERROR_BUSY; + } + + iqconverter_int16_free(device->cnv_i); + device->cnv_i = iqconverter_int16_create(kernel, len); + + return AIRSPY_SUCCESS; + } + + int ADDCALL airspy_set_lna_gain(airspy_device_t* device, uint8_t value) + { + int result; + uint8_t retval; + uint8_t length; + + if (value > 14) + value = 14; + + length = 1; + + result = libusb_control_transfer( + device->usb_device, + LIBUSB_ENDPOINT_IN | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE, + AIRSPY_SET_LNA_GAIN, + 0, + value, + &retval, + length, + LIBUSB_CTRL_TIMEOUT_MS); + + if (result < length) + { + return AIRSPY_ERROR_LIBUSB; + } + else { + return AIRSPY_SUCCESS; + } + } + + int ADDCALL airspy_set_mixer_gain(airspy_device_t* device, uint8_t value) + { + int result; + uint8_t retval; + uint8_t length; + + if (value > 15) + value = 15; + + length = 1; + + result = libusb_control_transfer( + device->usb_device, + LIBUSB_ENDPOINT_IN | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE, + AIRSPY_SET_MIXER_GAIN, + 0, + value, + &retval, + length, + LIBUSB_CTRL_TIMEOUT_MS); + + if (result < length) + { + return AIRSPY_ERROR_LIBUSB; + } + else { + return AIRSPY_SUCCESS; + } + } + + int ADDCALL airspy_set_vga_gain(airspy_device_t* device, uint8_t value) + { + int result; + uint8_t retval; + uint8_t length; + + if (value > 15) + value = 15; + + length = 1; + + result = libusb_control_transfer( + device->usb_device, + LIBUSB_ENDPOINT_IN | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE, + AIRSPY_SET_VGA_GAIN, + 0, + value, + &retval, + length, + LIBUSB_CTRL_TIMEOUT_MS); + + if (result < length) + { + return AIRSPY_ERROR_LIBUSB; + } + else { + return AIRSPY_SUCCESS; + } + } + + int ADDCALL airspy_set_lna_agc(airspy_device_t* device, uint8_t value) + { + int result; + uint8_t retval; + uint8_t length; + + length = 1; + + result = libusb_control_transfer( + device->usb_device, + LIBUSB_ENDPOINT_IN | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE, + AIRSPY_SET_LNA_AGC, + 0, + value, + &retval, + length, + LIBUSB_CTRL_TIMEOUT_MS); + + if (result < length) + { + return AIRSPY_ERROR_LIBUSB; + } + else { + return AIRSPY_SUCCESS; + } + } + + int ADDCALL airspy_set_mixer_agc(airspy_device_t* device, uint8_t value) + { + int result; + uint8_t retval; + uint8_t length; + + length = 1; + + result = libusb_control_transfer( + device->usb_device, + LIBUSB_ENDPOINT_IN | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE, + AIRSPY_SET_MIXER_AGC, + 0, + value, + &retval, + length, + LIBUSB_CTRL_TIMEOUT_MS); + + if (result < length) + { + return AIRSPY_ERROR_LIBUSB; + } + else { + return AIRSPY_SUCCESS; + } + } + + int ADDCALL airspy_set_linearity_gain(struct airspy_device* device, uint8_t value) + { + int rc; + + if (value >= GAIN_COUNT) + { + value = GAIN_COUNT - 1; + } + + value = GAIN_COUNT - 1 - value; + + rc = airspy_set_mixer_agc(device, 0); + if (rc < 0) + return rc; + + rc = airspy_set_lna_agc(device, 0); + if (rc < 0) + return rc; + + rc = airspy_set_vga_gain(device, airspy_linearity_vga_gains[value]); + if (rc < 0) + return rc; + + rc = airspy_set_mixer_gain(device, airspy_linearity_mixer_gains[value]); + if (rc < 0) + return rc; + + rc = airspy_set_lna_gain(device, airspy_linearity_lna_gains[value]); + if (rc < 0) + return rc; + + return AIRSPY_SUCCESS; + } + + int ADDCALL airspy_set_sensitivity_gain(struct airspy_device* device, uint8_t value) + { + int rc; + + if (value >= GAIN_COUNT) + { + value = GAIN_COUNT - 1; + } + + value = GAIN_COUNT - 1 - value; + + rc = airspy_set_mixer_agc(device, 0); + if (rc < 0) + return rc; + + rc = airspy_set_lna_agc(device, 0); + if (rc < 0) + return rc; + + rc = airspy_set_vga_gain(device, airspy_sensitivity_vga_gains[value]); + if (rc < 0) + return rc; + + rc = airspy_set_mixer_gain(device, airspy_sensitivity_mixer_gains[value]); + if (rc < 0) + return rc; + + rc = airspy_set_lna_gain(device, airspy_sensitivity_lna_gains[value]); + if (rc < 0) + return rc; + + return AIRSPY_SUCCESS; + } + + int ADDCALL airspy_set_rf_bias(airspy_device_t* device, uint8_t value) + { + return airspy_gpio_write(device, GPIO_PORT1, GPIO_PIN13, value); + } + + int ADDCALL airspy_set_packing(airspy_device_t* device, uint8_t value) + { + int result; + uint8_t retval; + bool packing_enabled; + + if (device->streaming) + { + return AIRSPY_ERROR_BUSY; + } + + result = libusb_control_transfer( + device->usb_device, + LIBUSB_ENDPOINT_IN | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE, + AIRSPY_SET_PACKING, + 0, + value, + &retval, + 1, + LIBUSB_CTRL_TIMEOUT_MS); + + if (result < 1) + { + return AIRSPY_ERROR_LIBUSB; + } + + packing_enabled = value ? true : false; + if (packing_enabled != device->packing_enabled) + { + cancel_transfers(device); + free_transfers(device); + + device->packing_enabled = packing_enabled; + device->buffer_size = packing_enabled ? (6144 * 24) : 262144; + + result = allocate_transfers(device); + if (result != 0) + { + return AIRSPY_ERROR_NO_MEM; + } + } + + return AIRSPY_SUCCESS; + } + + int ADDCALL airspy_is_streaming(airspy_device_t* device) + { + return (device->streaming == true && device->stop_requested == false); + } + + const char* ADDCALL airspy_error_name(enum airspy_error errcode) + { + switch (errcode) + { + case AIRSPY_SUCCESS: + return "AIRSPY_SUCCESS"; + + case AIRSPY_TRUE: + return "AIRSPY_TRUE"; + + case AIRSPY_ERROR_INVALID_PARAM: + return "AIRSPY_ERROR_INVALID_PARAM"; + + case AIRSPY_ERROR_NOT_FOUND: + return "AIRSPY_ERROR_NOT_FOUND"; + + case AIRSPY_ERROR_BUSY: + return "AIRSPY_ERROR_BUSY"; + + case AIRSPY_ERROR_NO_MEM: + return "AIRSPY_ERROR_NO_MEM"; + + case AIRSPY_ERROR_LIBUSB: + return "AIRSPY_ERROR_LIBUSB"; + + case AIRSPY_ERROR_THREAD: + return "AIRSPY_ERROR_THREAD"; + + case AIRSPY_ERROR_STREAMING_THREAD_ERR: + return "AIRSPY_ERROR_STREAMING_THREAD_ERR"; + + case AIRSPY_ERROR_STREAMING_STOPPED: + return "AIRSPY_ERROR_STREAMING_STOPPED"; + + case AIRSPY_ERROR_OTHER: + return "AIRSPY_ERROR_OTHER"; + + default: + return "airspy unknown error"; + } + } + + const char* ADDCALL airspy_board_id_name(enum airspy_board_id board_id) + { + switch (board_id) + { + case AIRSPY_BOARD_ID_PROTO_AIRSPY: + return "AIRSPY"; + + case AIRSPY_BOARD_ID_INVALID: + return "Invalid Board ID"; + + default: + return "Unknown Board ID"; + } + } + +#ifdef __cplusplus +} // __cplusplus defined. +#endif diff --git a/Radio/HW/AirSpy/src/airspy.h b/Radio/HW/AirSpy/src/airspy.h new file mode 100644 index 0000000..573298b --- /dev/null +++ b/Radio/HW/AirSpy/src/airspy.h @@ -0,0 +1,224 @@ +/* +Copyright (c) 2012, Jared Boone <jared@sharebrained.com> +Copyright (c) 2013, Michael Ossmann <mike@ossmann.com> +Copyright (c) 2013-2016, Benjamin Vernoux <bvernoux@airspy.com> +Copyright (C) 2013-2016, Youssef Touil <youssef@airspy.com> + +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + + Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + Neither the name of AirSpy nor the names of its contributors may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifndef __AIRSPY_H__ +#define __AIRSPY_H__ + +#include <stdint.h> +#include "airspy_commands.h" + +#define AIRSPY_VERSION "1.0.12" +#define AIRSPY_VER_MAJOR 1 +#define AIRSPY_VER_MINOR 0 +#define AIRSPY_VER_REVISION 12 + +#ifdef _WIN32 + #define ADD_EXPORTS + + /* You should define ADD_EXPORTS *only* when building the DLL. */ + #ifdef ADD_EXPORTS + #define ADDAPI __declspec(dllexport) + #else + #define ADDAPI __declspec(dllimport) + #endif + + /* Define calling convention in one place, for convenience. */ + #define ADDCALL __cdecl + +#else /* _WIN32 not defined. */ + + /* Define with no value on non-Windows OSes. */ + #define ADDAPI + #define ADDCALL + +#endif + +#ifdef __cplusplus +extern "C" +{ +#endif + +enum airspy_error +{ + AIRSPY_SUCCESS = 0, + AIRSPY_TRUE = 1, + AIRSPY_ERROR_INVALID_PARAM = -2, + AIRSPY_ERROR_NOT_FOUND = -5, + AIRSPY_ERROR_BUSY = -6, + AIRSPY_ERROR_NO_MEM = -11, + AIRSPY_ERROR_UNSUPPORTED = -12, + AIRSPY_ERROR_LIBUSB = -1000, + AIRSPY_ERROR_THREAD = -1001, + AIRSPY_ERROR_STREAMING_THREAD_ERR = -1002, + AIRSPY_ERROR_STREAMING_STOPPED = -1003, + AIRSPY_ERROR_OTHER = -9999, +}; + +enum airspy_board_id +{ + AIRSPY_BOARD_ID_PROTO_AIRSPY = 0, + AIRSPY_BOARD_ID_INVALID = 0xFF, +}; + +enum airspy_sample_type +{ + AIRSPY_SAMPLE_FLOAT32_IQ = 0, /* 2 * 32bit float per sample */ + AIRSPY_SAMPLE_FLOAT32_REAL = 1, /* 1 * 32bit float per sample */ + AIRSPY_SAMPLE_INT16_IQ = 2, /* 2 * 16bit int per sample */ + AIRSPY_SAMPLE_INT16_REAL = 3, /* 1 * 16bit int per sample */ + AIRSPY_SAMPLE_UINT16_REAL = 4, /* 1 * 16bit unsigned int per sample */ + AIRSPY_SAMPLE_RAW = 5, /* Raw packed samples from the device */ + AIRSPY_SAMPLE_END = 6 /* Number of supported sample types */ +}; + +#define MAX_CONFIG_PAGE_SIZE (0x10000) + +struct airspy_device; + +typedef struct { + struct airspy_device* device; + void* ctx; + void* samples; + int sample_count; + uint64_t dropped_samples; + enum airspy_sample_type sample_type; +} airspy_transfer_t, airspy_transfer; + +typedef struct { + uint32_t part_id[2]; + uint32_t serial_no[4]; +} airspy_read_partid_serialno_t; + +typedef struct { + uint32_t major_version; + uint32_t minor_version; + uint32_t revision; +} airspy_lib_version_t; + +typedef int (*airspy_sample_block_cb_fn)(airspy_transfer* transfer); + +extern ADDAPI void ADDCALL airspy_lib_version(airspy_lib_version_t* lib_version); +/* airspy_init() deprecated */ +extern ADDAPI int ADDCALL airspy_init(void); +/* airspy_exit() deprecated */ +extern ADDAPI int ADDCALL airspy_exit(void); + +extern ADDAPI int ADDCALL airspy_list_devices(uint64_t *serials, int count); + +extern ADDAPI int ADDCALL airspy_open_sn(struct airspy_device** device, uint64_t serial_number); +extern ADDAPI int ADDCALL airspy_open_fd(struct airspy_device** device, int fd); +extern ADDAPI int ADDCALL airspy_open(struct airspy_device** device); +extern ADDAPI int ADDCALL airspy_close(struct airspy_device* device); + +/* Use airspy_get_samplerates(device, buffer, 0) to get the number of available sample rates. It will be returned in the first element of buffer */ +extern ADDAPI int ADDCALL airspy_get_samplerates(struct airspy_device* device, uint32_t* buffer, const uint32_t len); + +/* Parameter samplerate can be either the index of a samplerate or directly its value in Hz within the list returned by airspy_get_samplerates() */ +extern ADDAPI int ADDCALL airspy_set_samplerate(struct airspy_device* device, uint32_t samplerate); + +extern ADDAPI int ADDCALL airspy_set_conversion_filter_float32(struct airspy_device* device, const float *kernel, const uint32_t len); +extern ADDAPI int ADDCALL airspy_set_conversion_filter_int16(struct airspy_device* device, const int16_t *kernel, const uint32_t len); + +extern ADDAPI int ADDCALL airspy_start_rx(struct airspy_device* device, airspy_sample_block_cb_fn callback, void* rx_ctx); +extern ADDAPI int ADDCALL airspy_stop_rx(struct airspy_device* device); + +/* return AIRSPY_TRUE if success */ +extern ADDAPI int ADDCALL airspy_is_streaming(struct airspy_device* device); + +extern ADDAPI int ADDCALL airspy_si5351c_write(struct airspy_device* device, uint8_t register_number, uint8_t value); +extern ADDAPI int ADDCALL airspy_si5351c_read(struct airspy_device* device, uint8_t register_number, uint8_t* value); + +extern ADDAPI int ADDCALL airspy_config_write(struct airspy_device* device, const uint8_t page_index, const uint16_t length, unsigned char *data); +extern ADDAPI int ADDCALL airspy_config_read(struct airspy_device* device, const uint8_t page_index, const uint16_t length, unsigned char *data); + +extern ADDAPI int ADDCALL airspy_r820t_write(struct airspy_device* device, uint8_t register_number, uint8_t value); +extern ADDAPI int ADDCALL airspy_r820t_read(struct airspy_device* device, uint8_t register_number, uint8_t* value); + +/* Parameter value shall be 0=clear GPIO or 1=set GPIO */ +extern ADDAPI int ADDCALL airspy_gpio_write(struct airspy_device* device, airspy_gpio_port_t port, airspy_gpio_pin_t pin, uint8_t value); +/* Parameter value corresponds to GPIO state 0 or 1 */ +extern ADDAPI int ADDCALL airspy_gpio_read(struct airspy_device* device, airspy_gpio_port_t port, airspy_gpio_pin_t pin, uint8_t* value); + +/* Parameter value shall be 0=GPIO Input direction or 1=GPIO Output direction */ +extern ADDAPI int ADDCALL airspy_gpiodir_write(struct airspy_device* device, airspy_gpio_port_t port, airspy_gpio_pin_t pin, uint8_t value); +extern ADDAPI int ADDCALL airspy_gpiodir_read(struct airspy_device* device, airspy_gpio_port_t port, airspy_gpio_pin_t pin, uint8_t* value); + +extern ADDAPI int ADDCALL airspy_spiflash_erase(struct airspy_device* device); +extern ADDAPI int ADDCALL airspy_spiflash_write(struct airspy_device* device, const uint32_t address, const uint16_t length, unsigned char* const data); +extern ADDAPI int ADDCALL airspy_spiflash_read(struct airspy_device* device, const uint32_t address, const uint16_t length, unsigned char* data); + +extern ADDAPI int ADDCALL airspy_board_id_read(struct airspy_device* device, uint8_t* value); +/* Parameter length shall be at least 128bytes to avoid possible string clipping */ +extern ADDAPI int ADDCALL airspy_version_string_read(struct airspy_device* device, char* version, uint8_t length); + +extern ADDAPI int ADDCALL airspy_board_partid_serialno_read(struct airspy_device* device, airspy_read_partid_serialno_t* read_partid_serialno); + +extern ADDAPI int ADDCALL airspy_set_sample_type(struct airspy_device* device, enum airspy_sample_type sample_type); + +/* Parameter freq_hz shall be between 24000000(24MHz) and 1750000000(1.75GHz) */ +extern ADDAPI int ADDCALL airspy_set_freq(struct airspy_device* device, const uint32_t freq_hz); + +/* Parameter value shall be between 0 and 15 */ +extern ADDAPI int ADDCALL airspy_set_lna_gain(struct airspy_device* device, uint8_t value); + +/* Parameter value shall be between 0 and 15 */ +extern ADDAPI int ADDCALL airspy_set_mixer_gain(struct airspy_device* device, uint8_t value); + +/* Parameter value shall be between 0 and 15 */ +extern ADDAPI int ADDCALL airspy_set_vga_gain(struct airspy_device* device, uint8_t value); + +/* Parameter value: + 0=Disable LNA Automatic Gain Control + 1=Enable LNA Automatic Gain Control +*/ +extern ADDAPI int ADDCALL airspy_set_lna_agc(struct airspy_device* device, uint8_t value); +/* Parameter value: + 0=Disable MIXER Automatic Gain Control + 1=Enable MIXER Automatic Gain Control +*/ +extern ADDAPI int ADDCALL airspy_set_mixer_agc(struct airspy_device* device, uint8_t value); + +/* Parameter value: 0..21 */ +extern ADDAPI int ADDCALL airspy_set_linearity_gain(struct airspy_device* device, uint8_t value); + +/* Parameter value: 0..21 */ +extern ADDAPI int ADDCALL airspy_set_sensitivity_gain(struct airspy_device* device, uint8_t value); + +/* Parameter value shall be 0=Disable BiasT or 1=Enable BiasT */ +extern ADDAPI int ADDCALL airspy_set_rf_bias(struct airspy_device* dev, uint8_t value); + +/* Parameter value shall be 0=Disable Packing or 1=Enable Packing */ +extern ADDAPI int ADDCALL airspy_set_packing(struct airspy_device* device, uint8_t value); + +extern ADDAPI const char* ADDCALL airspy_error_name(enum airspy_error errcode); +extern ADDAPI const char* ADDCALL airspy_board_id_name(enum airspy_board_id board_id); + +/* Parameter sector_num shall be between 2 & 13 (sector 0 & 1 are reserved) */ +extern ADDAPI int ADDCALL airspy_spiflash_erase_sector(struct airspy_device* device, const uint16_t sector_num); + +#ifdef __cplusplus +} // __cplusplus defined. +#endif + +#endif//__AIRSPY_H__ diff --git a/Radio/HW/AirSpy/src/airspy_commands.h b/Radio/HW/AirSpy/src/airspy_commands.h new file mode 100644 index 0000000..21194f8 --- /dev/null +++ b/Radio/HW/AirSpy/src/airspy_commands.h @@ -0,0 +1,145 @@ +/* +Copyright (c) 2013-2016, Benjamin Vernoux <bvernoux@airspy.com> + +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + + Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + Neither the name of AirSpy nor the names of its contributors may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifndef __AIRSPY_COMMANDS_H__ +#define __AIRSPY_COMMANDS_H__ + +#include <stdint.h> + +#ifdef __cplusplus +extern "C" +{ +#endif + +typedef enum +{ + RECEIVER_MODE_OFF = 0, + RECEIVER_MODE_RX = 1 +} receiver_mode_t; + +/* + Note: airspy_samplerate_t is now obsolete and left for backward compatibility. + The list of supported sample rates should be retrieved at run time by calling airspy_get_samplerates(). + Refer to the Airspy Tools for illustrations. +*/ +typedef enum +{ + AIRSPY_SAMPLERATE_10MSPS = 0, /* 12bits 10MHz IQ */ + AIRSPY_SAMPLERATE_2_5MSPS = 1, /* 12bits 2.5MHz IQ */ + AIRSPY_SAMPLERATE_END = 2 /* End index for sample rate (corresponds to number of samplerate) */ +} airspy_samplerate_t; + + +#define AIRSPY_CONF_CMD_SHIFT_BIT (3) // Up to 3bits=8 samplerates (airspy_samplerate_t enum shall not exceed 7) + +// Commands (usb vendor request) shared between Firmware and Host. +#define AIRSPY_CMD_MAX (27) +typedef enum +{ + AIRSPY_INVALID = 0 , + AIRSPY_RECEIVER_MODE = 1 , + AIRSPY_SI5351C_WRITE = 2 , + AIRSPY_SI5351C_READ = 3 , + AIRSPY_R820T_WRITE = 4 , + AIRSPY_R820T_READ = 5 , + AIRSPY_SPIFLASH_ERASE = 6 , + AIRSPY_SPIFLASH_WRITE = 7 , + AIRSPY_SPIFLASH_READ = 8 , + AIRSPY_BOARD_ID_READ = 9 , + AIRSPY_VERSION_STRING_READ = 10, + AIRSPY_BOARD_PARTID_SERIALNO_READ = 11, + AIRSPY_SET_SAMPLERATE = 12, + AIRSPY_SET_FREQ = 13, + AIRSPY_SET_LNA_GAIN = 14, + AIRSPY_SET_MIXER_GAIN = 15, + AIRSPY_SET_VGA_GAIN = 16, + AIRSPY_SET_LNA_AGC = 17, + AIRSPY_SET_MIXER_AGC = 18, + AIRSPY_MS_VENDOR_CMD = 19, + AIRSPY_SET_RF_BIAS_CMD = 20, + AIRSPY_GPIO_WRITE = 21, + AIRSPY_GPIO_READ = 22, + AIRSPY_GPIODIR_WRITE = 23, + AIRSPY_GPIODIR_READ = 24, + AIRSPY_GET_SAMPLERATES = 25, + AIRSPY_SET_PACKING = 26, + AIRSPY_SPIFLASH_ERASE_SECTOR = AIRSPY_CMD_MAX +} airspy_vendor_request; + +typedef enum +{ + CONFIG_CALIBRATION = 0, + //CONFIG_META = 1, +} airspy_common_config_pages_t; + +typedef enum +{ + GPIO_PORT0 = 0, + GPIO_PORT1 = 1, + GPIO_PORT2 = 2, + GPIO_PORT3 = 3, + GPIO_PORT4 = 4, + GPIO_PORT5 = 5, + GPIO_PORT6 = 6, + GPIO_PORT7 = 7 +} airspy_gpio_port_t; + +typedef enum +{ + GPIO_PIN0 = 0, + GPIO_PIN1 = 1, + GPIO_PIN2 = 2, + GPIO_PIN3 = 3, + GPIO_PIN4 = 4, + GPIO_PIN5 = 5, + GPIO_PIN6 = 6, + GPIO_PIN7 = 7, + GPIO_PIN8 = 8, + GPIO_PIN9 = 9, + GPIO_PIN10 = 10, + GPIO_PIN11 = 11, + GPIO_PIN12 = 12, + GPIO_PIN13 = 13, + GPIO_PIN14 = 14, + GPIO_PIN15 = 15, + GPIO_PIN16 = 16, + GPIO_PIN17 = 17, + GPIO_PIN18 = 18, + GPIO_PIN19 = 19, + GPIO_PIN20 = 20, + GPIO_PIN21 = 21, + GPIO_PIN22 = 22, + GPIO_PIN23 = 23, + GPIO_PIN24 = 24, + GPIO_PIN25 = 25, + GPIO_PIN26 = 26, + GPIO_PIN27 = 27, + GPIO_PIN28 = 28, + GPIO_PIN29 = 29, + GPIO_PIN30 = 30, + GPIO_PIN31 = 31 +} airspy_gpio_pin_t; + +#ifdef __cplusplus +} // __cplusplus defined. +#endif + +#endif//__AIRSPY_COMMANDS_H__ diff --git a/Radio/HW/AirSpy/src/filters.h b/Radio/HW/AirSpy/src/filters.h new file mode 100644 index 0000000..30da6e4 --- /dev/null +++ b/Radio/HW/AirSpy/src/filters.h @@ -0,0 +1,134 @@ +/* +Copyright (C) 2014, Youssef Touil <youssef@airspy.com> + +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. +*/ + +#ifndef FILTERS_H +#define FILTERS_H + +#include <stdint.h> + +#define HB_KERNEL_FLOAT_LEN 47 + +const float HB_KERNEL_FLOAT[HB_KERNEL_FLOAT_LEN] = +{ + -0.000998606272947510, + 0.000000000000000000, + 0.001695637278417295, + 0.000000000000000000, + -0.003054430179754289, + 0.000000000000000000, + 0.005055504379767936, + 0.000000000000000000, + -0.007901319195893647, + 0.000000000000000000, + 0.011873357051047719, + 0.000000000000000000, + -0.017411159379930066, + 0.000000000000000000, + 0.025304817427568772, + 0.000000000000000000, + -0.037225225204559217, + 0.000000000000000000, + 0.057533286997004301, + 0.000000000000000000, + -0.102327462004259350, + 0.000000000000000000, + 0.317034472508947400, + 0.500000000000000000, + 0.317034472508947400, + 0.000000000000000000, + -0.102327462004259350, + 0.000000000000000000, + 0.057533286997004301, + 0.000000000000000000, + -0.037225225204559217, + 0.000000000000000000, + 0.025304817427568772, + 0.000000000000000000, + -0.017411159379930066, + 0.000000000000000000, + 0.011873357051047719, + 0.000000000000000000, + -0.007901319195893647, + 0.000000000000000000, + 0.005055504379767936, + 0.000000000000000000, + -0.003054430179754289, + 0.000000000000000000, + 0.001695637278417295, + 0.000000000000000000, + -0.000998606272947510 +}; + +#define HB_KERNEL_INT16_LEN 47 + +const int16_t HB_KERNEL_INT16[HB_KERNEL_INT16_LEN] = +{ + -33, + 0, + 56, + 0, + -100, + 0, + 166, + 0, + -259, + 0, + 389, + 0, + -571, + 0, + 829, + 0, + -1220, + 0, + 1885, + 0, + -3353, + 0, + 10389, + 16384, + 10389, + 0, + -3353, + 0, + 1885, + 0, + -1220, + 0, + 829, + 0, + -571, + 0, + 389, + 0, + -259, + 0, + 166, + 0, + -100, + 0, + 56, + 0, + -33 +}; + +#endif // FILTERS_H diff --git a/Radio/HW/AirSpy/src/iqconverter_float.c b/Radio/HW/AirSpy/src/iqconverter_float.c new file mode 100644 index 0000000..c656cdb --- /dev/null +++ b/Radio/HW/AirSpy/src/iqconverter_float.c @@ -0,0 +1,502 @@ +/* +Copyright (C) 2014, Youssef Touil <youssef@airspy.com> + +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 "iqconverter_float.h" +#include <stdlib.h> +#include <string.h> + +#include <stdio.h> + +#if defined(__MINGW32__) && !defined(__MINGW64_VERSION_MAJOR) + #include <malloc.h> + #define _aligned_malloc __mingw_aligned_malloc + #define _aligned_free __mingw_aligned_free + #define _inline inline + #define FIR_STANDARD +#elif defined(__APPLE__) + #include <malloc/malloc.h> + #define _aligned_malloc(size, alignment) malloc(size) + #define _aligned_free(mem) free(mem) + #define _inline inline + #define FIR_STANDARD +#elif defined(__FreeBSD__) + #define USE_SSE2 +#include <immintrin.h> + #define _inline inline + #define _aligned_free(mem) free(mem) +void *_aligned_malloc(size_t size, size_t alignment) +{ + void *result; + if (posix_memalign(&result, alignment, size) == 0) + return result; + return 0; +} +#elif defined(__GNUC__) && !defined(__MINGW64_VERSION_MAJOR) + #include <malloc.h> + #define _aligned_malloc(size, alignment) memalign(alignment, size) + #define _aligned_free(mem) free(mem) + #define _inline inline +#else + #if (_MSC_VER >= 1800) + //#define USE_SSE2 + //#include <immintrin.h> + #endif +#endif + +#define SIZE_FACTOR 32 +#define DEFAULT_ALIGNMENT 16 +#define HPF_COEFF 0.01f + +#if defined(_MSC_VER) + #define ALIGNED __declspec(align(DEFAULT_ALIGNMENT)) +#else + #define ALIGNED +#endif + +iqconverter_float_t *iqconverter_float_create(const float *hb_kernel, int len) +{ + int i, j; + size_t buffer_size; + iqconverter_float_t *cnv = (iqconverter_float_t *) _aligned_malloc(sizeof(iqconverter_float_t), DEFAULT_ALIGNMENT); + + cnv->len = len / 2 + 1; + cnv->hbc = hb_kernel[len / 2]; + + buffer_size = cnv->len * sizeof(float); + + cnv->fir_kernel = (float *) _aligned_malloc(buffer_size, DEFAULT_ALIGNMENT); + cnv->fir_queue = (float *) _aligned_malloc(buffer_size * SIZE_FACTOR, DEFAULT_ALIGNMENT); + cnv->delay_line = (float *) _aligned_malloc(buffer_size / 2, DEFAULT_ALIGNMENT); + + iqconverter_float_reset(cnv); + + for (i = 0, j = 0; i < cnv->len; i++, j += 2) + { + cnv->fir_kernel[i] = hb_kernel[j]; + } + + return cnv; +} + +void iqconverter_float_free(iqconverter_float_t *cnv) +{ + _aligned_free(cnv->fir_kernel); + _aligned_free(cnv->fir_queue); + _aligned_free(cnv->delay_line); + _aligned_free(cnv); +} + +void iqconverter_float_reset(iqconverter_float_t *cnv) +{ + cnv->avg = 0.0f; + cnv->fir_index = 0; + cnv->delay_index = 0; + memset(cnv->delay_line, 0, cnv->len * sizeof(float) / 2); + memset(cnv->fir_queue, 0, cnv->len * sizeof(float) * SIZE_FACTOR); +} + +static _inline float process_fir_taps(const float *kernel, const float *queue, int len) +{ + int i; + +#ifdef USE_SSE2 + + __m128 acc = _mm_set_ps(0, 0, 0, 0); + +#else + + float sum = 0.0f; + +#endif + + if (len >= 8) + { + int it = len >> 3; + +#ifdef USE_SSE2 + + for (i = 0; i < it; i++)
+ {
+ __m128 head1 = _mm_loadu_ps(queue);
+ __m128 kern1 = _mm_load_ps(kernel);
+ __m128 head2 = _mm_loadu_ps(queue + 4);
+ __m128 kern2 = _mm_load_ps(kernel + 4);
+
+ __m128 mul1 = _mm_mul_ps(kern1, head1);
+ __m128 mul2 = _mm_mul_ps(kern2, head2);
+
+ mul1 = _mm_add_ps(mul1, mul2);
+
+ acc = _mm_add_ps(acc, mul1);
+
+ queue += 8;
+ kernel += 8;
+ } + +#else + + for (i = 0; i < it; i++) + { + sum += kernel[0] * queue[0] + + kernel[1] * queue[1] + + kernel[2] * queue[2] + + kernel[3] * queue[3] + + kernel[4] * queue[4] + + kernel[5] * queue[5] + + kernel[6] * queue[6] + + kernel[7] * queue[7];
+
+ queue += 8;
+ kernel += 8; + } + +#endif + len &= 7; + } + + if (len >= 4) + { + +#ifdef USE_SSE2 + + __m128 head = _mm_loadu_ps(queue);
+ __m128 kern = _mm_load_ps(kernel);
+ __m128 mul = _mm_mul_ps(kern, head); + acc = _mm_add_ps(acc, mul); + +#else + + sum += kernel[0] * queue[0] + + kernel[1] * queue[1] + + kernel[2] * queue[2] + + kernel[3] * queue[3]; + +#endif + + kernel += 4; + queue += 4; + len &= 3; + }
+
+#ifdef USE_SSE2 +
+ __m128 t = _mm_add_ps(acc, _mm_movehl_ps(acc, acc));
+ acc = _mm_add_ss(t, _mm_shuffle_ps(t, t, 1)); + +#ifdef __FreeBSD__ + float sum = acc[0]; +#else + float sum = acc.m128_f32[0]; +#endif + +#endif + + if (len >= 2) + { + sum += kernel[0] * queue[0] + + kernel[1] * queue[1]; + + //kernel += 2; + //queue += 2; + //len &= 1; + } + + //if (len >= 1) + //{ + // sum += kernel[0] * queue[0]; + //} + + return sum; +} + +static void fir_interleaved_4(iqconverter_float_t *cnv, float *samples, int len) +{ + int i; + int fir_index = cnv->fir_index; + int fir_len = cnv->len; + float *fir_kernel = cnv->fir_kernel; + float *fir_queue = cnv->fir_queue; + float *queue; + float acc; + + for (i = 0; i < len; i += 2) + { + queue = fir_queue + fir_index; + + queue[0] = samples[i]; + + acc = fir_kernel[0] * (queue[0] + queue[4 - 1]) + + fir_kernel[1] * (queue[1] + queue[4 - 2]); + + samples[i] = acc; + + if (--fir_index < 0) + { + fir_index = fir_len * (SIZE_FACTOR - 1); + memcpy(fir_queue + fir_index + 1, fir_queue, (fir_len - 1) * sizeof(float)); + } + } + + cnv->fir_index = fir_index; +} + +static void fir_interleaved_8(iqconverter_float_t *cnv, float *samples, int len) +{ + int i; + int fir_index = cnv->fir_index; + int fir_len = cnv->len; + float *fir_kernel = cnv->fir_kernel; + float *fir_queue = cnv->fir_queue; + float *queue; + float acc; + + for (i = 0; i < len; i += 2) + { + queue = fir_queue + fir_index; + + queue[0] = samples[i]; + + acc = fir_kernel[0] * (queue[0] + queue[8 - 1]) + + fir_kernel[1] * (queue[1] + queue[8 - 2]) + + fir_kernel[2] * (queue[2] + queue[8 - 3]) + + fir_kernel[3] * (queue[3] + queue[8 - 4]); + + samples[i] = acc; + + if (--fir_index < 0) + { + fir_index = fir_len * (SIZE_FACTOR - 1); + memcpy(fir_queue + fir_index + 1, fir_queue, (fir_len - 1) * sizeof(float)); + } + } + + cnv->fir_index = fir_index; +} + +static void fir_interleaved_12(iqconverter_float_t *cnv, float *samples, int len) +{ + int i; + int fir_index = cnv->fir_index; + int fir_len = cnv->len; + float *fir_kernel = cnv->fir_kernel; + float *fir_queue = cnv->fir_queue; + float *queue; + float acc = 0; + + for (i = 0; i < len; i += 2) + { + queue = fir_queue + fir_index; + + queue[0] = samples[i]; + + acc = fir_kernel[0] * (queue[0] + queue[12 - 1]) + + fir_kernel[1] * (queue[1] + queue[12 - 2]) + + fir_kernel[2] * (queue[2] + queue[12 - 3]) + + fir_kernel[3] * (queue[3] + queue[12 - 4]) + + fir_kernel[4] * (queue[4] + queue[12 - 5]) + + fir_kernel[5] * (queue[5] + queue[12 - 6]); + + samples[i] = acc; + + if (--fir_index < 0) + { + fir_index = fir_len * (SIZE_FACTOR - 1); + memcpy(fir_queue + fir_index + 1, fir_queue, (fir_len - 1) * sizeof(float)); + } + } + + cnv->fir_index = fir_index; +} + +static void fir_interleaved_24(iqconverter_float_t *cnv, float *samples, int len) +{ + int i; + int fir_index = cnv->fir_index; + int fir_len = cnv->len; + float *fir_kernel = cnv->fir_kernel; + float *fir_queue = cnv->fir_queue; + float *queue; + float acc = 0; + + for (i = 0; i < len; i += 2) + { + queue = fir_queue + fir_index; + + queue[0] = samples[i]; + + acc = fir_kernel[0] * (queue[0] + queue[24 - 1]) + + fir_kernel[1] * (queue[1] + queue[24 - 2]) + + fir_kernel[2] * (queue[2] + queue[24 - 3]) + + fir_kernel[3] * (queue[3] + queue[24 - 4]) + + fir_kernel[4] * (queue[4] + queue[24 - 5]) + + fir_kernel[5] * (queue[5] + queue[24 - 6]) + + fir_kernel[6] * (queue[6] + queue[24 - 7]) + + fir_kernel[7] * (queue[7] + queue[24 - 8]) + + fir_kernel[8] * (queue[8] + queue[24 - 9]) + + fir_kernel[9] * (queue[9] + queue[24 - 10]) + + fir_kernel[10] * (queue[10] + queue[24 - 11]) + + fir_kernel[11] * (queue[11] + queue[24 - 12]); + + samples[i] = acc; + + if (--fir_index < 0) + { + fir_index = fir_len * (SIZE_FACTOR - 1); + memcpy(fir_queue + fir_index + 1, fir_queue, (fir_len - 1) * sizeof(float)); + } + } + + cnv->fir_index = fir_index; +} + +static void fir_interleaved_generic(iqconverter_float_t *cnv, float *samples, int len) +{ + int i; + int fir_index = cnv->fir_index; + int fir_len = cnv->len; + float *fir_kernel = cnv->fir_kernel; + float *fir_queue = cnv->fir_queue; + float *queue; + + for (i = 0; i < len; i += 2) + { + queue = fir_queue + fir_index; + + queue[0] = samples[i]; + + samples[i] = process_fir_taps(fir_kernel, queue, fir_len); + + if (--fir_index < 0) + { + fir_index = fir_len * (SIZE_FACTOR - 1); + memcpy(fir_queue + fir_index + 1, fir_queue, (fir_len - 1) * sizeof(float)); + } + } + + cnv->fir_index = fir_index; +} + +static void fir_interleaved(iqconverter_float_t *cnv, float *samples, int len) +{ + switch (cnv->len) + { + case 4: + fir_interleaved_4(cnv, samples, len); + break; + case 8: + fir_interleaved_8(cnv, samples, len); + break; + case 12: + fir_interleaved_12(cnv, samples, len); + break; + case 24: + fir_interleaved_24(cnv, samples, len); + break; + default: + fir_interleaved_generic(cnv, samples, len); + break; + } +} + +static void delay_interleaved(iqconverter_float_t *cnv, float *samples, int len) +{ + int i; + ALIGNED int index; + ALIGNED int half_len; + ALIGNED float res; + + half_len = cnv->len >> 1; + index = cnv->delay_index; + + for (i = 0; i < len; i += 2) + { + res = cnv->delay_line[index]; + cnv->delay_line[index] = samples[i]; + samples[i] = res; + + if (++index >= half_len) + { + index = 0; + } + } + + cnv->delay_index = index; +} + +#define SCALE (0.01f) + +static void remove_dc(iqconverter_float_t *cnv, float *samples, int len) +{ + int i; + ALIGNED float avg = cnv->avg; + + for (i = 0; i < len; i++) + { + samples[i] -= avg; + avg += SCALE * samples[i]; + } + + cnv->avg = avg; +} + +static void translate_fs_4(iqconverter_float_t *cnv, float *samples, int len) +{ + int i; + ALIGNED float hbc = cnv->hbc; + +#ifdef USE_SSE2 + + float *buf = samples; + ALIGNED __m128 vec; + ALIGNED __m128 rot = _mm_set_ps(hbc, 1.0f, -hbc, -1.0f); + + for (i = 0; i < len / 4; i++, buf +=4) + { + vec = _mm_loadu_ps(buf); + vec = _mm_mul_ps(vec, rot); + _mm_storeu_ps(buf, vec); + } + +#else + + int j; + + for (i = 0; i < len / 4; i++) + { + j = i << 2; + samples[j + 0] = -samples[j + 0]; + samples[j + 1] = -samples[j + 1] * hbc; + //samples[j + 2] = samples[j + 2]; + samples[j + 3] = samples[j + 3] * hbc; + } + +#endif + + fir_interleaved(cnv, samples, len); + delay_interleaved(cnv, samples + 1, len); +} + +void iqconverter_float_process(iqconverter_float_t *cnv, float *samples, int len) +{ + remove_dc(cnv, samples, len); + translate_fs_4(cnv, samples, len); +} diff --git a/Radio/HW/AirSpy/src/iqconverter_float.h b/Radio/HW/AirSpy/src/iqconverter_float.h new file mode 100644 index 0000000..f82b8b3 --- /dev/null +++ b/Radio/HW/AirSpy/src/iqconverter_float.h @@ -0,0 +1,47 @@ +/* +Copyright (C) 2014, Youssef Touil <youssef@airspy.com> + +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. +*/ + +#ifndef IQCONVERTER_FLOAT_H +#define IQCONVERTER_FLOAT_H + +#include <stdint.h> + +#define IQCONVERTER_NZEROS 2 +#define IQCONVERTER_NPOLES 2 + +typedef struct { + float avg; + float hbc; + int len; + int fir_index; + int delay_index; + float *fir_kernel; + float *fir_queue; + float *delay_line; +} iqconverter_float_t; + +iqconverter_float_t *iqconverter_float_create(const float *hb_kernel, int len); +void iqconverter_float_free(iqconverter_float_t *cnv); +void iqconverter_float_reset(iqconverter_float_t *cnv); +void iqconverter_float_process(iqconverter_float_t *cnv, float *samples, int len); + +#endif // IQCONVERTER_FLOAT_H diff --git a/Radio/HW/AirSpy/src/iqconverter_int16.c b/Radio/HW/AirSpy/src/iqconverter_int16.c new file mode 100644 index 0000000..abb3109 --- /dev/null +++ b/Radio/HW/AirSpy/src/iqconverter_int16.c @@ -0,0 +1,205 @@ +/* +Copyright (C) 2014, Youssef Touil <youssef@airspy.com> + +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 "iqconverter_int16.h" +#include <stdlib.h> +#include <string.h> + +#if defined(__MINGW32__) && !defined(__MINGW64_VERSION_MAJOR) + #include <malloc.h> + #define _aligned_malloc __mingw_aligned_malloc + #define _aligned_free __mingw_aligned_free + #define _inline inline +#elif defined(__APPLE__) + #include <malloc/malloc.h> + #define _aligned_malloc(size, alignment) malloc(size) + #define _aligned_free(mem) free(mem) + #define _inline inline +#elif defined(__FreeBSD__) + #define _inline inline + #define _aligned_free(mem) free(mem) +void * _aligned_malloc(size_t size, size_t alignment); +#elif defined(__GNUC__) && !defined(__MINGW64_VERSION_MAJOR) + #include <malloc.h> + #define _aligned_malloc(size, alignment) memalign(alignment, size) + #define _aligned_free(mem) free(mem) + #define _inline inline +#endif + +#define SIZE_FACTOR 16 +#define DEFAULT_ALIGNMENT 16 + +iqconverter_int16_t *iqconverter_int16_create(const int16_t *hb_kernel, int len) +{ + int i; + size_t buffer_size; + iqconverter_int16_t *cnv = (iqconverter_int16_t *) _aligned_malloc(sizeof(iqconverter_int16_t), DEFAULT_ALIGNMENT); + + cnv->len = len / 2 + 1; + + buffer_size = cnv->len * sizeof(int32_t); + + cnv->fir_kernel = (int32_t *) _aligned_malloc(buffer_size, DEFAULT_ALIGNMENT); + cnv->fir_queue = (int32_t *) _aligned_malloc(buffer_size * SIZE_FACTOR, DEFAULT_ALIGNMENT); + cnv->delay_line = (int16_t *) _aligned_malloc(buffer_size / 4, DEFAULT_ALIGNMENT); + + iqconverter_int16_reset(cnv); + + for (i = 0; i < cnv->len; i++) + { + cnv->fir_kernel[i] = hb_kernel[i * 2]; + } + + return cnv; +} + +void iqconverter_int16_free(iqconverter_int16_t *cnv) +{ + _aligned_free(cnv->fir_kernel); + _aligned_free(cnv->fir_queue); + _aligned_free(cnv->delay_line); + _aligned_free(cnv); +} + +void iqconverter_int16_reset(iqconverter_int16_t *cnv) +{ + cnv->fir_index = 0; + cnv->delay_index = 0; + cnv->old_x = 0; + cnv->old_y = 0; + cnv->old_e = 0; + memset(cnv->delay_line, 0, cnv->len * sizeof(int16_t) / 4); + memset(cnv->fir_queue, 0, cnv->len * sizeof(int16_t) * SIZE_FACTOR); +} + +static void fir_interleaved(iqconverter_int16_t *cnv, int16_t *samples, int len) +{ + int i; + int j; + int fir_index; + int fir_len; + int32_t *queue; + int32_t acc; + + fir_len = cnv->len; + fir_index = cnv->fir_index; + + for (i = 0; i < len; i += 2) + { + queue = cnv->fir_queue + fir_index; + + queue[0] = samples[i]; + + acc = 0; + + // Auto vectorization works on VS2012, VS2013 and GCC + for (j = 0; j < fir_len; j++) + { + acc += cnv->fir_kernel[j] * queue[j]; + } + + if (--fir_index < 0) + { + fir_index = cnv->len * (SIZE_FACTOR - 1); + memcpy(cnv->fir_queue + fir_index + 1, cnv->fir_queue, (cnv->len - 1) * sizeof(int32_t)); + } + + samples[i] = acc >> 15; + } + + cnv->fir_index = fir_index; +} + +static void delay_interleaved(iqconverter_int16_t *cnv, int16_t *samples, int len) +{ + int i; + int index; + int half_len; + int16_t res; + + half_len = cnv->len >> 1; + index = cnv->delay_index; + + for (i = 0; i < len; i += 2) + { + res = cnv->delay_line[index]; + cnv->delay_line[index] = samples[i]; + samples[i] = res; + + if (++index >= half_len) + { + index = 0; + } + } + + cnv->delay_index = index; +} + +static void remove_dc(iqconverter_int16_t *cnv, int16_t *samples, int len) +{ + int i; + int32_t u, old_e; + int16_t x, y, w, s, old_x, old_y; + + old_x = cnv->old_x; + old_y = cnv->old_y; + old_e = cnv->old_e; + + for (i = 0; i < len; i++) + { + x = samples[i]; + w = x - old_x; + u = old_e + (int32_t) old_y * 32100; + s = u >> 15; + y = w + s; + old_e = u - (s << 15); + old_x = x; + old_y = y; + samples[i] = y; + } + + cnv->old_x = old_x; + cnv->old_y = old_y; + cnv->old_e = old_e; +} + +static void translate_fs_4(iqconverter_int16_t *cnv, int16_t *samples, int len) +{ + int i; + + for (i = 0; i < len; i += 4) + { + samples[i + 0] = -samples[i + 0]; + samples[i + 1] = -samples[i + 1] >> 1; + //samples[i + 2] = samples[i + 2]; + samples[i + 3] = samples[i + 3] >> 1; + } + + fir_interleaved(cnv, samples, len); + delay_interleaved(cnv, samples + 1, len); +} + +void iqconverter_int16_process(iqconverter_int16_t *cnv, int16_t *samples, int len) +{ + remove_dc(cnv, samples, len); + translate_fs_4(cnv, samples, len); +} diff --git a/Radio/HW/AirSpy/src/iqconverter_int16.h b/Radio/HW/AirSpy/src/iqconverter_int16.h new file mode 100644 index 0000000..10c867d --- /dev/null +++ b/Radio/HW/AirSpy/src/iqconverter_int16.h @@ -0,0 +1,45 @@ +/* +Copyright (C) 2014, Youssef Touil <youssef@airspy.com> + +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. +*/ + +#ifndef IQCONVERTER_INT16_H +#define IQCONVERTER_INT16_H + +#include <stdint.h> + +typedef struct { + int len; + int fir_index; + int delay_index; + int16_t old_x; + int16_t old_y; + int32_t old_e; + int32_t *fir_kernel; + int32_t *fir_queue; + int16_t *delay_line; +} iqconverter_int16_t; + +iqconverter_int16_t *iqconverter_int16_create(const int16_t *hb_kernel, int len); +void iqconverter_int16_free(iqconverter_int16_t *cnv); +void iqconverter_int16_reset(iqconverter_int16_t *cnv); +void iqconverter_int16_process(iqconverter_int16_t *cnv, int16_t *samples, int len); + +#endif // IQCONVERTER_INT16_H diff --git a/Radio/HW/AirSpyHF/src/airspyhf.c b/Radio/HW/AirSpyHF/src/airspyhf.c new file mode 100644 index 0000000..efacd03 --- /dev/null +++ b/Radio/HW/AirSpyHF/src/airspyhf.c @@ -0,0 +1,1910 @@ +/*
+Copyright (c) 2013-2023, Youssef Touil <youssef@airspy.com>
+Copyright (c) 2013-2017, Ian Gilmour <ian@sdrsharp.com>
+Copyright (c) 2013-2017, Benjamin Vernoux <bvernoux@airspy.com>
+Copyright (c) 2013, Michael Ossmann <mike@ossmann.com>
+Copyright (c) 2012, Jared Boone <jared@sharebrained.com>
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
+
+ Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
+ Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+ Neither the name of Airspy HF+ nor the names of its contributors may be used to endorse or promote products derived from this software
+ without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+#ifdef _WIN32
+#define HAVE_STRUCT_TIMESPEC
+#define _CRT_SECURE_NO_WARNINGS
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include "libusb.h"
+#include <pthread.h>
+#include <math.h>
+
+#include "iqbalancer.h"
+#include "airspyhf.h"
+#include "airspyhf_commands.h"
+
+#ifndef bool
+typedef int bool;
+#define true 1
+#define false 0
+#endif
+
+#ifndef M_PI
+#define M_PI (3.14159265359)
+#endif
+
+#define MAX(a,b) ((a) > (b) ? a : b)
+#define MIN(a,b) ((a) < (b) ? a : b)
+
+#define SAMPLES_TO_TRANSFER (1024 * 2)
+#define SERIAL_NUMBER_UNUSED (0)
+#define FILE_DESCRIPTOR_UNUSED (-1)
+#define RAW_BUFFER_COUNT (8)
+#define AIRSPYHF_SERIAL_SIZE (28)
+
+#define MAX_SAMPLERATE_INDEX (100)
+#define DEFAULT_SAMPLERATE (768000)
+
+#define DEFAULT_ATT_STEP_COUNT (9)
+#define DEFAULT_ATT_STEP_INCREMENT (6.0f)
+
+#define CALIBRATION_MAGIC (0xA5CA71B0)
+
+#define DEFAULT_IF_SHIFT (5000)
+#define MIN_ZERO_IF_LO (180)
+#define MIN_LOW_IF_LO (84)
+
+#define STR_PREFIX_SERIAL_AIRSPYHF_SIZE (12)
+static const char str_prefix_serial_airspyhf[STR_PREFIX_SERIAL_AIRSPYHF_SIZE] =
+{ 'A', 'I', 'R', 'S', 'P', 'Y', 'H', 'F', ' ', 'S', 'N', ':' };
+
+#define INITIAL_PHASE (0.00006f)
+#define INITIAL_AMPLITUDE (-0.0045f)
+
+#define LIBUSB_CTRL_TIMEOUT_MS (500)
+
+#define IQ_BALANCER_EVAL_SKIP (RAW_BUFFER_COUNT)
+
+#pragma pack(push,1)
+
+typedef struct {
+ int16_t im;
+ int16_t re;
+} airspyhf_complex_int16_t;
+
+#pragma pack(pop)
+
+typedef struct airspyhf_device
+{
+ libusb_context* usb_context;
+ libusb_device_handle* usb_device;
+ struct libusb_transfer** transfers;
+ airspyhf_sample_block_cb_fn callback;
+ pthread_t transfer_thread;
+ pthread_t consumer_thread;
+ bool transfer_thread_running;
+ bool consumer_thread_running;
+ pthread_cond_t consumer_cv;
+ pthread_mutex_t consumer_mp;
+ uint32_t supported_samplerate_count;
+ uint32_t *supported_samplerates;
+ uint8_t *samplerate_architectures;
+ uint32_t supported_att_step_count;
+ float *supported_att_steps;
+ volatile uint32_t current_samplerate;
+ volatile double freq_hz;
+ volatile uint32_t freq_khz;
+ volatile double freq_delta_hz;
+ volatile double freq_shift;
+ volatile int32_t calibration_ppb;
+ volatile int32_t calibration_vctcxo;
+ volatile uint32_t frontend_options;
+ volatile float optimal_point;
+ uint8_t enable_dsp;
+ uint8_t is_low_if;
+ float filter_gain;
+ airspyhf_complex_float_t vec;
+ struct iq_balancer_t *iq_balancer;
+ volatile int32_t iq_balancer_eval_skip;
+ uint32_t transfer_count;
+ int32_t transfer_live;
+ uint32_t buffer_size;
+ uint32_t dropped_buffers;
+ uint32_t dropped_buffers_queue[RAW_BUFFER_COUNT];
+ airspyhf_complex_int16_t *received_samples_queue[RAW_BUFFER_COUNT];
+ volatile bool streaming;
+ volatile bool stop_requested;
+ volatile int received_samples_queue_head;
+ volatile int received_samples_queue_tail;
+ volatile int received_buffer_count;
+ airspyhf_complex_float_t *output_buffer;
+ void* ctx;
+} airspyhf_device_t;
+
+typedef struct flash_config
+{
+ uint32_t magic_number;
+ int32_t calibration_ppb;
+ int32_t calibration_vctcxo;
+ uint32_t frontend_options;
+} flash_config_t;
+
+static const uint16_t airspyhf_usb_vid = 0x03EB;
+static const uint16_t airspyhf_usb_pid = 0x800C;
+
+static int airspyhf_config_read(airspyhf_device_t* device, uint8_t *buffer, uint16_t length);
+
+static int cancel_transfers(airspyhf_device_t* device)
+{
+ uint32_t transfer_index;
+ struct timeval canceltv = { 0, 50 };
+
+ if (device->transfers != NULL)
+ {
+ for (transfer_index = 0; transfer_index<device->transfer_count; transfer_index++)
+ {
+ if (device->transfers[transfer_index] != NULL)
+ {
+ libusb_cancel_transfer(device->transfers[transfer_index]);
+ }
+ }
+
+ for (transfer_index = 0; transfer_index < device->transfer_count && device->transfer_live > 0; transfer_index++)
+ libusb_handle_events_timeout_completed(device->usb_context, &canceltv, NULL);
+
+ return AIRSPYHF_SUCCESS;
+ }
+ else
+ {
+ return AIRSPYHF_ERROR;
+ }
+}
+
+static int free_transfers(airspyhf_device_t* device)
+{
+ int i;
+ uint32_t transfer_index;
+
+ if (device->transfers != NULL)
+ {
+ free(device->output_buffer);
+ device->output_buffer = NULL;
+
+ for (transfer_index = 0; transfer_index < device->transfer_count; transfer_index++)
+ {
+ if (device->transfers[transfer_index] != NULL)
+ {
+ libusb_free_transfer(device->transfers[transfer_index]);
+ free(device->transfers[transfer_index]->buffer);
+ device->transfers[transfer_index] = NULL;
+ }
+ }
+ free(device->transfers);
+ device->transfers = NULL;
+
+ for (i = 0; i < RAW_BUFFER_COUNT; i++)
+ {
+ if (device->received_samples_queue[i] != NULL)
+ {
+ free(device->received_samples_queue[i]);
+ device->received_samples_queue[i] = NULL;
+ }
+ }
+ }
+
+ return AIRSPYHF_SUCCESS;
+}
+
+static int allocate_transfers(airspyhf_device_t* const device)
+{
+ int i;
+ uint32_t transfer_index;
+
+ if (device->transfers == NULL)
+ {
+ device->output_buffer = (airspyhf_complex_float_t *) malloc((device->buffer_size / sizeof(airspyhf_complex_int16_t)) * sizeof(airspyhf_complex_float_t));
+
+ for (i = 0; i < RAW_BUFFER_COUNT; i++)
+ {
+ device->received_samples_queue[i] = (airspyhf_complex_int16_t *) malloc(device->buffer_size);
+ if (device->received_samples_queue[i] == NULL)
+ {
+ return AIRSPYHF_ERROR;
+ }
+
+ memset(device->received_samples_queue[i], 0, device->buffer_size);
+ }
+
+ device->transfers = (struct libusb_transfer**) calloc(device->transfer_count, sizeof(struct libusb_transfer));
+ if (device->transfers == NULL)
+ {
+ return AIRSPYHF_ERROR;
+ }
+
+ for (transfer_index = 0; transfer_index<device->transfer_count; transfer_index++)
+ {
+ device->transfers[transfer_index] = libusb_alloc_transfer(0);
+ if (device->transfers[transfer_index] == NULL)
+ {
+ return AIRSPYHF_ERROR;
+ }
+
+ libusb_fill_bulk_transfer(
+ device->transfers[transfer_index],
+ device->usb_device,
+ 0,
+ (unsigned char*) malloc(device->buffer_size),
+ device->buffer_size,
+ NULL,
+ device,
+ 0);
+
+ if (device->transfers[transfer_index]->buffer == NULL)
+ {
+ return AIRSPYHF_ERROR;
+ }
+ }
+ return AIRSPYHF_SUCCESS;
+ }
+ else
+ {
+ return AIRSPYHF_ERROR;
+ }
+}
+
+static int prepare_transfers(airspyhf_device_t* device, const uint_fast8_t endpoint_address, libusb_transfer_cb_fn callback)
+{
+ int error;
+ uint32_t transfer_index;
+
+ if (device->transfers != NULL)
+ {
+ for (transfer_index = 0; transfer_index<device->transfer_count; transfer_index++)
+ {
+ device->transfers[transfer_index]->endpoint = endpoint_address;
+ device->transfers[transfer_index]->callback = callback;
+
+ error = libusb_submit_transfer(device->transfers[transfer_index]);
+ if (error != 0)
+ {
+ return AIRSPYHF_ERROR;
+ }
+ else
+ device->transfer_live++;
+ }
+ return AIRSPYHF_SUCCESS;
+ }
+
+ return AIRSPYHF_ERROR;
+}
+
+static void multiply_complex_complex(airspyhf_complex_float_t *a, const airspyhf_complex_float_t *b)
+{
+ float re = a->re * b->re - a->im * b->im;
+ a->im = a->im * b->re + a->re * b->im;
+ a->re = re;
+}
+
+static void multiply_complex_real(airspyhf_complex_float_t *a, const float b)
+{
+ a->re *= b;
+ a->im *= b;
+}
+
+static inline void rotate_complex(airspyhf_complex_float_t *vec, const airspyhf_complex_float_t *rot)
+{
+ multiply_complex_complex(vec, rot);
+
+ float norm = 1.99f - (vec->re * vec->re + vec->im * vec->im);
+
+ multiply_complex_real(vec, norm);
+}
+
+static void convert_samples(airspyhf_device_t* device, airspyhf_complex_int16_t *src, airspyhf_complex_float_t *dest, int count)
+{
+ const float scale = 1.0f / 32768;
+
+ int i;
+ airspyhf_complex_float_t vec;
+ airspyhf_complex_float_t rot;
+ double angle;
+ float conversion_gain;
+ uint8_t iqb_eval_skip = 0;
+
+ conversion_gain = scale * device->filter_gain;
+
+ for (i = 0; i < count; i++)
+ {
+ dest[i].re = src[i].re * conversion_gain;
+ dest[i].im = src[i].im * conversion_gain;
+ }
+
+ if (device->iq_balancer_eval_skip > 0)
+ {
+ device->iq_balancer_eval_skip--;
+ iqb_eval_skip = 1;
+ }
+
+ if (device->enable_dsp)
+ {
+ if (!device->is_low_if)
+ {
+ // Zero IF requires external IQ correction
+ iq_balancer_process(device->iq_balancer, dest, count, iqb_eval_skip);
+ }
+
+ // Fine tuning
+ if (device->freq_shift != 0)
+ {
+ angle = 2.0 * M_PI * device->freq_shift / (double) device->current_samplerate;
+
+ vec = device->vec;
+
+ rot.re = (float) cos(angle);
+ rot.im = (float) -sin(angle);
+
+ for (i = 0; i < count; i++)
+ {
+ rotate_complex(&vec, &rot);
+ multiply_complex_complex(&dest[i], &vec);
+ }
+
+ device->vec = vec;
+ }
+ }
+}
+
+static void* consumer_threadproc(void *arg)
+{
+ int sample_count;
+ airspyhf_complex_int16_t *input_samples;
+ uint32_t dropped_buffers;
+ airspyhf_device_t* device = (airspyhf_device_t*) arg;
+ airspyhf_transfer_t transfer;
+
+#ifdef _WIN32
+
+ SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_HIGHEST);
+
+#endif
+
+ pthread_mutex_lock(&device->consumer_mp);
+
+ while (device->streaming && !device->stop_requested)
+ {
+ while (device->received_buffer_count == 0 && device->streaming && !device->stop_requested)
+ {
+ pthread_cond_wait(&device->consumer_cv, &device->consumer_mp);
+ }
+ if (!device->streaming || device->stop_requested)
+ {
+ break;
+ }
+
+ input_samples = (airspyhf_complex_int16_t *) device->received_samples_queue[device->received_samples_queue_tail];
+ dropped_buffers = device->dropped_buffers_queue[device->received_samples_queue_tail];
+ device->received_samples_queue_tail = (device->received_samples_queue_tail + 1) & (RAW_BUFFER_COUNT - 1);
+
+ pthread_mutex_unlock(&device->consumer_mp);
+
+ sample_count = device->buffer_size / sizeof(airspyhf_complex_int16_t);
+
+ convert_samples(device, input_samples, device->output_buffer, sample_count);
+
+ transfer.device = device;
+ transfer.ctx = device->ctx;
+ transfer.samples = device->output_buffer;
+ transfer.sample_count = sample_count;
+ transfer.dropped_samples = (uint64_t) dropped_buffers * (uint64_t) sample_count;
+
+ if (device->callback(&transfer) != 0)
+ {
+ device->streaming = false;
+ }
+
+ pthread_mutex_lock(&device->consumer_mp);
+ device->received_buffer_count--;
+ }
+
+ device->streaming = false;
+
+ pthread_mutex_unlock(&device->consumer_mp);
+
+ return NULL;
+}
+
+static void LIBUSB_CALL airspyhf_libusb_transfer_callback(struct libusb_transfer* usb_transfer)
+{
+ airspyhf_complex_int16_t *temp;
+ airspyhf_device_t* device = (airspyhf_device_t*) usb_transfer->user_data;
+
+ device->transfer_live--;
+ if (!device->streaming || device->stop_requested)
+ {
+ return;
+ }
+
+ if (usb_transfer->status == LIBUSB_TRANSFER_COMPLETED && usb_transfer->actual_length == usb_transfer->length)
+ {
+ pthread_mutex_lock(&device->consumer_mp);
+
+ if (device->received_buffer_count < RAW_BUFFER_COUNT)
+ {
+ temp = device->received_samples_queue[device->received_samples_queue_head];
+ device->received_samples_queue[device->received_samples_queue_head] = (airspyhf_complex_int16_t *) usb_transfer->buffer;
+ usb_transfer->buffer = (uint8_t *) temp;
+
+ device->dropped_buffers_queue[device->received_samples_queue_head] = device->dropped_buffers;
+ device->dropped_buffers = 0;
+
+ device->received_samples_queue_head = (device->received_samples_queue_head + 1) & (RAW_BUFFER_COUNT - 1);
+ device->received_buffer_count++;
+
+ pthread_cond_signal(&device->consumer_cv);
+ }
+ else
+ {
+ device->dropped_buffers++;
+ }
+
+ pthread_mutex_unlock(&device->consumer_mp);
+
+ if (libusb_submit_transfer(usb_transfer) != 0)
+ {
+ device->streaming = false;
+ }
+ else
+ device->transfer_live ++;
+ }
+ else
+ {
+ device->streaming = false;
+ }
+}
+
+static void* transfer_threadproc(void* arg)
+{
+ airspyhf_device_t* device = (struct airspyhf_device*) arg;
+ int error;
+ struct timeval timeout = { 0, 500000 };
+
+#ifdef _WIN32
+
+ SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_HIGHEST);
+
+#endif
+
+ while (device->streaming && !device->stop_requested)
+ {
+ error = libusb_handle_events_timeout_completed(device->usb_context, &timeout, NULL);
+ if (error < 0)
+ {
+ if (error != LIBUSB_ERROR_INTERRUPTED)
+ device->streaming = false;
+ }
+ }
+
+ device->streaming = false;
+
+ return NULL;
+}
+
+static int kill_io_threads(airspyhf_device_t* device)
+{
+ struct timeval timeout = { 0, 0 };
+
+ if (device->stop_requested)
+ {
+ device->stop_requested = false;
+ device->streaming = false;
+ cancel_transfers(device);
+
+ pthread_mutex_lock(&device->consumer_mp);
+ pthread_cond_signal(&device->consumer_cv);
+ pthread_mutex_unlock(&device->consumer_mp);
+
+ if (device->transfer_thread_running) {
+ pthread_join(device->transfer_thread, NULL);
+ device->transfer_thread_running = false;
+ }
+ if (device->consumer_thread_running) {
+ pthread_join(device->consumer_thread, NULL);
+ device->consumer_thread_running = false;
+ }
+
+ libusb_handle_events_timeout_completed(device->usb_context, &timeout, NULL);
+ }
+
+ return AIRSPYHF_SUCCESS;
+}
+
+static int create_io_threads(airspyhf_device_t* device, airspyhf_sample_block_cb_fn callback)
+{
+ int result;
+ pthread_attr_t attr;
+
+ if (!device->streaming && !device->stop_requested)
+ {
+ device->callback = callback;
+ device->streaming = true;
+
+ result = prepare_transfers(device, LIBUSB_ENDPOINT_IN | AIRSPYHF_ENDPOINT_IN, (libusb_transfer_cb_fn)airspyhf_libusb_transfer_callback);
+ if (result != AIRSPYHF_SUCCESS)
+ {
+ return result;
+ }
+
+ device->received_samples_queue_head = 0;
+ device->received_samples_queue_tail = 0;
+ device->received_buffer_count = 0;
+
+ pthread_attr_init(&attr);
+ pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
+
+ result = pthread_create(&device->consumer_thread, &attr, consumer_threadproc, device);
+ if (result != 0)
+ {
+ return AIRSPYHF_ERROR;
+ }
+ device->consumer_thread_running = true;
+
+ result = pthread_create(&device->transfer_thread, &attr, transfer_threadproc, device);
+ if (result != 0)
+ {
+ return AIRSPYHF_ERROR;
+ }
+ device->transfer_thread_running = true;
+
+ pthread_attr_destroy(&attr);
+ }
+ else {
+ return AIRSPYHF_ERROR;
+ }
+
+ return AIRSPYHF_SUCCESS;
+}
+
+static void airspyhf_open_exit(airspyhf_device_t* device)
+{
+ if (device->usb_device != NULL)
+ {
+ libusb_release_interface(device->usb_device, 0);
+ libusb_close(device->usb_device);
+ device->usb_device = NULL;
+ }
+ libusb_exit(device->usb_context);
+ device->usb_context = NULL;
+}
+
+static int airspyhf_read_samplerates_from_fw(airspyhf_device_t* device, uint32_t* buffer, const uint32_t len)
+{
+ int result;
+
+ result = libusb_control_transfer(
+ device->usb_device,
+ LIBUSB_ENDPOINT_IN | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE,
+ AIRSPYHF_GET_SAMPLERATES,
+ 0,
+ len,
+ (unsigned char*) buffer,
+ (len > 0 ? len : 1) * (int16_t) sizeof(uint32_t),
+ LIBUSB_CTRL_TIMEOUT_MS);
+
+ if (result < 1)
+ {
+ return AIRSPYHF_ERROR;
+ }
+ return AIRSPYHF_SUCCESS;
+}
+
+static int airspyhf_read_att_steps_from_fw(airspyhf_device_t* device, void* buffer, const uint32_t count)
+{
+ int result;
+
+ result = libusb_control_transfer(
+ device->usb_device,
+ LIBUSB_ENDPOINT_IN | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE,
+ AIRSPYHF_GET_ATT_STEPS,
+ 0,
+ count,
+ (unsigned char*) buffer,
+ (int16_t)(count > 0 ? (count * sizeof(float)) : sizeof(int32_t)),
+ LIBUSB_CTRL_TIMEOUT_MS);
+
+ if (result < 1)
+ {
+ return AIRSPYHF_ERROR;
+ }
+ return AIRSPYHF_SUCCESS;
+}
+
+static int airspyhf_read_samplerate_architectures_from_fw(airspyhf_device_t* device, uint8_t* buffer, const uint32_t len)
+{
+ int result;
+
+ result = libusb_control_transfer(
+ device->usb_device,
+ LIBUSB_ENDPOINT_IN | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE,
+ AIRSPYHF_GET_SAMPLERATE_ARCHITECTURES,
+ 0,
+ len,
+ (unsigned char*) buffer,
+ (len > 0 ? len : 1) * (int16_t) sizeof(uint32_t),
+ LIBUSB_CTRL_TIMEOUT_MS);
+
+ if (result < 1)
+ {
+ return AIRSPYHF_ERROR;
+ }
+
+ return AIRSPYHF_SUCCESS;
+}
+
+
+static void airspyhf_open_device(airspyhf_device_t* device,
+ int* ret,
+ uint16_t vid,
+ uint16_t pid,
+ uint64_t serial_number_val)
+{
+ int i;
+ int result;
+ libusb_device_handle** libusb_dev_handle;
+ int serial_number_len;
+ libusb_device_handle* dev_handle;
+ libusb_device *dev;
+ libusb_device** devices = NULL;
+
+ ssize_t cnt;
+ int serial_descriptor_index;
+ struct libusb_device_descriptor device_descriptor;
+ unsigned char serial_number[AIRSPYHF_SERIAL_SIZE + 1];
+
+ libusb_dev_handle = &device->usb_device;
+ *libusb_dev_handle = NULL;
+
+ cnt = libusb_get_device_list(device->usb_context, &devices);
+ if (cnt < 0)
+ {
+ *ret = AIRSPYHF_ERROR;
+ return;
+ }
+
+ i = 0;
+ while ((dev = devices[i++]) != NULL)
+ {
+ libusb_get_device_descriptor(dev, &device_descriptor);
+
+ if ((device_descriptor.idVendor == vid) &&
+ (device_descriptor.idProduct == pid))
+ {
+ if (serial_number_val != SERIAL_NUMBER_UNUSED)
+ {
+ serial_descriptor_index = device_descriptor.iSerialNumber;
+ if (serial_descriptor_index > 0)
+ {
+ if (libusb_open(dev, libusb_dev_handle) != 0)
+ {
+ *libusb_dev_handle = NULL;
+ continue;
+ }
+ dev_handle = *libusb_dev_handle;
+ serial_number_len = libusb_get_string_descriptor_ascii(dev_handle,
+ serial_descriptor_index,
+ serial_number,
+ sizeof(serial_number));
+
+ if (serial_number_len == AIRSPYHF_SERIAL_SIZE && !memcmp(str_prefix_serial_airspyhf, serial_number, STR_PREFIX_SERIAL_AIRSPYHF_SIZE))
+ {
+ uint64_t serial = SERIAL_NUMBER_UNUSED;
+ // use same code to determine device's serial number as in airspyhf_list_devices()
+ {
+ char *start, *end;
+ serial_number[AIRSPYHF_SERIAL_SIZE] = 0;
+ start = (char*)(serial_number + STR_PREFIX_SERIAL_AIRSPYHF_SIZE);
+ end = NULL;
+ serial = strtoull(start, &end, 16);
+ }
+ if (serial == serial_number_val)
+ {
+ result = libusb_set_configuration(dev_handle, 1);
+ if (result != 0)
+ {
+ libusb_close(dev_handle);
+ *libusb_dev_handle = NULL;
+ continue;
+ }
+ result = libusb_claim_interface(dev_handle, 0);
+ if (result != 0)
+ {
+ libusb_close(dev_handle);
+ *libusb_dev_handle = NULL;
+ continue;
+ }
+
+ result = libusb_set_interface_alt_setting(dev_handle, 0, 1);
+ if (result != 0)
+ {
+ libusb_close(dev_handle);
+ *libusb_dev_handle = NULL;
+ continue;
+ }
+
+ break;
+ }
+ else
+ {
+ libusb_close(dev_handle);
+ *libusb_dev_handle = NULL;
+ continue;
+ }
+ }
+ else
+ {
+ libusb_close(dev_handle);
+ *libusb_dev_handle = NULL;
+ continue;
+ }
+ }
+ }
+ else
+ {
+ if (libusb_open(dev, libusb_dev_handle) == 0)
+ {
+ dev_handle = *libusb_dev_handle;
+ result = libusb_set_configuration(dev_handle, 1);
+ if (result != 0)
+ {
+ libusb_close(dev_handle);
+ *libusb_dev_handle = NULL;
+ continue;
+ }
+ result = libusb_claim_interface(dev_handle, 0);
+ if (result != 0)
+ {
+ libusb_close(dev_handle);
+ *libusb_dev_handle = NULL;
+ continue;
+ }
+
+ result = libusb_set_interface_alt_setting(dev_handle, 0, 1);
+ if (result != 0)
+ {
+ libusb_close(dev_handle);
+ *libusb_dev_handle = NULL;
+ continue;
+ }
+ break;
+ }
+ }
+ }
+ }
+ libusb_free_device_list(devices, 1);
+
+ dev_handle = device->usb_device;
+ if (dev_handle == NULL)
+ {
+ *ret = AIRSPYHF_ERROR;
+ return;
+ }
+
+ *ret = AIRSPYHF_SUCCESS;
+ return;
+}
+
+static void airspyhf_open_device_fd(airspyhf_device_t* device,
+ int* ret,
+ int fd) {
+ int result = -1;
+
+#ifdef __ANDROID__
+ result = libusb_wrap_sys_device(device->usb_context, (intptr_t)fd, &device->usb_device);
+#else
+ device->usb_device = NULL;
+ *ret = AIRSPYHF_UNSUPPORTED;
+ return;
+#endif
+
+ if (result != 0 || device->usb_device == NULL)
+ {
+ *ret = AIRSPYHF_ERROR;
+ return;
+ }
+
+ result = libusb_set_configuration(device->usb_device, 1);
+ if (result != 0)
+ {
+ libusb_close(device->usb_device);
+ device->usb_device = NULL;
+ *ret = AIRSPYHF_ERROR;
+ return;
+ }
+
+ result = libusb_claim_interface(device->usb_device, 0);
+ if (result != 0)
+ {
+ libusb_close(device->usb_device);
+ device->usb_device = NULL;
+ *ret = AIRSPYHF_ERROR;
+ return;
+ }
+
+ result = libusb_set_interface_alt_setting(device->usb_device, 0, 1);
+ if (result != 0)
+ {
+ libusb_close(device->usb_device);
+ device->usb_device = NULL;
+ *ret = AIRSPYHF_ERROR;
+ return;
+ }
+
+ *ret = AIRSPYHF_SUCCESS;
+ return;
+}
+
+int airspyhf_list_devices(uint64_t *serials, int count)
+{
+ libusb_device_handle* libusb_dev_handle;
+ struct libusb_context *context;
+ libusb_device** devices = NULL;
+ libusb_device *dev;
+ struct libusb_device_descriptor device_descriptor;
+
+ int serial_descriptor_index;
+ int serial_number_len;
+ int output_count;
+ int i;
+ unsigned char serial_number[AIRSPYHF_SERIAL_SIZE + 1];
+
+ if (serials)
+ {
+ memset(serials, 0, sizeof(uint64_t) * count);
+ }
+
+#ifdef __ANDROID__
+ // LibUSB does not support device discovery on android
+ libusb_set_option(NULL, LIBUSB_OPTION_NO_DEVICE_DISCOVERY, NULL);
+#endif
+
+ if (libusb_init(&context) != 0)
+ {
+ return AIRSPYHF_ERROR;
+ }
+
+ if (libusb_get_device_list(context, &devices) < 0)
+ {
+ return AIRSPYHF_ERROR;
+ }
+
+ i = 0;
+ output_count = 0;
+ while ((dev = devices[i++]) != NULL && (!serials || output_count < count))
+ {
+ libusb_get_device_descriptor(dev, &device_descriptor);
+
+ if ((device_descriptor.idVendor == airspyhf_usb_vid) &&
+ (device_descriptor.idProduct == airspyhf_usb_pid))
+ {
+ serial_descriptor_index = device_descriptor.iSerialNumber;
+ if (serial_descriptor_index > 0)
+ {
+ if (libusb_open(dev, &libusb_dev_handle) != 0)
+ {
+ continue;
+ }
+
+ serial_number_len = libusb_get_string_descriptor_ascii(libusb_dev_handle,
+ serial_descriptor_index,
+ serial_number,
+ sizeof(serial_number));
+
+ if (serial_number_len == AIRSPYHF_SERIAL_SIZE && !memcmp(str_prefix_serial_airspyhf, serial_number, STR_PREFIX_SERIAL_AIRSPYHF_SIZE))
+ {
+ char *start, *end;
+ uint64_t serial;
+
+ serial_number[AIRSPYHF_SERIAL_SIZE] = 0;
+ start = (char*)(serial_number + STR_PREFIX_SERIAL_AIRSPYHF_SIZE);
+ end = NULL;
+ serial = strtoull(start, &end, 16);
+ if (serial == 0 && start == end)
+ {
+ libusb_close(libusb_dev_handle);
+ continue;
+ }
+
+ if (serials)
+ {
+ serials[output_count] = serial;
+ }
+ output_count++;
+ }
+
+ libusb_close(libusb_dev_handle);
+ }
+ }
+ }
+
+ libusb_free_device_list(devices, 1);
+ libusb_exit(context);
+ return output_count;
+}
+
+static int airspyhf_open_init(airspyhf_device_t** device, uint64_t serial_number, int fd)
+{
+ airspyhf_device_t* lib_device;
+ flash_config_t config;
+ int libusb_error;
+ int result;
+
+ *device = NULL;
+
+ lib_device = (airspyhf_device_t*) calloc(1, sizeof(airspyhf_device_t));
+ if (lib_device == NULL)
+ {
+ return AIRSPYHF_ERROR;
+ }
+
+#ifdef __ANDROID__
+ // LibUSB does not support device discovery on android
+ libusb_set_option(NULL, LIBUSB_OPTION_NO_DEVICE_DISCOVERY, NULL);
+#endif
+
+ libusb_error = libusb_init(&lib_device->usb_context);
+ if (libusb_error != 0)
+ {
+ free(lib_device);
+ return AIRSPYHF_ERROR;
+ }
+
+ if (fd == FILE_DESCRIPTOR_UNUSED) {
+ airspyhf_open_device(lib_device,
+ &result,
+ airspyhf_usb_vid,
+ airspyhf_usb_pid,
+ serial_number);
+ }
+ else {
+ airspyhf_open_device_fd(lib_device,
+ &result,
+ fd);
+ }
+
+ if (lib_device->usb_device == NULL)
+ {
+ libusb_exit(lib_device->usb_context);
+ free(lib_device);
+ return result;
+ }
+
+ lib_device->transfers = NULL;
+ lib_device->callback = NULL;
+ lib_device->transfer_count = 16;
+ lib_device->buffer_size = SAMPLES_TO_TRANSFER * sizeof(airspyhf_complex_int16_t);
+ lib_device->streaming = false;
+ lib_device->stop_requested = false;
+
+ lib_device->supported_samplerates = NULL;
+ lib_device->samplerate_architectures = NULL;
+
+ result = airspyhf_read_samplerates_from_fw(lib_device, &lib_device->supported_samplerate_count, 0);
+ if (result == AIRSPYHF_SUCCESS)
+ {
+ lib_device->supported_samplerates = (uint32_t *)malloc(lib_device->supported_samplerate_count * sizeof(uint32_t));
+ result = airspyhf_read_samplerates_from_fw(lib_device, lib_device->supported_samplerates, lib_device->supported_samplerate_count);
+ if (result == AIRSPYHF_SUCCESS)
+ {
+ lib_device->samplerate_architectures = (uint8_t *)malloc(lib_device->supported_samplerate_count * sizeof(uint8_t));
+ result = airspyhf_read_samplerate_architectures_from_fw(lib_device, lib_device->samplerate_architectures, lib_device->supported_samplerate_count);
+ if (result != AIRSPYHF_SUCCESS)
+ {
+ memset(lib_device->samplerate_architectures, 0, lib_device->supported_samplerate_count * sizeof(uint8_t)); // Assume Zero IF for all
+ result = AIRSPYHF_SUCCESS; // Clear this error for backward compatibility.
+ }
+ }
+ else
+ {
+ free(lib_device->supported_samplerates);
+ lib_device->supported_samplerates = NULL;
+ }
+ }
+
+ if (result != AIRSPYHF_SUCCESS)
+ {
+ // Assume one default sample rate with Zero IF
+
+ lib_device->supported_samplerate_count = 1;
+ lib_device->supported_samplerates = (uint32_t *) malloc(lib_device->supported_samplerate_count * sizeof(uint32_t));
+ lib_device->supported_samplerates[0] = DEFAULT_SAMPLERATE;
+
+ lib_device->samplerate_architectures = (uint8_t *) malloc(lib_device->supported_samplerate_count * sizeof(uint8_t));
+ lib_device->samplerate_architectures[0] = 0;
+ }
+
+ lib_device->current_samplerate = lib_device->supported_samplerates[0];
+ lib_device->is_low_if = lib_device->samplerate_architectures[0];
+
+ result = airspyhf_read_att_steps_from_fw(lib_device, &lib_device->supported_att_step_count, 0);
+ if (result == AIRSPYHF_SUCCESS)
+ {
+ lib_device->supported_att_steps = (float*)malloc(lib_device->supported_att_step_count * sizeof(float));
+ result = airspyhf_read_att_steps_from_fw(lib_device, lib_device->supported_att_steps, lib_device->supported_att_step_count);
+ if (result != AIRSPYHF_SUCCESS)
+ {
+ free(lib_device->supported_att_steps);
+ lib_device->supported_att_steps = NULL;
+ lib_device->supported_att_step_count = 0;
+ }
+ }
+
+ if (result != AIRSPYHF_SUCCESS)
+ {
+ // Assume ATT steps of the Airspy HF+ Discovery
+
+ lib_device->supported_att_step_count = DEFAULT_ATT_STEP_COUNT;
+ lib_device->supported_att_steps = (float*)malloc(lib_device->supported_att_step_count * sizeof(float));
+ for (uint32_t i = 0; i < lib_device->supported_att_step_count; i++)
+ {
+ lib_device->supported_att_steps[i] = i * DEFAULT_ATT_STEP_INCREMENT;
+ }
+ }
+
+ result = allocate_transfers(lib_device);
+ if (result != 0)
+ {
+ airspyhf_open_exit(lib_device);
+ free(lib_device);
+ return AIRSPYHF_ERROR;
+ }
+
+ pthread_cond_init(&lib_device->consumer_cv, NULL);
+ pthread_mutex_init(&lib_device->consumer_mp, NULL);
+
+ lib_device->freq_hz = 0;
+ lib_device->freq_khz = 0;
+ lib_device->freq_delta_hz = 0;
+ lib_device->freq_shift = 0;
+ lib_device->vec.re = 1.0f;
+ lib_device->vec.im = 0.0f;
+ lib_device->optimal_point = 0.0f;
+ lib_device->filter_gain = 1.0f;
+ lib_device->enable_dsp = 1;
+
+ if (airspyhf_config_read(lib_device, (uint8_t *) &config, sizeof(config)) == AIRSPYHF_SUCCESS)
+ {
+ if (config.magic_number == CALIBRATION_MAGIC)
+ {
+ lib_device->calibration_ppb = config.calibration_ppb;
+ airspyhf_set_vctcxo_calibration(lib_device, config.calibration_vctcxo);
+ airspyhf_set_frontend_options(lib_device, config.frontend_options);
+ }
+ else
+ {
+ lib_device->calibration_ppb = 0;
+ lib_device->calibration_vctcxo = 0;
+ lib_device->frontend_options = 0;
+ }
+ }
+ else
+ {
+ lib_device->calibration_ppb = 0;
+ lib_device->calibration_vctcxo = 0;
+ lib_device->frontend_options = 0;
+ }
+
+ lib_device->iq_balancer = iq_balancer_create(INITIAL_PHASE, INITIAL_AMPLITUDE);
+ lib_device->iq_balancer_eval_skip = 0;
+
+ *device = lib_device;
+
+ return AIRSPYHF_SUCCESS;
+}
+
+void ADDCALL airspyhf_lib_version(airspyhf_lib_version_t* lib_version)
+{
+ lib_version->major_version = AIRSPYHF_VER_MAJOR;
+ lib_version->minor_version = AIRSPYHF_VER_MINOR;
+ lib_version->revision = AIRSPYHF_VER_REVISION;
+}
+
+int ADDCALL airspyhf_open_sn(airspyhf_device_t** device, uint64_t serial_number)
+{
+ int result;
+
+ result = airspyhf_open_init(device, serial_number, FILE_DESCRIPTOR_UNUSED);
+ return result;
+}
+
+int ADDCALL airspyhf_open_fd(airspyhf_device_t** device, int fd)
+{
+ int result;
+
+ result = airspyhf_open_init(device, SERIAL_NUMBER_UNUSED, fd);
+ return result;
+}
+
+int ADDCALL airspyhf_open(airspyhf_device_t** device)
+{
+ int result;
+
+ result = airspyhf_open_init(device, SERIAL_NUMBER_UNUSED, FILE_DESCRIPTOR_UNUSED);
+ return result;
+}
+
+int ADDCALL airspyhf_close(airspyhf_device_t* device)
+{
+ int result;
+
+ result = AIRSPYHF_SUCCESS;
+
+ if (device != NULL)
+ {
+ result = airspyhf_stop(device);
+ free_transfers(device);
+ airspyhf_open_exit(device);
+
+ free(device->supported_samplerates);
+ free(device->samplerate_architectures);
+ free(device->supported_att_steps);
+ iq_balancer_destroy(device->iq_balancer);
+
+ pthread_cond_destroy(&device->consumer_cv);
+ pthread_mutex_destroy(&device->consumer_mp);
+
+ free(device);
+ }
+
+ return result;
+}
+
+int ADDCALL airspyhf_get_output_size(airspyhf_device_t * device)
+{
+ // Todo: Make this configurable
+ return SAMPLES_TO_TRANSFER;
+}
+
+int ADDCALL airspyhf_is_low_if(airspyhf_device_t* device)
+{
+ return device->is_low_if;
+}
+
+int ADDCALL airspyhf_get_samplerates(airspyhf_device_t* device, uint32_t* buffer, const uint32_t len)
+{
+ if (len == 0)
+ {
+ *buffer = device->supported_samplerate_count;
+ }
+ else if (len <= device->supported_samplerate_count)
+ {
+ memcpy(buffer, device->supported_samplerates, len * sizeof(uint32_t));
+ }
+ else
+ {
+ return AIRSPYHF_ERROR;
+ }
+
+ return AIRSPYHF_SUCCESS;
+}
+
+int ADDCALL airspyhf_set_samplerate(airspyhf_device_t* device, uint32_t samplerate)
+{
+ int result;
+ uint32_t i;
+ uint8_t gain;
+
+ if (samplerate > MAX_SAMPLERATE_INDEX)
+ {
+ for (i = 0; i < device->supported_samplerate_count; i++)
+ {
+ if (samplerate == device->supported_samplerates[i])
+ {
+ samplerate = i;
+ break;
+ }
+ }
+ }
+
+ if (samplerate >= device->supported_samplerate_count)
+ {
+ return AIRSPYHF_ERROR;
+ }
+
+ device->current_samplerate = device->supported_samplerates[samplerate];
+ device->is_low_if = device->samplerate_architectures[samplerate];
+
+ libusb_clear_halt(device->usb_device, LIBUSB_ENDPOINT_IN | 1);
+
+ if (!device->is_low_if && device->freq_khz < MIN_ZERO_IF_LO)
+ {
+ uint8_t buf[4];
+ buf[0] = (uint8_t)((MIN_ZERO_IF_LO >> 24) & 0xff);
+ buf[1] = (uint8_t)((MIN_ZERO_IF_LO >> 16) & 0xff);
+ buf[2] = (uint8_t)((MIN_ZERO_IF_LO >> 8) & 0xff);
+ buf[3] = (uint8_t)((MIN_ZERO_IF_LO) & 0xff);
+
+ result = libusb_control_transfer(
+ device->usb_device,
+ LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE,
+ AIRSPYHF_SET_FREQ,
+ 0,
+ 0,
+ (unsigned char*) &buf,
+ sizeof(buf),
+ LIBUSB_CTRL_TIMEOUT_MS);
+
+ if (result < sizeof(buf))
+ {
+ return AIRSPYHF_ERROR;
+ }
+
+ device->freq_khz = MIN_ZERO_IF_LO;
+ }
+
+ result = libusb_control_transfer(
+ device->usb_device,
+ LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE,
+ AIRSPYHF_SET_SAMPLERATE,
+ 0,
+ samplerate,
+ NULL,
+ 0,
+ LIBUSB_CTRL_TIMEOUT_MS);
+
+ if (result != 0)
+ {
+ return AIRSPYHF_ERROR;
+ }
+
+ result = libusb_control_transfer(
+ device->usb_device,
+ LIBUSB_ENDPOINT_IN | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE,
+ AIRSPYHF_GET_FILTER_GAIN,
+ 0,
+ 0,
+ &gain,
+ sizeof(uint8_t),
+ LIBUSB_CTRL_TIMEOUT_MS);
+
+ if (result == sizeof(uint8_t))
+ {
+ device->filter_gain = powf(10.0f, (float) gain * -0.05f);
+ }
+ else
+ {
+ device->filter_gain = 1.0;
+ }
+
+ airspyhf_set_freq_double(device, device->freq_hz);
+
+ return AIRSPYHF_SUCCESS;
+}
+
+int ADDCALL airspyhf_set_receiver_mode(airspyhf_device_t* device, receiver_mode_t value)
+{
+ int result;
+ result = libusb_control_transfer(
+ device->usb_device,
+ LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE,
+ AIRSPYHF_RECEIVER_MODE,
+ value,
+ 0,
+ NULL,
+ 0,
+ LIBUSB_CTRL_TIMEOUT_MS);
+
+ if (result != 0)
+ {
+ return AIRSPYHF_ERROR;
+ }
+
+ return AIRSPYHF_SUCCESS;
+}
+
+int ADDCALL airspyhf_start(airspyhf_device_t* device, airspyhf_sample_block_cb_fn callback, void* ctx)
+{
+ int result;
+
+ memset(device->dropped_buffers_queue, 0, RAW_BUFFER_COUNT * sizeof(uint32_t));
+ device->dropped_buffers = 0;
+
+ device->vec.re = 1.0f;
+ device->vec.im = 0.0f;
+
+ result = airspyhf_set_receiver_mode(device, RECEIVER_MODE_OFF);
+ if (result != AIRSPYHF_SUCCESS)
+ {
+ return result;
+ }
+
+ libusb_clear_halt(device->usb_device, LIBUSB_ENDPOINT_IN | AIRSPYHF_ENDPOINT_IN);
+
+ result = airspyhf_set_receiver_mode(device, RECEIVER_MODE_ON);
+ if (result == AIRSPYHF_SUCCESS)
+ {
+ device->ctx = ctx;
+ result = create_io_threads(device, callback);
+ }
+
+ return result;
+}
+
+int ADDCALL airspyhf_is_streaming(airspyhf_device_t* device)
+{
+ return device->streaming && !device->stop_requested;
+}
+
+int ADDCALL airspyhf_stop(airspyhf_device_t* device)
+{
+ int result1, result2;
+ device->stop_requested = true;
+ result1 = airspyhf_set_receiver_mode(device, RECEIVER_MODE_OFF);
+ result2 = kill_io_threads(device);
+
+#ifndef _WIN32
+ libusb_interrupt_event_handler(device->usb_context);
+#endif
+
+ if (result2 != AIRSPYHF_SUCCESS)
+ {
+ return result2;
+ }
+ return result1;
+}
+
+
+int ADDCALL airspyhf_set_freq(airspyhf_device_t* device, const uint32_t freq_hz)
+{
+ return airspyhf_set_freq_double(device, freq_hz);
+}
+
+int ADDCALL airspyhf_set_freq_double(airspyhf_device_t* device, const double freq_hz)
+{
+ int result;
+ uint8_t buf[4];
+ double if_shift = (device->enable_dsp && !device->is_low_if) ? DEFAULT_IF_SHIFT : 0;
+ double adjusted_freq_hz = freq_hz * (1.0e9 + device->calibration_ppb) * 1.0e-9;
+ uint32_t lo_low_khz = device->is_low_if ? MIN_LOW_IF_LO : MIN_ZERO_IF_LO;
+ uint32_t freq_khz = MAX(lo_low_khz, (uint32_t)round((adjusted_freq_hz + if_shift) * 1e-3));
+
+ if (device->freq_khz != freq_khz)
+ {
+ device->iq_balancer_eval_skip = IQ_BALANCER_EVAL_SKIP;
+
+ buf[0] = (uint8_t)((freq_khz >> 24) & 0xff);
+ buf[1] = (uint8_t)((freq_khz >> 16) & 0xff);
+ buf[2] = (uint8_t)((freq_khz >> 8) & 0xff);
+ buf[3] = (uint8_t)((freq_khz) & 0xff);
+
+ result = libusb_control_transfer(
+ device->usb_device,
+ LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE,
+ AIRSPYHF_SET_FREQ,
+ 0,
+ 0,
+ (unsigned char*)&buf,
+ sizeof(buf),
+ LIBUSB_CTRL_TIMEOUT_MS);
+
+ if (result < sizeof(buf))
+ {
+ return AIRSPYHF_ERROR;
+ }
+
+ device->freq_khz = freq_khz;
+
+ result = libusb_control_transfer(
+ device->usb_device,
+ LIBUSB_ENDPOINT_IN | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE,
+ AIRSPYHF_GET_FREQ_DELTA,
+ 0,
+ 0,
+ (unsigned char*)&buf,
+ sizeof(buf),
+ LIBUSB_CTRL_TIMEOUT_MS);
+
+ if (result == sizeof(buf))
+ {
+ device->freq_delta_hz = (((int8_t)buf[3] << 16) | (buf[2] << 8) | buf[1]) * 1e3 / (1 << buf[0]);
+ }
+
+ iq_balancer_set_optimal_point(device->iq_balancer, device->optimal_point);
+ }
+
+ device->freq_hz = freq_hz;
+ device->freq_shift = adjusted_freq_hz - freq_khz * 1e3 + device->freq_delta_hz;
+
+ return AIRSPYHF_SUCCESS;
+}
+
+int ADDCALL airspyhf_get_frontend_options(airspyhf_device_t* device, uint32_t* flags)
+{
+ if (flags)
+ {
+ *flags = device->frontend_options;
+ return AIRSPYHF_SUCCESS;
+ }
+
+ return AIRSPYHF_ERROR;
+}
+
+int ADDCALL airspyhf_set_frontend_options(airspyhf_device_t* device, uint32_t flags)
+{
+ int result;
+
+ device->frontend_options = flags;
+
+ result = libusb_control_transfer(
+ device->usb_device,
+ LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE,
+ AIRSPYHF_SET_FRONTEND_OPTIONS,
+ (uint16_t)(flags & 0xffff),
+ (uint16_t)(flags >> 16),
+ NULL,
+ 0,
+ LIBUSB_CTRL_TIMEOUT_MS);
+
+ if (result < 0)
+ {
+ return AIRSPYHF_ERROR;
+ }
+ return AIRSPYHF_SUCCESS;
+}
+
+static int airspyhf_config_write(airspyhf_device_t* device, uint8_t *buffer, uint16_t length)
+{
+ int result;
+ uint8_t buf[256];
+
+ memset(buf, 0, sizeof(buf));
+
+ memcpy(buf, buffer, MIN(length, sizeof(buf)));
+
+ result = libusb_control_transfer(
+ device->usb_device,
+ LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE,
+ AIRSPYHF_CONFIG_WRITE,
+ 0,
+ 0,
+ buf,
+ sizeof(buf),
+ LIBUSB_CTRL_TIMEOUT_MS);
+
+ if (result < sizeof(buf))
+ {
+ return AIRSPYHF_ERROR;
+ }
+
+ return AIRSPYHF_SUCCESS;
+}
+
+static int airspyhf_config_read(airspyhf_device_t* device, uint8_t *buffer, uint16_t length)
+{
+ uint8_t buf[256];
+ int result;
+
+ result = libusb_control_transfer(
+ device->usb_device,
+ LIBUSB_ENDPOINT_IN | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE,
+ AIRSPYHF_CONFIG_READ,
+ 0,
+ 0,
+ buf,
+ sizeof(buf),
+ LIBUSB_CTRL_TIMEOUT_MS);
+
+ memcpy(buffer, buf, MIN(length, sizeof(buf)));
+
+ if (result < sizeof(buf))
+ {
+ return AIRSPYHF_ERROR;
+ }
+
+ return AIRSPYHF_SUCCESS;
+}
+
+int ADDCALL airspyhf_get_calibration(airspyhf_device_t* device, int32_t* ppb)
+{
+ if (ppb)
+ {
+ *ppb = device->calibration_ppb;
+ return AIRSPYHF_SUCCESS;
+ }
+
+ return AIRSPYHF_ERROR;
+}
+
+int ADDCALL airspyhf_set_calibration(airspyhf_device_t* device, int32_t ppb)
+{
+ device->calibration_ppb = ppb;
+ return airspyhf_set_freq_double(device, device->freq_hz);
+}
+
+int ADDCALL airspyhf_get_vctcxo_calibration(airspyhf_device_t* device, uint16_t* vc)
+{
+ if (vc)
+ {
+ *vc = device->calibration_vctcxo;
+ return AIRSPYHF_SUCCESS;
+ }
+
+ return AIRSPYHF_ERROR;
+}
+
+int ADDCALL airspyhf_set_vctcxo_calibration(airspyhf_device_t* device, uint16_t vc)
+{
+ int result;
+ device->calibration_vctcxo = vc;
+
+ result = libusb_control_transfer(
+ device->usb_device,
+ LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE,
+ AIRSPYHF_SET_VCTCXO_CALIBRATION,
+ vc,
+ 0,
+ NULL,
+ 0,
+ LIBUSB_CTRL_TIMEOUT_MS);
+
+ if (result < 0)
+ {
+ return AIRSPYHF_ERROR;
+ }
+ return AIRSPYHF_SUCCESS;
+}
+
+int ADDCALL airspyhf_flash_configuration(airspyhf_device_t* device)
+{
+ flash_config_t config;
+ config.magic_number = CALIBRATION_MAGIC;
+ config.calibration_ppb = device->calibration_ppb;
+ config.calibration_vctcxo = device->calibration_vctcxo;
+ config.frontend_options = device->frontend_options;
+
+ if (airspyhf_is_streaming(device))
+ {
+ return AIRSPYHF_ERROR;
+ }
+
+ if (airspyhf_config_write(device, (uint8_t *)&config, sizeof(config)) != AIRSPYHF_SUCCESS)
+ {
+ return AIRSPYHF_ERROR;
+ }
+
+ return AIRSPYHF_SUCCESS;
+}
+
+int ADDCALL airspyhf_set_optimal_iq_correction_point(airspyhf_device_t* device, float w)
+{
+ device->optimal_point = w;
+ iq_balancer_set_optimal_point(device->iq_balancer, device->optimal_point);
+ return AIRSPYHF_SUCCESS;
+}
+
+int ADDCALL airspyhf_iq_balancer_configure(airspyhf_device_t* device, int buffers_to_skip, int fft_integration, int fft_overlap, int correlation_integration)
+{
+ iq_balancer_configure(device->iq_balancer, buffers_to_skip, fft_integration, fft_overlap, correlation_integration);
+ return AIRSPYHF_SUCCESS;
+}
+
+int ADDCALL airspyhf_set_lib_dsp(airspyhf_device_t* device, const uint8_t flag)
+{
+ device->enable_dsp = flag;
+ return AIRSPYHF_SUCCESS;
+}
+
+int ADDCALL airspyhf_board_partid_serialno_read(airspyhf_device_t* device, airspyhf_read_partid_serialno_t* read_partid_serialno)
+{
+ uint8_t length;
+ int result;
+
+ length = sizeof(airspyhf_read_partid_serialno_t);
+ result = libusb_control_transfer(
+ device->usb_device,
+ LIBUSB_ENDPOINT_IN | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE,
+ AIRSPYHF_GET_SERIALNO_BOARDID,
+ 0,
+ 0,
+ (unsigned char*)read_partid_serialno,
+ length,
+ LIBUSB_CTRL_TIMEOUT_MS);
+
+ if (result < length)
+ {
+ return AIRSPYHF_ERROR;
+ }
+
+ return AIRSPYHF_SUCCESS;
+}
+
+int ADDCALL airspyhf_version_string_read(airspyhf_device_t* device, char* version, uint8_t length)
+{
+ int result;
+ char version_local[MAX_VERSION_STRING_SIZE];
+
+ result = libusb_control_transfer(
+ device->usb_device,
+ LIBUSB_ENDPOINT_IN | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE,
+ AIRSPYHF_GET_VERSION_STRING,
+ 0,
+ 0,
+ (unsigned char*) version_local,
+ (MAX_VERSION_STRING_SIZE - 1),
+ LIBUSB_CTRL_TIMEOUT_MS);
+
+ if (result < 0)
+ {
+ return AIRSPYHF_ERROR;
+ }
+ else
+ {
+ if (length > 0)
+ {
+ memcpy(version, version_local, length - 1);
+ version[length - 1] = 0;
+ return AIRSPYHF_SUCCESS;
+ }
+ else
+ {
+ return AIRSPYHF_ERROR;
+ }
+ }
+}
+
+int ADDCALL airspyhf_get_att_steps(airspyhf_device_t* device, void* buffer, const uint32_t len)
+{
+ if (len == 0)
+ {
+ *(uint32_t*)buffer = device->supported_att_step_count;
+ }
+ else if (len <= device->supported_att_step_count)
+ {
+ memcpy(buffer, device->supported_att_steps, len * sizeof(float));
+ }
+ else
+ {
+ return AIRSPYHF_ERROR;
+ }
+
+ return AIRSPYHF_SUCCESS;
+}
+
+int ADDCALL airspyhf_set_att(airspyhf_device_t* device, float att)
+{
+ int result;
+ uint16_t att_index = 0;
+
+ for (uint32_t i = 0; i < device->supported_att_step_count; i++)
+ {
+ if (device->supported_att_steps[i] >= att)
+ {
+ att_index = i;
+ break;
+ }
+ }
+
+ result = libusb_control_transfer(
+ device->usb_device,
+ LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE,
+ AIRSPYHF_SET_ATT,
+ att_index,
+ 0,
+ NULL,
+ 0,
+ LIBUSB_CTRL_TIMEOUT_MS);
+
+ if (result < 0)
+ {
+ return AIRSPYHF_ERROR;
+ }
+
+ return AIRSPYHF_SUCCESS;
+}
+
+int ADDCALL airspyhf_set_bias_tee(airspyhf_device_t* device, int8_t value)
+{
+ int result;
+
+ result = libusb_control_transfer(
+ device->usb_device,
+ LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE,
+ AIRSPYHF_SET_BIAS_TEE,
+ (uint16_t)value,
+ 0,
+ NULL,
+ 0,
+ LIBUSB_CTRL_TIMEOUT_MS);
+
+ if (result < 0)
+ {
+ return AIRSPYHF_ERROR;
+ }
+
+ return AIRSPYHF_SUCCESS;
+}
+
+int ADDCALL airspyhf_get_bias_tee_count(airspyhf_device_t* device, int32_t* value)
+{
+ uint8_t length;
+ int result;
+
+ length = sizeof(airspyhf_read_partid_serialno_t);
+ result = libusb_control_transfer(
+ device->usb_device,
+ LIBUSB_ENDPOINT_IN | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE,
+ AIRSPYHF_GET_BIAS_TEE_COUNT,
+ 0,
+ 0,
+ (unsigned char*)value,
+ length,
+ LIBUSB_CTRL_TIMEOUT_MS);
+
+ if (result < length)
+ {
+ *value = 0;
+ return AIRSPYHF_ERROR;
+ }
+
+ return AIRSPYHF_SUCCESS;
+}
+
+int ADDCALL airspyhf_get_bias_tee_name(airspyhf_device_t* device, int32_t index, char* version, uint8_t length)
+{
+ int result;
+ char name_local[MAX_NAME_STRING_SIZE];
+
+ result = libusb_control_transfer(
+ device->usb_device,
+ LIBUSB_ENDPOINT_IN | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE,
+ AIRSPYHF_GET_BIAS_TEE_NAME,
+ 0,
+ (int16_t)index,
+ (unsigned char*)name_local,
+ (MAX_NAME_STRING_SIZE - 1),
+ LIBUSB_CTRL_TIMEOUT_MS);
+
+ if (result < 0)
+ {
+ return AIRSPYHF_ERROR;
+ }
+ else
+ {
+ if (length > 0)
+ {
+ memcpy(version, name_local, length - 1);
+ version[length - 1] = 0;
+ return AIRSPYHF_SUCCESS;
+ }
+ else
+ {
+ return AIRSPYHF_ERROR;
+ }
+ }
+}
+
+// Legacy for HF+ dual and HF+ Discovery
+
+int ADDCALL airspyhf_set_hf_att(airspyhf_device_t* device, uint8_t att_index)
+{
+ int result;
+
+ result = libusb_control_transfer(
+ device->usb_device,
+ LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE,
+ AIRSPYHF_SET_ATT,
+ (uint16_t)att_index,
+ 0,
+ NULL,
+ 0,
+ LIBUSB_CTRL_TIMEOUT_MS);
+
+ if (result < 0)
+ {
+ return AIRSPYHF_ERROR;
+ }
+
+ return AIRSPYHF_SUCCESS;
+}
+
+int ADDCALL airspyhf_set_hf_lna(airspyhf_device_t* device, uint8_t flag)
+{
+ int result;
+
+ result = libusb_control_transfer(
+ device->usb_device,
+ LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE,
+ AIRSPYHF_SET_LNA,
+ (uint16_t) flag,
+ 0,
+ NULL,
+ 0,
+ LIBUSB_CTRL_TIMEOUT_MS);
+
+ if (result < 0)
+ {
+ return AIRSPYHF_ERROR;
+ }
+
+ return AIRSPYHF_SUCCESS;
+}
+
+int ADDCALL airspyhf_set_user_output(airspyhf_device_t* device, airspyhf_user_output_t pin, airspyhf_user_output_state_t value)
+{
+ int result;
+
+ result = libusb_control_transfer(
+ device->usb_device,
+ LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE,
+ AIRSPYHF_SET_USER_OUTPUT,
+ (uint16_t)pin,
+ (uint16_t)value,
+ NULL,
+ 0,
+ LIBUSB_CTRL_TIMEOUT_MS);
+
+ if (result < 0)
+ {
+ return AIRSPYHF_ERROR;
+ }
+
+ return AIRSPYHF_SUCCESS;
+}
+
+int ADDCALL airspyhf_set_hf_agc(airspyhf_device_t* device, uint8_t flag)
+{
+ int result;
+
+ result = libusb_control_transfer(
+ device->usb_device,
+ LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE,
+ AIRSPYHF_SET_AGC,
+ (uint16_t)flag,
+ 0,
+ NULL,
+ 0,
+ LIBUSB_CTRL_TIMEOUT_MS);
+
+ if (result < 0)
+ {
+ return AIRSPYHF_ERROR;
+ }
+
+ return AIRSPYHF_SUCCESS;
+}
+
+int ADDCALL airspyhf_set_hf_agc_threshold(airspyhf_device_t* device, uint8_t flag)
+{
+ int result;
+
+ result = libusb_control_transfer(
+ device->usb_device,
+ LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE,
+ AIRSPYHF_SET_AGC_THRESHOLD,
+ (uint16_t)flag,
+ 0,
+ NULL,
+ 0,
+ LIBUSB_CTRL_TIMEOUT_MS);
+
+ if (result < 0)
+ {
+ return AIRSPYHF_ERROR;
+ }
+
+ return AIRSPYHF_SUCCESS;
+}
diff --git a/Radio/HW/AirSpyHF/src/airspyhf.h b/Radio/HW/AirSpyHF/src/airspyhf.h new file mode 100644 index 0000000..2879120 --- /dev/null +++ b/Radio/HW/AirSpyHF/src/airspyhf.h @@ -0,0 +1,171 @@ +/* +Copyright (c) 2013-2024, Youssef Touil <youssef@airspy.com> +Copyright (c) 2013-2017, Ian Gilmour <ian@sdrsharp.com> +Copyright (c) 2013-2017, Benjamin Vernoux <bvernoux@airspy.com> +Copyright (c) 2013, Michael Ossmann <mike@ossmann.com> +Copyright (c) 2012, Jared Boone <jared@sharebrained.com> + +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + + Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + Neither the name of Airspy HF+ nor the names of its contributors may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifndef __AIRSPYHF_H__ +#define __AIRSPYHF_H__ + +#include <stdint.h> + +#define AIRSPYHF_VERSION "1.8.0" +#define AIRSPYHF_VER_MAJOR 1 +#define AIRSPYHF_VER_MINOR 8 +#define AIRSPYHF_VER_REVISION 0 + +#define AIRSPYHF_ENDPOINT_IN (1) + +#if defined(_WIN32) && !defined(STATIC_AIRSPYHFPLUS) + #define ADD_EXPORTS + + /* You should define ADD_EXPORTS *only* when building the DLL. */ + #ifdef ADD_EXPORTS + #define ADDAPI __declspec(dllexport) + #else + #define ADDAPI __declspec(dllimport) + #endif + + /* Define calling convention in one place, for convenience. */ + #define ADDCALL __cdecl + +#else /* _WIN32 not defined. */ + + /* Define with no value on non-Windows OSes. */ + #define ADDAPI + #define ADDCALL + +#endif + +#ifdef __cplusplus +extern "C" +{ +#endif + +enum airspyhf_error +{ + AIRSPYHF_SUCCESS = 0, + AIRSPYHF_ERROR = -1, + AIRSPYHF_UNSUPPORTED = -2 +}; + +typedef struct { + float re; + float im; +} airspyhf_complex_float_t; + +typedef struct { + uint32_t part_id; + uint32_t serial_no[4]; +} airspyhf_read_partid_serialno_t; + +enum airspyhf_board_id +{ + AIRSPYHF_BOARD_ID_UNKNOWN_AIRSPYHF = 0, + AIRSPYHF_BOARD_ID_AIRSPYHF_REV_A = 1, + AIRSPYHF_BOARD_ID_AIRSPYHF_DISCOVERY_REV_A = 2, + AIRSPYHF_BOARD_ID_INVALID = 0xFF, +}; + +typedef struct airspyhf_device airspyhf_device_t; + +typedef struct { + airspyhf_device_t* device; + void* ctx; + airspyhf_complex_float_t* samples; + int sample_count; + uint64_t dropped_samples; +} airspyhf_transfer_t; + +typedef struct { + uint32_t major_version; + uint32_t minor_version; + uint32_t revision; +} airspyhf_lib_version_t; + +#define MAX_NAME_STRING_SIZE (64) +#define MAX_VERSION_STRING_SIZE MAX_NAME_STRING_SIZE + +typedef int (*airspyhf_sample_block_cb_fn) (airspyhf_transfer_t* transfer_fn); + +extern ADDAPI void ADDCALL airspyhf_lib_version(airspyhf_lib_version_t* lib_version); +extern ADDAPI int ADDCALL airspyhf_list_devices(uint64_t *serials, int count); +extern ADDAPI int ADDCALL airspyhf_open(airspyhf_device_t** device); +extern ADDAPI int ADDCALL airspyhf_open_sn(airspyhf_device_t** device, uint64_t serial_number); +extern ADDAPI int ADDCALL airspyhf_open_fd(airspyhf_device_t** device, int fd); +extern ADDAPI int ADDCALL airspyhf_close(airspyhf_device_t* device); +extern ADDAPI int ADDCALL airspyhf_get_output_size(airspyhf_device_t* device); /* Returns the number of IQ samples to expect in the callback */ +extern ADDAPI int ADDCALL airspyhf_start(airspyhf_device_t* device, airspyhf_sample_block_cb_fn callback, void* ctx); +extern ADDAPI int ADDCALL airspyhf_stop(airspyhf_device_t* device); +extern ADDAPI int ADDCALL airspyhf_is_streaming(airspyhf_device_t* device); +extern ADDAPI int ADDCALL airspyhf_is_low_if(airspyhf_device_t* device); /* Tells if the current sample rate is Zero-IF (0) or Low-IF (1) */ +extern ADDAPI int ADDCALL airspyhf_set_freq(airspyhf_device_t* device, const uint32_t freq_hz); +extern ADDAPI int ADDCALL airspyhf_set_freq_double(airspyhf_device_t* device, const double freq_hz); +extern ADDAPI int ADDCALL airspyhf_set_lib_dsp(airspyhf_device_t* device, const uint8_t flag); /* Enables/Disables the IQ Correction, IF shift and Fine Tuning. */ +extern ADDAPI int ADDCALL airspyhf_get_samplerates(airspyhf_device_t* device, uint32_t* buffer, const uint32_t len); +extern ADDAPI int ADDCALL airspyhf_set_samplerate(airspyhf_device_t* device, uint32_t samplerate); +extern ADDAPI int ADDCALL airspyhf_set_att(airspyhf_device_t* device, float value); +extern ADDAPI int ADDCALL airspyhf_get_att_steps(airspyhf_device_t* device, void* buffer, const uint32_t len); +extern ADDAPI int ADDCALL airspyhf_set_bias_tee(airspyhf_device_t* device, int8_t value); +extern ADDAPI int ADDCALL airspyhf_get_bias_tee_count(airspyhf_device_t* device, int32_t* count); +extern ADDAPI int ADDCALL airspyhf_get_bias_tee_name(airspyhf_device_t* device, int32_t index, char* version, uint8_t length); +extern ADDAPI int ADDCALL airspyhf_get_calibration(airspyhf_device_t* device, int32_t* ppb); +extern ADDAPI int ADDCALL airspyhf_set_calibration(airspyhf_device_t* device, int32_t ppb); +extern ADDAPI int ADDCALL airspyhf_get_vctcxo_calibration(airspyhf_device_t* device, uint16_t* vc); +extern ADDAPI int ADDCALL airspyhf_set_vctcxo_calibration(airspyhf_device_t* device, uint16_t vc); +extern ADDAPI int ADDCALL airspyhf_get_frontend_options(airspyhf_device_t* device, uint32_t* flags); +extern ADDAPI int ADDCALL airspyhf_set_frontend_options(airspyhf_device_t* device, uint32_t flags); +extern ADDAPI int ADDCALL airspyhf_set_optimal_iq_correction_point(airspyhf_device_t* device, float w); +extern ADDAPI int ADDCALL airspyhf_iq_balancer_configure(airspyhf_device_t* device, int buffers_to_skip, int fft_integration, int fft_overlap, int correlation_integration); +extern ADDAPI int ADDCALL airspyhf_flash_configuration(airspyhf_device_t* device); /* streaming needs to be stopped */ +extern ADDAPI int ADDCALL airspyhf_board_partid_serialno_read(airspyhf_device_t* device, airspyhf_read_partid_serialno_t* read_partid_serialno); +extern ADDAPI int ADDCALL airspyhf_version_string_read(airspyhf_device_t* device, char* version, uint8_t length); + +// Legacy stuff for backward compatibility + +typedef enum +{ + AIRSPYHF_USER_OUTPUT_0 = 0, + AIRSPYHF_USER_OUTPUT_1 = 1, + AIRSPYHF_USER_OUTPUT_2 = 2, + AIRSPYHF_USER_OUTPUT_3 = 3 +} airspyhf_user_output_t; + +typedef enum +{ + AIRSPYHF_USER_OUTPUT_LOW = 0, + AIRSPYHF_USER_OUTPUT_HIGH = 1 +} airspyhf_user_output_state_t; + +#define airspyhf_flash_calibration airspyhf_flash_configuration + +extern ADDAPI int ADDCALL airspyhf_set_user_output(airspyhf_device_t* device, airspyhf_user_output_t pin, airspyhf_user_output_state_t value); +extern ADDAPI int ADDCALL airspyhf_set_hf_agc(airspyhf_device_t* device, uint8_t flag); /* 0 = off, 1 = on */ +extern ADDAPI int ADDCALL airspyhf_set_hf_agc_threshold(airspyhf_device_t* device, uint8_t flag); /* when agc on: 0 = low, 1 = high */ +extern ADDAPI int ADDCALL airspyhf_set_hf_att(airspyhf_device_t* device, uint8_t att_index); /* Possible values: 0..8 Range: 0..48 dB Attenuation with 6 dB steps */ +extern ADDAPI int ADDCALL airspyhf_set_hf_lna(airspyhf_device_t* device, uint8_t flag); /* 0 or 1: 1 to activate LNA (alias PreAmp): 1 = +6 dB gain - compensated in digital */ + +#ifdef __cplusplus +} +#endif + +#endif//__AIRSPYHF_H__ diff --git a/Radio/HW/AirSpyHF/src/airspyhf_commands.h b/Radio/HW/AirSpyHF/src/airspyhf_commands.h new file mode 100644 index 0000000..db19690 --- /dev/null +++ b/Radio/HW/AirSpyHF/src/airspyhf_commands.h @@ -0,0 +1,63 @@ +/*
+Copyright (c) 2013-2023, Youssef Touil <youssef@airspy.com>
+Copyright (c) 2013-2017, Ian Gilmour <ian@sdrsharp.com>
+Copyright (c) 2013-2017, Benjamin Vernoux <bvernoux@airspy.com>
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
+
+Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
+Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+Neither the name of Airspy HF+ nor the names of its contributors may be used to endorse or promote products derived from this software
+without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+#ifndef __AIRSPYHF_COMMANDS_H__
+#define __AIRSPYHF_COMMANDS_H__
+
+#include <stdint.h>
+
+typedef enum
+{
+ RECEIVER_MODE_OFF = 0,
+ RECEIVER_MODE_ON = 1
+} receiver_mode_t;
+
+#define AIRSPYHF_CMD_MAX (22)
+typedef enum
+{
+ AIRSPYHF_INVALID = 0,
+ AIRSPYHF_RECEIVER_MODE = 1,
+ AIRSPYHF_SET_FREQ = 2,
+ AIRSPYHF_GET_SAMPLERATES = 3,
+ AIRSPYHF_SET_SAMPLERATE = 4,
+ AIRSPYHF_CONFIG_READ = 5,
+ AIRSPYHF_CONFIG_WRITE = 6,
+ AIRSPYHF_GET_SERIALNO_BOARDID = 7,
+ AIRSPYHF_SET_USER_OUTPUT = 8,
+ AIRSPYHF_GET_VERSION_STRING = 9,
+ AIRSPYHF_SET_AGC = 10,
+ AIRSPYHF_SET_AGC_THRESHOLD = 11,
+ AIRSPYHF_SET_ATT = 12,
+ AIRSPYHF_SET_LNA = 13,
+ AIRSPYHF_GET_SAMPLERATE_ARCHITECTURES = 14,
+ AIRSPYHF_GET_FILTER_GAIN = 15,
+ AIRSPYHF_GET_FREQ_DELTA = 16,
+ AIRSPYHF_SET_VCTCXO_CALIBRATION = 17,
+ AIRSPYHF_SET_FRONTEND_OPTIONS = 18,
+ AIRSPYHF_GET_ATT_STEPS = 19,
+ AIRSPYHF_GET_BIAS_TEE_COUNT = 20,
+ AIRSPYHF_GET_BIAS_TEE_NAME = 21,
+ AIRSPYHF_SET_BIAS_TEE = AIRSPYHF_CMD_MAX
+} airspyhf_vendor_request;
+
+#endif
diff --git a/Radio/HW/AirSpyHF/src/iqbalancer.c b/Radio/HW/AirSpyHF/src/iqbalancer.c new file mode 100644 index 0000000..2bf5901 --- /dev/null +++ b/Radio/HW/AirSpyHF/src/iqbalancer.c @@ -0,0 +1,564 @@ +/* +Copyright (c) 2016-2023, Youssef Touil <youssef@airspy.com> +Copyright (c) 2018, Leif Asbrink <leif@sm5bsz.com> + +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + + Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + Neither the name of Airspy HF+ nor the names of its contributors may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include <stdlib.h> +#include <stdint.h> +#include <stdio.h> +#include <string.h> +#include <math.h> + +#include "iqbalancer.h" + +#ifndef MATH_PI +#define MATH_PI 3.14159265359 +#endif + +#define EPSILON 0.01f +#define WorkingBufferLength (FFTBins * (1 + FFTIntegration / FFTOverlap)) + +struct iq_balancer_t +{ + float phase; + float last_phase; + + float amplitude; + float last_amplitude; + + float iavg; + float qavg; + float integrated_total_power; + float integrated_image_power; + float maximum_image_power; + + float raw_phases[MaxLookback]; + float raw_amplitudes[MaxLookback]; + + int skipped_buffers; + int buffers_to_skip; + int working_buffer_pos; + int fft_integration; + int fft_overlap; + int correlation_integration; + + int no_of_avg; + int no_of_raw; + int raw_ptr; + int optimal_bin; + int reset_flag; + int *power_flag; + + complex_t *corr; + complex_t *corr_plus; + complex_t *working_buffer; + float *boost; +}; + +static uint8_t __lib_initialized = 0; +static float __fft_window[FFTBins]; +static float __boost_window[FFTBins]; + +static void __init_library() +{ + int i; + + if (__lib_initialized) + { + return; + } + + const int length = FFTBins - 1; + + for (i = 0; i <= length; i++) + { + __fft_window[i] = (float)( + +0.35875f + - 0.48829f * cos(2.0 * MATH_PI * i / length) + + 0.14128f * cos(4.0 * MATH_PI * i / length) + - 0.01168f * cos(6.0 * MATH_PI * i / length) + ); + __boost_window[i] = (float)(1.0 / BoostFactor + 1.0 / exp(pow(i * 2.0 / BinsToOptimize, 2.0))); + } + + __lib_initialized = 1; +} + +static void window(complex_t *buffer, int length) +{ + int i; + for (i = 0; i < length; i++) + { + buffer[i].re *= __fft_window[i]; + buffer[i].im *= __fft_window[i]; + } +} + +static void fft(complex_t *buffer, int length) +{ + int nm1 = length - 1; + int nd2 = length / 2; + int i, j, jm1, k, l, m, le, le2, ip; + complex_t u, t, r; + + m = 0; + i = length; + while (i > 1) + { + ++m; + i = (i >> 1); + } + + j = nd2; + + for (i = 1; i < nm1; ++i) + { + if (i < j) + { + t = buffer[j]; + buffer[j] = buffer[i]; + buffer[i] = t; + } + + k = nd2; + + while (k <= j) + { + j = j - k; + k = k / 2; + } + + j += k; + } + + for (l = 1; l <= m; ++l) + { + le = 1 << l; + le2 = le / 2; + + u.re = 1.0f; + u.im = 0.0f; + + r.re = (float)cos(MATH_PI / le2); + r.im = (float)-sin(MATH_PI / le2); + + for (j = 1; j <= le2; ++j) + { + jm1 = j - 1; + + for (i = jm1; i <= nm1; i += le) + { + ip = i + le2; + + t.re = u.re * buffer[ip].re - u.im * buffer[ip].im; + t.im = u.im * buffer[ip].re + u.re * buffer[ip].im; + + buffer[ip].re = buffer[i].re - t.re; + buffer[ip].im = buffer[i].im - t.im; + + buffer[i].re += t.re; + buffer[i].im += t.im; + } + + t.re = u.re * r.re - u.im * r.im; + t.im = u.im * r.re + u.re * r.im; + + u.re = t.re; + u.im = t.im; + } + } + + for (i = 0; i < nd2; i++) + { + j = nd2 + i; + t = buffer[i]; + buffer[i] = buffer[j]; + buffer[j] = t; + } +} + +static void cancel_dc(struct iq_balancer_t *iq_balancer, complex_t* iq, int length, uint8_t skip_eval) +{ + int i; + float iavg = iq_balancer->iavg; + float qavg = iq_balancer->qavg; + + if (skip_eval) + { + for (i = 0; i < length; i++) + { + iq[i].re -= iavg; + iq[i].im -= qavg; + } + } + else + { + for (i = 0; i < length; i++) + { + iavg += DcTimeConst * (iq[i].re - iavg); + qavg += DcTimeConst * (iq[i].im - qavg); + + iq[i].re -= iavg; + iq[i].im -= qavg; + } + iq_balancer->iavg = iavg; + iq_balancer->qavg = qavg; + } +} + +static float adjust_benchmark(struct iq_balancer_t *iq_balancer, complex_t *iq, float phase, float amplitude, int skip_power_calculation) +{ + int i; + float sum = 0; + for (i = 0; i < FFTBins; i++) + { + float re = iq[i].re; + float im = iq[i].im; + + iq[i].re += phase * im; + iq[i].im += phase * re; + + iq[i].re *= 1 + amplitude; + iq[i].im *= 1 - amplitude; + if (!skip_power_calculation) + sum += re * re + im * im; + } + return sum; +} + +static complex_t multiply_complex_complex(complex_t *a, const complex_t *b) +{ + complex_t result; + result.re = a->re * b->re - a->im * b->im; + result.im = a->im * b->re + a->re * b->im; + return result; +} + +static int compute_corr(struct iq_balancer_t *iq_balancer, complex_t* iq, complex_t* ccorr, int length, int step) +{ + complex_t cc, fftPtr[FFTBins]; + int n, m; + int i, j; + int count = 0; + float power; + float phase = iq_balancer->phase + step * PhaseStep; + float amplitude = iq_balancer->amplitude + step * AmplitudeStep; + + for (n = 0, m = 0; n <= length - FFTBins && m < iq_balancer->fft_integration; n += FFTBins / iq_balancer->fft_overlap, m++) + { + memcpy(fftPtr, iq + n, FFTBins * sizeof(complex_t)); + power = adjust_benchmark(iq_balancer, fftPtr, phase, amplitude, step); + if (step == 0) + { + if (power > MinimumPower) + { + iq_balancer->power_flag[m] = 1; + iq_balancer->integrated_total_power += power; + } + else + { + iq_balancer->power_flag[m] = 0; + } + } + if (iq_balancer->power_flag[m] == 1) + { + count++; + window(fftPtr, FFTBins); + fft(fftPtr, FFTBins); + for (i = EdgeBinsToSkip, j = FFTBins - EdgeBinsToSkip; i <= FFTBins - EdgeBinsToSkip; i++, j--) + { + cc = multiply_complex_complex(fftPtr + i, fftPtr + j); + ccorr[i].re += cc.re; + ccorr[i].im += cc.im; + + ccorr[j].re = ccorr[i].re; + ccorr[j].im = ccorr[i].im; + } + if (step == 0) + { + for (i = EdgeBinsToSkip; i <= FFTBins - EdgeBinsToSkip; i++) + { + power = fftPtr[i].re * fftPtr[i].re + fftPtr[i].im * fftPtr[i].im; + iq_balancer->boost[i] += power; + if (iq_balancer->optimal_bin == FFTBins / 2) + { + iq_balancer->integrated_image_power += power; + } + else + { + iq_balancer->integrated_image_power += power * __boost_window[abs(FFTBins - i - iq_balancer->optimal_bin)]; + } + } + } + } + } + + return count; +} + +static complex_t utility(struct iq_balancer_t *iq_balancer, complex_t* ccorr) +{ + int i; + int j; + float invskip = 1.0f / EdgeBinsToSkip; + complex_t acc = { 0, 0 }; + for (i = EdgeBinsToSkip, j = FFTBins - EdgeBinsToSkip; i <= FFTBins - EdgeBinsToSkip; i++, j--) + { + int distance = abs(i - FFTBins / 2); + if (distance > CenterBinsToSkip) + { + float weight = (distance > EdgeBinsToSkip) ? 1.0f : (distance * invskip); + if (iq_balancer->optimal_bin != FFTBins / 2) + { + weight *= __boost_window[abs(iq_balancer->optimal_bin - i)]; + } + weight *= iq_balancer->boost[j] / (iq_balancer->boost[i] + EPSILON); + acc.re += ccorr[i].re * weight; + acc.im += ccorr[i].im * weight; + } + } + return acc; +} + +static void estimate_imbalance(struct iq_balancer_t *iq_balancer, complex_t* iq, int length) +{ + int i, j; + float amplitude, phase, mu; + complex_t a, b; + + if (iq_balancer->reset_flag) + { + iq_balancer->reset_flag = 0; + iq_balancer->no_of_avg = -BuffersToSkipOnReset; + iq_balancer->maximum_image_power = 0; + } + + if (iq_balancer->no_of_avg < 0) + { + iq_balancer->no_of_avg++; + return; + } + else if (iq_balancer->no_of_avg == 0) + { + iq_balancer->integrated_image_power = 0; + iq_balancer->integrated_total_power = 0; + memset(iq_balancer->boost, 0, FFTBins * sizeof(float)); + memset(iq_balancer->corr, 0, FFTBins * sizeof(complex_t)); + memset(iq_balancer->corr_plus, 0, FFTBins * sizeof(complex_t)); + } + + iq_balancer->maximum_image_power *= MaxPowerDecay; + + i = compute_corr(iq_balancer, iq, iq_balancer->corr, length, 0); + if (i == 0) + return; + + iq_balancer->no_of_avg += i; + compute_corr(iq_balancer, iq, iq_balancer->corr_plus, length, 1); + + if (iq_balancer->no_of_avg <= iq_balancer->correlation_integration * iq_balancer->fft_integration) + return; + + iq_balancer->no_of_avg = 0; + + if (iq_balancer->optimal_bin == FFTBins / 2) + { + if (iq_balancer->integrated_total_power < iq_balancer->maximum_image_power) + return; + iq_balancer->maximum_image_power = iq_balancer->integrated_total_power; + } + else + { + if (iq_balancer->integrated_image_power - iq_balancer->integrated_total_power * BoostWindowNorm < iq_balancer->maximum_image_power * PowerThreshold) + return; + iq_balancer->maximum_image_power = iq_balancer->integrated_image_power - iq_balancer->integrated_total_power * BoostWindowNorm; + } + + a = utility(iq_balancer, iq_balancer->corr); + b = utility(iq_balancer, iq_balancer->corr_plus); + + mu = a.im - b.im; + if (fabs(mu) > MinDeltaMu) + { + mu = a.im / mu; + if (mu < -MaxMu) + mu = -MaxMu; + else if (mu > MaxMu) + mu = MaxMu; + } + else + { + mu = 0; + } + + phase = iq_balancer->phase + PhaseStep * mu; + + mu = a.re - b.re; + if (fabs(mu) > MinDeltaMu) + { + mu = a.re / mu; + if (mu < -MaxMu) + mu = -MaxMu; + else if (mu > MaxMu) + mu = MaxMu; + } + else + { + mu = 0; + } + + amplitude = iq_balancer->amplitude + AmplitudeStep * mu; + + if (iq_balancer->no_of_raw < MaxLookback) + iq_balancer->no_of_raw++; + iq_balancer->raw_amplitudes[iq_balancer->raw_ptr] = amplitude; + iq_balancer->raw_phases[iq_balancer->raw_ptr] = phase; + i = iq_balancer->raw_ptr; + for (j = 0; j < iq_balancer->no_of_raw - 1; j++) + { + i = (i + MaxLookback - 1) & (MaxLookback - 1); + phase += iq_balancer->raw_phases[i]; + amplitude += iq_balancer->raw_amplitudes[i]; + } + phase /= iq_balancer->no_of_raw; + amplitude /= iq_balancer->no_of_raw; + iq_balancer->raw_ptr = (iq_balancer->raw_ptr + 1) & (MaxLookback - 1); + + iq_balancer->phase = phase; + iq_balancer->amplitude = amplitude; +} + +static void adjust_phase_amplitude(struct iq_balancer_t *iq_balancer, complex_t* iq, int length) +{ + int i; + float scale = 1.0f / (length - 1); + + for (i = 0; i < length; i++) + { + float phase = (i * iq_balancer->last_phase + (length - 1 - i) * iq_balancer->phase) * scale; + float amplitude = (i * iq_balancer->last_amplitude + (length - 1 - i) * iq_balancer->amplitude) * scale; + + float re = iq[i].re; + float im = iq[i].im; + + iq[i].re += phase * im; + iq[i].im += phase * re; + + iq[i].re *= 1 + amplitude; + iq[i].im *= 1 - amplitude; + } + + iq_balancer->last_phase = iq_balancer->phase; + iq_balancer->last_amplitude = iq_balancer->amplitude; +} + +void ADDCALL iq_balancer_process(struct iq_balancer_t *iq_balancer, complex_t* iq, int length, uint8_t skip_eval) +{ + int count; + + cancel_dc(iq_balancer, iq, length, skip_eval); + + if (!skip_eval) + { + count = WorkingBufferLength - iq_balancer->working_buffer_pos; + if (count >= length) + { + count = length; + } + memcpy(iq_balancer->working_buffer + iq_balancer->working_buffer_pos, iq, count * sizeof(complex_t)); + iq_balancer->working_buffer_pos += count; + if (iq_balancer->working_buffer_pos >= WorkingBufferLength) + { + iq_balancer->working_buffer_pos = 0; + + if (++iq_balancer->skipped_buffers > iq_balancer->buffers_to_skip) + { + iq_balancer->skipped_buffers = 0; + estimate_imbalance(iq_balancer, iq_balancer->working_buffer, WorkingBufferLength); + } + } + } + + adjust_phase_amplitude(iq_balancer, iq, length); +} + +void ADDCALL iq_balancer_set_optimal_point(struct iq_balancer_t *iq_balancer, float w) +{ + if (w < -0.5f) + { + w = -0.5f; + } + else if (w > 0.5f) + { + w = 0.5f; + } + + iq_balancer->optimal_bin = (int)floor(FFTBins * (0.5 + w)); + iq_balancer->reset_flag = 1; +} + +void ADDCALL iq_balancer_configure(struct iq_balancer_t *iq_balancer, int buffers_to_skip, int fft_integration, int fft_overlap, int correlation_integration) +{ + iq_balancer->buffers_to_skip = buffers_to_skip; + iq_balancer->fft_integration = fft_integration; + iq_balancer->fft_overlap = fft_overlap; + iq_balancer->correlation_integration = correlation_integration; + + free(iq_balancer->power_flag); + iq_balancer->power_flag = (int *)malloc(iq_balancer->fft_integration * sizeof(int)); + memset(iq_balancer->power_flag, 0, iq_balancer->fft_integration * sizeof(int)); + + iq_balancer->reset_flag = 1; +} + +struct iq_balancer_t * ADDCALL iq_balancer_create(float initial_phase, float initial_amplitude) +{ + struct iq_balancer_t *instance = (struct iq_balancer_t *) malloc(sizeof(struct iq_balancer_t)); + memset(instance, 0, sizeof(struct iq_balancer_t)); + + instance->phase = initial_phase; + instance->amplitude = initial_amplitude; + + instance->optimal_bin = FFTBins / 2; + + instance->buffers_to_skip = BuffersToSkip; + instance->fft_integration = FFTIntegration; + instance->fft_overlap = FFTOverlap; + instance->correlation_integration = CorrelationIntegration; + + instance->corr = (complex_t *)malloc(FFTBins * sizeof(complex_t)); + instance->corr_plus = (complex_t *)malloc(FFTBins * sizeof(complex_t)); + instance->working_buffer = (complex_t *)malloc(WorkingBufferLength * sizeof(complex_t)); + instance->boost = (float *)malloc(FFTBins * sizeof(float)); + instance->power_flag = (int *)malloc(instance->fft_integration * sizeof(int)); + + __init_library(); + return instance; +} + +void ADDCALL iq_balancer_destroy(struct iq_balancer_t *iq_balancer) +{ + free(iq_balancer->corr); + free(iq_balancer->corr_plus); + free(iq_balancer->working_buffer); + free(iq_balancer->boost); + free(iq_balancer->power_flag); + free(iq_balancer); +} diff --git a/Radio/HW/AirSpyHF/src/iqbalancer.h b/Radio/HW/AirSpyHF/src/iqbalancer.h new file mode 100644 index 0000000..baa1374 --- /dev/null +++ b/Radio/HW/AirSpyHF/src/iqbalancer.h @@ -0,0 +1,68 @@ +/* +Copyright (c) 2016-2023, Youssef Touil <youssef@airspy.com> +Copyright (c) 2018, Leif Asbrink <leif@sm5bsz.com> + +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + + Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + Neither the name of Airspy HF+ nor the names of its contributors may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifndef __IQ_BALANCER_H__ +#define __IQ_BALANCER_H__ + +#include "airspyhf.h" + +#define FFTBins (4 * 1024) +#define BoostFactor 100000.0 +#define BinsToOptimize (FFTBins/25) +#define EdgeBinsToSkip (FFTBins/22) +#define CenterBinsToSkip 2 +#define MaxLookback 4 +#define PhaseStep 1e-2f +#define AmplitudeStep 1e-2f +#define MaxMu 50.0f +#define MinDeltaMu 0.1f +#define DcTimeConst 1e-4f +#define MinimumPower 0.01f +#define PowerThreshold 0.5f +#define BuffersToSkipOnReset 2 +#define MaxPowerDecay 0.98f +#define MaxPowerRatio 0.8f +#define BoostWindowNorm (MaxPowerRatio / 95) + +#if defined(__arm__) && !defined(__force_hiq__) + #define BuffersToSkip 4 + #define FFTIntegration 2 + #define FFTOverlap 1 + #define CorrelationIntegration 4 +#else + #define BuffersToSkip 2 + #define FFTIntegration 4 + #define FFTOverlap 2 + #define CorrelationIntegration 16 +#endif + +struct iq_balancer_t; + +typedef airspyhf_complex_float_t complex_t; + +ADDAPI struct iq_balancer_t * ADDCALL iq_balancer_create(float initial_phase, float initial_amplitude); +ADDAPI void ADDCALL iq_balancer_set_optimal_point(struct iq_balancer_t *iq_balancer, float w); +ADDAPI void ADDCALL iq_balancer_configure(struct iq_balancer_t *iq_balancer, int buffers_to_skip, int fft_integration, int fft_overlap, int correlation_integration); +ADDAPI void ADDCALL iq_balancer_process(struct iq_balancer_t *iq_balancer, complex_t* iq, int length, uint8_t skip_eval); +ADDAPI void ADDCALL iq_balancer_destroy(struct iq_balancer_t *iq_balancer); + +#endif diff --git a/Radio/HW/RtlSdr/RtlSdr.swift b/Radio/HW/RtlSdr/RtlSdr.swift deleted file mode 100644 index 3b9063c..0000000 --- a/Radio/HW/RtlSdr/RtlSdr.swift +++ /dev/null @@ -1,12 +0,0 @@ -// -// RtlSdr.swift -// PrySDR -// -// Created by Jacky Jack on 25/10/2024. -// - - -/// Wrapper for librtlsdr library -class RtlSdr { - -} diff --git a/Radio/HW/RtlSdr/libusb/config.h b/Radio/HW/RtlSdr/libusb/config.h new file mode 100644 index 0000000..59f3463 --- /dev/null +++ b/Radio/HW/RtlSdr/libusb/config.h @@ -0,0 +1,37 @@ +/* config.h. Manually generated for Xcode. */ + +#include <AvailabilityMacros.h> + +/* Define to the attribute for default visibility. */ +#define DEFAULT_VISIBILITY __attribute__ ((visibility ("default"))) + +/* Define to 1 to enable message logging. */ +#define ENABLE_LOGGING 1 + +/* On 10.12 and later, use newly available clock_*() functions */ +#if MAC_OS_X_VERSION_MIN_REQUIRED >= 101200 +/* Define to 1 if you have the `clock_gettime' function. */ +#define HAVE_CLOCK_GETTIME 1 +#endif + +/* On 10.6 and later, use newly available pthread_threadid_np() function */ +#if MAC_OS_X_VERSION_MIN_REQUIRED >= 1060 +/* Define to 1 if you have the 'pthread_threadid_np' function. */ +#define HAVE_PTHREAD_THREADID_NP 1 +#endif + +/* Define to 1 if the system has the type `nfds_t'. */ +#define HAVE_NFDS_T 1 + +/* Define to 1 if you have the <sys/time.h> header file. */ +#define HAVE_SYS_TIME_H 1 + +/* Define to 1 if compiling for a POSIX platform. */ +#define PLATFORM_POSIX 1 + +/* Define to the attribute for enabling parameter checks on printf-like + functions. */ +#define PRINTF_FORMAT(a, b) __attribute__ ((__format__ (__printf__, a, b))) + +/* Enable GNU extensions. */ +#define _GNU_SOURCE 1 diff --git a/Radio/HW/RtlSdr/libusb/core.c b/Radio/HW/RtlSdr/libusb/core.c new file mode 100644 index 0000000..7893ac2 --- /dev/null +++ b/Radio/HW/RtlSdr/libusb/core.c @@ -0,0 +1,2737 @@ +/* -*- Mode: C; indent-tabs-mode:t ; c-basic-offset:8 -*- */ +/* + * Core functions for libusb + * Copyright © 2012-2013 Nathan Hjelm <hjelmn@cs.unm.edu> + * Copyright © 2007-2008 Daniel Drake <dsd@gentoo.org> + * Copyright © 2001 Johannes Erdfelt <johannes@erdfelt.com> + * + * 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 "libusbi.h" +#include "version.h" + +#ifdef __ANDROID__ +#include <android/log.h> +#endif +#include <stdio.h> +#include <string.h> +#ifdef HAVE_SYSLOG +#include <syslog.h> +#endif + +static const struct libusb_version libusb_version_internal = + { LIBUSB_MAJOR, LIBUSB_MINOR, LIBUSB_MICRO, LIBUSB_NANO, + LIBUSB_RC, "http://libusb.info" }; +static struct timespec timestamp_origin; +#if defined(ENABLE_LOGGING) && !defined(USE_SYSTEM_LOGGING_FACILITY) +static libusb_log_cb log_handler; +#endif + +struct libusb_context *usbi_default_context; +static int default_context_refcnt; +static usbi_mutex_static_t default_context_lock = USBI_MUTEX_INITIALIZER; +static struct usbi_option default_context_options[LIBUSB_OPTION_MAX]; + + +usbi_mutex_static_t active_contexts_lock = USBI_MUTEX_INITIALIZER; +struct list_head active_contexts_list; + +/** + * \mainpage libusb-1.0 API Reference + * + * \section intro Introduction + * + * libusb is an open source library that allows you to communicate with USB + * devices from user space. For more info, see the + * <a href="http://libusb.info">libusb homepage</a>. + * + * This documentation is aimed at application developers wishing to + * communicate with USB peripherals from their own software. After reviewing + * this documentation, feedback and questions can be sent to the + * <a href="http://mailing-list.libusb.info">libusb-devel mailing list</a>. + * + * This documentation assumes knowledge of how to operate USB devices from + * a software standpoint (descriptors, configurations, interfaces, endpoints, + * control/bulk/interrupt/isochronous transfers, etc). Full information + * can be found in the <a href="http://www.usb.org/developers/docs/">USB 3.0 + * Specification</a> which is available for free download. You can probably + * find less verbose introductions by searching the web. + * + * \section API Application Programming Interface (API) + * + * See the \ref libusb_api page for a complete list of the libusb functions. + * + * \section features Library features + * + * - All transfer types supported (control/bulk/interrupt/isochronous) + * - 2 transfer interfaces: + * -# Synchronous (simple) + * -# Asynchronous (more complicated, but more powerful) + * - Thread safe (although the asynchronous interface means that you + * usually won't need to thread) + * - Lightweight with lean API + * - Compatible with libusb-0.1 through the libusb-compat-0.1 translation layer + * - Hotplug support (on some platforms). See \ref libusb_hotplug. + * + * \section gettingstarted Getting Started + * + * To begin reading the API documentation, start with the Modules page which + * links to the different categories of libusb's functionality. + * + * One decision you will have to make is whether to use the synchronous + * or the asynchronous data transfer interface. The \ref libusb_io documentation + * provides some insight into this topic. + * + * Some example programs can be found in the libusb source distribution under + * the "examples" subdirectory. The libusb homepage includes a list of + * real-life project examples which use libusb. + * + * \section errorhandling Error handling + * + * libusb functions typically return 0 on success or a negative error code + * on failure. These negative error codes relate to LIBUSB_ERROR constants + * which are listed on the \ref libusb_misc "miscellaneous" documentation page. + * + * \section msglog Debug message logging + * + * libusb uses stderr for all logging. By default, logging is set to NONE, + * which means that no output will be produced. However, unless the library + * has been compiled with logging disabled, then any application calls to + * libusb_set_option(ctx, LIBUSB_OPTION_LOG_LEVEL, level), or the setting of the + * environmental variable LIBUSB_DEBUG outside of the application, can result + * in logging being produced. Your application should therefore not close + * stderr, but instead direct it to the null device if its output is + * undesirable. + * + * The libusb_set_option(ctx, LIBUSB_OPTION_LOG_LEVEL, level) function can be + * used to enable logging of certain messages. Under standard configuration, + * libusb doesn't really log much so you are advised to use this function + * to enable all error/warning/ informational messages. It will help debug + * problems with your software. + * + * The logged messages are unstructured. There is no one-to-one correspondence + * between messages being logged and success or failure return codes from + * libusb functions. There is no format to the messages, so you should not + * try to capture or parse them. They are not and will not be localized. + * These messages are not intended to being passed to your application user; + * instead, you should interpret the error codes returned from libusb functions + * and provide appropriate notification to the user. The messages are simply + * there to aid you as a programmer, and if you're confused because you're + * getting a strange error code from a libusb function, enabling message + * logging may give you a suitable explanation. + * + * The LIBUSB_DEBUG environment variable can be used to enable message logging + * at run-time. This environment variable should be set to a log level number, + * which is interpreted the same as the + * libusb_set_option(ctx, LIBUSB_OPTION_LOG_LEVEL, level) parameter. When this + * environment variable is set, the message logging verbosity level is fixed + * and libusb_set_option(ctx, LIBUSB_OPTION_LOG_LEVEL, level) effectively does + * nothing. + * + * libusb can be compiled without any logging functions, useful for embedded + * systems. In this case, libusb_set_option(ctx, LIBUSB_OPTION_LOG_LEVEL, level) + * and the LIBUSB_DEBUG environment variable have no effects. + * + * libusb can also be compiled with verbose debugging messages always. When + * the library is compiled in this way, all messages of all verbosities are + * always logged. libusb_set_option(ctx, LIBUSB_OPTION_LOG_LEVEL, level) and + * the LIBUSB_DEBUG environment variable have no effects. + * + * \section remarks Other remarks + * + * libusb does have imperfections. The \ref libusb_caveats "caveats" page attempts + * to document these. + */ + +/** + * \page libusb_caveats Caveats + * + * \section threadsafety Thread safety + * + * libusb is designed to be completely thread-safe, but as with any API it + * cannot prevent a user from sabotaging themselves, either intentionally or + * otherwise. + * + * Observe the following general guidelines: + * + * - Calls to functions that release a resource (e.g. libusb_close(), + * libusb_free_config_descriptor()) should not be called concurrently on + * the same resource. This is no different than concurrently calling free() + * on the same allocated pointer. + * - Each individual \ref libusb_transfer should be prepared by a single + * thread. In other words, no two threads should ever be concurrently + * filling out the fields of a \ref libusb_transfer. You can liken this to + * calling sprintf() with the same destination buffer from multiple threads. + * The results will likely not be what you want unless the input parameters + * are all the same, but its best to avoid this situation entirely. + * - Both the \ref libusb_transfer structure and its associated data buffer + * should not be accessed between the time the transfer is submitted and the + * time the completion callback is invoked. You can think of "ownership" of + * these things as being transferred to libusb while the transfer is active. + * - The various "setter" functions (e.g. libusb_set_log_cb(), + * libusb_set_pollfd_notifiers()) should not be called concurrently on the + * resource. Though doing so will not lead to any undefined behavior, it + * will likely produce results that the application does not expect. + * + * Rules for multiple threads and asynchronous I/O are detailed + * \ref libusb_mtasync "here". + * + * \section fork Fork considerations + * + * libusb is <em>not</em> designed to work across fork() calls. Depending on + * the platform, there may be resources in the parent process that are not + * available to the child (e.g. the hotplug monitor thread on Linux). In + * addition, since the parent and child will share libusb's internal file + * descriptors, using libusb in any way from the child could cause the parent + * process's \ref libusb_context to get into an inconsistent state. + * + * On Linux, libusb's file descriptors will be marked as CLOEXEC, which means + * that it is safe to fork() and exec() without worrying about the child + * process needing to clean up state or having access to these file descriptors. + * Other platforms may not be so forgiving, so consider yourself warned! + * + * \section devresets Device resets + * + * The libusb_reset_device() function allows you to reset a device. If your + * program has to call such a function, it should obviously be aware that + * the reset will cause device state to change (e.g. register values may be + * reset). + * + * The problem is that any other program could reset the device your program + * is working with, at any time. libusb does not offer a mechanism to inform + * you when this has happened, so if someone else resets your device it will + * not be clear to your own program why the device state has changed. + * + * Ultimately, this is a limitation of writing drivers in user space. + * Separation from the USB stack in the underlying kernel makes it difficult + * for the operating system to deliver such notifications to your program. + * The Linux kernel USB stack allows such reset notifications to be delivered + * to in-kernel USB drivers, but it is not clear how such notifications could + * be delivered to second-class drivers that live in user space. + * + * \section blockonly Blocking-only functionality + * + * The functionality listed below is only available through synchronous, + * blocking functions. There are no asynchronous/non-blocking alternatives, + * and no clear ways of implementing these. + * + * - Configuration activation (libusb_set_configuration()) + * - Interface/alternate setting activation (libusb_set_interface_alt_setting()) + * - Releasing of interfaces (libusb_release_interface()) + * - Clearing of halt/stall condition (libusb_clear_halt()) + * - Device resets (libusb_reset_device()) + * + * \section configsel Configuration selection and handling + * + * When libusb presents a device handle to an application, there is a chance + * that the corresponding device may be in unconfigured state. For devices + * with multiple configurations, there is also a chance that the configuration + * currently selected is not the one that the application wants to use. + * + * The obvious solution is to add a call to libusb_set_configuration() early + * on during your device initialization routines, but there are caveats to + * be aware of: + * -# If the device is already in the desired configuration, calling + * libusb_set_configuration() using the same configuration value will cause + * a lightweight device reset. This may not be desirable behaviour. + * -# In the case where the desired configuration is already active, libusb + * may not even be able to perform a lightweight device reset. For example, + * take my USB keyboard with fingerprint reader: I'm interested in driving + * the fingerprint reader interface through libusb, but the kernel's + * USB-HID driver will almost always have claimed the keyboard interface. + * Because the kernel has claimed an interface, it is not even possible to + * perform the lightweight device reset, so libusb_set_configuration() will + * fail. (Luckily the device in question only has a single configuration.) + * -# libusb will be unable to set a configuration if other programs or + * drivers have claimed interfaces. In particular, this means that kernel + * drivers must be detached from all the interfaces before + * libusb_set_configuration() may succeed. + * + * One solution to some of the above problems is to consider the currently + * active configuration. If the configuration we want is already active, then + * we don't have to select any configuration: +\code +cfg = -1; +libusb_get_configuration(dev, &cfg); +if (cfg != desired) + libusb_set_configuration(dev, desired); +\endcode + * + * This is probably suitable for most scenarios, but is inherently racy: + * another application or driver may change the selected configuration + * <em>after</em> the libusb_get_configuration() call. + * + * Even in cases where libusb_set_configuration() succeeds, consider that other + * applications or drivers may change configuration after your application + * calls libusb_set_configuration(). + * + * One possible way to lock your device into a specific configuration is as + * follows: + * -# Set the desired configuration (or use the logic above to realise that + * it is already in the desired configuration) + * -# Claim the interface that you wish to use + * -# Check that the currently active configuration is the one that you want + * to use. + * + * The above method works because once an interface is claimed, no application + * or driver is able to select another configuration. + * + * \section earlycomp Early transfer completion + * + * NOTE: This section is currently Linux-centric. I am not sure if any of these + * considerations apply to Darwin or other platforms. + * + * When a transfer completes early (i.e. when less data is received/sent in + * any one packet than the transfer buffer allows for) then libusb is designed + * to terminate the transfer immediately, not transferring or receiving any + * more data unless other transfers have been queued by the user. + * + * On legacy platforms, libusb is unable to do this in all situations. After + * the incomplete packet occurs, "surplus" data may be transferred. For recent + * versions of libusb, this information is kept (the data length of the + * transfer is updated) and, for device-to-host transfers, any surplus data was + * added to the buffer. Still, this is not a nice solution because it loses the + * information about the end of the short packet, and the user probably wanted + * that surplus data to arrive in the next logical transfer. + * + * \section zlp Zero length packets + * + * - libusb is able to send a packet of zero length to an endpoint simply by + * submitting a transfer of zero length. + * - The \ref libusb_transfer_flags::LIBUSB_TRANSFER_ADD_ZERO_PACKET + * "LIBUSB_TRANSFER_ADD_ZERO_PACKET" flag is currently supported on Linux, + * Darwin and Windows (WinUSB). + */ + +/** + * \page libusb_contexts Contexts + * + * It is possible that libusb may be used simultaneously from two independent + * libraries linked into the same executable. For example, if your application + * has a plugin-like system which allows the user to dynamically load a range + * of modules into your program, it is feasible that two independently + * developed modules may both use libusb. + * + * libusb is written to allow for these multiple user scenarios. The two + * "instances" of libusb will not interfere: libusb_set_option() calls + * from one user will not affect the same settings for other users, other + * users can continue using libusb after one of them calls libusb_exit(), etc. + * + * This is made possible through libusb's <em>context</em> concept. When you + * call libusb_init(), you are (optionally) given a context. You can then pass + * this context pointer back into future libusb functions. + * + * In order to keep things simple for more simplistic applications, it is + * legal to pass NULL to all functions requiring a context pointer (as long as + * you're sure no other code will attempt to use libusb from the same process). + * When you pass NULL, the default context will be used. The default context + * is created the first time a process calls libusb_init() when no other + * context is alive. Contexts are destroyed during libusb_exit(). + * + * The default context is reference-counted and can be shared. That means that + * if libusb_init(NULL) is called twice within the same process, the two + * users end up sharing the same context. The deinitialization and freeing of + * the default context will only happen when the last user calls libusb_exit(). + * In other words, the default context is created and initialized when its + * reference count goes from 0 to 1, and is deinitialized and destroyed when + * its reference count goes from 1 to 0. + * + * You may be wondering why only a subset of libusb functions require a + * context pointer in their function definition. Internally, libusb stores + * context pointers in other objects (e.g. libusb_device instances) and hence + * can infer the context from those objects. + */ + + /** + * \page libusb_api Application Programming Interface + * + * This is the complete list of libusb functions, structures and + * enumerations in alphabetical order. + * + * \section Functions + * - libusb_alloc_streams() + * - libusb_alloc_transfer() + * - libusb_attach_kernel_driver() + * - libusb_bulk_transfer() + * - libusb_cancel_transfer() + * - libusb_claim_interface() + * - libusb_clear_halt() + * - libusb_close() + * - libusb_control_transfer() + * - libusb_control_transfer_get_data() + * - libusb_control_transfer_get_setup() + * - libusb_cpu_to_le16() + * - libusb_detach_kernel_driver() + * - libusb_dev_mem_alloc() + * - libusb_dev_mem_free() + * - libusb_error_name() + * - libusb_event_handler_active() + * - libusb_event_handling_ok() + * - libusb_exit() + * - libusb_fill_bulk_stream_transfer() + * - libusb_fill_bulk_transfer() + * - libusb_fill_control_setup() + * - libusb_fill_control_transfer() + * - libusb_fill_interrupt_transfer() + * - libusb_fill_iso_transfer() + * - libusb_free_bos_descriptor() + * - libusb_free_config_descriptor() + * - libusb_free_container_id_descriptor() + * - libusb_free_device_list() + * - libusb_free_pollfds() + * - libusb_free_ss_endpoint_companion_descriptor() + * - libusb_free_ss_usb_device_capability_descriptor() + * - libusb_free_streams() + * - libusb_free_transfer() + * - libusb_free_usb_2_0_extension_descriptor() + * - libusb_get_active_config_descriptor() + * - libusb_get_bos_descriptor() + * - libusb_get_bus_number() + * - libusb_get_config_descriptor() + * - libusb_get_config_descriptor_by_value() + * - libusb_get_configuration() + * - libusb_get_container_id_descriptor() + * - libusb_get_descriptor() + * - libusb_get_device() + * - libusb_get_device_address() + * - libusb_get_device_descriptor() + * - libusb_get_device_list() + * - libusb_get_device_speed() + * - libusb_get_iso_packet_buffer() + * - libusb_get_iso_packet_buffer_simple() + * - libusb_get_max_iso_packet_size() + * - libusb_get_max_packet_size() + * - libusb_get_next_timeout() + * - libusb_get_parent() + * - libusb_get_pollfds() + * - libusb_get_port_number() + * - libusb_get_port_numbers() + * - libusb_get_port_path() + * - libusb_get_ss_endpoint_companion_descriptor() + * - libusb_get_ss_usb_device_capability_descriptor() + * - libusb_get_string_descriptor() + * - libusb_get_string_descriptor_ascii() + * - libusb_get_usb_2_0_extension_descriptor() + * - libusb_get_version() + * - libusb_handle_events() + * - libusb_handle_events_completed() + * - libusb_handle_events_locked() + * - libusb_handle_events_timeout() + * - libusb_handle_events_timeout_completed() + * - libusb_has_capability() + * - libusb_hotplug_deregister_callback() + * - libusb_hotplug_register_callback() + * - libusb_init() + * - libusb_interrupt_event_handler() + * - libusb_interrupt_transfer() + * - libusb_kernel_driver_active() + * - libusb_lock_events() + * - libusb_lock_event_waiters() + * - libusb_open() + * - libusb_open_device_with_vid_pid() + * - libusb_pollfds_handle_timeouts() + * - libusb_ref_device() + * - libusb_release_interface() + * - libusb_reset_device() + * - libusb_set_auto_detach_kernel_driver() + * - libusb_set_configuration() + * - libusb_set_debug() + * - libusb_set_log_cb() + * - libusb_set_interface_alt_setting() + * - libusb_set_iso_packet_lengths() + * - libusb_set_option() + * - libusb_setlocale() + * - libusb_set_pollfd_notifiers() + * - libusb_strerror() + * - libusb_submit_transfer() + * - libusb_transfer_get_stream_id() + * - libusb_transfer_set_stream_id() + * - libusb_try_lock_events() + * - libusb_unlock_events() + * - libusb_unlock_event_waiters() + * - libusb_unref_device() + * - libusb_wait_for_event() + * - libusb_wrap_sys_device() + * + * \section Structures + * - libusb_bos_descriptor + * - libusb_bos_dev_capability_descriptor + * - libusb_config_descriptor + * - libusb_container_id_descriptor + * - \ref libusb_context + * - libusb_control_setup + * - \ref libusb_device + * - libusb_device_descriptor + * - \ref libusb_device_handle + * - libusb_endpoint_descriptor + * - libusb_interface + * - libusb_interface_descriptor + * - libusb_iso_packet_descriptor + * - libusb_pollfd + * - libusb_ss_endpoint_companion_descriptor + * - libusb_ss_usb_device_capability_descriptor + * - libusb_transfer + * - libusb_usb_2_0_extension_descriptor + * - libusb_version + * + * \section Enums + * - \ref libusb_bos_type + * - \ref libusb_capability + * - \ref libusb_class_code + * - \ref libusb_descriptor_type + * - \ref libusb_endpoint_direction + * - \ref libusb_endpoint_transfer_type + * - \ref libusb_error + * - \ref libusb_iso_sync_type + * - \ref libusb_iso_usage_type + * - \ref libusb_log_level + * - \ref libusb_option + * - \ref libusb_request_recipient + * - \ref libusb_request_type + * - \ref libusb_speed + * - \ref libusb_ss_usb_device_capability_attributes + * - \ref libusb_standard_request + * - \ref libusb_supported_speed + * - \ref libusb_transfer_flags + * - \ref libusb_transfer_status + * - \ref libusb_transfer_type + * - \ref libusb_usb_2_0_extension_attributes + */ + +/** + * @defgroup libusb_lib Library initialization/deinitialization + * This page details how to initialize and deinitialize libusb. Initialization + * must be performed before using any libusb functionality, and similarly you + * must not call any libusb functions after deinitialization. + */ + +/** + * @defgroup libusb_dev Device handling and enumeration + * The functionality documented below is designed to help with the following + * operations: + * - Enumerating the USB devices currently attached to the system + * - Choosing a device to operate from your software + * - Opening and closing the chosen device + * + * \section nutshell In a nutshell... + * + * The description below really makes things sound more complicated than they + * actually are. The following sequence of function calls will be suitable + * for almost all scenarios and does not require you to have such a deep + * understanding of the resource management issues: + * \code +// discover devices +libusb_device **list; +libusb_device *found = NULL; +ssize_t cnt = libusb_get_device_list(NULL, &list); +ssize_t i = 0; +int err = 0; +if (cnt < 0) + error(); + +for (i = 0; i < cnt; i++) { + libusb_device *device = list[i]; + if (is_interesting(device)) { + found = device; + break; + } +} + +if (found) { + libusb_device_handle *handle; + + err = libusb_open(found, &handle); + if (err) + error(); + // etc +} + +libusb_free_device_list(list, 1); +\endcode + * + * The two important points: + * - You asked libusb_free_device_list() to unreference the devices (2nd + * parameter) + * - You opened the device before freeing the list and unreferencing the + * devices + * + * If you ended up with a handle, you can now proceed to perform I/O on the + * device. + * + * \section devshandles Devices and device handles + * libusb has a concept of a USB device, represented by the + * \ref libusb_device opaque type. A device represents a USB device that + * is currently or was previously connected to the system. Using a reference + * to a device, you can determine certain information about the device (e.g. + * you can read the descriptor data). + * + * The libusb_get_device_list() function can be used to obtain a list of + * devices currently connected to the system. This is known as device + * discovery. Devices can also be discovered with the hotplug mechanism, + * whereby a callback function registered with libusb_hotplug_register_callback() + * will be called when a device of interest is connected or disconnected. + * + * Just because you have a reference to a device does not mean it is + * necessarily usable. The device may have been unplugged, you may not have + * permission to operate such device, or another program or driver may be + * using the device. + * + * When you've found a device that you'd like to operate, you must ask + * libusb to open the device using the libusb_open() function. Assuming + * success, libusb then returns you a <em>device handle</em> + * (a \ref libusb_device_handle pointer). All "real" I/O operations then + * operate on the handle rather than the original device pointer. + * + * \section devref Device discovery and reference counting + * + * Device discovery (i.e. calling libusb_get_device_list()) returns a + * freshly-allocated list of devices. The list itself must be freed when + * you are done with it. libusb also needs to know when it is OK to free + * the contents of the list - the devices themselves. + * + * To handle these issues, libusb provides you with two separate items: + * - A function to free the list itself + * - A reference counting system for the devices inside + * + * New devices presented by the libusb_get_device_list() function all have a + * reference count of 1. You can increase and decrease reference count using + * libusb_ref_device() and libusb_unref_device(). A device is destroyed when + * its reference count reaches 0. + * + * With the above information in mind, the process of opening a device can + * be viewed as follows: + * -# Discover devices using libusb_get_device_list() or libusb_hotplug_register_callback(). + * -# Choose the device that you want to operate, and call libusb_open(). + * -# Unref all devices in the discovered device list. + * -# Free the discovered device list. + * + * The order is important - you must not unreference the device before + * attempting to open it, because unreferencing it may destroy the device. + * + * For convenience, the libusb_free_device_list() function includes a + * parameter to optionally unreference all the devices in the list before + * freeing the list itself. This combines steps 3 and 4 above. + * + * As an implementation detail, libusb_open() actually adds a reference to + * the device in question. This is because the device remains available + * through the handle via libusb_get_device(). The reference is deleted during + * libusb_close(). + */ + +/** @defgroup libusb_misc Miscellaneous */ + +/* we traverse usbfs without knowing how many devices we are going to find. + * so we create this discovered_devs model which is similar to a linked-list + * which grows when required. it can be freed once discovery has completed, + * eliminating the need for a list node in the libusb_device structure + * itself. */ +#define DISCOVERED_DEVICES_SIZE_STEP 16 + +static struct discovered_devs *discovered_devs_alloc(void) +{ + struct discovered_devs *ret = + malloc(sizeof(*ret) + (sizeof(void *) * DISCOVERED_DEVICES_SIZE_STEP)); + + if (ret) { + ret->len = 0; + ret->capacity = DISCOVERED_DEVICES_SIZE_STEP; + } + return ret; +} + +static void discovered_devs_free(struct discovered_devs *discdevs) +{ + size_t i; + + for (i = 0; i < discdevs->len; i++) + libusb_unref_device(discdevs->devices[i]); + + free(discdevs); +} + +/* append a device to the discovered devices collection. may realloc itself, + * returning new discdevs. returns NULL on realloc failure. */ +struct discovered_devs *discovered_devs_append( + struct discovered_devs *discdevs, struct libusb_device *dev) +{ + size_t len = discdevs->len; + size_t capacity; + struct discovered_devs *new_discdevs; + + /* if there is space, just append the device */ + if (len < discdevs->capacity) { + discdevs->devices[len] = libusb_ref_device(dev); + discdevs->len++; + return discdevs; + } + + /* exceeded capacity, need to grow */ + usbi_dbg(DEVICE_CTX(dev), "need to increase capacity"); + capacity = discdevs->capacity + DISCOVERED_DEVICES_SIZE_STEP; + /* can't use usbi_reallocf here because in failure cases it would + * free the existing discdevs without unreferencing its devices. */ + new_discdevs = realloc(discdevs, + sizeof(*discdevs) + (sizeof(void *) * capacity)); + if (!new_discdevs) { + discovered_devs_free(discdevs); + return NULL; + } + + discdevs = new_discdevs; + discdevs->capacity = capacity; + discdevs->devices[len] = libusb_ref_device(dev); + discdevs->len++; + + return discdevs; +} + +/* Allocate a new device with a specific session ID. The returned device has + * a reference count of 1. */ +struct libusb_device *usbi_alloc_device(struct libusb_context *ctx, + unsigned long session_id) +{ + size_t priv_size = usbi_backend.device_priv_size; + struct libusb_device *dev = calloc(1, PTR_ALIGN(sizeof(*dev)) + priv_size); + + if (!dev) + return NULL; + + usbi_atomic_store(&dev->refcnt, 1); + + dev->ctx = ctx; + dev->session_data = session_id; + dev->speed = LIBUSB_SPEED_UNKNOWN; + + if (!libusb_has_capability(LIBUSB_CAP_HAS_HOTPLUG)) + usbi_connect_device(dev); + + return dev; +} + +void usbi_connect_device(struct libusb_device *dev) +{ + struct libusb_context *ctx = DEVICE_CTX(dev); + + usbi_atomic_store(&dev->attached, 1); + + usbi_mutex_lock(&dev->ctx->usb_devs_lock); + list_add(&dev->list, &dev->ctx->usb_devs); + usbi_mutex_unlock(&dev->ctx->usb_devs_lock); + + usbi_hotplug_notification(ctx, dev, LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED); +} + +void usbi_disconnect_device(struct libusb_device *dev) +{ + struct libusb_context *ctx = DEVICE_CTX(dev); + + usbi_atomic_store(&dev->attached, 0); + + usbi_mutex_lock(&ctx->usb_devs_lock); + list_del(&dev->list); + usbi_mutex_unlock(&ctx->usb_devs_lock); + + usbi_hotplug_notification(ctx, dev, LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT); +} + +/* Perform some final sanity checks on a newly discovered device. If this + * function fails (negative return code), the device should not be added + * to the discovered device list. */ +int usbi_sanitize_device(struct libusb_device *dev) +{ + uint8_t num_configurations; + + if (dev->device_descriptor.bLength != LIBUSB_DT_DEVICE_SIZE || + dev->device_descriptor.bDescriptorType != LIBUSB_DT_DEVICE) { + usbi_err(DEVICE_CTX(dev), "invalid device descriptor"); + return LIBUSB_ERROR_IO; + } + + num_configurations = dev->device_descriptor.bNumConfigurations; + if (num_configurations > USB_MAXCONFIG) { + usbi_err(DEVICE_CTX(dev), "too many configurations"); + return LIBUSB_ERROR_IO; + } else if (0 == num_configurations) { + usbi_dbg(DEVICE_CTX(dev), "zero configurations, maybe an unauthorized device"); + } + + return 0; +} + +/* Examine libusb's internal list of known devices, looking for one with + * a specific session ID. Returns the matching device if it was found, and + * NULL otherwise. */ +struct libusb_device *usbi_get_device_by_session_id(struct libusb_context *ctx, + unsigned long session_id) +{ + struct libusb_device *dev; + struct libusb_device *ret = NULL; + + usbi_mutex_lock(&ctx->usb_devs_lock); + for_each_device(ctx, dev) { + if (dev->session_data == session_id) { + ret = libusb_ref_device(dev); + break; + } + } + usbi_mutex_unlock(&ctx->usb_devs_lock); + + return ret; +} + +/** @ingroup libusb_dev + * Returns a list of USB devices currently attached to the system. This is + * your entry point into finding a USB device to operate. + * + * You are expected to unreference all the devices when you are done with + * them, and then free the list with libusb_free_device_list(). Note that + * libusb_free_device_list() can unref all the devices for you. Be careful + * not to unreference a device you are about to open until after you have + * opened it. + * + * This return value of this function indicates the number of devices in + * the resultant list. The list is actually one element larger, as it is + * NULL-terminated. + * + * \param ctx the context to operate on, or NULL for the default context + * \param list output location for a list of devices. Must be later freed with + * libusb_free_device_list(). + * \returns the number of devices in the outputted list, or any + * \ref libusb_error according to errors encountered by the backend. + */ +ssize_t API_EXPORTED libusb_get_device_list(libusb_context *ctx, + libusb_device ***list) +{ + struct discovered_devs *discdevs = discovered_devs_alloc(); + struct libusb_device **ret; + int r = 0; + ssize_t i, len; + + usbi_dbg(ctx, " "); + + if (!discdevs) + return LIBUSB_ERROR_NO_MEM; + + ctx = usbi_get_context(ctx); + + if (libusb_has_capability(LIBUSB_CAP_HAS_HOTPLUG)) { + /* backend provides hotplug support */ + struct libusb_device *dev; + + if (usbi_backend.hotplug_poll) + usbi_backend.hotplug_poll(); + + usbi_mutex_lock(&ctx->usb_devs_lock); + for_each_device(ctx, dev) { + discdevs = discovered_devs_append(discdevs, dev); + + if (!discdevs) { + r = LIBUSB_ERROR_NO_MEM; + break; + } + } + usbi_mutex_unlock(&ctx->usb_devs_lock); + } else { + /* backend does not provide hotplug support */ + r = usbi_backend.get_device_list(ctx, &discdevs); + } + + if (r < 0) { + len = r; + goto out; + } + + /* convert discovered_devs into a list */ + len = (ssize_t)discdevs->len; + ret = calloc((size_t)len + 1, sizeof(struct libusb_device *)); + if (!ret) { + len = LIBUSB_ERROR_NO_MEM; + goto out; + } + + ret[len] = NULL; + for (i = 0; i < len; i++) { + struct libusb_device *dev = discdevs->devices[i]; + ret[i] = libusb_ref_device(dev); + } + *list = ret; + +out: + if (discdevs) + discovered_devs_free(discdevs); + return len; +} + +/** \ingroup libusb_dev + * Frees a list of devices previously discovered using + * libusb_get_device_list(). If the unref_devices parameter is set, the + * reference count of each device in the list is decremented by 1. + * \param list the list to free + * \param unref_devices whether to unref the devices in the list + */ +void API_EXPORTED libusb_free_device_list(libusb_device **list, + int unref_devices) +{ + if (!list) + return; + + if (unref_devices) { + int i = 0; + struct libusb_device *dev; + + while ((dev = list[i++]) != NULL) + libusb_unref_device(dev); + } + free(list); +} + +/** \ingroup libusb_dev + * Get the number of the bus that a device is connected to. + * \param dev a device + * \returns the bus number + */ +uint8_t API_EXPORTED libusb_get_bus_number(libusb_device *dev) +{ + return dev->bus_number; +} + +/** \ingroup libusb_dev + * Get the number of the port that a device is connected to. + * Unless the OS does something funky, or you are hot-plugging USB extension cards, + * the port number returned by this call is usually guaranteed to be uniquely tied + * to a physical port, meaning that different devices plugged on the same physical + * port should return the same port number. + * + * But outside of this, there is no guarantee that the port number returned by this + * call will remain the same, or even match the order in which ports have been + * numbered by the HUB/HCD manufacturer. + * + * \param dev a device + * \returns the port number (0 if not available) + */ +uint8_t API_EXPORTED libusb_get_port_number(libusb_device *dev) +{ + return dev->port_number; +} + +/** \ingroup libusb_dev + * Get the list of all port numbers from root for the specified device + * + * Since version 1.0.16, \ref LIBUSB_API_VERSION >= 0x01000102 + * \param dev a device + * \param port_numbers the array that should contain the port numbers + * \param port_numbers_len the maximum length of the array. As per the USB 3.0 + * specs, the current maximum limit for the depth is 7. + * \returns the number of elements filled + * \returns LIBUSB_ERROR_OVERFLOW if the array is too small + */ +int API_EXPORTED libusb_get_port_numbers(libusb_device *dev, + uint8_t *port_numbers, int port_numbers_len) +{ + int i = port_numbers_len; + struct libusb_context *ctx = DEVICE_CTX(dev); + + if (port_numbers_len <= 0) + return LIBUSB_ERROR_INVALID_PARAM; + + // HCDs can be listed as devices with port #0 + while((dev) && (dev->port_number != 0)) { + if (--i < 0) { + usbi_warn(ctx, "port numbers array is too small"); + return LIBUSB_ERROR_OVERFLOW; + } + port_numbers[i] = dev->port_number; + dev = dev->parent_dev; + } + if (i < port_numbers_len) + memmove(port_numbers, &port_numbers[i], port_numbers_len - i); + return port_numbers_len - i; +} + +/** \ingroup libusb_dev + * \deprecated Please use \ref libusb_get_port_numbers() instead. + */ +int API_EXPORTED libusb_get_port_path(libusb_context *ctx, libusb_device *dev, + uint8_t *port_numbers, uint8_t port_numbers_len) +{ + UNUSED(ctx); + + return libusb_get_port_numbers(dev, port_numbers, port_numbers_len); +} + +/** \ingroup libusb_dev + * Get the the parent from the specified device. + * \param dev a device + * \returns the device parent or NULL if not available + * You should issue a \ref libusb_get_device_list() before calling this + * function and make sure that you only access the parent before issuing + * \ref libusb_free_device_list(). The reason is that libusb currently does + * not maintain a permanent list of device instances, and therefore can + * only guarantee that parents are fully instantiated within a + * libusb_get_device_list() - libusb_free_device_list() block. + */ +DEFAULT_VISIBILITY +libusb_device * LIBUSB_CALL libusb_get_parent(libusb_device *dev) +{ + return dev->parent_dev; +} + +/** \ingroup libusb_dev + * Get the address of the device on the bus it is connected to. + * \param dev a device + * \returns the device address + */ +uint8_t API_EXPORTED libusb_get_device_address(libusb_device *dev) +{ + return dev->device_address; +} + +/** \ingroup libusb_dev + * Get the negotiated connection speed for a device. + * \param dev a device + * \returns a \ref libusb_speed code, where LIBUSB_SPEED_UNKNOWN means that + * the OS doesn't know or doesn't support returning the negotiated speed. + */ +int API_EXPORTED libusb_get_device_speed(libusb_device *dev) +{ + return dev->speed; +} + +static const struct libusb_endpoint_descriptor *find_endpoint( + struct libusb_config_descriptor *config, unsigned char endpoint) +{ + int iface_idx; + for (iface_idx = 0; iface_idx < config->bNumInterfaces; iface_idx++) { + const struct libusb_interface *iface = &config->interface[iface_idx]; + int altsetting_idx; + + for (altsetting_idx = 0; altsetting_idx < iface->num_altsetting; + altsetting_idx++) { + const struct libusb_interface_descriptor *altsetting + = &iface->altsetting[altsetting_idx]; + int ep_idx; + + for (ep_idx = 0; ep_idx < altsetting->bNumEndpoints; ep_idx++) { + const struct libusb_endpoint_descriptor *ep = + &altsetting->endpoint[ep_idx]; + if (ep->bEndpointAddress == endpoint) + return ep; + } + } + } + return NULL; +} + +/** \ingroup libusb_dev + * Convenience function to retrieve the wMaxPacketSize value for a particular + * endpoint in the active device configuration. + * + * This function was originally intended to be of assistance when setting up + * isochronous transfers, but a design mistake resulted in this function + * instead. It simply returns the wMaxPacketSize value without considering + * its contents. If you're dealing with isochronous transfers, you probably + * want libusb_get_max_iso_packet_size() instead. + * + * \param dev a device + * \param endpoint address of the endpoint in question + * \returns the wMaxPacketSize value + * \returns LIBUSB_ERROR_NOT_FOUND if the endpoint does not exist + * \returns LIBUSB_ERROR_OTHER on other failure + */ +int API_EXPORTED libusb_get_max_packet_size(libusb_device *dev, + unsigned char endpoint) +{ + struct libusb_config_descriptor *config; + const struct libusb_endpoint_descriptor *ep; + int r; + + r = libusb_get_active_config_descriptor(dev, &config); + if (r < 0) { + usbi_err(DEVICE_CTX(dev), + "could not retrieve active config descriptor"); + return LIBUSB_ERROR_OTHER; + } + + ep = find_endpoint(config, endpoint); + if (!ep) { + r = LIBUSB_ERROR_NOT_FOUND; + goto out; + } + + r = ep->wMaxPacketSize; + +out: + libusb_free_config_descriptor(config); + return r; +} + +/** \ingroup libusb_dev + * Calculate the maximum packet size which a specific endpoint is capable is + * sending or receiving in the duration of 1 microframe + * + * Only the active configuration is examined. The calculation is based on the + * wMaxPacketSize field in the endpoint descriptor as described in section + * 9.6.6 in the USB 2.0 specifications. + * + * If acting on an isochronous or interrupt endpoint, this function will + * multiply the value found in bits 0:10 by the number of transactions per + * microframe (determined by bits 11:12). Otherwise, this function just + * returns the numeric value found in bits 0:10. For USB 3.0 device, it + * will attempts to retrieve the Endpoint Companion Descriptor to return + * wBytesPerInterval. + * + * This function is useful for setting up isochronous transfers, for example + * you might pass the return value from this function to + * libusb_set_iso_packet_lengths() in order to set the length field of every + * isochronous packet in a transfer. + * + * Since v1.0.3. + * + * \param dev a device + * \param endpoint address of the endpoint in question + * \returns the maximum packet size which can be sent/received on this endpoint + * \returns LIBUSB_ERROR_NOT_FOUND if the endpoint does not exist + * \returns LIBUSB_ERROR_OTHER on other failure + */ +int API_EXPORTED libusb_get_max_iso_packet_size(libusb_device *dev, + unsigned char endpoint) +{ + struct libusb_config_descriptor *config; + const struct libusb_endpoint_descriptor *ep; + struct libusb_ss_endpoint_companion_descriptor *ss_ep_cmp; + enum libusb_endpoint_transfer_type ep_type; + uint16_t val; + int r; + int speed; + + r = libusb_get_active_config_descriptor(dev, &config); + if (r < 0) { + usbi_err(DEVICE_CTX(dev), + "could not retrieve active config descriptor"); + return LIBUSB_ERROR_OTHER; + } + + ep = find_endpoint(config, endpoint); + if (!ep) { + r = LIBUSB_ERROR_NOT_FOUND; + goto out; + } + + speed = libusb_get_device_speed(dev); + if (speed >= LIBUSB_SPEED_SUPER) { + r = libusb_get_ss_endpoint_companion_descriptor(dev->ctx, ep, &ss_ep_cmp); + if (r == LIBUSB_SUCCESS) { + r = ss_ep_cmp->wBytesPerInterval; + libusb_free_ss_endpoint_companion_descriptor(ss_ep_cmp); + } + } + + /* If the device isn't a SuperSpeed device or retrieving the SS endpoint didn't worked. */ + if (speed < LIBUSB_SPEED_SUPER || r < 0) { + val = ep->wMaxPacketSize; + ep_type = (enum libusb_endpoint_transfer_type) (ep->bmAttributes & 0x3); + + r = val & 0x07ff; + if (ep_type == LIBUSB_ENDPOINT_TRANSFER_TYPE_ISOCHRONOUS + || ep_type == LIBUSB_ENDPOINT_TRANSFER_TYPE_INTERRUPT) + r *= (1 + ((val >> 11) & 3)); + } + +out: + libusb_free_config_descriptor(config); + return r; +} + +/** \ingroup libusb_dev + * Increment the reference count of a device. + * \param dev the device to reference + * \returns the same device + */ +DEFAULT_VISIBILITY +libusb_device * LIBUSB_CALL libusb_ref_device(libusb_device *dev) +{ + long refcnt; + + refcnt = usbi_atomic_inc(&dev->refcnt); + assert(refcnt >= 2); + + return dev; +} + +/** \ingroup libusb_dev + * Decrement the reference count of a device. If the decrement operation + * causes the reference count to reach zero, the device shall be destroyed. + * \param dev the device to unreference + */ +void API_EXPORTED libusb_unref_device(libusb_device *dev) +{ + long refcnt; + + if (!dev) + return; + + refcnt = usbi_atomic_dec(&dev->refcnt); + assert(refcnt >= 0); + + if (refcnt == 0) { + usbi_dbg(DEVICE_CTX(dev), "destroy device %d.%d", dev->bus_number, dev->device_address); + + libusb_unref_device(dev->parent_dev); + + if (usbi_backend.destroy_device) + usbi_backend.destroy_device(dev); + + if (!libusb_has_capability(LIBUSB_CAP_HAS_HOTPLUG)) { + /* backend does not support hotplug */ + usbi_disconnect_device(dev); + } + + free(dev); + } +} + +/** \ingroup libusb_dev + * Wrap a platform-specific system device handle and obtain a libusb device + * handle for the underlying device. The handle allows you to use libusb to + * perform I/O on the device in question. + * + * Call libusb_set_option(NULL, LIBUSB_OPTION_NO_DEVICE_DISCOVERY) before + * libusb_init() if you want to skip enumeration of USB devices. In particular, + * this might be needed on Android if you don't have authority to access USB + * devices in general. + * + * On Linux, the system device handle must be a valid file descriptor opened + * on the device node. + * + * The system device handle must remain open until libusb_close() is called. + * The system device handle will not be closed by libusb_close(). + * + * Internally, this function creates a temporary device and makes it + * available to you through libusb_get_device(). This device is destroyed + * during libusb_close(). The device shall not be opened through libusb_open(). + * + * This is a non-blocking function; no requests are sent over the bus. + * + * Since version 1.0.23, \ref LIBUSB_API_VERSION >= 0x01000107 + * + * \param ctx the context to operate on, or NULL for the default context + * \param sys_dev the platform-specific system device handle + * \param dev_handle output location for the returned device handle pointer. Only + * populated when the return code is 0. + * \returns 0 on success + * \returns LIBUSB_ERROR_NO_MEM on memory allocation failure + * \returns LIBUSB_ERROR_ACCESS if the user has insufficient permissions + * \returns LIBUSB_ERROR_NOT_SUPPORTED if the operation is not supported on this + * platform + * \returns another LIBUSB_ERROR code on other failure + */ +int API_EXPORTED libusb_wrap_sys_device(libusb_context *ctx, intptr_t sys_dev, + libusb_device_handle **dev_handle) +{ + struct libusb_device_handle *_dev_handle; + size_t priv_size = usbi_backend.device_handle_priv_size; + int r; + + usbi_dbg(ctx, "wrap_sys_device 0x%" PRIxPTR, (uintptr_t)sys_dev); + + ctx = usbi_get_context(ctx); + + if (!usbi_backend.wrap_sys_device) + return LIBUSB_ERROR_NOT_SUPPORTED; + + _dev_handle = calloc(1, PTR_ALIGN(sizeof(*_dev_handle)) + priv_size); + if (!_dev_handle) + return LIBUSB_ERROR_NO_MEM; + + usbi_mutex_init(&_dev_handle->lock); + + r = usbi_backend.wrap_sys_device(ctx, _dev_handle, sys_dev); + if (r < 0) { + usbi_dbg(ctx, "wrap_sys_device 0x%" PRIxPTR " returns %d", (uintptr_t)sys_dev, r); + usbi_mutex_destroy(&_dev_handle->lock); + free(_dev_handle); + return r; + } + + usbi_mutex_lock(&ctx->open_devs_lock); + list_add(&_dev_handle->list, &ctx->open_devs); + usbi_mutex_unlock(&ctx->open_devs_lock); + *dev_handle = _dev_handle; + + return 0; +} + +/** \ingroup libusb_dev + * Open a device and obtain a device handle. A handle allows you to perform + * I/O on the device in question. + * + * Internally, this function adds a reference to the device and makes it + * available to you through libusb_get_device(). This reference is removed + * during libusb_close(). + * + * This is a non-blocking function; no requests are sent over the bus. + * + * \param dev the device to open + * \param dev_handle output location for the returned device handle pointer. Only + * populated when the return code is 0. + * \returns 0 on success + * \returns LIBUSB_ERROR_NO_MEM on memory allocation failure + * \returns LIBUSB_ERROR_ACCESS if the user has insufficient permissions + * \returns LIBUSB_ERROR_NO_DEVICE if the device has been disconnected + * \returns another LIBUSB_ERROR code on other failure + */ +int API_EXPORTED libusb_open(libusb_device *dev, + libusb_device_handle **dev_handle) +{ + struct libusb_context *ctx = DEVICE_CTX(dev); + struct libusb_device_handle *_dev_handle; + size_t priv_size = usbi_backend.device_handle_priv_size; + int r; + + usbi_dbg(DEVICE_CTX(dev), "open %d.%d", dev->bus_number, dev->device_address); + + if (!usbi_atomic_load(&dev->attached)) + return LIBUSB_ERROR_NO_DEVICE; + + _dev_handle = calloc(1, PTR_ALIGN(sizeof(*_dev_handle)) + priv_size); + if (!_dev_handle) + return LIBUSB_ERROR_NO_MEM; + + usbi_mutex_init(&_dev_handle->lock); + + _dev_handle->dev = libusb_ref_device(dev); + + r = usbi_backend.open(_dev_handle); + if (r < 0) { + usbi_dbg(DEVICE_CTX(dev), "open %d.%d returns %d", dev->bus_number, dev->device_address, r); + libusb_unref_device(dev); + usbi_mutex_destroy(&_dev_handle->lock); + free(_dev_handle); + return r; + } + + usbi_mutex_lock(&ctx->open_devs_lock); + list_add(&_dev_handle->list, &ctx->open_devs); + usbi_mutex_unlock(&ctx->open_devs_lock); + *dev_handle = _dev_handle; + + return 0; +} + +/** \ingroup libusb_dev + * Convenience function for finding a device with a particular + * <tt>idVendor</tt>/<tt>idProduct</tt> combination. This function is intended + * for those scenarios where you are using libusb to knock up a quick test + * application - it allows you to avoid calling libusb_get_device_list() and + * worrying about traversing/freeing the list. + * + * This function has limitations and is hence not intended for use in real + * applications: if multiple devices have the same IDs it will only + * give you the first one, etc. + * + * \param ctx the context to operate on, or NULL for the default context + * \param vendor_id the idVendor value to search for + * \param product_id the idProduct value to search for + * \returns a device handle for the first found device, or NULL on error + * or if the device could not be found. */ +DEFAULT_VISIBILITY +libusb_device_handle * LIBUSB_CALL libusb_open_device_with_vid_pid( + libusb_context *ctx, uint16_t vendor_id, uint16_t product_id) +{ + struct libusb_device **devs; + struct libusb_device *found = NULL; + struct libusb_device *dev; + struct libusb_device_handle *dev_handle = NULL; + size_t i = 0; + int r; + + if (libusb_get_device_list(ctx, &devs) < 0) + return NULL; + + while ((dev = devs[i++]) != NULL) { + struct libusb_device_descriptor desc; + r = libusb_get_device_descriptor(dev, &desc); + if (r < 0) + goto out; + if (desc.idVendor == vendor_id && desc.idProduct == product_id) { + found = dev; + break; + } + } + + if (found) { + r = libusb_open(found, &dev_handle); + if (r < 0) + dev_handle = NULL; + } + +out: + libusb_free_device_list(devs, 1); + return dev_handle; +} + +static void do_close(struct libusb_context *ctx, + struct libusb_device_handle *dev_handle) +{ + struct usbi_transfer *itransfer; + struct usbi_transfer *tmp; + + /* remove any transfers in flight that are for this device */ + usbi_mutex_lock(&ctx->flying_transfers_lock); + + /* safe iteration because transfers may be being deleted */ + for_each_transfer_safe(ctx, itransfer, tmp) { + struct libusb_transfer *transfer = + USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer); + + if (transfer->dev_handle != dev_handle) + continue; + + usbi_mutex_lock(&itransfer->lock); + if (!(itransfer->state_flags & USBI_TRANSFER_DEVICE_DISAPPEARED)) { + usbi_err(ctx, "Device handle closed while transfer was still being processed, but the device is still connected as far as we know"); + + if (itransfer->state_flags & USBI_TRANSFER_CANCELLING) + usbi_warn(ctx, "A cancellation for an in-flight transfer hasn't completed but closing the device handle"); + else + usbi_err(ctx, "A cancellation hasn't even been scheduled on the transfer for which the device is closing"); + } + usbi_mutex_unlock(&itransfer->lock); + + /* remove from the list of in-flight transfers and make sure + * we don't accidentally use the device handle in the future + * (or that such accesses will be easily caught and identified as a crash) + */ + list_del(&itransfer->list); + transfer->dev_handle = NULL; + + /* it is up to the user to free up the actual transfer struct. this is + * just making sure that we don't attempt to process the transfer after + * the device handle is invalid + */ + usbi_dbg(ctx, "Removed transfer %p from the in-flight list because device handle %p closed", + transfer, dev_handle); + } + usbi_mutex_unlock(&ctx->flying_transfers_lock); + + usbi_mutex_lock(&ctx->open_devs_lock); + list_del(&dev_handle->list); + usbi_mutex_unlock(&ctx->open_devs_lock); + + usbi_backend.close(dev_handle); + libusb_unref_device(dev_handle->dev); + usbi_mutex_destroy(&dev_handle->lock); + free(dev_handle); +} + +/** \ingroup libusb_dev + * Close a device handle. Should be called on all open handles before your + * application exits. + * + * Internally, this function destroys the reference that was added by + * libusb_open() on the given device. + * + * This is a non-blocking function; no requests are sent over the bus. + * + * \param dev_handle the device handle to close + */ +void API_EXPORTED libusb_close(libusb_device_handle *dev_handle) +{ + struct libusb_context *ctx; + unsigned int event_flags; + int handling_events; + + if (!dev_handle) + return; + ctx = HANDLE_CTX(dev_handle); + usbi_dbg(ctx, " "); + + handling_events = usbi_handling_events(ctx); + + /* Similarly to libusb_open(), we want to interrupt all event handlers + * at this point. More importantly, we want to perform the actual close of + * the device while holding the event handling lock (preventing any other + * thread from doing event handling) because we will be removing a file + * descriptor from the polling loop. If this is being called by the current + * event handler, we can bypass the interruption code because we already + * hold the event handling lock. */ + + if (!handling_events) { + /* Record that we are closing a device. + * Only signal an event if there are no prior pending events. */ + usbi_mutex_lock(&ctx->event_data_lock); + event_flags = ctx->event_flags; + if (!ctx->device_close++) + ctx->event_flags |= USBI_EVENT_DEVICE_CLOSE; + if (!event_flags) + usbi_signal_event(&ctx->event); + usbi_mutex_unlock(&ctx->event_data_lock); + + /* take event handling lock */ + libusb_lock_events(ctx); + } + + /* Close the device */ + do_close(ctx, dev_handle); + + if (!handling_events) { + /* We're done with closing this device. + * Clear the event pipe if there are no further pending events. */ + usbi_mutex_lock(&ctx->event_data_lock); + if (!--ctx->device_close) + ctx->event_flags &= ~USBI_EVENT_DEVICE_CLOSE; + if (!ctx->event_flags) + usbi_clear_event(&ctx->event); + usbi_mutex_unlock(&ctx->event_data_lock); + + /* Release event handling lock and wake up event waiters */ + libusb_unlock_events(ctx); + } +} + +/** \ingroup libusb_dev + * Get the underlying device for a device handle. This function does not modify + * the reference count of the returned device, so do not feel compelled to + * unreference it when you are done. + * \param dev_handle a device handle + * \returns the underlying device + */ +DEFAULT_VISIBILITY +libusb_device * LIBUSB_CALL libusb_get_device(libusb_device_handle *dev_handle) +{ + return dev_handle->dev; +} + +/** \ingroup libusb_dev + * Determine the bConfigurationValue of the currently active configuration. + * + * You could formulate your own control request to obtain this information, + * but this function has the advantage that it may be able to retrieve the + * information from operating system caches (no I/O involved). + * + * If the OS does not cache this information, then this function will block + * while a control transfer is submitted to retrieve the information. + * + * This function will return a value of 0 in the <tt>config</tt> output + * parameter if the device is in unconfigured state. + * + * \param dev_handle a device handle + * \param config output location for the bConfigurationValue of the active + * configuration (only valid for return code 0) + * \returns 0 on success + * \returns LIBUSB_ERROR_NO_DEVICE if the device has been disconnected + * \returns another LIBUSB_ERROR code on other failure + */ +int API_EXPORTED libusb_get_configuration(libusb_device_handle *dev_handle, + int *config) +{ + int r = LIBUSB_ERROR_NOT_SUPPORTED; + uint8_t tmp = 0; + struct libusb_context *ctx = HANDLE_CTX(dev_handle); + + usbi_dbg(ctx, " "); + if (usbi_backend.get_configuration) + r = usbi_backend.get_configuration(dev_handle, &tmp); + + if (r == LIBUSB_ERROR_NOT_SUPPORTED) { + usbi_dbg(ctx, "falling back to control message"); + r = libusb_control_transfer(dev_handle, LIBUSB_ENDPOINT_IN, + LIBUSB_REQUEST_GET_CONFIGURATION, 0, 0, &tmp, 1, 1000); + if (r == 1) { + r = 0; + } else if (r == 0) { + usbi_err(ctx, "zero bytes returned in ctrl transfer?"); + r = LIBUSB_ERROR_IO; + } else { + usbi_dbg(ctx, "control failed, error %d", r); + } + } + + if (r == 0) { + usbi_dbg(ctx, "active config %u", tmp); + *config = (int)tmp; + } + + return r; +} + +/** \ingroup libusb_dev + * Set the active configuration for a device. + * + * The operating system may or may not have already set an active + * configuration on the device. It is up to your application to ensure the + * correct configuration is selected before you attempt to claim interfaces + * and perform other operations. + * + * If you call this function on a device already configured with the selected + * configuration, then this function will act as a lightweight device reset: + * it will issue a SET_CONFIGURATION request using the current configuration, + * causing most USB-related device state to be reset (altsetting reset to zero, + * endpoint halts cleared, toggles reset). + * + * Not all backends support setting the configuration from user space, which + * will be indicated by the return code LIBUSB_ERROR_NOT_SUPPORTED. As this + * suggests that the platform is handling the device configuration itself, + * this error should generally be safe to ignore. + * + * You cannot change/reset configuration if your application has claimed + * interfaces. It is advised to set the desired configuration before claiming + * interfaces. + * + * Alternatively you can call libusb_release_interface() first. Note if you + * do things this way you must ensure that auto_detach_kernel_driver for + * <tt>dev</tt> is 0, otherwise the kernel driver will be re-attached when you + * release the interface(s). + * + * You cannot change/reset configuration if other applications or drivers have + * claimed interfaces. + * + * A configuration value of -1 will put the device in unconfigured state. + * The USB specifications state that a configuration value of 0 does this, + * however buggy devices exist which actually have a configuration 0. + * + * You should always use this function rather than formulating your own + * SET_CONFIGURATION control request. This is because the underlying operating + * system needs to know when such changes happen. + * + * This is a blocking function. + * + * \param dev_handle a device handle + * \param configuration the bConfigurationValue of the configuration you + * wish to activate, or -1 if you wish to put the device in an unconfigured + * state + * \returns 0 on success + * \returns LIBUSB_ERROR_NOT_FOUND if the requested configuration does not exist + * \returns LIBUSB_ERROR_BUSY if interfaces are currently claimed + * \returns LIBUSB_ERROR_NOT_SUPPORTED if setting or changing the configuration + * is not supported by the backend + * \returns LIBUSB_ERROR_NO_DEVICE if the device has been disconnected + * \returns another LIBUSB_ERROR code on other failure + * \see libusb_set_auto_detach_kernel_driver() + */ +int API_EXPORTED libusb_set_configuration(libusb_device_handle *dev_handle, + int configuration) +{ + usbi_dbg(HANDLE_CTX(dev_handle), "configuration %d", configuration); + if (configuration < -1 || configuration > (int)UINT8_MAX) + return LIBUSB_ERROR_INVALID_PARAM; + return usbi_backend.set_configuration(dev_handle, configuration); +} + +/** \ingroup libusb_dev + * Claim an interface on a given device handle. You must claim the interface + * you wish to use before you can perform I/O on any of its endpoints. + * + * It is legal to attempt to claim an already-claimed interface, in which + * case libusb just returns 0 without doing anything. + * + * If auto_detach_kernel_driver is set to 1 for <tt>dev</tt>, the kernel driver + * will be detached if necessary, on failure the detach error is returned. + * + * Claiming of interfaces is a purely logical operation; it does not cause + * any requests to be sent over the bus. Interface claiming is used to + * instruct the underlying operating system that your application wishes + * to take ownership of the interface. + * + * This is a non-blocking function. + * + * \param dev_handle a device handle + * \param interface_number the <tt>bInterfaceNumber</tt> of the interface you + * wish to claim + * \returns 0 on success + * \returns LIBUSB_ERROR_NOT_FOUND if the requested interface does not exist + * \returns LIBUSB_ERROR_BUSY if another program or driver has claimed the + * interface + * \returns LIBUSB_ERROR_NO_DEVICE if the device has been disconnected + * \returns a LIBUSB_ERROR code on other failure + * \see libusb_set_auto_detach_kernel_driver() + */ +int API_EXPORTED libusb_claim_interface(libusb_device_handle *dev_handle, + int interface_number) +{ + int r = 0; + + usbi_dbg(HANDLE_CTX(dev_handle), "interface %d", interface_number); + if (interface_number < 0 || interface_number >= USB_MAXINTERFACES) + return LIBUSB_ERROR_INVALID_PARAM; + + if (!usbi_atomic_load(&dev_handle->dev->attached)) + return LIBUSB_ERROR_NO_DEVICE; + + usbi_mutex_lock(&dev_handle->lock); + if (dev_handle->claimed_interfaces & (1U << interface_number)) + goto out; + + r = usbi_backend.claim_interface(dev_handle, (uint8_t)interface_number); + if (r == 0) + dev_handle->claimed_interfaces |= 1U << interface_number; + +out: + usbi_mutex_unlock(&dev_handle->lock); + return r; +} + +/** \ingroup libusb_dev + * Release an interface previously claimed with libusb_claim_interface(). You + * should release all claimed interfaces before closing a device handle. + * + * This is a blocking function. A SET_INTERFACE control request will be sent + * to the device, resetting interface state to the first alternate setting. + * + * If auto_detach_kernel_driver is set to 1 for <tt>dev</tt>, the kernel + * driver will be re-attached after releasing the interface. + * + * \param dev_handle a device handle + * \param interface_number the <tt>bInterfaceNumber</tt> of the + * previously-claimed interface + * \returns 0 on success + * \returns LIBUSB_ERROR_NOT_FOUND if the interface was not claimed + * \returns LIBUSB_ERROR_NO_DEVICE if the device has been disconnected + * \returns another LIBUSB_ERROR code on other failure + * \see libusb_set_auto_detach_kernel_driver() + */ +int API_EXPORTED libusb_release_interface(libusb_device_handle *dev_handle, + int interface_number) +{ + int r; + + usbi_dbg(HANDLE_CTX(dev_handle), "interface %d", interface_number); + if (interface_number < 0 || interface_number >= USB_MAXINTERFACES) + return LIBUSB_ERROR_INVALID_PARAM; + + usbi_mutex_lock(&dev_handle->lock); + if (!(dev_handle->claimed_interfaces & (1U << interface_number))) { + r = LIBUSB_ERROR_NOT_FOUND; + goto out; + } + + r = usbi_backend.release_interface(dev_handle, (uint8_t)interface_number); + if (r == 0) + dev_handle->claimed_interfaces &= ~(1U << interface_number); + +out: + usbi_mutex_unlock(&dev_handle->lock); + return r; +} + +/** \ingroup libusb_dev + * Activate an alternate setting for an interface. The interface must have + * been previously claimed with libusb_claim_interface(). + * + * You should always use this function rather than formulating your own + * SET_INTERFACE control request. This is because the underlying operating + * system needs to know when such changes happen. + * + * This is a blocking function. + * + * \param dev_handle a device handle + * \param interface_number the <tt>bInterfaceNumber</tt> of the + * previously-claimed interface + * \param alternate_setting the <tt>bAlternateSetting</tt> of the alternate + * setting to activate + * \returns 0 on success + * \returns LIBUSB_ERROR_NOT_FOUND if the interface was not claimed, or the + * requested alternate setting does not exist + * \returns LIBUSB_ERROR_NO_DEVICE if the device has been disconnected + * \returns another LIBUSB_ERROR code on other failure + */ +int API_EXPORTED libusb_set_interface_alt_setting(libusb_device_handle *dev_handle, + int interface_number, int alternate_setting) +{ + usbi_dbg(HANDLE_CTX(dev_handle), "interface %d altsetting %d", + interface_number, alternate_setting); + if (interface_number < 0 || interface_number >= USB_MAXINTERFACES) + return LIBUSB_ERROR_INVALID_PARAM; + if (alternate_setting < 0 || alternate_setting > (int)UINT8_MAX) + return LIBUSB_ERROR_INVALID_PARAM; + + if (!usbi_atomic_load(&dev_handle->dev->attached)) { + usbi_mutex_unlock(&dev_handle->lock); + return LIBUSB_ERROR_NO_DEVICE; + } + + usbi_mutex_lock(&dev_handle->lock); + if (!(dev_handle->claimed_interfaces & (1U << interface_number))) { + usbi_mutex_unlock(&dev_handle->lock); + return LIBUSB_ERROR_NOT_FOUND; + } + usbi_mutex_unlock(&dev_handle->lock); + + return usbi_backend.set_interface_altsetting(dev_handle, + (uint8_t)interface_number, (uint8_t)alternate_setting); +} + +/** \ingroup libusb_dev + * Clear the halt/stall condition for an endpoint. Endpoints with halt status + * are unable to receive or transmit data until the halt condition is stalled. + * + * You should cancel all pending transfers before attempting to clear the halt + * condition. + * + * This is a blocking function. + * + * \param dev_handle a device handle + * \param endpoint the endpoint to clear halt status + * \returns 0 on success + * \returns LIBUSB_ERROR_NOT_FOUND if the endpoint does not exist + * \returns LIBUSB_ERROR_NO_DEVICE if the device has been disconnected + * \returns another LIBUSB_ERROR code on other failure + */ +int API_EXPORTED libusb_clear_halt(libusb_device_handle *dev_handle, + unsigned char endpoint) +{ + usbi_dbg(HANDLE_CTX(dev_handle), "endpoint 0x%x", endpoint); + if (!usbi_atomic_load(&dev_handle->dev->attached)) + return LIBUSB_ERROR_NO_DEVICE; + + return usbi_backend.clear_halt(dev_handle, endpoint); +} + +/** \ingroup libusb_dev + * Perform a USB port reset to reinitialize a device. The system will attempt + * to restore the previous configuration and alternate settings after the + * reset has completed. + * + * If the reset fails, the descriptors change, or the previous state cannot be + * restored, the device will appear to be disconnected and reconnected. This + * means that the device handle is no longer valid (you should close it) and + * rediscover the device. A return code of LIBUSB_ERROR_NOT_FOUND indicates + * when this is the case. + * + * This is a blocking function which usually incurs a noticeable delay. + * + * \param dev_handle a handle of the device to reset + * \returns 0 on success + * \returns LIBUSB_ERROR_NOT_FOUND if re-enumeration is required, or if the + * device has been disconnected + * \returns another LIBUSB_ERROR code on other failure + */ +int API_EXPORTED libusb_reset_device(libusb_device_handle *dev_handle) +{ + usbi_dbg(HANDLE_CTX(dev_handle), " "); + if (!usbi_atomic_load(&dev_handle->dev->attached)) + return LIBUSB_ERROR_NO_DEVICE; + + if (usbi_backend.reset_device) + return usbi_backend.reset_device(dev_handle); + else + return LIBUSB_ERROR_NOT_SUPPORTED; +} + +/** \ingroup libusb_asyncio + * Allocate up to num_streams usb bulk streams on the specified endpoints. This + * function takes an array of endpoints rather then a single endpoint because + * some protocols require that endpoints are setup with similar stream ids. + * All endpoints passed in must belong to the same interface. + * + * Note this function may return less streams then requested. Also note that the + * same number of streams are allocated for each endpoint in the endpoint array. + * + * Stream id 0 is reserved, and should not be used to communicate with devices. + * If libusb_alloc_streams() returns with a value of N, you may use stream ids + * 1 to N. + * + * Since version 1.0.19, \ref LIBUSB_API_VERSION >= 0x01000103 + * + * \param dev_handle a device handle + * \param num_streams number of streams to try to allocate + * \param endpoints array of endpoints to allocate streams on + * \param num_endpoints length of the endpoints array + * \returns number of streams allocated, or a LIBUSB_ERROR code on failure + */ +int API_EXPORTED libusb_alloc_streams(libusb_device_handle *dev_handle, + uint32_t num_streams, unsigned char *endpoints, int num_endpoints) +{ + usbi_dbg(HANDLE_CTX(dev_handle), "streams %u eps %d", (unsigned)num_streams, num_endpoints); + + if (!num_streams || !endpoints || num_endpoints <= 0) + return LIBUSB_ERROR_INVALID_PARAM; + + if (!usbi_atomic_load(&dev_handle->dev->attached)) + return LIBUSB_ERROR_NO_DEVICE; + + if (usbi_backend.alloc_streams) + return usbi_backend.alloc_streams(dev_handle, num_streams, endpoints, + num_endpoints); + else + return LIBUSB_ERROR_NOT_SUPPORTED; +} + +/** \ingroup libusb_asyncio + * Free usb bulk streams allocated with libusb_alloc_streams(). + * + * Note streams are automatically free-ed when releasing an interface. + * + * Since version 1.0.19, \ref LIBUSB_API_VERSION >= 0x01000103 + * + * \param dev_handle a device handle + * \param endpoints array of endpoints to free streams on + * \param num_endpoints length of the endpoints array + * \returns LIBUSB_SUCCESS, or a LIBUSB_ERROR code on failure + */ +int API_EXPORTED libusb_free_streams(libusb_device_handle *dev_handle, + unsigned char *endpoints, int num_endpoints) +{ + usbi_dbg(HANDLE_CTX(dev_handle), "eps %d", num_endpoints); + + if (!endpoints || num_endpoints <= 0) + return LIBUSB_ERROR_INVALID_PARAM; + + if (!usbi_atomic_load(&dev_handle->dev->attached)) + return LIBUSB_ERROR_NO_DEVICE; + + if (usbi_backend.free_streams) + return usbi_backend.free_streams(dev_handle, endpoints, + num_endpoints); + else + return LIBUSB_ERROR_NOT_SUPPORTED; +} + +/** \ingroup libusb_asyncio + * Attempts to allocate a block of persistent DMA memory suitable for transfers + * against the given device. If successful, will return a block of memory + * that is suitable for use as "buffer" in \ref libusb_transfer against this + * device. Using this memory instead of regular memory means that the host + * controller can use DMA directly into the buffer to increase performance, and + * also that transfers can no longer fail due to kernel memory fragmentation. + * + * Note that this means you should not modify this memory (or even data on + * the same cache lines) when a transfer is in progress, although it is legal + * to have several transfers going on within the same memory block. + * + * Will return NULL on failure. Many systems do not support such zero-copy + * and will always return NULL. Memory allocated with this function must be + * freed with \ref libusb_dev_mem_free. Specifically, this means that the + * flag \ref LIBUSB_TRANSFER_FREE_BUFFER cannot be used to free memory allocated + * with this function. + * + * Since version 1.0.21, \ref LIBUSB_API_VERSION >= 0x01000105 + * + * \param dev_handle a device handle + * \param length size of desired data buffer + * \returns a pointer to the newly allocated memory, or NULL on failure + */ +DEFAULT_VISIBILITY +unsigned char * LIBUSB_CALL libusb_dev_mem_alloc(libusb_device_handle *dev_handle, + size_t length) +{ + if (!usbi_atomic_load(&dev_handle->dev->attached)) + return NULL; + + if (usbi_backend.dev_mem_alloc) + return usbi_backend.dev_mem_alloc(dev_handle, length); + else + return NULL; +} + +/** \ingroup libusb_asyncio + * Free device memory allocated with libusb_dev_mem_alloc(). + * + * \param dev_handle a device handle + * \param buffer pointer to the previously allocated memory + * \param length size of previously allocated memory + * \returns LIBUSB_SUCCESS, or a LIBUSB_ERROR code on failure + */ +int API_EXPORTED libusb_dev_mem_free(libusb_device_handle *dev_handle, + unsigned char *buffer, size_t length) +{ + if (usbi_backend.dev_mem_free) + return usbi_backend.dev_mem_free(dev_handle, buffer, length); + else + return LIBUSB_ERROR_NOT_SUPPORTED; +} + +/** \ingroup libusb_dev + * Determine if a kernel driver is active on an interface. If a kernel driver + * is active, you cannot claim the interface, and libusb will be unable to + * perform I/O. + * + * This functionality is not available on Windows. + * + * \param dev_handle a device handle + * \param interface_number the interface to check + * \returns 0 if no kernel driver is active + * \returns 1 if a kernel driver is active + * \returns LIBUSB_ERROR_NO_DEVICE if the device has been disconnected + * \returns LIBUSB_ERROR_NOT_SUPPORTED on platforms where the functionality + * is not available + * \returns another LIBUSB_ERROR code on other failure + * \see libusb_detach_kernel_driver() + */ +int API_EXPORTED libusb_kernel_driver_active(libusb_device_handle *dev_handle, + int interface_number) +{ + usbi_dbg(HANDLE_CTX(dev_handle), "interface %d", interface_number); + + if (interface_number < 0 || interface_number >= USB_MAXINTERFACES) + return LIBUSB_ERROR_INVALID_PARAM; + + if (!usbi_atomic_load(&dev_handle->dev->attached)) + return LIBUSB_ERROR_NO_DEVICE; + + if (usbi_backend.kernel_driver_active) + return usbi_backend.kernel_driver_active(dev_handle, (uint8_t)interface_number); + else + return LIBUSB_ERROR_NOT_SUPPORTED; +} + +/** \ingroup libusb_dev + * Detach a kernel driver from an interface. If successful, you will then be + * able to claim the interface and perform I/O. + * + * This functionality is not available on Windows. + * + * Note that libusb itself also talks to the device through a special kernel + * driver, if this driver is already attached to the device, this call will + * not detach it and return LIBUSB_ERROR_NOT_FOUND. + * + * \param dev_handle a device handle + * \param interface_number the interface to detach the driver from + * \returns 0 on success + * \returns LIBUSB_ERROR_NOT_FOUND if no kernel driver was active + * \returns LIBUSB_ERROR_INVALID_PARAM if the interface does not exist + * \returns LIBUSB_ERROR_NO_DEVICE if the device has been disconnected + * \returns LIBUSB_ERROR_NOT_SUPPORTED on platforms where the functionality + * is not available + * \returns another LIBUSB_ERROR code on other failure + * \see libusb_kernel_driver_active() + */ +int API_EXPORTED libusb_detach_kernel_driver(libusb_device_handle *dev_handle, + int interface_number) +{ + usbi_dbg(HANDLE_CTX(dev_handle), "interface %d", interface_number); + + if (interface_number < 0 || interface_number >= USB_MAXINTERFACES) + return LIBUSB_ERROR_INVALID_PARAM; + + if (!usbi_atomic_load(&dev_handle->dev->attached)) + return LIBUSB_ERROR_NO_DEVICE; + + if (usbi_backend.detach_kernel_driver) + return usbi_backend.detach_kernel_driver(dev_handle, (uint8_t)interface_number); + else + return LIBUSB_ERROR_NOT_SUPPORTED; +} + +/** \ingroup libusb_dev + * Re-attach an interface's kernel driver, which was previously detached + * using libusb_detach_kernel_driver(). + * + * This functionality is not available on Windows. + * + * \param dev_handle a device handle + * \param interface_number the interface to attach the driver from + * \returns 0 on success + * \returns LIBUSB_ERROR_NOT_FOUND if no kernel driver was active + * \returns LIBUSB_ERROR_INVALID_PARAM if the interface does not exist + * \returns LIBUSB_ERROR_NO_DEVICE if the device has been disconnected + * \returns LIBUSB_ERROR_NOT_SUPPORTED on platforms where the functionality + * is not available + * \returns LIBUSB_ERROR_BUSY if the driver cannot be attached because the + * interface is claimed by a program or driver + * \returns another LIBUSB_ERROR code on other failure + * \see libusb_kernel_driver_active() + */ +int API_EXPORTED libusb_attach_kernel_driver(libusb_device_handle *dev_handle, + int interface_number) +{ + usbi_dbg(HANDLE_CTX(dev_handle), "interface %d", interface_number); + + if (interface_number < 0 || interface_number >= USB_MAXINTERFACES) + return LIBUSB_ERROR_INVALID_PARAM; + + if (!usbi_atomic_load(&dev_handle->dev->attached)) + return LIBUSB_ERROR_NO_DEVICE; + + if (usbi_backend.attach_kernel_driver) + return usbi_backend.attach_kernel_driver(dev_handle, (uint8_t)interface_number); + else + return LIBUSB_ERROR_NOT_SUPPORTED; +} + +/** \ingroup libusb_dev + * Enable/disable libusb's automatic kernel driver detachment. When this is + * enabled libusb will automatically detach the kernel driver on an interface + * when claiming the interface, and attach it when releasing the interface. + * + * Automatic kernel driver detachment is disabled on newly opened device + * handles by default. + * + * On platforms which do not have LIBUSB_CAP_SUPPORTS_DETACH_KERNEL_DRIVER + * this function will return LIBUSB_ERROR_NOT_SUPPORTED, and libusb will + * continue as if this function was never called. + * + * \param dev_handle a device handle + * \param enable whether to enable or disable auto kernel driver detachment + * + * \returns LIBUSB_SUCCESS on success + * \returns LIBUSB_ERROR_NOT_SUPPORTED on platforms where the functionality + * is not available + * \see libusb_claim_interface() + * \see libusb_release_interface() + * \see libusb_set_configuration() + */ +int API_EXPORTED libusb_set_auto_detach_kernel_driver( + libusb_device_handle *dev_handle, int enable) +{ + if (!(usbi_backend.caps & USBI_CAP_SUPPORTS_DETACH_KERNEL_DRIVER)) + return LIBUSB_ERROR_NOT_SUPPORTED; + + dev_handle->auto_detach_kernel_driver = enable; + return LIBUSB_SUCCESS; +} + +/** \ingroup libusb_lib + * \deprecated Use libusb_set_option() instead using the + * \ref LIBUSB_OPTION_LOG_LEVEL option. + */ +void API_EXPORTED libusb_set_debug(libusb_context *ctx, int level) +{ +#if defined(ENABLE_LOGGING) && !defined(ENABLE_DEBUG_LOGGING) + ctx = usbi_get_context(ctx); + if (!ctx->debug_fixed) { + level = CLAMP(level, LIBUSB_LOG_LEVEL_NONE, LIBUSB_LOG_LEVEL_DEBUG); + ctx->debug = (enum libusb_log_level)level; + } +#else + UNUSED(ctx); + UNUSED(level); +#endif +} + +/** \ingroup libusb_lib + * Set log handler. + * + * libusb will redirect its log messages to the provided callback function. + * libusb supports redirection of per context and global log messages. + * Log messages sent to the context will be sent to the global log handler too. + * + * If libusb is compiled without message logging or USE_SYSTEM_LOGGING_FACILITY + * is defined then global callback function will never be called. + * If ENABLE_DEBUG_LOGGING is defined then per context callback function will + * never be called. + * + * Since version 1.0.23, \ref LIBUSB_API_VERSION >= 0x01000107 + * + * \param ctx context on which to assign log handler, or NULL for the default + * context. Parameter ignored if only LIBUSB_LOG_CB_GLOBAL mode is requested. + * \param cb pointer to the callback function, or NULL to stop log + * messages redirection + * \param mode mode of callback function operation. Several modes can be + * selected for a single callback function, see \ref libusb_log_cb_mode for + * a description. + * \see libusb_log_cb, libusb_log_cb_mode + */ +void API_EXPORTED libusb_set_log_cb(libusb_context *ctx, libusb_log_cb cb, + int mode) +{ +#if defined(ENABLE_LOGGING) && (!defined(ENABLE_DEBUG_LOGGING) || !defined(USE_SYSTEM_LOGGING_FACILITY)) +#if !defined(USE_SYSTEM_LOGGING_FACILITY) + if (mode & LIBUSB_LOG_CB_GLOBAL) + log_handler = cb; +#endif +#if !defined(ENABLE_DEBUG_LOGGING) + if (mode & LIBUSB_LOG_CB_CONTEXT) { + ctx = usbi_get_context(ctx); + ctx->log_handler = cb; + } +#else + UNUSED(ctx); +#endif +#else + UNUSED(ctx); + UNUSED(cb); + UNUSED(mode); +#endif +} + +/** \ingroup libusb_lib + * Set an option in the library. + * + * Use this function to configure a specific option within the library. + * + * Some options require one or more arguments to be provided. Consult each + * option's documentation for specific requirements. + * + * If the context ctx is NULL, the option will be added to a list of default + * options that will be applied to all subsequently created contexts. + * + * Since version 1.0.22, \ref LIBUSB_API_VERSION >= 0x01000106 + * + * \param ctx context on which to operate + * \param option which option to set + * \param ... any required arguments for the specified option + * + * \returns LIBUSB_SUCCESS on success + * \returns LIBUSB_ERROR_INVALID_PARAM if the option or arguments are invalid + * \returns LIBUSB_ERROR_NOT_SUPPORTED if the option is valid but not supported + * on this platform + * \returns LIBUSB_ERROR_NOT_FOUND if LIBUSB_OPTION_USE_USBDK is valid on this platform but UsbDk is not available + */ +int API_EXPORTED libusb_set_option(libusb_context *ctx, + enum libusb_option option, ...) +{ + int arg = 0, r = LIBUSB_SUCCESS; + va_list ap; + + va_start(ap, option); + if (LIBUSB_OPTION_LOG_LEVEL == option) { + arg = va_arg(ap, int); + if (arg < LIBUSB_LOG_LEVEL_NONE || arg > LIBUSB_LOG_LEVEL_DEBUG) { + r = LIBUSB_ERROR_INVALID_PARAM; + } + } + va_end(ap); + + if (LIBUSB_SUCCESS != r) { + return r; + } + + if (option >= LIBUSB_OPTION_MAX) { + return LIBUSB_ERROR_INVALID_PARAM; + } + + if (NULL == ctx) { + usbi_mutex_static_lock(&default_context_lock); + default_context_options[option].is_set = 1; + if (LIBUSB_OPTION_LOG_LEVEL == option) { + default_context_options[option].arg.ival = arg; + } + usbi_mutex_static_unlock(&default_context_lock); + } + + ctx = usbi_get_context(ctx); + if (NULL == ctx) { + return LIBUSB_SUCCESS; + } + + switch (option) { + case LIBUSB_OPTION_LOG_LEVEL: +#if defined(ENABLE_LOGGING) && !defined(ENABLE_DEBUG_LOGGING) + if (!ctx->debug_fixed) + ctx->debug = (enum libusb_log_level)arg; +#endif + break; + + /* Handle all backend-specific options here */ + case LIBUSB_OPTION_USE_USBDK: + case LIBUSB_OPTION_NO_DEVICE_DISCOVERY: + if (usbi_backend.set_option) + return usbi_backend.set_option(ctx, option, ap); + + return LIBUSB_ERROR_NOT_SUPPORTED; + break; + + case LIBUSB_OPTION_MAX: + default: + return LIBUSB_ERROR_INVALID_PARAM; + } + + return LIBUSB_SUCCESS;; +} + +#if defined(ENABLE_LOGGING) && !defined(ENABLE_DEBUG_LOGGING) +/* returns the log level as defined in the LIBUSB_DEBUG environment variable. + * if LIBUSB_DEBUG is not present or not a number, returns LIBUSB_LOG_LEVEL_NONE. + * value is clamped to ensure it is within the valid range of possibilities. + */ +static enum libusb_log_level get_env_debug_level(void) +{ + const char *dbg = getenv("LIBUSB_DEBUG"); + enum libusb_log_level level; + if (dbg) { + int dbg_level = atoi(dbg); + dbg_level = CLAMP(dbg_level, LIBUSB_LOG_LEVEL_NONE, LIBUSB_LOG_LEVEL_DEBUG); + level = (enum libusb_log_level)dbg_level; + } else { + level = LIBUSB_LOG_LEVEL_NONE; + } + return level; +} +#endif + +/** \ingroup libusb_lib + * Initialize libusb. This function must be called before calling any other + * libusb function. + * + * If you do not provide an output location for a context pointer, a default + * context will be created. If there was already a default context, it will + * be reused (and nothing will be initialized/reinitialized). + * + * \param ctx Optional output location for context pointer. + * Only valid on return code 0. + * \returns 0 on success, or a LIBUSB_ERROR code on failure + * \see libusb_contexts + */ +int API_EXPORTED libusb_init(libusb_context **ctx) +{ + size_t priv_size = usbi_backend.context_priv_size; + struct libusb_context *_ctx; + int r; + + usbi_mutex_static_lock(&default_context_lock); + + if (!ctx && usbi_default_context) { + usbi_dbg(usbi_default_context, "reusing default context"); + default_context_refcnt++; + usbi_mutex_static_unlock(&default_context_lock); + return 0; + } + + /* check for first init */ + if (!active_contexts_list.next) { + list_init(&active_contexts_list); + usbi_get_monotonic_time(×tamp_origin); + } + + _ctx = calloc(1, PTR_ALIGN(sizeof(*_ctx)) + priv_size); + if (!_ctx) { + usbi_mutex_static_unlock(&default_context_lock); + return LIBUSB_ERROR_NO_MEM; + } + +#if defined(ENABLE_LOGGING) && !defined(ENABLE_DEBUG_LOGGING) + if (NULL == ctx && default_context_options[LIBUSB_OPTION_LOG_LEVEL].is_set) { + _ctx->debug = default_context_options[LIBUSB_OPTION_LOG_LEVEL].arg.ival; + } else { + _ctx->debug = get_env_debug_level(); + } + if (_ctx->debug != LIBUSB_LOG_LEVEL_NONE) + _ctx->debug_fixed = 1; +#endif + + usbi_mutex_init(&_ctx->usb_devs_lock); + usbi_mutex_init(&_ctx->open_devs_lock); + list_init(&_ctx->usb_devs); + list_init(&_ctx->open_devs); + + /* apply default options to all new contexts */ + for (enum libusb_option option = 0 ; option < LIBUSB_OPTION_MAX ; option++) { + if (LIBUSB_OPTION_LOG_LEVEL == option || !default_context_options[option].is_set) { + continue; + } + r = libusb_set_option(_ctx, option); + if (LIBUSB_SUCCESS != r) + goto err_free_ctx; + } + + /* default context must be initialized before calling usbi_dbg */ + if (!ctx) { + usbi_default_context = _ctx; + default_context_refcnt = 1; + usbi_dbg(usbi_default_context, "created default context"); + } + + usbi_dbg(_ctx, "libusb v%u.%u.%u.%u%s", libusb_version_internal.major, libusb_version_internal.minor, + libusb_version_internal.micro, libusb_version_internal.nano, libusb_version_internal.rc); + + r = usbi_io_init(_ctx); + if (r < 0) + goto err_free_ctx; + + usbi_mutex_static_lock(&active_contexts_lock); + list_add(&_ctx->list, &active_contexts_list); + usbi_mutex_static_unlock(&active_contexts_lock); + + usbi_hotplug_init(_ctx); + + if (usbi_backend.init) { + r = usbi_backend.init(_ctx); + if (r) + goto err_io_exit; + } + + if (ctx) + *ctx = _ctx; + + usbi_mutex_static_unlock(&default_context_lock); + + return 0; + +err_io_exit: + usbi_mutex_static_lock(&active_contexts_lock); + list_del(&_ctx->list); + usbi_mutex_static_unlock(&active_contexts_lock); + + usbi_hotplug_exit(_ctx); + usbi_io_exit(_ctx); + +err_free_ctx: + if (!ctx) { + /* clear default context that was not fully initialized */ + usbi_default_context = NULL; + default_context_refcnt = 0; + } + + usbi_mutex_destroy(&_ctx->open_devs_lock); + usbi_mutex_destroy(&_ctx->usb_devs_lock); + + free(_ctx); + + usbi_mutex_static_unlock(&default_context_lock); + + return r; +} + +/** \ingroup libusb_lib + * Deinitialize libusb. Should be called after closing all open devices and + * before your application terminates. + * \param ctx the context to deinitialize, or NULL for the default context + */ +void API_EXPORTED libusb_exit(libusb_context *ctx) +{ + struct libusb_context *_ctx; + struct libusb_device *dev; + + usbi_mutex_static_lock(&default_context_lock); + + /* if working with default context, only actually do the deinitialization + * if we're the last user */ + if (!ctx) { + if (!usbi_default_context) { + usbi_dbg(ctx, "no default context, not initialized?"); + usbi_mutex_static_unlock(&default_context_lock); + return; + } + + if (--default_context_refcnt > 0) { + usbi_dbg(ctx, "not destroying default context"); + usbi_mutex_static_unlock(&default_context_lock); + return; + } + + usbi_dbg(ctx, "destroying default context"); + _ctx = usbi_default_context; + } else { + usbi_dbg(ctx, " "); + _ctx = ctx; + } + + usbi_mutex_static_lock(&active_contexts_lock); + list_del(&_ctx->list); + usbi_mutex_static_unlock(&active_contexts_lock); + + if (usbi_backend.exit) + usbi_backend.exit(_ctx); + + if (!ctx) + usbi_default_context = NULL; + + usbi_mutex_static_unlock(&default_context_lock); + + /* Don't bother with locking after this point because unless there is + * an application bug, nobody will be accessing the context. */ + + usbi_hotplug_exit(_ctx); + usbi_io_exit(_ctx); + + for_each_device(_ctx, dev) { + usbi_warn(_ctx, "device %d.%d still referenced", + dev->bus_number, dev->device_address); + } + + if (!list_empty(&_ctx->open_devs)) + usbi_warn(_ctx, "application left some devices open"); + + usbi_mutex_destroy(&_ctx->open_devs_lock); + usbi_mutex_destroy(&_ctx->usb_devs_lock); + + free(_ctx); +} + +/** \ingroup libusb_misc + * Check at runtime if the loaded library has a given capability. + * This call should be performed after \ref libusb_init(), to ensure the + * backend has updated its capability set. + * + * \param capability the \ref libusb_capability to check for + * \returns nonzero if the running library has the capability, 0 otherwise + */ +int API_EXPORTED libusb_has_capability(uint32_t capability) +{ + switch (capability) { + case LIBUSB_CAP_HAS_CAPABILITY: + return 1; + case LIBUSB_CAP_HAS_HOTPLUG: + return !(usbi_backend.get_device_list); + case LIBUSB_CAP_HAS_HID_ACCESS: + return (usbi_backend.caps & USBI_CAP_HAS_HID_ACCESS); + case LIBUSB_CAP_SUPPORTS_DETACH_KERNEL_DRIVER: + return (usbi_backend.caps & USBI_CAP_SUPPORTS_DETACH_KERNEL_DRIVER); + } + return 0; +} + +#ifdef ENABLE_LOGGING + +/* this is defined in libusbi.h if needed */ +#ifdef LIBUSB_PRINTF_WIN32 +/* + * Prior to VS2015, Microsoft did not provide the snprintf() function and + * provided a vsnprintf() that did not guarantee NUL-terminated output. + * Microsoft did provide a _snprintf() function, but again it did not + * guarantee NULL-terminated output. + * + * The below implementations guarantee NUL-terminated output and are + * C99 compliant. + */ + +int usbi_snprintf(char *str, size_t size, const char *format, ...) +{ + va_list args; + int ret; + + va_start(args, format); + ret = usbi_vsnprintf(str, size, format, args); + va_end(args); + + return ret; +} + +int usbi_vsnprintf(char *str, size_t size, const char *format, va_list args) +{ + int ret; + + ret = _vsnprintf(str, size, format, args); + if (ret < 0 || ret == (int)size) { + /* Output is truncated, ensure buffer is NUL-terminated and + * determine how many characters would have been written. */ + str[size - 1] = '\0'; + if (ret < 0) + ret = _vsnprintf(NULL, 0, format, args); + } + + return ret; +} +#endif /* LIBUSB_PRINTF_WIN32 */ + +static void log_str(enum libusb_log_level level, const char *str) +{ +#if defined(USE_SYSTEM_LOGGING_FACILITY) +#if defined(__ANDROID__) + int priority; + switch (level) { + case LIBUSB_LOG_LEVEL_NONE: return; /* Impossible, but keeps compiler happy */ + case LIBUSB_LOG_LEVEL_ERROR: priority = ANDROID_LOG_ERROR; break; + case LIBUSB_LOG_LEVEL_WARNING: priority = ANDROID_LOG_WARN; break; + case LIBUSB_LOG_LEVEL_INFO: priority = ANDROID_LOG_INFO; break; + case LIBUSB_LOG_LEVEL_DEBUG: priority = ANDROID_LOG_DEBUG; break; + default: priority = ANDROID_LOG_UNKNOWN; + } + __android_log_write(priority, "libusb", str); +#elif defined(_WIN32) + UNUSED(level); + OutputDebugStringA(str); +#elif defined(HAVE_SYSLOG) + int syslog_level; + switch (level) { + case LIBUSB_LOG_LEVEL_NONE: return; /* Impossible, but keeps compiler happy */ + case LIBUSB_LOG_LEVEL_ERROR: syslog_level = LOG_ERR; break; + case LIBUSB_LOG_LEVEL_WARNING: syslog_level = LOG_WARNING; break; + case LIBUSB_LOG_LEVEL_INFO: syslog_level = LOG_INFO; break; + case LIBUSB_LOG_LEVEL_DEBUG: syslog_level = LOG_DEBUG; break; + default: syslog_level = LOG_INFO; + } + syslog(syslog_level, "%s", str); +#else /* All of gcc, Clang, Xcode seem to use #warning */ +#warning System logging is not supported on this platform. Logging to stderr will be used instead. + UNUSED(level); + fputs(str, stderr); +#endif +#else + /* Global log handler */ + if (log_handler) + log_handler(NULL, level, str); + else + fputs(str, stderr); +#endif /* USE_SYSTEM_LOGGING_FACILITY */ +} + +static void log_v(struct libusb_context *ctx, enum libusb_log_level level, + const char *function, const char *format, va_list args) +{ + const char *prefix; + char buf[USBI_MAX_LOG_LEN]; + int global_debug, header_len, text_len; + static int has_debug_header_been_displayed = 0; + +#ifdef ENABLE_DEBUG_LOGGING + global_debug = 1; + UNUSED(ctx); +#else + enum libusb_log_level ctx_level; + + ctx = usbi_get_context(ctx); + if (ctx) + ctx_level = ctx->debug; + else + ctx_level = get_env_debug_level(); + + if (ctx_level < level) + return; + + global_debug = (ctx_level == LIBUSB_LOG_LEVEL_DEBUG); +#endif + + switch (level) { + case LIBUSB_LOG_LEVEL_NONE: /* Impossible, but keeps compiler happy */ + return; + case LIBUSB_LOG_LEVEL_ERROR: + prefix = "error"; + break; + case LIBUSB_LOG_LEVEL_WARNING: + prefix = "warning"; + break; + case LIBUSB_LOG_LEVEL_INFO: + prefix = "info"; + break; + case LIBUSB_LOG_LEVEL_DEBUG: + prefix = "debug"; + break; + default: + prefix = "unknown"; + break; + } + + if (global_debug) { + struct timespec timestamp; + + if (!has_debug_header_been_displayed) { + has_debug_header_been_displayed = 1; + log_str(LIBUSB_LOG_LEVEL_DEBUG, "[timestamp] [threadID] facility level [function call] <message>" USBI_LOG_LINE_END); + log_str(LIBUSB_LOG_LEVEL_DEBUG, "--------------------------------------------------------------------------------" USBI_LOG_LINE_END); + } + + usbi_get_monotonic_time(×tamp); + TIMESPEC_SUB(×tamp, ×tamp_origin, ×tamp); + + header_len = snprintf(buf, sizeof(buf), + "[%2ld.%06ld] [%08x] libusb: %s [%s] ", + (long)timestamp.tv_sec, (long)(timestamp.tv_nsec / 1000L), usbi_get_tid(), prefix, function); + } else { + header_len = snprintf(buf, sizeof(buf), + "libusb: %s [%s] ", prefix, function); + } + + if (header_len < 0 || header_len >= (int)sizeof(buf)) { + /* Somehow snprintf() failed to write to the buffer, + * remove the header so something useful is output. */ + header_len = 0; + } + + text_len = vsnprintf(buf + header_len, sizeof(buf) - (size_t)header_len, + format, args); + if (text_len < 0 || text_len + header_len >= (int)sizeof(buf)) { + /* Truncated log output. On some platforms a -1 return value means + * that the output was truncated. */ + text_len = (int)sizeof(buf) - header_len; + } + if (header_len + text_len + (int)sizeof(USBI_LOG_LINE_END) >= (int)sizeof(buf)) { + /* Need to truncate the text slightly to fit on the terminator. */ + text_len -= (header_len + text_len + (int)sizeof(USBI_LOG_LINE_END)) - (int)sizeof(buf); + } + strcpy(buf + header_len + text_len, USBI_LOG_LINE_END); + + log_str(level, buf); + + /* Per-context log handler */ +#ifndef ENABLE_DEBUG_LOGGING + if (ctx && ctx->log_handler) + ctx->log_handler(ctx, level, buf); +#endif +} + +void usbi_log(struct libusb_context *ctx, enum libusb_log_level level, + const char *function, const char *format, ...) +{ + va_list args; + + va_start(args, format); + log_v(ctx, level, function, format, args); + va_end(args); +} + +#endif /* ENABLE_LOGGING */ + +/** \ingroup libusb_misc + * Returns a constant NULL-terminated string with the ASCII name of a libusb + * error or transfer status code. The caller must not free() the returned + * string. + * + * \param error_code The \ref libusb_error or libusb_transfer_status code to + * return the name of. + * \returns The error name, or the string **UNKNOWN** if the value of + * error_code is not a known error / status code. + */ +DEFAULT_VISIBILITY const char * LIBUSB_CALL libusb_error_name(int error_code) +{ + switch (error_code) { + case LIBUSB_ERROR_IO: + return "LIBUSB_ERROR_IO"; + case LIBUSB_ERROR_INVALID_PARAM: + return "LIBUSB_ERROR_INVALID_PARAM"; + case LIBUSB_ERROR_ACCESS: + return "LIBUSB_ERROR_ACCESS"; + case LIBUSB_ERROR_NO_DEVICE: + return "LIBUSB_ERROR_NO_DEVICE"; + case LIBUSB_ERROR_NOT_FOUND: + return "LIBUSB_ERROR_NOT_FOUND"; + case LIBUSB_ERROR_BUSY: + return "LIBUSB_ERROR_BUSY"; + case LIBUSB_ERROR_TIMEOUT: + return "LIBUSB_ERROR_TIMEOUT"; + case LIBUSB_ERROR_OVERFLOW: + return "LIBUSB_ERROR_OVERFLOW"; + case LIBUSB_ERROR_PIPE: + return "LIBUSB_ERROR_PIPE"; + case LIBUSB_ERROR_INTERRUPTED: + return "LIBUSB_ERROR_INTERRUPTED"; + case LIBUSB_ERROR_NO_MEM: + return "LIBUSB_ERROR_NO_MEM"; + case LIBUSB_ERROR_NOT_SUPPORTED: + return "LIBUSB_ERROR_NOT_SUPPORTED"; + case LIBUSB_ERROR_OTHER: + return "LIBUSB_ERROR_OTHER"; + + case LIBUSB_TRANSFER_ERROR: + return "LIBUSB_TRANSFER_ERROR"; + case LIBUSB_TRANSFER_TIMED_OUT: + return "LIBUSB_TRANSFER_TIMED_OUT"; + case LIBUSB_TRANSFER_CANCELLED: + return "LIBUSB_TRANSFER_CANCELLED"; + case LIBUSB_TRANSFER_STALL: + return "LIBUSB_TRANSFER_STALL"; + case LIBUSB_TRANSFER_NO_DEVICE: + return "LIBUSB_TRANSFER_NO_DEVICE"; + case LIBUSB_TRANSFER_OVERFLOW: + return "LIBUSB_TRANSFER_OVERFLOW"; + + case 0: + return "LIBUSB_SUCCESS / LIBUSB_TRANSFER_COMPLETED"; + default: + return "**UNKNOWN**"; + } +} + +/** \ingroup libusb_misc + * Returns a pointer to const struct libusb_version with the version + * (major, minor, micro, nano and rc) of the running library. + */ +DEFAULT_VISIBILITY +const struct libusb_version * LIBUSB_CALL libusb_get_version(void) +{ + return &libusb_version_internal; +} diff --git a/Radio/HW/RtlSdr/libusb/descriptor.c b/Radio/HW/RtlSdr/libusb/descriptor.c new file mode 100644 index 0000000..253ef1c --- /dev/null +++ b/Radio/HW/RtlSdr/libusb/descriptor.c @@ -0,0 +1,1139 @@ +/* -*- Mode: C; indent-tabs-mode:t ; c-basic-offset:8 -*- */ +/* + * USB descriptor handling functions for libusb + * Copyright © 2007 Daniel Drake <dsd@gentoo.org> + * Copyright © 2001 Johannes Erdfelt <johannes@erdfelt.com> + * + * 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 "libusbi.h" + +#include <string.h> + +#define DESC_HEADER_LENGTH 2 + +/** @defgroup libusb_desc USB descriptors + * This page details how to examine the various standard USB descriptors + * for detected devices + */ + +#define READ_LE16(p) ((uint16_t) \ + (((uint16_t)((p)[1]) << 8) | \ + ((uint16_t)((p)[0])))) + +#define READ_LE32(p) ((uint32_t) \ + (((uint32_t)((p)[3]) << 24) | \ + ((uint32_t)((p)[2]) << 16) | \ + ((uint32_t)((p)[1]) << 8) | \ + ((uint32_t)((p)[0])))) + +static void parse_descriptor(const void *source, const char *descriptor, void *dest) +{ + const uint8_t *sp = source; + uint8_t *dp = dest; + char field_type; + + while (*descriptor) { + field_type = *descriptor++; + switch (field_type) { + case 'b': /* 8-bit byte */ + *dp++ = *sp++; + break; + case 'w': /* 16-bit word, convert from little endian to CPU */ + dp += ((uintptr_t)dp & 1); /* Align to 16-bit word boundary */ + + *((uint16_t *)dp) = READ_LE16(sp); + sp += 2; + dp += 2; + break; + case 'd': /* 32-bit word, convert from little endian to CPU */ + dp += 4 - ((uintptr_t)dp & 3); /* Align to 32-bit word boundary */ + + *((uint32_t *)dp) = READ_LE32(sp); + sp += 4; + dp += 4; + break; + case 'u': /* 16 byte UUID */ + memcpy(dp, sp, 16); + sp += 16; + dp += 16; + break; + } + } +} + +static void clear_endpoint(struct libusb_endpoint_descriptor *endpoint) +{ + free((void *)endpoint->extra); +} + +static int parse_endpoint(struct libusb_context *ctx, + struct libusb_endpoint_descriptor *endpoint, const uint8_t *buffer, int size) +{ + const struct usbi_descriptor_header *header; + const uint8_t *begin; + void *extra; + int parsed = 0; + int len; + + if (size < DESC_HEADER_LENGTH) { + usbi_err(ctx, "short endpoint descriptor read %d/%d", + size, DESC_HEADER_LENGTH); + return LIBUSB_ERROR_IO; + } + + header = (const struct usbi_descriptor_header *)buffer; + if (header->bDescriptorType != LIBUSB_DT_ENDPOINT) { + usbi_err(ctx, "unexpected descriptor 0x%x (expected 0x%x)", + header->bDescriptorType, LIBUSB_DT_ENDPOINT); + return parsed; + } else if (header->bLength < LIBUSB_DT_ENDPOINT_SIZE) { + usbi_err(ctx, "invalid endpoint bLength (%u)", header->bLength); + return LIBUSB_ERROR_IO; + } else if (header->bLength > size) { + usbi_warn(ctx, "short endpoint descriptor read %d/%u", + size, header->bLength); + return parsed; + } + + if (header->bLength >= LIBUSB_DT_ENDPOINT_AUDIO_SIZE) + parse_descriptor(buffer, "bbbbwbbb", endpoint); + else + parse_descriptor(buffer, "bbbbwb", endpoint); + + buffer += header->bLength; + size -= header->bLength; + parsed += header->bLength; + + /* Skip over the rest of the Class Specific or Vendor Specific */ + /* descriptors */ + begin = buffer; + while (size >= DESC_HEADER_LENGTH) { + header = (const struct usbi_descriptor_header *)buffer; + if (header->bLength < DESC_HEADER_LENGTH) { + usbi_err(ctx, "invalid extra ep desc len (%u)", + header->bLength); + return LIBUSB_ERROR_IO; + } else if (header->bLength > size) { + usbi_warn(ctx, "short extra ep desc read %d/%u", + size, header->bLength); + return parsed; + } + + /* If we find another "proper" descriptor then we're done */ + if (header->bDescriptorType == LIBUSB_DT_ENDPOINT || + header->bDescriptorType == LIBUSB_DT_INTERFACE || + header->bDescriptorType == LIBUSB_DT_CONFIG || + header->bDescriptorType == LIBUSB_DT_DEVICE) + break; + + usbi_dbg(ctx, "skipping descriptor 0x%x", header->bDescriptorType); + buffer += header->bLength; + size -= header->bLength; + parsed += header->bLength; + } + + /* Copy any unknown descriptors into a storage area for drivers */ + /* to later parse */ + len = (int)(buffer - begin); + if (len <= 0) + return parsed; + + extra = malloc((size_t)len); + if (!extra) + return LIBUSB_ERROR_NO_MEM; + + memcpy(extra, begin, len); + endpoint->extra = extra; + endpoint->extra_length = len; + + return parsed; +} + +static void clear_interface(struct libusb_interface *usb_interface) +{ + int i; + + if (usb_interface->altsetting) { + for (i = 0; i < usb_interface->num_altsetting; i++) { + struct libusb_interface_descriptor *ifp = + (struct libusb_interface_descriptor *) + usb_interface->altsetting + i; + + free((void *)ifp->extra); + if (ifp->endpoint) { + uint8_t j; + + for (j = 0; j < ifp->bNumEndpoints; j++) + clear_endpoint((struct libusb_endpoint_descriptor *) + ifp->endpoint + j); + } + free((void *)ifp->endpoint); + } + } + free((void *)usb_interface->altsetting); + usb_interface->altsetting = NULL; +} + +static int parse_interface(libusb_context *ctx, + struct libusb_interface *usb_interface, const uint8_t *buffer, int size) +{ + int len; + int r; + int parsed = 0; + int interface_number = -1; + const struct usbi_descriptor_header *header; + const struct usbi_interface_descriptor *if_desc; + struct libusb_interface_descriptor *ifp; + const uint8_t *begin; + + while (size >= LIBUSB_DT_INTERFACE_SIZE) { + struct libusb_interface_descriptor *altsetting; + + altsetting = realloc((void *)usb_interface->altsetting, + sizeof(*altsetting) * (size_t)(usb_interface->num_altsetting + 1)); + if (!altsetting) { + r = LIBUSB_ERROR_NO_MEM; + goto err; + } + usb_interface->altsetting = altsetting; + + ifp = altsetting + usb_interface->num_altsetting; + parse_descriptor(buffer, "bbbbbbbbb", ifp); + if (ifp->bDescriptorType != LIBUSB_DT_INTERFACE) { + usbi_err(ctx, "unexpected descriptor 0x%x (expected 0x%x)", + ifp->bDescriptorType, LIBUSB_DT_INTERFACE); + return parsed; + } else if (ifp->bLength < LIBUSB_DT_INTERFACE_SIZE) { + usbi_err(ctx, "invalid interface bLength (%u)", + ifp->bLength); + r = LIBUSB_ERROR_IO; + goto err; + } else if (ifp->bLength > size) { + usbi_warn(ctx, "short intf descriptor read %d/%u", + size, ifp->bLength); + return parsed; + } else if (ifp->bNumEndpoints > USB_MAXENDPOINTS) { + usbi_err(ctx, "too many endpoints (%u)", ifp->bNumEndpoints); + r = LIBUSB_ERROR_IO; + goto err; + } + + usb_interface->num_altsetting++; + ifp->extra = NULL; + ifp->extra_length = 0; + ifp->endpoint = NULL; + + if (interface_number == -1) + interface_number = ifp->bInterfaceNumber; + + /* Skip over the interface */ + buffer += ifp->bLength; + parsed += ifp->bLength; + size -= ifp->bLength; + + begin = buffer; + + /* Skip over any interface, class or vendor descriptors */ + while (size >= DESC_HEADER_LENGTH) { + header = (const struct usbi_descriptor_header *)buffer; + if (header->bLength < DESC_HEADER_LENGTH) { + usbi_err(ctx, + "invalid extra intf desc len (%u)", + header->bLength); + r = LIBUSB_ERROR_IO; + goto err; + } else if (header->bLength > size) { + usbi_warn(ctx, + "short extra intf desc read %d/%u", + size, header->bLength); + return parsed; + } + + /* If we find another "proper" descriptor then we're done */ + if (header->bDescriptorType == LIBUSB_DT_INTERFACE || + header->bDescriptorType == LIBUSB_DT_ENDPOINT || + header->bDescriptorType == LIBUSB_DT_CONFIG || + header->bDescriptorType == LIBUSB_DT_DEVICE) + break; + + buffer += header->bLength; + parsed += header->bLength; + size -= header->bLength; + } + + /* Copy any unknown descriptors into a storage area for */ + /* drivers to later parse */ + len = (int)(buffer - begin); + if (len > 0) { + void *extra = malloc((size_t)len); + + if (!extra) { + r = LIBUSB_ERROR_NO_MEM; + goto err; + } + + memcpy(extra, begin, len); + ifp->extra = extra; + ifp->extra_length = len; + } + + if (ifp->bNumEndpoints > 0) { + struct libusb_endpoint_descriptor *endpoint; + uint8_t i; + + endpoint = calloc(ifp->bNumEndpoints, sizeof(*endpoint)); + if (!endpoint) { + r = LIBUSB_ERROR_NO_MEM; + goto err; + } + + ifp->endpoint = endpoint; + for (i = 0; i < ifp->bNumEndpoints; i++) { + r = parse_endpoint(ctx, endpoint + i, buffer, size); + if (r < 0) + goto err; + if (r == 0) { + ifp->bNumEndpoints = i; + break; + } + + buffer += r; + parsed += r; + size -= r; + } + } + + /* We check to see if it's an alternate to this one */ + if_desc = (const struct usbi_interface_descriptor *)buffer; + if (size < LIBUSB_DT_INTERFACE_SIZE || + if_desc->bDescriptorType != LIBUSB_DT_INTERFACE || + if_desc->bInterfaceNumber != interface_number) + return parsed; + } + + return parsed; +err: + clear_interface(usb_interface); + return r; +} + +static void clear_configuration(struct libusb_config_descriptor *config) +{ + uint8_t i; + + if (config->interface) { + for (i = 0; i < config->bNumInterfaces; i++) + clear_interface((struct libusb_interface *) + config->interface + i); + } + free((void *)config->interface); + free((void *)config->extra); +} + +static int parse_configuration(struct libusb_context *ctx, + struct libusb_config_descriptor *config, const uint8_t *buffer, int size) +{ + uint8_t i; + int r; + const struct usbi_descriptor_header *header; + struct libusb_interface *usb_interface; + + if (size < LIBUSB_DT_CONFIG_SIZE) { + usbi_err(ctx, "short config descriptor read %d/%d", + size, LIBUSB_DT_CONFIG_SIZE); + return LIBUSB_ERROR_IO; + } + + parse_descriptor(buffer, "bbwbbbbb", config); + if (config->bDescriptorType != LIBUSB_DT_CONFIG) { + usbi_err(ctx, "unexpected descriptor 0x%x (expected 0x%x)", + config->bDescriptorType, LIBUSB_DT_CONFIG); + return LIBUSB_ERROR_IO; + } else if (config->bLength < LIBUSB_DT_CONFIG_SIZE) { + usbi_err(ctx, "invalid config bLength (%u)", config->bLength); + return LIBUSB_ERROR_IO; + } else if (config->bLength > size) { + usbi_err(ctx, "short config descriptor read %d/%u", + size, config->bLength); + return LIBUSB_ERROR_IO; + } else if (config->bNumInterfaces > USB_MAXINTERFACES) { + usbi_err(ctx, "too many interfaces (%u)", config->bNumInterfaces); + return LIBUSB_ERROR_IO; + } + + usb_interface = calloc(config->bNumInterfaces, sizeof(*usb_interface)); + if (!usb_interface) + return LIBUSB_ERROR_NO_MEM; + + config->interface = usb_interface; + + buffer += config->bLength; + size -= config->bLength; + + for (i = 0; i < config->bNumInterfaces; i++) { + int len; + const uint8_t *begin; + + /* Skip over the rest of the Class Specific or Vendor */ + /* Specific descriptors */ + begin = buffer; + while (size >= DESC_HEADER_LENGTH) { + header = (const struct usbi_descriptor_header *)buffer; + if (header->bLength < DESC_HEADER_LENGTH) { + usbi_err(ctx, + "invalid extra config desc len (%u)", + header->bLength); + r = LIBUSB_ERROR_IO; + goto err; + } else if (header->bLength > size) { + usbi_warn(ctx, + "short extra config desc read %d/%u", + size, header->bLength); + config->bNumInterfaces = i; + return size; + } + + /* If we find another "proper" descriptor then we're done */ + if (header->bDescriptorType == LIBUSB_DT_ENDPOINT || + header->bDescriptorType == LIBUSB_DT_INTERFACE || + header->bDescriptorType == LIBUSB_DT_CONFIG || + header->bDescriptorType == LIBUSB_DT_DEVICE) + break; + + usbi_dbg(ctx, "skipping descriptor 0x%x", header->bDescriptorType); + buffer += header->bLength; + size -= header->bLength; + } + + /* Copy any unknown descriptors into a storage area for */ + /* drivers to later parse */ + len = (int)(buffer - begin); + if (len > 0) { + uint8_t *extra = realloc((void *)config->extra, + (size_t)(config->extra_length + len)); + + if (!extra) { + r = LIBUSB_ERROR_NO_MEM; + goto err; + } + + memcpy(extra + config->extra_length, begin, len); + config->extra = extra; + config->extra_length += len; + } + + r = parse_interface(ctx, usb_interface + i, buffer, size); + if (r < 0) + goto err; + if (r == 0) { + config->bNumInterfaces = i; + break; + } + + buffer += r; + size -= r; + } + + return size; + +err: + clear_configuration(config); + return r; +} + +static int raw_desc_to_config(struct libusb_context *ctx, + const uint8_t *buf, int size, struct libusb_config_descriptor **config) +{ + struct libusb_config_descriptor *_config = calloc(1, sizeof(*_config)); + int r; + + if (!_config) + return LIBUSB_ERROR_NO_MEM; + + r = parse_configuration(ctx, _config, buf, size); + if (r < 0) { + usbi_err(ctx, "parse_configuration failed with error %d", r); + free(_config); + return r; + } else if (r > 0) { + usbi_warn(ctx, "still %d bytes of descriptor data left", r); + } + + *config = _config; + return LIBUSB_SUCCESS; +} + +static int get_active_config_descriptor(struct libusb_device *dev, + uint8_t *buffer, size_t size) +{ + int r = usbi_backend.get_active_config_descriptor(dev, buffer, size); + + if (r < 0) + return r; + + if (r < LIBUSB_DT_CONFIG_SIZE) { + usbi_err(DEVICE_CTX(dev), "short config descriptor read %d/%d", + r, LIBUSB_DT_CONFIG_SIZE); + return LIBUSB_ERROR_IO; + } else if (r != (int)size) { + usbi_warn(DEVICE_CTX(dev), "short config descriptor read %d/%d", + r, (int)size); + } + + return r; +} + +static int get_config_descriptor(struct libusb_device *dev, uint8_t config_idx, + uint8_t *buffer, size_t size) +{ + int r = usbi_backend.get_config_descriptor(dev, config_idx, buffer, size); + + if (r < 0) + return r; + if (r < LIBUSB_DT_CONFIG_SIZE) { + usbi_err(DEVICE_CTX(dev), "short config descriptor read %d/%d", + r, LIBUSB_DT_CONFIG_SIZE); + return LIBUSB_ERROR_IO; + } else if (r != (int)size) { + usbi_warn(DEVICE_CTX(dev), "short config descriptor read %d/%d", + r, (int)size); + } + + return r; +} + +/** \ingroup libusb_desc + * Get the USB device descriptor for a given device. + * + * This is a non-blocking function; the device descriptor is cached in memory. + * + * Note since libusb-1.0.16, \ref LIBUSB_API_VERSION >= 0x01000102, this + * function always succeeds. + * + * \param dev the device + * \param desc output location for the descriptor data + * \returns 0 on success or a LIBUSB_ERROR code on failure + */ +int API_EXPORTED libusb_get_device_descriptor(libusb_device *dev, + struct libusb_device_descriptor *desc) +{ + usbi_dbg(DEVICE_CTX(dev), " "); + static_assert(sizeof(dev->device_descriptor) == LIBUSB_DT_DEVICE_SIZE, + "struct libusb_device_descriptor is not expected size"); + *desc = dev->device_descriptor; + return 0; +} + +/** \ingroup libusb_desc + * Get the USB configuration descriptor for the currently active configuration. + * This is a non-blocking function which does not involve any requests being + * sent to the device. + * + * \param dev a device + * \param config output location for the USB configuration descriptor. Only + * valid if 0 was returned. Must be freed with libusb_free_config_descriptor() + * after use. + * \returns 0 on success + * \returns LIBUSB_ERROR_NOT_FOUND if the device is in unconfigured state + * \returns another LIBUSB_ERROR code on error + * \see libusb_get_config_descriptor + */ +int API_EXPORTED libusb_get_active_config_descriptor(libusb_device *dev, + struct libusb_config_descriptor **config) +{ + union usbi_config_desc_buf _config; + uint16_t config_len; + uint8_t *buf; + int r; + + r = get_active_config_descriptor(dev, _config.buf, sizeof(_config.buf)); + if (r < 0) + return r; + + config_len = libusb_le16_to_cpu(_config.desc.wTotalLength); + buf = malloc(config_len); + if (!buf) + return LIBUSB_ERROR_NO_MEM; + + r = get_active_config_descriptor(dev, buf, config_len); + if (r >= 0) + r = raw_desc_to_config(DEVICE_CTX(dev), buf, r, config); + + free(buf); + return r; +} + +/** \ingroup libusb_desc + * Get a USB configuration descriptor based on its index. + * This is a non-blocking function which does not involve any requests being + * sent to the device. + * + * \param dev a device + * \param config_index the index of the configuration you wish to retrieve + * \param config output location for the USB configuration descriptor. Only + * valid if 0 was returned. Must be freed with libusb_free_config_descriptor() + * after use. + * \returns 0 on success + * \returns LIBUSB_ERROR_NOT_FOUND if the configuration does not exist + * \returns another LIBUSB_ERROR code on error + * \see libusb_get_active_config_descriptor() + * \see libusb_get_config_descriptor_by_value() + */ +int API_EXPORTED libusb_get_config_descriptor(libusb_device *dev, + uint8_t config_index, struct libusb_config_descriptor **config) +{ + union usbi_config_desc_buf _config; + uint16_t config_len; + uint8_t *buf; + int r; + + usbi_dbg(DEVICE_CTX(dev), "index %u", config_index); + if (config_index >= dev->device_descriptor.bNumConfigurations) + return LIBUSB_ERROR_NOT_FOUND; + + r = get_config_descriptor(dev, config_index, _config.buf, sizeof(_config.buf)); + if (r < 0) + return r; + + config_len = libusb_le16_to_cpu(_config.desc.wTotalLength); + buf = malloc(config_len); + if (!buf) + return LIBUSB_ERROR_NO_MEM; + + r = get_config_descriptor(dev, config_index, buf, config_len); + if (r >= 0) + r = raw_desc_to_config(DEVICE_CTX(dev), buf, r, config); + + free(buf); + return r; +} + +/** \ingroup libusb_desc + * Get a USB configuration descriptor with a specific bConfigurationValue. + * This is a non-blocking function which does not involve any requests being + * sent to the device. + * + * \param dev a device + * \param bConfigurationValue the bConfigurationValue of the configuration you + * wish to retrieve + * \param config output location for the USB configuration descriptor. Only + * valid if 0 was returned. Must be freed with libusb_free_config_descriptor() + * after use. + * \returns 0 on success + * \returns LIBUSB_ERROR_NOT_FOUND if the configuration does not exist + * \returns another LIBUSB_ERROR code on error + * \see libusb_get_active_config_descriptor() + * \see libusb_get_config_descriptor() + */ +int API_EXPORTED libusb_get_config_descriptor_by_value(libusb_device *dev, + uint8_t bConfigurationValue, struct libusb_config_descriptor **config) +{ + uint8_t idx; + int r; + + if (usbi_backend.get_config_descriptor_by_value) { + void *buf; + + r = usbi_backend.get_config_descriptor_by_value(dev, + bConfigurationValue, &buf); + if (r < 0) + return r; + + return raw_desc_to_config(DEVICE_CTX(dev), buf, r, config); + } + + usbi_dbg(DEVICE_CTX(dev), "value %u", bConfigurationValue); + for (idx = 0; idx < dev->device_descriptor.bNumConfigurations; idx++) { + union usbi_config_desc_buf _config; + + r = get_config_descriptor(dev, idx, _config.buf, sizeof(_config.buf)); + if (r < 0) + return r; + + if (_config.desc.bConfigurationValue == bConfigurationValue) + return libusb_get_config_descriptor(dev, idx, config); + } + + return LIBUSB_ERROR_NOT_FOUND; +} + +/** \ingroup libusb_desc + * Free a configuration descriptor obtained from + * libusb_get_active_config_descriptor() or libusb_get_config_descriptor(). + * It is safe to call this function with a NULL config parameter, in which + * case the function simply returns. + * + * \param config the configuration descriptor to free + */ +void API_EXPORTED libusb_free_config_descriptor( + struct libusb_config_descriptor *config) +{ + if (!config) + return; + + clear_configuration(config); + free(config); +} + +/** \ingroup libusb_desc + * Get an endpoints superspeed endpoint companion descriptor (if any) + * + * \param ctx the context to operate on, or NULL for the default context + * \param endpoint endpoint descriptor from which to get the superspeed + * endpoint companion descriptor + * \param ep_comp output location for the superspeed endpoint companion + * descriptor. Only valid if 0 was returned. Must be freed with + * libusb_free_ss_endpoint_companion_descriptor() after use. + * \returns 0 on success + * \returns LIBUSB_ERROR_NOT_FOUND if the configuration does not exist + * \returns another LIBUSB_ERROR code on error + */ +int API_EXPORTED libusb_get_ss_endpoint_companion_descriptor( + libusb_context *ctx, + const struct libusb_endpoint_descriptor *endpoint, + struct libusb_ss_endpoint_companion_descriptor **ep_comp) +{ + struct usbi_descriptor_header *header; + const uint8_t *buffer = endpoint->extra; + int size = endpoint->extra_length; + + *ep_comp = NULL; + + while (size >= DESC_HEADER_LENGTH) { + header = (struct usbi_descriptor_header *)buffer; + if (header->bDescriptorType != LIBUSB_DT_SS_ENDPOINT_COMPANION) { + if (header->bLength < DESC_HEADER_LENGTH) { + usbi_err(ctx, "invalid descriptor length %u", + header->bLength); + return LIBUSB_ERROR_IO; + } + buffer += header->bLength; + size -= header->bLength; + continue; + } else if (header->bLength < LIBUSB_DT_SS_ENDPOINT_COMPANION_SIZE) { + usbi_err(ctx, "invalid ss-ep-comp-desc length %u", + header->bLength); + return LIBUSB_ERROR_IO; + } else if (header->bLength > size) { + usbi_err(ctx, "short ss-ep-comp-desc read %d/%u", + size, header->bLength); + return LIBUSB_ERROR_IO; + } + + *ep_comp = malloc(sizeof(**ep_comp)); + if (!*ep_comp) + return LIBUSB_ERROR_NO_MEM; + parse_descriptor(buffer, "bbbbw", *ep_comp); + return LIBUSB_SUCCESS; + } + return LIBUSB_ERROR_NOT_FOUND; +} + +/** \ingroup libusb_desc + * Free a superspeed endpoint companion descriptor obtained from + * libusb_get_ss_endpoint_companion_descriptor(). + * It is safe to call this function with a NULL ep_comp parameter, in which + * case the function simply returns. + * + * \param ep_comp the superspeed endpoint companion descriptor to free + */ +void API_EXPORTED libusb_free_ss_endpoint_companion_descriptor( + struct libusb_ss_endpoint_companion_descriptor *ep_comp) +{ + free(ep_comp); +} + +static int parse_bos(struct libusb_context *ctx, + struct libusb_bos_descriptor **bos, + const uint8_t *buffer, int size) +{ + struct libusb_bos_descriptor *_bos; + const struct usbi_bos_descriptor *bos_desc; + const struct usbi_descriptor_header *header; + uint8_t i; + + if (size < LIBUSB_DT_BOS_SIZE) { + usbi_err(ctx, "short bos descriptor read %d/%d", + size, LIBUSB_DT_BOS_SIZE); + return LIBUSB_ERROR_IO; + } + + bos_desc = (const struct usbi_bos_descriptor *)buffer; + if (bos_desc->bDescriptorType != LIBUSB_DT_BOS) { + usbi_err(ctx, "unexpected descriptor 0x%x (expected 0x%x)", + bos_desc->bDescriptorType, LIBUSB_DT_BOS); + return LIBUSB_ERROR_IO; + } else if (bos_desc->bLength < LIBUSB_DT_BOS_SIZE) { + usbi_err(ctx, "invalid bos bLength (%u)", bos_desc->bLength); + return LIBUSB_ERROR_IO; + } else if (bos_desc->bLength > size) { + usbi_err(ctx, "short bos descriptor read %d/%u", + size, bos_desc->bLength); + return LIBUSB_ERROR_IO; + } + + _bos = calloc(1, sizeof(*_bos) + bos_desc->bNumDeviceCaps * sizeof(void *)); + if (!_bos) + return LIBUSB_ERROR_NO_MEM; + + parse_descriptor(buffer, "bbwb", _bos); + buffer += _bos->bLength; + size -= _bos->bLength; + + /* Get the device capability descriptors */ + for (i = 0; i < _bos->bNumDeviceCaps; i++) { + if (size < LIBUSB_DT_DEVICE_CAPABILITY_SIZE) { + usbi_warn(ctx, "short dev-cap descriptor read %d/%d", + size, LIBUSB_DT_DEVICE_CAPABILITY_SIZE); + break; + } + header = (const struct usbi_descriptor_header *)buffer; + if (header->bDescriptorType != LIBUSB_DT_DEVICE_CAPABILITY) { + usbi_warn(ctx, "unexpected descriptor 0x%x (expected 0x%x)", + header->bDescriptorType, LIBUSB_DT_DEVICE_CAPABILITY); + break; + } else if (header->bLength < LIBUSB_DT_DEVICE_CAPABILITY_SIZE) { + usbi_err(ctx, "invalid dev-cap bLength (%u)", + header->bLength); + libusb_free_bos_descriptor(_bos); + return LIBUSB_ERROR_IO; + } else if (header->bLength > size) { + usbi_warn(ctx, "short dev-cap descriptor read %d/%u", + size, header->bLength); + break; + } + + _bos->dev_capability[i] = malloc(header->bLength); + if (!_bos->dev_capability[i]) { + libusb_free_bos_descriptor(_bos); + return LIBUSB_ERROR_NO_MEM; + } + memcpy(_bos->dev_capability[i], buffer, header->bLength); + buffer += header->bLength; + size -= header->bLength; + } + _bos->bNumDeviceCaps = i; + *bos = _bos; + + return LIBUSB_SUCCESS; +} + +/** \ingroup libusb_desc + * Get a Binary Object Store (BOS) descriptor + * This is a BLOCKING function, which will send requests to the device. + * + * \param dev_handle the handle of an open libusb device + * \param bos output location for the BOS descriptor. Only valid if 0 was returned. + * Must be freed with \ref libusb_free_bos_descriptor() after use. + * \returns 0 on success + * \returns LIBUSB_ERROR_NOT_FOUND if the device doesn't have a BOS descriptor + * \returns another LIBUSB_ERROR code on error + */ +int API_EXPORTED libusb_get_bos_descriptor(libusb_device_handle *dev_handle, + struct libusb_bos_descriptor **bos) +{ + union usbi_bos_desc_buf _bos; + uint16_t bos_len; + uint8_t *bos_data; + int r; + struct libusb_context *ctx = HANDLE_CTX(dev_handle); + + /* Read the BOS. This generates 2 requests on the bus, + * one for the header, and one for the full BOS */ + r = libusb_get_descriptor(dev_handle, LIBUSB_DT_BOS, 0, _bos.buf, sizeof(_bos.buf)); + if (r < 0) { + if (r != LIBUSB_ERROR_PIPE) + usbi_err(ctx, "failed to read BOS (%d)", r); + return r; + } + if (r < LIBUSB_DT_BOS_SIZE) { + usbi_err(ctx, "short BOS read %d/%d", + r, LIBUSB_DT_BOS_SIZE); + return LIBUSB_ERROR_IO; + } + + bos_len = libusb_le16_to_cpu(_bos.desc.wTotalLength); + usbi_dbg(ctx, "found BOS descriptor: size %u bytes, %u capabilities", + bos_len, _bos.desc.bNumDeviceCaps); + bos_data = calloc(1, bos_len); + if (!bos_data) + return LIBUSB_ERROR_NO_MEM; + + r = libusb_get_descriptor(dev_handle, LIBUSB_DT_BOS, 0, bos_data, bos_len); + if (r >= 0) { + if (r != (int)bos_len) + usbi_warn(ctx, "short BOS read %d/%u", r, bos_len); + r = parse_bos(HANDLE_CTX(dev_handle), bos, bos_data, r); + } else { + usbi_err(ctx, "failed to read BOS (%d)", r); + } + + free(bos_data); + return r; +} + +/** \ingroup libusb_desc + * Free a BOS descriptor obtained from libusb_get_bos_descriptor(). + * It is safe to call this function with a NULL bos parameter, in which + * case the function simply returns. + * + * \param bos the BOS descriptor to free + */ +void API_EXPORTED libusb_free_bos_descriptor(struct libusb_bos_descriptor *bos) +{ + uint8_t i; + + if (!bos) + return; + + for (i = 0; i < bos->bNumDeviceCaps; i++) + free(bos->dev_capability[i]); + free(bos); +} + +/** \ingroup libusb_desc + * Get an USB 2.0 Extension descriptor + * + * \param ctx the context to operate on, or NULL for the default context + * \param dev_cap Device Capability descriptor with a bDevCapabilityType of + * \ref libusb_capability_type::LIBUSB_BT_USB_2_0_EXTENSION + * LIBUSB_BT_USB_2_0_EXTENSION + * \param usb_2_0_extension output location for the USB 2.0 Extension + * descriptor. Only valid if 0 was returned. Must be freed with + * libusb_free_usb_2_0_extension_descriptor() after use. + * \returns 0 on success + * \returns a LIBUSB_ERROR code on error + */ +int API_EXPORTED libusb_get_usb_2_0_extension_descriptor( + libusb_context *ctx, + struct libusb_bos_dev_capability_descriptor *dev_cap, + struct libusb_usb_2_0_extension_descriptor **usb_2_0_extension) +{ + struct libusb_usb_2_0_extension_descriptor *_usb_2_0_extension; + + if (dev_cap->bDevCapabilityType != LIBUSB_BT_USB_2_0_EXTENSION) { + usbi_err(ctx, "unexpected bDevCapabilityType 0x%x (expected 0x%x)", + dev_cap->bDevCapabilityType, + LIBUSB_BT_USB_2_0_EXTENSION); + return LIBUSB_ERROR_INVALID_PARAM; + } else if (dev_cap->bLength < LIBUSB_BT_USB_2_0_EXTENSION_SIZE) { + usbi_err(ctx, "short dev-cap descriptor read %u/%d", + dev_cap->bLength, LIBUSB_BT_USB_2_0_EXTENSION_SIZE); + return LIBUSB_ERROR_IO; + } + + _usb_2_0_extension = malloc(sizeof(*_usb_2_0_extension)); + if (!_usb_2_0_extension) + return LIBUSB_ERROR_NO_MEM; + + parse_descriptor(dev_cap, "bbbd", _usb_2_0_extension); + + *usb_2_0_extension = _usb_2_0_extension; + return LIBUSB_SUCCESS; +} + +/** \ingroup libusb_desc + * Free a USB 2.0 Extension descriptor obtained from + * libusb_get_usb_2_0_extension_descriptor(). + * It is safe to call this function with a NULL usb_2_0_extension parameter, + * in which case the function simply returns. + * + * \param usb_2_0_extension the USB 2.0 Extension descriptor to free + */ +void API_EXPORTED libusb_free_usb_2_0_extension_descriptor( + struct libusb_usb_2_0_extension_descriptor *usb_2_0_extension) +{ + free(usb_2_0_extension); +} + +/** \ingroup libusb_desc + * Get a SuperSpeed USB Device Capability descriptor + * + * \param ctx the context to operate on, or NULL for the default context + * \param dev_cap Device Capability descriptor with a bDevCapabilityType of + * \ref libusb_capability_type::LIBUSB_BT_SS_USB_DEVICE_CAPABILITY + * LIBUSB_BT_SS_USB_DEVICE_CAPABILITY + * \param ss_usb_device_cap output location for the SuperSpeed USB Device + * Capability descriptor. Only valid if 0 was returned. Must be freed with + * libusb_free_ss_usb_device_capability_descriptor() after use. + * \returns 0 on success + * \returns a LIBUSB_ERROR code on error + */ +int API_EXPORTED libusb_get_ss_usb_device_capability_descriptor( + libusb_context *ctx, + struct libusb_bos_dev_capability_descriptor *dev_cap, + struct libusb_ss_usb_device_capability_descriptor **ss_usb_device_cap) +{ + struct libusb_ss_usb_device_capability_descriptor *_ss_usb_device_cap; + + if (dev_cap->bDevCapabilityType != LIBUSB_BT_SS_USB_DEVICE_CAPABILITY) { + usbi_err(ctx, "unexpected bDevCapabilityType 0x%x (expected 0x%x)", + dev_cap->bDevCapabilityType, + LIBUSB_BT_SS_USB_DEVICE_CAPABILITY); + return LIBUSB_ERROR_INVALID_PARAM; + } else if (dev_cap->bLength < LIBUSB_BT_SS_USB_DEVICE_CAPABILITY_SIZE) { + usbi_err(ctx, "short dev-cap descriptor read %u/%d", + dev_cap->bLength, LIBUSB_BT_SS_USB_DEVICE_CAPABILITY_SIZE); + return LIBUSB_ERROR_IO; + } + + _ss_usb_device_cap = malloc(sizeof(*_ss_usb_device_cap)); + if (!_ss_usb_device_cap) + return LIBUSB_ERROR_NO_MEM; + + parse_descriptor(dev_cap, "bbbbwbbw", _ss_usb_device_cap); + + *ss_usb_device_cap = _ss_usb_device_cap; + return LIBUSB_SUCCESS; +} + +/** \ingroup libusb_desc + * Free a SuperSpeed USB Device Capability descriptor obtained from + * libusb_get_ss_usb_device_capability_descriptor(). + * It is safe to call this function with a NULL ss_usb_device_cap + * parameter, in which case the function simply returns. + * + * \param ss_usb_device_cap the SuperSpeed USB Device Capability descriptor + * to free + */ +void API_EXPORTED libusb_free_ss_usb_device_capability_descriptor( + struct libusb_ss_usb_device_capability_descriptor *ss_usb_device_cap) +{ + free(ss_usb_device_cap); +} + +/** \ingroup libusb_desc + * Get a Container ID descriptor + * + * \param ctx the context to operate on, or NULL for the default context + * \param dev_cap Device Capability descriptor with a bDevCapabilityType of + * \ref libusb_capability_type::LIBUSB_BT_CONTAINER_ID + * LIBUSB_BT_CONTAINER_ID + * \param container_id output location for the Container ID descriptor. + * Only valid if 0 was returned. Must be freed with + * libusb_free_container_id_descriptor() after use. + * \returns 0 on success + * \returns a LIBUSB_ERROR code on error + */ +int API_EXPORTED libusb_get_container_id_descriptor(libusb_context *ctx, + struct libusb_bos_dev_capability_descriptor *dev_cap, + struct libusb_container_id_descriptor **container_id) +{ + struct libusb_container_id_descriptor *_container_id; + + if (dev_cap->bDevCapabilityType != LIBUSB_BT_CONTAINER_ID) { + usbi_err(ctx, "unexpected bDevCapabilityType 0x%x (expected 0x%x)", + dev_cap->bDevCapabilityType, + LIBUSB_BT_CONTAINER_ID); + return LIBUSB_ERROR_INVALID_PARAM; + } else if (dev_cap->bLength < LIBUSB_BT_CONTAINER_ID_SIZE) { + usbi_err(ctx, "short dev-cap descriptor read %u/%d", + dev_cap->bLength, LIBUSB_BT_CONTAINER_ID_SIZE); + return LIBUSB_ERROR_IO; + } + + _container_id = malloc(sizeof(*_container_id)); + if (!_container_id) + return LIBUSB_ERROR_NO_MEM; + + parse_descriptor(dev_cap, "bbbbu", _container_id); + + *container_id = _container_id; + return LIBUSB_SUCCESS; +} + +/** \ingroup libusb_desc + * Free a Container ID descriptor obtained from + * libusb_get_container_id_descriptor(). + * It is safe to call this function with a NULL container_id parameter, + * in which case the function simply returns. + * + * \param container_id the Container ID descriptor to free + */ +void API_EXPORTED libusb_free_container_id_descriptor( + struct libusb_container_id_descriptor *container_id) +{ + free(container_id); +} + +/** \ingroup libusb_desc + * Retrieve a string descriptor in C style ASCII. + * + * Wrapper around libusb_get_string_descriptor(). Uses the first language + * supported by the device. + * + * \param dev_handle a device handle + * \param desc_index the index of the descriptor to retrieve + * \param data output buffer for ASCII string descriptor + * \param length size of data buffer + * \returns number of bytes returned in data, or LIBUSB_ERROR code on failure + */ +int API_EXPORTED libusb_get_string_descriptor_ascii(libusb_device_handle *dev_handle, + uint8_t desc_index, unsigned char *data, int length) +{ + union usbi_string_desc_buf str; + int r, si, di; + uint16_t langid, wdata; + + /* Asking for the zero'th index is special - it returns a string + * descriptor that contains all the language IDs supported by the + * device. Typically there aren't many - often only one. Language + * IDs are 16 bit numbers, and they start at the third byte in the + * descriptor. There's also no point in trying to read descriptor 0 + * with this function. See USB 2.0 specification section 9.6.7 for + * more information. + */ + + if (desc_index == 0) + return LIBUSB_ERROR_INVALID_PARAM; + + r = libusb_get_string_descriptor(dev_handle, 0, 0, str.buf, 4); + if (r < 0) + return r; + else if (r != 4 || str.desc.bLength < 4) + return LIBUSB_ERROR_IO; + else if (str.desc.bDescriptorType != LIBUSB_DT_STRING) + return LIBUSB_ERROR_IO; + else if (str.desc.bLength & 1) + usbi_warn(HANDLE_CTX(dev_handle), "suspicious bLength %u for language ID string descriptor", str.desc.bLength); + + langid = libusb_le16_to_cpu(str.desc.wData[0]); + r = libusb_get_string_descriptor(dev_handle, desc_index, langid, str.buf, sizeof(str.buf)); + if (r < 0) + return r; + else if (r < DESC_HEADER_LENGTH || str.desc.bLength > r) + return LIBUSB_ERROR_IO; + else if (str.desc.bDescriptorType != LIBUSB_DT_STRING) + return LIBUSB_ERROR_IO; + else if ((str.desc.bLength & 1) || str.desc.bLength != r) + usbi_warn(HANDLE_CTX(dev_handle), "suspicious bLength %u for string descriptor (read %d)", str.desc.bLength, r); + + di = 0; + for (si = 2; si < str.desc.bLength; si += 2) { + if (di >= (length - 1)) + break; + + wdata = libusb_le16_to_cpu(str.desc.wData[di]); + if (wdata < 0x80) + data[di++] = (unsigned char)wdata; + else + data[di++] = '?'; /* non-ASCII */ + } + + data[di] = 0; + return di; +} diff --git a/Radio/HW/RtlSdr/libusb/hotplug.c b/Radio/HW/RtlSdr/libusb/hotplug.c new file mode 100644 index 0000000..402e693 --- /dev/null +++ b/Radio/HW/RtlSdr/libusb/hotplug.c @@ -0,0 +1,466 @@ +/* -*- Mode: C; indent-tabs-mode:t ; c-basic-offset:8 -*- */ +/* + * Hotplug functions for libusb + * Copyright © 2012-2021 Nathan Hjelm <hjelmn@mac.com> + * Copyright © 2012-2013 Peter Stuge <peter@stuge.se> + * + * 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 "libusbi.h" + +/** + * @defgroup libusb_hotplug Device hotplug event notification + * This page details how to use the libusb hotplug interface, where available. + * + * Be mindful that not all platforms currently implement hotplug notification and + * that you should first call on \ref libusb_has_capability() with parameter + * \ref LIBUSB_CAP_HAS_HOTPLUG to confirm that hotplug support is available. + * + * \page libusb_hotplug Device hotplug event notification + * + * \section hotplug_intro Introduction + * + * Version 1.0.16, \ref LIBUSB_API_VERSION >= 0x01000102, has added support + * for hotplug events on <b>some</b> platforms (you should test if your platform + * supports hotplug notification by calling \ref libusb_has_capability() with + * parameter \ref LIBUSB_CAP_HAS_HOTPLUG). + * + * This interface allows you to request notification for the arrival and departure + * of matching USB devices. + * + * To receive hotplug notification you register a callback by calling + * \ref libusb_hotplug_register_callback(). This function will optionally return + * a callback handle that can be passed to \ref libusb_hotplug_deregister_callback(). + * + * A callback function must return an int (0 or 1) indicating whether the callback is + * expecting additional events. Returning 0 will rearm the callback and 1 will cause + * the callback to be deregistered. Note that when callbacks are called from + * libusb_hotplug_register_callback() because of the \ref LIBUSB_HOTPLUG_ENUMERATE + * flag, the callback return value is ignored. In other words, you cannot cause a + * callback to be deregistered by returning 1 when it is called from + * libusb_hotplug_register_callback(). + * + * Callbacks for a particular context are automatically deregistered by libusb_exit(). + * + * As of 1.0.16 there are two supported hotplug events: + * - LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED: A device has arrived and is ready to use + * - LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT: A device has left and is no longer available + * + * A hotplug event can listen for either or both of these events. + * + * Note: If you receive notification that a device has left and you have any + * libusb_device_handles for the device it is up to you to call libusb_close() + * on each device handle to free up any remaining resources associated with the device. + * Once a device has left any libusb_device_handle associated with the device + * are invalid and will remain so even if the device comes back. + * + * When handling a LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED event it is considered + * safe to call any libusb function that takes a libusb_device. It also safe to + * open a device and submit asynchronous transfers. However, most other functions + * that take a libusb_device_handle are <b>not</b> safe to call. Examples of such + * functions are any of the \ref libusb_syncio "synchronous API" functions or the blocking + * functions that retrieve various \ref libusb_desc "USB descriptors". These functions must + * be used outside of the context of the hotplug callback. + * + * When handling a LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT event the only safe function + * is libusb_get_device_descriptor(). + * + * The following code provides an example of the usage of the hotplug interface: +\code +#include <stdio.h> +#include <stdlib.h> +#include <time.h> +#include <libusb.h> + +static int count = 0; + +int hotplug_callback(struct libusb_context *ctx, struct libusb_device *dev, + libusb_hotplug_event event, void *user_data) { + static libusb_device_handle *dev_handle = NULL; + struct libusb_device_descriptor desc; + int rc; + + (void)libusb_get_device_descriptor(dev, &desc); + + if (LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED == event) { + rc = libusb_open(dev, &dev_handle); + if (LIBUSB_SUCCESS != rc) { + printf("Could not open USB device\n"); + } + } else if (LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT == event) { + if (dev_handle) { + libusb_close(dev_handle); + dev_handle = NULL; + } + } else { + printf("Unhandled event %d\n", event); + } + count++; + + return 0; +} + +int main (void) { + libusb_hotplug_callback_handle callback_handle; + int rc; + + libusb_init(NULL); + + rc = libusb_hotplug_register_callback(NULL, LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED | + LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT, 0, 0x045a, 0x5005, + LIBUSB_HOTPLUG_MATCH_ANY, hotplug_callback, NULL, + &callback_handle); + if (LIBUSB_SUCCESS != rc) { + printf("Error creating a hotplug callback\n"); + libusb_exit(NULL); + return EXIT_FAILURE; + } + + while (count < 2) { + libusb_handle_events_completed(NULL, NULL); + nanosleep(&(struct timespec){0, 10000000UL}, NULL); + } + + libusb_hotplug_deregister_callback(NULL, callback_handle); + libusb_exit(NULL); + + return 0; +} +\endcode + */ + +#define VALID_HOTPLUG_EVENTS \ + (LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED | \ + LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT) + +#define VALID_HOTPLUG_FLAGS \ + (LIBUSB_HOTPLUG_ENUMERATE) + +void usbi_hotplug_init(struct libusb_context *ctx) +{ + /* check for hotplug support */ + if (!libusb_has_capability(LIBUSB_CAP_HAS_HOTPLUG)) + return; + + usbi_mutex_init(&ctx->hotplug_cbs_lock); + list_init(&ctx->hotplug_cbs); + ctx->next_hotplug_cb_handle = 1; + usbi_atomic_store(&ctx->hotplug_ready, 1); +} + +void usbi_hotplug_exit(struct libusb_context *ctx) +{ + struct usbi_hotplug_callback *hotplug_cb, *next_cb; + struct usbi_hotplug_message *msg; + struct libusb_device *dev, *next_dev; + + /* check for hotplug support */ + if (!libusb_has_capability(LIBUSB_CAP_HAS_HOTPLUG)) + return; + + /* free all registered hotplug callbacks */ + for_each_hotplug_cb_safe(ctx, hotplug_cb, next_cb) { + list_del(&hotplug_cb->list); + free(hotplug_cb); + } + + /* free all pending hotplug messages */ + while (!list_empty(&ctx->hotplug_msgs)) { + msg = list_first_entry(&ctx->hotplug_msgs, struct usbi_hotplug_message, list); + + /* if the device left, the message holds a reference + * and we must drop it */ + if (msg->event == LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT) + libusb_unref_device(msg->device); + + list_del(&msg->list); + free(msg); + } + + /* free all discovered devices. due to parent references loop until no devices are freed. */ + for_each_device_safe(ctx, dev, next_dev) { + /* remove the device from the usb_devs list only if there are no + * references held, otherwise leave it on the list so that a + * warning message will be shown */ + if (usbi_atomic_load(&dev->refcnt) == 1) { + list_del(&dev->list); + } + if (dev->parent_dev && usbi_atomic_load(&dev->parent_dev->refcnt) == 1) { + /* the parent was before this device in the list and will be released. + remove it from the list. this is safe as parent_dev can not be + equal to next_dev. */ + assert (dev->parent_dev != next_dev); + list_del(&dev->parent_dev->list); + } + libusb_unref_device(dev); + } + + usbi_mutex_destroy(&ctx->hotplug_cbs_lock); +} + +static int usbi_hotplug_match_cb(struct libusb_device *dev, + libusb_hotplug_event event, struct usbi_hotplug_callback *hotplug_cb) +{ + if (!(hotplug_cb->flags & event)) { + return 0; + } + + if ((hotplug_cb->flags & USBI_HOTPLUG_VENDOR_ID_VALID) && + hotplug_cb->vendor_id != dev->device_descriptor.idVendor) { + return 0; + } + + if ((hotplug_cb->flags & USBI_HOTPLUG_PRODUCT_ID_VALID) && + hotplug_cb->product_id != dev->device_descriptor.idProduct) { + return 0; + } + + if ((hotplug_cb->flags & USBI_HOTPLUG_DEV_CLASS_VALID) && + hotplug_cb->dev_class != dev->device_descriptor.bDeviceClass) { + return 0; + } + + return hotplug_cb->cb(DEVICE_CTX(dev), dev, event, hotplug_cb->user_data); +} + +void usbi_hotplug_notification(struct libusb_context *ctx, struct libusb_device *dev, + libusb_hotplug_event event) +{ + struct usbi_hotplug_message *msg; + unsigned int event_flags; + + /* Only generate a notification if hotplug is ready. This prevents hotplug + * notifications from being generated during initial enumeration or if the + * backend does not support hotplug. */ + if (!usbi_atomic_load(&ctx->hotplug_ready)) + return; + + msg = calloc(1, sizeof(*msg)); + if (!msg) { + usbi_err(ctx, "error allocating hotplug message"); + return; + } + + msg->event = event; + msg->device = dev; + + /* Take the event data lock and add this message to the list. + * Only signal an event if there are no prior pending events. */ + usbi_mutex_lock(&ctx->event_data_lock); + event_flags = ctx->event_flags; + ctx->event_flags |= USBI_EVENT_HOTPLUG_MSG_PENDING; + list_add_tail(&msg->list, &ctx->hotplug_msgs); + if (!event_flags) + usbi_signal_event(&ctx->event); + usbi_mutex_unlock(&ctx->event_data_lock); +} + +void usbi_hotplug_process(struct libusb_context *ctx, struct list_head *hotplug_msgs) +{ + struct usbi_hotplug_callback *hotplug_cb, *next_cb; + struct usbi_hotplug_message *msg; + int r; + + usbi_mutex_lock(&ctx->hotplug_cbs_lock); + + /* dispatch all pending hotplug messages */ + while (!list_empty(hotplug_msgs)) { + msg = list_first_entry(hotplug_msgs, struct usbi_hotplug_message, list); + + for_each_hotplug_cb_safe(ctx, hotplug_cb, next_cb) { + /* skip callbacks that have unregistered */ + if (hotplug_cb->flags & USBI_HOTPLUG_NEEDS_FREE) + continue; + + usbi_mutex_unlock(&ctx->hotplug_cbs_lock); + r = usbi_hotplug_match_cb(msg->device, msg->event, hotplug_cb); + usbi_mutex_lock(&ctx->hotplug_cbs_lock); + + if (r) { + list_del(&hotplug_cb->list); + free(hotplug_cb); + } + } + + /* if the device left, the message holds a reference + * and we must drop it */ + if (msg->event == LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT) + libusb_unref_device(msg->device); + + list_del(&msg->list); + free(msg); + } + + /* free any callbacks that have unregistered */ + for_each_hotplug_cb_safe(ctx, hotplug_cb, next_cb) { + if (hotplug_cb->flags & USBI_HOTPLUG_NEEDS_FREE) { + usbi_dbg(ctx, "freeing hotplug cb %p with handle %d", + hotplug_cb, hotplug_cb->handle); + list_del(&hotplug_cb->list); + free(hotplug_cb); + } + } + + usbi_mutex_unlock(&ctx->hotplug_cbs_lock); +} + +int API_EXPORTED libusb_hotplug_register_callback(libusb_context *ctx, + int events, int flags, + int vendor_id, int product_id, int dev_class, + libusb_hotplug_callback_fn cb_fn, void *user_data, + libusb_hotplug_callback_handle *callback_handle) +{ + struct usbi_hotplug_callback *hotplug_cb; + + /* check for sane values */ + if (!events || (~VALID_HOTPLUG_EVENTS & events) || + (~VALID_HOTPLUG_FLAGS & flags) || + (LIBUSB_HOTPLUG_MATCH_ANY != vendor_id && (~0xffff & vendor_id)) || + (LIBUSB_HOTPLUG_MATCH_ANY != product_id && (~0xffff & product_id)) || + (LIBUSB_HOTPLUG_MATCH_ANY != dev_class && (~0xff & dev_class)) || + !cb_fn) { + return LIBUSB_ERROR_INVALID_PARAM; + } + + /* check for hotplug support */ + if (!libusb_has_capability(LIBUSB_CAP_HAS_HOTPLUG)) + return LIBUSB_ERROR_NOT_SUPPORTED; + + ctx = usbi_get_context(ctx); + + hotplug_cb = calloc(1, sizeof(*hotplug_cb)); + if (!hotplug_cb) + return LIBUSB_ERROR_NO_MEM; + + hotplug_cb->flags = (uint8_t)events; + if (LIBUSB_HOTPLUG_MATCH_ANY != vendor_id) { + hotplug_cb->flags |= USBI_HOTPLUG_VENDOR_ID_VALID; + hotplug_cb->vendor_id = (uint16_t)vendor_id; + } + if (LIBUSB_HOTPLUG_MATCH_ANY != product_id) { + hotplug_cb->flags |= USBI_HOTPLUG_PRODUCT_ID_VALID; + hotplug_cb->product_id = (uint16_t)product_id; + } + if (LIBUSB_HOTPLUG_MATCH_ANY != dev_class) { + hotplug_cb->flags |= USBI_HOTPLUG_DEV_CLASS_VALID; + hotplug_cb->dev_class = (uint8_t)dev_class; + } + hotplug_cb->cb = cb_fn; + hotplug_cb->user_data = user_data; + + usbi_mutex_lock(&ctx->hotplug_cbs_lock); + + /* protect the handle by the context hotplug lock */ + hotplug_cb->handle = ctx->next_hotplug_cb_handle++; + + /* handle the unlikely case of overflow */ + if (ctx->next_hotplug_cb_handle < 0) + ctx->next_hotplug_cb_handle = 1; + + list_add(&hotplug_cb->list, &ctx->hotplug_cbs); + + usbi_mutex_unlock(&ctx->hotplug_cbs_lock); + + usbi_dbg(ctx, "new hotplug cb %p with handle %d", hotplug_cb, hotplug_cb->handle); + + if ((flags & LIBUSB_HOTPLUG_ENUMERATE) && (events & LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED)) { + ssize_t i, len; + struct libusb_device **devs; + + len = libusb_get_device_list(ctx, &devs); + if (len < 0) { + libusb_hotplug_deregister_callback(ctx, hotplug_cb->handle); + return (int)len; + } + + for (i = 0; i < len; i++) { + usbi_hotplug_match_cb(devs[i], + LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED, + hotplug_cb); + } + + libusb_free_device_list(devs, 1); + } + + if (callback_handle) + *callback_handle = hotplug_cb->handle; + + return LIBUSB_SUCCESS; +} + +void API_EXPORTED libusb_hotplug_deregister_callback(libusb_context *ctx, + libusb_hotplug_callback_handle callback_handle) +{ + struct usbi_hotplug_callback *hotplug_cb; + int deregistered = 0; + + /* check for hotplug support */ + if (!libusb_has_capability(LIBUSB_CAP_HAS_HOTPLUG)) + return; + + usbi_dbg(ctx, "deregister hotplug cb %d", callback_handle); + + ctx = usbi_get_context(ctx); + + usbi_mutex_lock(&ctx->hotplug_cbs_lock); + for_each_hotplug_cb(ctx, hotplug_cb) { + if (callback_handle == hotplug_cb->handle) { + /* mark this callback for deregistration */ + hotplug_cb->flags |= USBI_HOTPLUG_NEEDS_FREE; + deregistered = 1; + break; + } + } + usbi_mutex_unlock(&ctx->hotplug_cbs_lock); + + if (deregistered) { + unsigned int event_flags; + + usbi_mutex_lock(&ctx->event_data_lock); + event_flags = ctx->event_flags; + ctx->event_flags |= USBI_EVENT_HOTPLUG_CB_DEREGISTERED; + if (!event_flags) + usbi_signal_event(&ctx->event); + usbi_mutex_unlock(&ctx->event_data_lock); + } +} + +DEFAULT_VISIBILITY +void * LIBUSB_CALL libusb_hotplug_get_user_data(libusb_context *ctx, + libusb_hotplug_callback_handle callback_handle) +{ + struct usbi_hotplug_callback *hotplug_cb; + void *user_data = NULL; + + /* check for hotplug support */ + if (!libusb_has_capability(LIBUSB_CAP_HAS_HOTPLUG)) + return NULL; + + usbi_dbg(ctx, "get hotplug cb %d user data", callback_handle); + + ctx = usbi_get_context(ctx); + + usbi_mutex_lock(&ctx->hotplug_cbs_lock); + for_each_hotplug_cb(ctx, hotplug_cb) { + if (callback_handle == hotplug_cb->handle) { + user_data = hotplug_cb->user_data; + break; + } + } + usbi_mutex_unlock(&ctx->hotplug_cbs_lock); + + return user_data; +} diff --git a/Radio/HW/RtlSdr/libusb/io.c b/Radio/HW/RtlSdr/libusb/io.c new file mode 100644 index 0000000..0d2ac9e --- /dev/null +++ b/Radio/HW/RtlSdr/libusb/io.c @@ -0,0 +1,2829 @@ +/* -*- Mode: C; indent-tabs-mode:t ; c-basic-offset:8 -*- */ +/* + * I/O functions for libusb + * Copyright © 2007-2009 Daniel Drake <dsd@gentoo.org> + * Copyright © 2001 Johannes Erdfelt <johannes@erdfelt.com> + * Copyright © 2019 Nathan Hjelm <hjelmn@cs.umm.edu> + * Copyright © 2019 Google LLC. All rights reserved. + * + * 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 "libusbi.h" + +/** + * \page libusb_io Synchronous and asynchronous device I/O + * + * \section io_intro Introduction + * + * If you're using libusb in your application, you're probably wanting to + * perform I/O with devices - you want to perform USB data transfers. + * + * libusb offers two separate interfaces for device I/O. This page aims to + * introduce the two in order to help you decide which one is more suitable + * for your application. You can also choose to use both interfaces in your + * application by considering each transfer on a case-by-case basis. + * + * Once you have read through the following discussion, you should consult the + * detailed API documentation pages for the details: + * - \ref libusb_syncio + * - \ref libusb_asyncio + * + * \section theory Transfers at a logical level + * + * At a logical level, USB transfers typically happen in two parts. For + * example, when reading data from a endpoint: + * -# A request for data is sent to the device + * -# Some time later, the incoming data is received by the host + * + * or when writing data to an endpoint: + * + * -# The data is sent to the device + * -# Some time later, the host receives acknowledgement from the device that + * the data has been transferred. + * + * There may be an indefinite delay between the two steps. Consider a + * fictional USB input device with a button that the user can press. In order + * to determine when the button is pressed, you would likely submit a request + * to read data on a bulk or interrupt endpoint and wait for data to arrive. + * Data will arrive when the button is pressed by the user, which is + * potentially hours later. + * + * libusb offers both a synchronous and an asynchronous interface to performing + * USB transfers. The main difference is that the synchronous interface + * combines both steps indicated above into a single function call, whereas + * the asynchronous interface separates them. + * + * \section sync The synchronous interface + * + * The synchronous I/O interface allows you to perform a USB transfer with + * a single function call. When the function call returns, the transfer has + * completed and you can parse the results. + * + * If you have used libusb-0.1 before, this I/O style will seem familiar to + * you. libusb-0.1 only offered a synchronous interface. + * + * In our input device example, to read button presses you might write code + * in the following style: +\code +unsigned char data[4]; +int actual_length; +int r = libusb_bulk_transfer(dev_handle, LIBUSB_ENDPOINT_IN, data, sizeof(data), &actual_length, 0); +if (r == 0 && actual_length == sizeof(data)) { + // results of the transaction can now be found in the data buffer + // parse them here and report button press +} else { + error(); +} +\endcode + * + * The main advantage of this model is simplicity: you did everything with + * a single simple function call. + * + * However, this interface has its limitations. Your application will sleep + * inside libusb_bulk_transfer() until the transaction has completed. If it + * takes the user 3 hours to press the button, your application will be + * sleeping for that long. Execution will be tied up inside the library - + * the entire thread will be useless for that duration. + * + * Another issue is that by tying up the thread with that single transaction + * there is no possibility of performing I/O with multiple endpoints and/or + * multiple devices simultaneously, unless you resort to creating one thread + * per transaction. + * + * Additionally, there is no opportunity to cancel the transfer after the + * request has been submitted. + * + * For details on how to use the synchronous API, see the + * \ref libusb_syncio "synchronous I/O API documentation" pages. + * + * \section async The asynchronous interface + * + * Asynchronous I/O is the most significant new feature in libusb-1.0. + * Although it is a more complex interface, it solves all the issues detailed + * above. + * + * Instead of providing which functions that block until the I/O has complete, + * libusb's asynchronous interface presents non-blocking functions which + * begin a transfer and then return immediately. Your application passes a + * callback function pointer to this non-blocking function, which libusb will + * call with the results of the transaction when it has completed. + * + * Transfers which have been submitted through the non-blocking functions + * can be cancelled with a separate function call. + * + * The non-blocking nature of this interface allows you to be simultaneously + * performing I/O to multiple endpoints on multiple devices, without having + * to use threads. + * + * This added flexibility does come with some complications though: + * - In the interest of being a lightweight library, libusb does not create + * threads and can only operate when your application is calling into it. Your + * application must call into libusb from it's main loop when events are ready + * to be handled, or you must use some other scheme to allow libusb to + * undertake whatever work needs to be done. + * - libusb also needs to be called into at certain fixed points in time in + * order to accurately handle transfer timeouts. + * - Memory handling becomes more complex. You cannot use stack memory unless + * the function with that stack is guaranteed not to return until the transfer + * callback has finished executing. + * - You generally lose some linearity from your code flow because submitting + * the transfer request is done in a separate function from where the transfer + * results are handled. This becomes particularly obvious when you want to + * submit a second transfer based on the results of an earlier transfer. + * + * Internally, libusb's synchronous interface is expressed in terms of function + * calls to the asynchronous interface. + * + * For details on how to use the asynchronous API, see the + * \ref libusb_asyncio "asynchronous I/O API" documentation pages. + */ + + +/** + * \page libusb_packetoverflow Packets and overflows + * + * \section packets Packet abstraction + * + * The USB specifications describe how data is transmitted in packets, with + * constraints on packet size defined by endpoint descriptors. The host must + * not send data payloads larger than the endpoint's maximum packet size. + * + * libusb and the underlying OS abstract out the packet concept, allowing you + * to request transfers of any size. Internally, the request will be divided + * up into correctly-sized packets. You do not have to be concerned with + * packet sizes, but there is one exception when considering overflows. + * + * \section overflow Bulk/interrupt transfer overflows + * + * When requesting data on a bulk endpoint, libusb requires you to supply a + * buffer and the maximum number of bytes of data that libusb can put in that + * buffer. However, the size of the buffer is not communicated to the device - + * the device is just asked to send any amount of data. + * + * There is no problem if the device sends an amount of data that is less than + * or equal to the buffer size. libusb reports this condition to you through + * the \ref libusb_transfer::actual_length "libusb_transfer.actual_length" + * field. + * + * Problems may occur if the device attempts to send more data than can fit in + * the buffer. libusb reports LIBUSB_TRANSFER_OVERFLOW for this condition but + * other behaviour is largely undefined: actual_length may or may not be + * accurate, the chunk of data that can fit in the buffer (before overflow) + * may or may not have been transferred. + * + * Overflows are nasty, but can be avoided. Even though you were told to + * ignore packets above, think about the lower level details: each transfer is + * split into packets (typically small, with a maximum size of 512 bytes). + * Overflows can only happen if the final packet in an incoming data transfer + * is smaller than the actual packet that the device wants to transfer. + * Therefore, you will never see an overflow if your transfer buffer size is a + * multiple of the endpoint's packet size: the final packet will either + * fill up completely or will be only partially filled. + */ + +/** + * @defgroup libusb_asyncio Asynchronous device I/O + * + * This page details libusb's asynchronous (non-blocking) API for USB device + * I/O. This interface is very powerful but is also quite complex - you will + * need to read this page carefully to understand the necessary considerations + * and issues surrounding use of this interface. Simplistic applications + * may wish to consider the \ref libusb_syncio "synchronous I/O API" instead. + * + * The asynchronous interface is built around the idea of separating transfer + * submission and handling of transfer completion (the synchronous model + * combines both of these into one). There may be a long delay between + * submission and completion, however the asynchronous submission function + * is non-blocking so will return control to your application during that + * potentially long delay. + * + * \section asyncabstraction Transfer abstraction + * + * For the asynchronous I/O, libusb implements the concept of a generic + * transfer entity for all types of I/O (control, bulk, interrupt, + * isochronous). The generic transfer object must be treated slightly + * differently depending on which type of I/O you are performing with it. + * + * This is represented by the public libusb_transfer structure type. + * + * \section asynctrf Asynchronous transfers + * + * We can view asynchronous I/O as a 5 step process: + * -# <b>Allocation</b>: allocate a libusb_transfer + * -# <b>Filling</b>: populate the libusb_transfer instance with information + * about the transfer you wish to perform + * -# <b>Submission</b>: ask libusb to submit the transfer + * -# <b>Completion handling</b>: examine transfer results in the + * libusb_transfer structure + * -# <b>Deallocation</b>: clean up resources + * + * + * \subsection asyncalloc Allocation + * + * This step involves allocating memory for a USB transfer. This is the + * generic transfer object mentioned above. At this stage, the transfer + * is "blank" with no details about what type of I/O it will be used for. + * + * Allocation is done with the libusb_alloc_transfer() function. You must use + * this function rather than allocating your own transfers. + * + * \subsection asyncfill Filling + * + * This step is where you take a previously allocated transfer and fill it + * with information to determine the message type and direction, data buffer, + * callback function, etc. + * + * You can either fill the required fields yourself or you can use the + * helper functions: libusb_fill_control_transfer(), libusb_fill_bulk_transfer() + * and libusb_fill_interrupt_transfer(). + * + * \subsection asyncsubmit Submission + * + * When you have allocated a transfer and filled it, you can submit it using + * libusb_submit_transfer(). This function returns immediately but can be + * regarded as firing off the I/O request in the background. + * + * \subsection asynccomplete Completion handling + * + * After a transfer has been submitted, one of four things can happen to it: + * + * - The transfer completes (i.e. some data was transferred) + * - The transfer has a timeout and the timeout expires before all data is + * transferred + * - The transfer fails due to an error + * - The transfer is cancelled + * + * Each of these will cause the user-specified transfer callback function to + * be invoked. It is up to the callback function to determine which of the + * above actually happened and to act accordingly. + * + * The user-specified callback is passed a pointer to the libusb_transfer + * structure which was used to setup and submit the transfer. At completion + * time, libusb has populated this structure with results of the transfer: + * success or failure reason, number of bytes of data transferred, etc. See + * the libusb_transfer structure documentation for more information. + * + * <b>Important Note</b>: The user-specified callback is called from an event + * handling context. It is therefore important that no calls are made into + * libusb that will attempt to perform any event handling. Examples of such + * functions are any listed in the \ref libusb_syncio "synchronous API" and any of + * the blocking functions that retrieve \ref libusb_desc "USB descriptors". + * + * \subsection Deallocation + * + * When a transfer has completed (i.e. the callback function has been invoked), + * you are advised to free the transfer (unless you wish to resubmit it, see + * below). Transfers are deallocated with libusb_free_transfer(). + * + * It is undefined behaviour to free a transfer which has not completed. + * + * \section asyncresubmit Resubmission + * + * You may be wondering why allocation, filling, and submission are all + * separated above where they could reasonably be combined into a single + * operation. + * + * The reason for separation is to allow you to resubmit transfers without + * having to allocate new ones every time. This is especially useful for + * common situations dealing with interrupt endpoints - you allocate one + * transfer, fill and submit it, and when it returns with results you just + * resubmit it for the next interrupt. + * + * \section asynccancel Cancellation + * + * Another advantage of using the asynchronous interface is that you have + * the ability to cancel transfers which have not yet completed. This is + * done by calling the libusb_cancel_transfer() function. + * + * libusb_cancel_transfer() is asynchronous/non-blocking in itself. When the + * cancellation actually completes, the transfer's callback function will + * be invoked, and the callback function should check the transfer status to + * determine that it was cancelled. + * + * Freeing the transfer after it has been cancelled but before cancellation + * has completed will result in undefined behaviour. + * + * \attention + * When a transfer is cancelled, some of the data may have been transferred. + * libusb will communicate this to you in the transfer callback. + * <b>Do not assume that no data was transferred.</b> + * + * \section asyncpartial Partial data transfer resulting from cancellation + * + * As noted above, some of the data may have been transferred at the time a + * transfer is cancelled. It is helpful to see how this is possible if you + * consider a bulk transfer to an endpoint with a packet size of 64 bytes. + * Supposing you submit a 512-byte transfer to this endpoint, the operating + * system will divide this transfer up into 8 separate 64-byte frames that the + * host controller will schedule for the device to transfer data. If this + * transfer is cancelled while the device is transferring data, a subset of + * these frames may be descheduled from the host controller before the device + * has the opportunity to finish transferring data to the host. + * + * What your application should do with a partial data transfer is a policy + * decision; there is no single answer that satisfies the needs of every + * application. The data that was successfully transferred should be + * considered entirely valid, but your application must decide what to do with + * the remaining data that was not transferred. Some possible actions to take + * are: + * - Resubmit another transfer for the remaining data, possibly with a shorter + * timeout + * - Discard the partially transferred data and report an error + * + * \section asynctimeout Timeouts + * + * When a transfer times out, libusb internally notes this and attempts to + * cancel the transfer. As noted in \ref asyncpartial "above", it is possible + * that some of the data may actually have been transferred. Your application + * should <b>always</b> check how much data was actually transferred once the + * transfer completes and act accordingly. + * + * \section bulk_overflows Overflows on device-to-host bulk/interrupt endpoints + * + * If your device does not have predictable transfer sizes (or it misbehaves), + * your application may submit a request for data on an IN endpoint which is + * smaller than the data that the device wishes to send. In some circumstances + * this will cause an overflow, which is a nasty condition to deal with. See + * the \ref libusb_packetoverflow page for discussion. + * + * \section asyncctrl Considerations for control transfers + * + * The <tt>libusb_transfer</tt> structure is generic and hence does not + * include specific fields for the control-specific setup packet structure. + * + * In order to perform a control transfer, you must place the 8-byte setup + * packet at the start of the data buffer. To simplify this, you could + * cast the buffer pointer to type struct libusb_control_setup, or you can + * use the helper function libusb_fill_control_setup(). + * + * The wLength field placed in the setup packet must be the length you would + * expect to be sent in the setup packet: the length of the payload that + * follows (or the expected maximum number of bytes to receive). However, + * the length field of the libusb_transfer object must be the length of + * the data buffer - i.e. it should be wLength <em>plus</em> the size of + * the setup packet (LIBUSB_CONTROL_SETUP_SIZE). + * + * If you use the helper functions, this is simplified for you: + * -# Allocate a buffer of size LIBUSB_CONTROL_SETUP_SIZE plus the size of the + * data you are sending/requesting. + * -# Call libusb_fill_control_setup() on the data buffer, using the transfer + * request size as the wLength value (i.e. do not include the extra space you + * allocated for the control setup). + * -# If this is a host-to-device transfer, place the data to be transferred + * in the data buffer, starting at offset LIBUSB_CONTROL_SETUP_SIZE. + * -# Call libusb_fill_control_transfer() to associate the data buffer with + * the transfer (and to set the remaining details such as callback and timeout). + * - Note that there is no parameter to set the length field of the transfer. + * The length is automatically inferred from the wLength field of the setup + * packet. + * -# Submit the transfer. + * + * The multi-byte control setup fields (wValue, wIndex and wLength) must + * be given in little-endian byte order (the endianness of the USB bus). + * Endianness conversion is transparently handled by + * libusb_fill_control_setup() which is documented to accept host-endian + * values. + * + * Further considerations are needed when handling transfer completion in + * your callback function: + * - As you might expect, the setup packet will still be sitting at the start + * of the data buffer. + * - If this was a device-to-host transfer, the received data will be sitting + * at offset LIBUSB_CONTROL_SETUP_SIZE into the buffer. + * - The actual_length field of the transfer structure is relative to the + * wLength of the setup packet, rather than the size of the data buffer. So, + * if your wLength was 4, your transfer's <tt>length</tt> was 12, then you + * should expect an <tt>actual_length</tt> of 4 to indicate that the data was + * transferred in entirety. + * + * To simplify parsing of setup packets and obtaining the data from the + * correct offset, you may wish to use the libusb_control_transfer_get_data() + * and libusb_control_transfer_get_setup() functions within your transfer + * callback. + * + * Even though control endpoints do not halt, a completed control transfer + * may have a LIBUSB_TRANSFER_STALL status code. This indicates the control + * request was not supported. + * + * \section asyncintr Considerations for interrupt transfers + * + * All interrupt transfers are performed using the polling interval presented + * by the bInterval value of the endpoint descriptor. + * + * \section asynciso Considerations for isochronous transfers + * + * Isochronous transfers are more complicated than transfers to + * non-isochronous endpoints. + * + * To perform I/O to an isochronous endpoint, allocate the transfer by calling + * libusb_alloc_transfer() with an appropriate number of isochronous packets. + * + * During filling, set \ref libusb_transfer::type "type" to + * \ref libusb_transfer_type::LIBUSB_TRANSFER_TYPE_ISOCHRONOUS + * "LIBUSB_TRANSFER_TYPE_ISOCHRONOUS", and set + * \ref libusb_transfer::num_iso_packets "num_iso_packets" to a value less than + * or equal to the number of packets you requested during allocation. + * libusb_alloc_transfer() does not set either of these fields for you, given + * that you might not even use the transfer on an isochronous endpoint. + * + * Next, populate the length field for the first num_iso_packets entries in + * the \ref libusb_transfer::iso_packet_desc "iso_packet_desc" array. Section + * 5.6.3 of the USB2 specifications describe how the maximum isochronous + * packet length is determined by the wMaxPacketSize field in the endpoint + * descriptor. + * Two functions can help you here: + * + * - libusb_get_max_iso_packet_size() is an easy way to determine the max + * packet size for an isochronous endpoint. Note that the maximum packet + * size is actually the maximum number of bytes that can be transmitted in + * a single microframe, therefore this function multiplies the maximum number + * of bytes per transaction by the number of transaction opportunities per + * microframe. + * - libusb_set_iso_packet_lengths() assigns the same length to all packets + * within a transfer, which is usually what you want. + * + * For outgoing transfers, you'll obviously fill the buffer and populate the + * packet descriptors in hope that all the data gets transferred. For incoming + * transfers, you must ensure the buffer has sufficient capacity for + * the situation where all packets transfer the full amount of requested data. + * + * Completion handling requires some extra consideration. The + * \ref libusb_transfer::actual_length "actual_length" field of the transfer + * is meaningless and should not be examined; instead you must refer to the + * \ref libusb_iso_packet_descriptor::actual_length "actual_length" field of + * each individual packet. + * + * The \ref libusb_transfer::status "status" field of the transfer is also a + * little misleading: + * - If the packets were submitted and the isochronous data microframes + * completed normally, status will have value + * \ref libusb_transfer_status::LIBUSB_TRANSFER_COMPLETED + * "LIBUSB_TRANSFER_COMPLETED". Note that bus errors and software-incurred + * delays are not counted as transfer errors; the transfer.status field may + * indicate COMPLETED even if some or all of the packets failed. Refer to + * the \ref libusb_iso_packet_descriptor::status "status" field of each + * individual packet to determine packet failures. + * - The status field will have value + * \ref libusb_transfer_status::LIBUSB_TRANSFER_ERROR + * "LIBUSB_TRANSFER_ERROR" only when serious errors were encountered. + * - Other transfer status codes occur with normal behaviour. + * + * The data for each packet will be found at an offset into the buffer that + * can be calculated as if each prior packet completed in full. The + * libusb_get_iso_packet_buffer() and libusb_get_iso_packet_buffer_simple() + * functions may help you here. + * + * \section asynclimits Transfer length limitations + * + * Some operating systems may impose limits on the length of the transfer data + * buffer or, in the case of isochronous transfers, the length of individual + * isochronous packets. Such limits can be difficult for libusb to detect, so + * in most cases the library will simply try and submit the transfer as set up + * by you. If the transfer fails to submit because it is too large, + * libusb_submit_transfer() will return + * \ref libusb_error::LIBUSB_ERROR_INVALID_PARAM "LIBUSB_ERROR_INVALID_PARAM". + * + * The following are known limits for control transfer lengths. Note that this + * length includes the 8-byte setup packet. + * - Linux (4,096 bytes) + * - Windows (4,096 bytes) + * + * \section asyncmem Memory caveats + * + * In most circumstances, it is not safe to use stack memory for transfer + * buffers. This is because the function that fired off the asynchronous + * transfer may return before libusb has finished using the buffer, and when + * the function returns it's stack gets destroyed. This is true for both + * host-to-device and device-to-host transfers. + * + * The only case in which it is safe to use stack memory is where you can + * guarantee that the function owning the stack space for the buffer does not + * return until after the transfer's callback function has completed. In every + * other case, you need to use heap memory instead. + * + * \section asyncflags Fine control + * + * Through using this asynchronous interface, you may find yourself repeating + * a few simple operations many times. You can apply a bitwise OR of certain + * flags to a transfer to simplify certain things: + * - \ref libusb_transfer_flags::LIBUSB_TRANSFER_SHORT_NOT_OK + * "LIBUSB_TRANSFER_SHORT_NOT_OK" results in transfers which transferred + * less than the requested amount of data being marked with status + * \ref libusb_transfer_status::LIBUSB_TRANSFER_ERROR "LIBUSB_TRANSFER_ERROR" + * (they would normally be regarded as COMPLETED) + * - \ref libusb_transfer_flags::LIBUSB_TRANSFER_FREE_BUFFER + * "LIBUSB_TRANSFER_FREE_BUFFER" allows you to ask libusb to free the transfer + * buffer when freeing the transfer. + * - \ref libusb_transfer_flags::LIBUSB_TRANSFER_FREE_TRANSFER + * "LIBUSB_TRANSFER_FREE_TRANSFER" causes libusb to automatically free the + * transfer after the transfer callback returns. + * + * \section asyncevent Event handling + * + * An asynchronous model requires that libusb perform work at various + * points in time - namely processing the results of previously-submitted + * transfers and invoking the user-supplied callback function. + * + * This gives rise to the libusb_handle_events() function which your + * application must call into when libusb has work do to. This gives libusb + * the opportunity to reap pending transfers, invoke callbacks, etc. + * + * \note + * All event handling is performed by whichever thread calls the + * libusb_handle_events() function. libusb does not invoke any callbacks + * outside of this context. Consequently, any callbacks will be run on the + * thread that calls the libusb_handle_events() function. + * + * When to call the libusb_handle_events() function depends on which model + * your application decides to use. The 2 different approaches: + * + * -# Repeatedly call libusb_handle_events() in blocking mode from a dedicated + * thread. + * -# Integrate libusb with your application's main event loop. libusb + * exposes a set of file descriptors which allow you to do this. + * + * The first approach has the big advantage that it will also work on Windows + * were libusb' poll API for select / poll integration is not available. So + * if you want to support Windows and use the async API, you must use this + * approach, see the \ref eventthread "Using an event handling thread" section + * below for details. + * + * If you prefer a single threaded approach with a single central event loop, + * see the \ref libusb_poll "polling and timing" section for how to integrate libusb + * into your application's main event loop. + * + * \section eventthread Using an event handling thread + * + * Lets begin with stating the obvious: If you're going to use a separate + * thread for libusb event handling, your callback functions MUST be + * thread-safe. + * + * Other then that doing event handling from a separate thread, is mostly + * simple. You can use an event thread function as follows: +\code +void *event_thread_func(void *ctx) +{ + while (event_thread_run) + libusb_handle_events(ctx); + + return NULL; +} +\endcode + * + * There is one caveat though, stopping this thread requires setting the + * event_thread_run variable to 0, and after that libusb_handle_events() needs + * to return control to event_thread_func. But unless some event happens, + * libusb_handle_events() will not return. + * + * There are 2 different ways of dealing with this, depending on if your + * application uses libusb' \ref libusb_hotplug "hotplug" support or not. + * + * Applications which do not use hotplug support, should not start the event + * thread until after their first call to libusb_open(), and should stop the + * thread when closing the last open device as follows: +\code +void my_close_handle(libusb_device_handle *dev_handle) +{ + if (open_devs == 1) + event_thread_run = 0; + + libusb_close(dev_handle); // This wakes up libusb_handle_events() + + if (open_devs == 1) + pthread_join(event_thread); + + open_devs--; +} +\endcode + * + * Applications using hotplug support should start the thread at program init, + * after having successfully called libusb_hotplug_register_callback(), and + * should stop the thread at program exit as follows: +\code +void my_libusb_exit(void) +{ + event_thread_run = 0; + libusb_hotplug_deregister_callback(ctx, hotplug_cb_handle); // This wakes up libusb_handle_events() + pthread_join(event_thread); + libusb_exit(ctx); +} +\endcode + */ + +/** + * @defgroup libusb_poll Polling and timing + * + * This page documents libusb's functions for polling events and timing. + * These functions are only necessary for users of the + * \ref libusb_asyncio "asynchronous API". If you are only using the simpler + * \ref libusb_syncio "synchronous API" then you do not need to ever call these + * functions. + * + * The justification for the functionality described here has already been + * discussed in the \ref asyncevent "event handling" section of the + * asynchronous API documentation. In summary, libusb does not create internal + * threads for event processing and hence relies on your application calling + * into libusb at certain points in time so that pending events can be handled. + * + * Your main loop is probably already calling poll() or select() or a + * variant on a set of file descriptors for other event sources (e.g. keyboard + * button presses, mouse movements, network sockets, etc). You then add + * libusb's file descriptors to your poll()/select() calls, and when activity + * is detected on such descriptors you know it is time to call + * libusb_handle_events(). + * + * There is one final event handling complication. libusb supports + * asynchronous transfers which time out after a specified time period. + * + * On some platforms a timerfd is used, so the timeout handling is just another + * fd, on other platforms this requires that libusb is called into at or after + * the timeout to handle it. So, in addition to considering libusb's file + * descriptors in your main event loop, you must also consider that libusb + * sometimes needs to be called into at fixed points in time even when there + * is no file descriptor activity, see \ref polltime details. + * + * In order to know precisely when libusb needs to be called into, libusb + * offers you a set of pollable file descriptors and information about when + * the next timeout expires. + * + * If you are using the asynchronous I/O API, you must take one of the two + * following options, otherwise your I/O will not complete. + * + * \section pollsimple The simple option + * + * If your application revolves solely around libusb and does not need to + * handle other event sources, you can have a program structure as follows: +\code +// initialize libusb +// find and open device +// maybe fire off some initial async I/O + +while (user_has_not_requested_exit) + libusb_handle_events(ctx); + +// clean up and exit +\endcode + * + * With such a simple main loop, you do not have to worry about managing + * sets of file descriptors or handling timeouts. libusb_handle_events() will + * handle those details internally. + * + * \section libusb_pollmain The more advanced option + * + * \note This functionality is currently only available on Unix-like platforms. + * On Windows, libusb_get_pollfds() simply returns NULL. Applications which + * want to support Windows are advised to use an \ref eventthread + * "event handling thread" instead. + * + * In more advanced applications, you will already have a main loop which + * is monitoring other event sources: network sockets, X11 events, mouse + * movements, etc. Through exposing a set of file descriptors, libusb is + * designed to cleanly integrate into such main loops. + * + * In addition to polling file descriptors for the other event sources, you + * take a set of file descriptors from libusb and monitor those too. When you + * detect activity on libusb's file descriptors, you call + * libusb_handle_events_timeout() in non-blocking mode. + * + * What's more, libusb may also need to handle events at specific moments in + * time. No file descriptor activity is generated at these times, so your + * own application needs to be continually aware of when the next one of these + * moments occurs (through calling libusb_get_next_timeout()), and then it + * needs to call libusb_handle_events_timeout() in non-blocking mode when + * these moments occur. This means that you need to adjust your + * poll()/select() timeout accordingly. + * + * libusb provides you with a set of file descriptors to poll and expects you + * to poll all of them, treating them as a single entity. The meaning of each + * file descriptor in the set is an internal implementation detail, + * platform-dependent and may vary from release to release. Don't try and + * interpret the meaning of the file descriptors, just do as libusb indicates, + * polling all of them at once. + * + * In pseudo-code, you want something that looks like: +\code +// initialise libusb + +libusb_get_pollfds(ctx) +while (user has not requested application exit) { + libusb_get_next_timeout(ctx); + poll(on libusb file descriptors plus any other event sources of interest, + using a timeout no larger than the value libusb just suggested) + if (poll() indicated activity on libusb file descriptors) + libusb_handle_events_timeout(ctx, &zero_tv); + if (time has elapsed to or beyond the libusb timeout) + libusb_handle_events_timeout(ctx, &zero_tv); + // handle events from other sources here +} + +// clean up and exit +\endcode + * + * \subsection polltime Notes on time-based events + * + * The above complication with having to track time and call into libusb at + * specific moments is a bit of a headache. For maximum compatibility, you do + * need to write your main loop as above, but you may decide that you can + * restrict the supported platforms of your application and get away with + * a more simplistic scheme. + * + * These time-based event complications are \b not required on the following + * platforms: + * - Darwin + * - Linux, provided that the following version requirements are satisfied: + * - Linux v2.6.27 or newer, compiled with timerfd support + * - glibc v2.9 or newer + * - libusb v1.0.5 or newer + * + * Under these configurations, libusb_get_next_timeout() will \em always return + * 0, so your main loop can be simplified to: +\code +// initialise libusb + +libusb_get_pollfds(ctx) +while (user has not requested application exit) { + poll(on libusb file descriptors plus any other event sources of interest, + using any timeout that you like) + if (poll() indicated activity on libusb file descriptors) + libusb_handle_events_timeout(ctx, &zero_tv); + // handle events from other sources here +} + +// clean up and exit +\endcode + * + * Do remember that if you simplify your main loop to the above, you will + * lose compatibility with some platforms (including legacy Linux platforms, + * and <em>any future platforms supported by libusb which may have time-based + * event requirements</em>). The resultant problems will likely appear as + * strange bugs in your application. + * + * You can use the libusb_pollfds_handle_timeouts() function to do a runtime + * check to see if it is safe to ignore the time-based event complications. + * If your application has taken the shortcut of ignoring libusb's next timeout + * in your main loop, then you are advised to check the return value of + * libusb_pollfds_handle_timeouts() during application startup, and to abort + * if the platform does suffer from these timing complications. + * + * \subsection fdsetchange Changes in the file descriptor set + * + * The set of file descriptors that libusb uses as event sources may change + * during the life of your application. Rather than having to repeatedly + * call libusb_get_pollfds(), you can set up notification functions for when + * the file descriptor set changes using libusb_set_pollfd_notifiers(). + * + * \subsection mtissues Multi-threaded considerations + * + * Unfortunately, the situation is complicated further when multiple threads + * come into play. If two threads are monitoring the same file descriptors, + * the fact that only one thread will be woken up when an event occurs causes + * some headaches. + * + * The events lock, event waiters lock, and libusb_handle_events_locked() + * entities are added to solve these problems. You do not need to be concerned + * with these entities otherwise. + * + * See the extra documentation: \ref libusb_mtasync + */ + +/** \page libusb_mtasync Multi-threaded applications and asynchronous I/O + * + * libusb is a thread-safe library, but extra considerations must be applied + * to applications which interact with libusb from multiple threads. + * + * The underlying issue that must be addressed is that all libusb I/O + * revolves around monitoring file descriptors through the poll()/select() + * system calls. This is directly exposed at the + * \ref libusb_asyncio "asynchronous interface" but it is important to note that the + * \ref libusb_syncio "synchronous interface" is implemented on top of the + * asynchronous interface, therefore the same considerations apply. + * + * The issue is that if two or more threads are concurrently calling poll() + * or select() on libusb's file descriptors then only one of those threads + * will be woken up when an event arrives. The others will be completely + * oblivious that anything has happened. + * + * Consider the following pseudo-code, which submits an asynchronous transfer + * then waits for its completion. This style is one way you could implement a + * synchronous interface on top of the asynchronous interface (and libusb + * does something similar, albeit more advanced due to the complications + * explained on this page). + * +\code +void cb(struct libusb_transfer *transfer) +{ + int *completed = transfer->user_data; + *completed = 1; +} + +void myfunc() { + struct libusb_transfer *transfer; + unsigned char buffer[LIBUSB_CONTROL_SETUP_SIZE] __attribute__ ((aligned (2))); + int completed = 0; + + transfer = libusb_alloc_transfer(0); + libusb_fill_control_setup(buffer, + LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_ENDPOINT_OUT, 0x04, 0x01, 0, 0); + libusb_fill_control_transfer(transfer, dev, buffer, cb, &completed, 1000); + libusb_submit_transfer(transfer); + + while (!completed) { + poll(libusb file descriptors, 120*1000); + if (poll indicates activity) + libusb_handle_events_timeout(ctx, &zero_tv); + } + printf("completed!"); + // other code here +} +\endcode + * + * Here we are <em>serializing</em> completion of an asynchronous event + * against a condition - the condition being completion of a specific transfer. + * The poll() loop has a long timeout to minimize CPU usage during situations + * when nothing is happening (it could reasonably be unlimited). + * + * If this is the only thread that is polling libusb's file descriptors, there + * is no problem: there is no danger that another thread will swallow up the + * event that we are interested in. On the other hand, if there is another + * thread polling the same descriptors, there is a chance that it will receive + * the event that we were interested in. In this situation, <tt>myfunc()</tt> + * will only realise that the transfer has completed on the next iteration of + * the loop, <em>up to 120 seconds later.</em> Clearly a two-minute delay is + * undesirable, and don't even think about using short timeouts to circumvent + * this issue! + * + * The solution here is to ensure that no two threads are ever polling the + * file descriptors at the same time. A naive implementation of this would + * impact the capabilities of the library, so libusb offers the scheme + * documented below to ensure no loss of functionality. + * + * Before we go any further, it is worth mentioning that all libusb-wrapped + * event handling procedures fully adhere to the scheme documented below. + * This includes libusb_handle_events() and its variants, and all the + * synchronous I/O functions - libusb hides this headache from you. + * + * \section Using libusb_handle_events() from multiple threads + * + * Even when only using libusb_handle_events() and synchronous I/O functions, + * you can still have a race condition. You might be tempted to solve the + * above with libusb_handle_events() like so: + * +\code + libusb_submit_transfer(transfer); + + while (!completed) { + libusb_handle_events(ctx); + } + printf("completed!"); +\endcode + * + * This however has a race between the checking of completed and + * libusb_handle_events() acquiring the events lock, so another thread + * could have completed the transfer, resulting in this thread hanging + * until either a timeout or another event occurs. See also commit + * 6696512aade99bb15d6792af90ae329af270eba6 which fixes this in the + * synchronous API implementation of libusb. + * + * Fixing this race requires checking the variable completed only after + * taking the event lock, which defeats the concept of just calling + * libusb_handle_events() without worrying about locking. This is why + * libusb-1.0.9 introduces the new libusb_handle_events_timeout_completed() + * and libusb_handle_events_completed() functions, which handles doing the + * completion check for you after they have acquired the lock: + * +\code + libusb_submit_transfer(transfer); + + while (!completed) { + libusb_handle_events_completed(ctx, &completed); + } + printf("completed!"); +\endcode + * + * This nicely fixes the race in our example. Note that if all you want to + * do is submit a single transfer and wait for its completion, then using + * one of the synchronous I/O functions is much easier. + * + * \note + * The `completed` variable must be modified while holding the event lock, + * otherwise a race condition can still exist. It is simplest to do so from + * within the transfer callback as shown above. + * + * \section eventlock The events lock + * + * The problem is when we consider the fact that libusb exposes file + * descriptors to allow for you to integrate asynchronous USB I/O into + * existing main loops, effectively allowing you to do some work behind + * libusb's back. If you do take libusb's file descriptors and pass them to + * poll()/select() yourself, you need to be aware of the associated issues. + * + * The first concept to be introduced is the events lock. The events lock + * is used to serialize threads that want to handle events, such that only + * one thread is handling events at any one time. + * + * You must take the events lock before polling libusb file descriptors, + * using libusb_lock_events(). You must release the lock as soon as you have + * aborted your poll()/select() loop, using libusb_unlock_events(). + * + * \section threadwait Letting other threads do the work for you + * + * Although the events lock is a critical part of the solution, it is not + * enough on it's own. You might wonder if the following is sufficient... +\code + libusb_lock_events(ctx); + while (!completed) { + poll(libusb file descriptors, 120*1000); + if (poll indicates activity) + libusb_handle_events_timeout(ctx, &zero_tv); + } + libusb_unlock_events(ctx); +\endcode + * ...and the answer is that it is not. This is because the transfer in the + * code shown above may take a long time (say 30 seconds) to complete, and + * the lock is not released until the transfer is completed. + * + * Another thread with similar code that wants to do event handling may be + * working with a transfer that completes after a few milliseconds. Despite + * having such a quick completion time, the other thread cannot check that + * status of its transfer until the code above has finished (30 seconds later) + * due to contention on the lock. + * + * To solve this, libusb offers you a mechanism to determine when another + * thread is handling events. It also offers a mechanism to block your thread + * until the event handling thread has completed an event (and this mechanism + * does not involve polling of file descriptors). + * + * After determining that another thread is currently handling events, you + * obtain the <em>event waiters</em> lock using libusb_lock_event_waiters(). + * You then re-check that some other thread is still handling events, and if + * so, you call libusb_wait_for_event(). + * + * libusb_wait_for_event() puts your application to sleep until an event + * occurs, or until a thread releases the events lock. When either of these + * things happen, your thread is woken up, and should re-check the condition + * it was waiting on. It should also re-check that another thread is handling + * events, and if not, it should start handling events itself. + * + * This looks like the following, as pseudo-code: +\code +retry: +if (libusb_try_lock_events(ctx) == 0) { + // we obtained the event lock: do our own event handling + while (!completed) { + if (!libusb_event_handling_ok(ctx)) { + libusb_unlock_events(ctx); + goto retry; + } + poll(libusb file descriptors, 120*1000); + if (poll indicates activity) + libusb_handle_events_locked(ctx, 0); + } + libusb_unlock_events(ctx); +} else { + // another thread is doing event handling. wait for it to signal us that + // an event has completed + libusb_lock_event_waiters(ctx); + + while (!completed) { + // now that we have the event waiters lock, double check that another + // thread is still handling events for us. (it may have ceased handling + // events in the time it took us to reach this point) + if (!libusb_event_handler_active(ctx)) { + // whoever was handling events is no longer doing so, try again + libusb_unlock_event_waiters(ctx); + goto retry; + } + + libusb_wait_for_event(ctx, NULL); + } + libusb_unlock_event_waiters(ctx); +} +printf("completed!\n"); +\endcode + * + * A naive look at the above code may suggest that this can only support + * one event waiter (hence a total of 2 competing threads, the other doing + * event handling), because the event waiter seems to have taken the event + * waiters lock while waiting for an event. However, the system does support + * multiple event waiters, because libusb_wait_for_event() actually drops + * the lock while waiting, and reacquires it before continuing. + * + * We have now implemented code which can dynamically handle situations where + * nobody is handling events (so we should do it ourselves), and it can also + * handle situations where another thread is doing event handling (so we can + * piggyback onto them). It is also equipped to handle a combination of + * the two, for example, another thread is doing event handling, but for + * whatever reason it stops doing so before our condition is met, so we take + * over the event handling. + * + * Four functions were introduced in the above pseudo-code. Their importance + * should be apparent from the code shown above. + * -# libusb_try_lock_events() is a non-blocking function which attempts + * to acquire the events lock but returns a failure code if it is contended. + * -# libusb_event_handling_ok() checks that libusb is still happy for your + * thread to be performing event handling. Sometimes, libusb needs to + * interrupt the event handler, and this is how you can check if you have + * been interrupted. If this function returns 0, the correct behaviour is + * for you to give up the event handling lock, and then to repeat the cycle. + * The following libusb_try_lock_events() will fail, so you will become an + * events waiter. For more information on this, read \ref fullstory below. + * -# libusb_handle_events_locked() is a variant of + * libusb_handle_events_timeout() that you can call while holding the + * events lock. libusb_handle_events_timeout() itself implements similar + * logic to the above, so be sure not to call it when you are + * "working behind libusb's back", as is the case here. + * -# libusb_event_handler_active() determines if someone is currently + * holding the events lock + * + * You might be wondering why there is no function to wake up all threads + * blocked on libusb_wait_for_event(). This is because libusb can do this + * internally: it will wake up all such threads when someone calls + * libusb_unlock_events() or when a transfer completes (at the point after its + * callback has returned). + * + * \subsection fullstory The full story + * + * The above explanation should be enough to get you going, but if you're + * really thinking through the issues then you may be left with some more + * questions regarding libusb's internals. If you're curious, read on, and if + * not, skip to the next section to avoid confusing yourself! + * + * The immediate question that may spring to mind is: what if one thread + * modifies the set of file descriptors that need to be polled while another + * thread is doing event handling? + * + * There are 2 situations in which this may happen. + * -# libusb_open() will add another file descriptor to the poll set, + * therefore it is desirable to interrupt the event handler so that it + * restarts, picking up the new descriptor. + * -# libusb_close() will remove a file descriptor from the poll set. There + * are all kinds of race conditions that could arise here, so it is + * important that nobody is doing event handling at this time. + * + * libusb handles these issues internally, so application developers do not + * have to stop their event handlers while opening/closing devices. Here's how + * it works, focusing on the libusb_close() situation first: + * + * -# During initialization, libusb opens an internal pipe, and it adds the read + * end of this pipe to the set of file descriptors to be polled. + * -# During libusb_close(), libusb writes some dummy data on this event pipe. + * This immediately interrupts the event handler. libusb also records + * internally that it is trying to interrupt event handlers for this + * high-priority event. + * -# At this point, some of the functions described above start behaving + * differently: + * - libusb_event_handling_ok() starts returning 1, indicating that it is NOT + * OK for event handling to continue. + * - libusb_try_lock_events() starts returning 1, indicating that another + * thread holds the event handling lock, even if the lock is uncontended. + * - libusb_event_handler_active() starts returning 1, indicating that + * another thread is doing event handling, even if that is not true. + * -# The above changes in behaviour result in the event handler stopping and + * giving up the events lock very quickly, giving the high-priority + * libusb_close() operation a "free ride" to acquire the events lock. All + * threads that are competing to do event handling become event waiters. + * -# With the events lock held inside libusb_close(), libusb can safely remove + * a file descriptor from the poll set, in the safety of knowledge that + * nobody is polling those descriptors or trying to access the poll set. + * -# After obtaining the events lock, the close operation completes very + * quickly (usually a matter of milliseconds) and then immediately releases + * the events lock. + * -# At the same time, the behaviour of libusb_event_handling_ok() and friends + * reverts to the original, documented behaviour. + * -# The release of the events lock causes the threads that are waiting for + * events to be woken up and to start competing to become event handlers + * again. One of them will succeed; it will then re-obtain the list of poll + * descriptors, and USB I/O will then continue as normal. + * + * libusb_open() is similar, and is actually a more simplistic case. Upon a + * call to libusb_open(): + * + * -# The device is opened and a file descriptor is added to the poll set. + * -# libusb sends some dummy data on the event pipe, and records that it + * is trying to modify the poll descriptor set. + * -# The event handler is interrupted, and the same behaviour change as for + * libusb_close() takes effect, causing all event handling threads to become + * event waiters. + * -# The libusb_open() implementation takes its free ride to the events lock. + * -# Happy that it has successfully paused the events handler, libusb_open() + * releases the events lock. + * -# The event waiter threads are all woken up and compete to become event + * handlers again. The one that succeeds will obtain the list of poll + * descriptors again, which will include the addition of the new device. + * + * \subsection concl Closing remarks + * + * The above may seem a little complicated, but hopefully I have made it clear + * why such complications are necessary. Also, do not forget that this only + * applies to applications that take libusb's file descriptors and integrate + * them into their own polling loops. + * + * You may decide that it is OK for your multi-threaded application to ignore + * some of the rules and locks detailed above, because you don't think that + * two threads can ever be polling the descriptors at the same time. If that + * is the case, then that's good news for you because you don't have to worry. + * But be careful here; remember that the synchronous I/O functions do event + * handling internally. If you have one thread doing event handling in a loop + * (without implementing the rules and locking semantics documented above) + * and another trying to send a synchronous USB transfer, you will end up with + * two threads monitoring the same descriptors, and the above-described + * undesirable behaviour occurring. The solution is for your polling thread to + * play by the rules; the synchronous I/O functions do so, and this will result + * in them getting along in perfect harmony. + * + * If you do have a dedicated thread doing event handling, it is perfectly + * legal for it to take the event handling lock for long periods of time. Any + * synchronous I/O functions you call from other threads will transparently + * fall back to the "event waiters" mechanism detailed above. The only + * consideration that your event handling thread must apply is the one related + * to libusb_event_handling_ok(): you must call this before every poll(), and + * give up the events lock if instructed. + */ + +int usbi_io_init(struct libusb_context *ctx) +{ + int r; + + usbi_mutex_init(&ctx->flying_transfers_lock); + usbi_mutex_init(&ctx->events_lock); + usbi_mutex_init(&ctx->event_waiters_lock); + usbi_cond_init(&ctx->event_waiters_cond); + usbi_mutex_init(&ctx->event_data_lock); + usbi_tls_key_create(&ctx->event_handling_key); + list_init(&ctx->flying_transfers); + list_init(&ctx->event_sources); + list_init(&ctx->removed_event_sources); + list_init(&ctx->hotplug_msgs); + list_init(&ctx->completed_transfers); + + r = usbi_create_event(&ctx->event); + if (r < 0) + goto err; + + r = usbi_add_event_source(ctx, USBI_EVENT_OS_HANDLE(&ctx->event), USBI_EVENT_POLL_EVENTS); + if (r < 0) + goto err_destroy_event; + +#ifdef HAVE_OS_TIMER + r = usbi_create_timer(&ctx->timer); + if (r == 0) { + usbi_dbg(ctx, "using timer for timeouts"); + r = usbi_add_event_source(ctx, USBI_TIMER_OS_HANDLE(&ctx->timer), USBI_TIMER_POLL_EVENTS); + if (r < 0) + goto err_destroy_timer; + } else { + usbi_dbg(ctx, "timer not available for timeouts"); + } +#endif + + return 0; + +#ifdef HAVE_OS_TIMER +err_destroy_timer: + usbi_destroy_timer(&ctx->timer); + usbi_remove_event_source(ctx, USBI_EVENT_OS_HANDLE(&ctx->event)); +#endif +err_destroy_event: + usbi_destroy_event(&ctx->event); +err: + usbi_mutex_destroy(&ctx->flying_transfers_lock); + usbi_mutex_destroy(&ctx->events_lock); + usbi_mutex_destroy(&ctx->event_waiters_lock); + usbi_cond_destroy(&ctx->event_waiters_cond); + usbi_mutex_destroy(&ctx->event_data_lock); + usbi_tls_key_delete(ctx->event_handling_key); + return r; +} + +static void cleanup_removed_event_sources(struct libusb_context *ctx) +{ + struct usbi_event_source *ievent_source, *tmp; + + for_each_removed_event_source_safe(ctx, ievent_source, tmp) { + list_del(&ievent_source->list); + free(ievent_source); + } +} + +void usbi_io_exit(struct libusb_context *ctx) +{ +#ifdef HAVE_OS_TIMER + if (usbi_using_timer(ctx)) { + usbi_remove_event_source(ctx, USBI_TIMER_OS_HANDLE(&ctx->timer)); + usbi_destroy_timer(&ctx->timer); + } +#endif + usbi_remove_event_source(ctx, USBI_EVENT_OS_HANDLE(&ctx->event)); + usbi_destroy_event(&ctx->event); + usbi_mutex_destroy(&ctx->flying_transfers_lock); + usbi_mutex_destroy(&ctx->events_lock); + usbi_mutex_destroy(&ctx->event_waiters_lock); + usbi_cond_destroy(&ctx->event_waiters_cond); + usbi_mutex_destroy(&ctx->event_data_lock); + usbi_tls_key_delete(ctx->event_handling_key); + cleanup_removed_event_sources(ctx); + free(ctx->event_data); +} + +static void calculate_timeout(struct usbi_transfer *itransfer) +{ + unsigned int timeout = + USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer)->timeout; + + if (!timeout) { + TIMESPEC_CLEAR(&itransfer->timeout); + return; + } + + usbi_get_monotonic_time(&itransfer->timeout); + + itransfer->timeout.tv_sec += timeout / 1000U; + itransfer->timeout.tv_nsec += (timeout % 1000U) * 1000000L; + if (itransfer->timeout.tv_nsec >= NSEC_PER_SEC) { + ++itransfer->timeout.tv_sec; + itransfer->timeout.tv_nsec -= NSEC_PER_SEC; + } +} + +/** \ingroup libusb_asyncio + * Allocate a libusb transfer with a specified number of isochronous packet + * descriptors. The returned transfer is pre-initialized for you. When the new + * transfer is no longer needed, it should be freed with + * libusb_free_transfer(). + * + * Transfers intended for non-isochronous endpoints (e.g. control, bulk, + * interrupt) should specify an iso_packets count of zero. + * + * For transfers intended for isochronous endpoints, specify an appropriate + * number of packet descriptors to be allocated as part of the transfer. + * The returned transfer is not specially initialized for isochronous I/O; + * you are still required to set the + * \ref libusb_transfer::num_iso_packets "num_iso_packets" and + * \ref libusb_transfer::type "type" fields accordingly. + * + * It is safe to allocate a transfer with some isochronous packets and then + * use it on a non-isochronous endpoint. If you do this, ensure that at time + * of submission, num_iso_packets is 0 and that type is set appropriately. + * + * \param iso_packets number of isochronous packet descriptors to allocate. Must be non-negative. + * \returns a newly allocated transfer, or NULL on error + */ +DEFAULT_VISIBILITY +struct libusb_transfer * LIBUSB_CALL libusb_alloc_transfer( + int iso_packets) +{ + size_t priv_size; + size_t alloc_size; + unsigned char *ptr; + struct usbi_transfer *itransfer; + struct libusb_transfer *transfer; + + assert(iso_packets >= 0); + if (iso_packets < 0) + return NULL; + + priv_size = PTR_ALIGN(usbi_backend.transfer_priv_size); + alloc_size = priv_size + + sizeof(struct usbi_transfer) + + sizeof(struct libusb_transfer) + + (sizeof(struct libusb_iso_packet_descriptor) * (size_t)iso_packets); + ptr = calloc(1, alloc_size); + if (!ptr) + return NULL; + + itransfer = (struct usbi_transfer *)(ptr + priv_size); + itransfer->num_iso_packets = iso_packets; + itransfer->priv = ptr; + usbi_mutex_init(&itransfer->lock); + transfer = USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer); + return transfer; +} + +/** \ingroup libusb_asyncio + * Free a transfer structure. This should be called for all transfers + * allocated with libusb_alloc_transfer(). + * + * If the \ref libusb_transfer_flags::LIBUSB_TRANSFER_FREE_BUFFER + * "LIBUSB_TRANSFER_FREE_BUFFER" flag is set and the transfer buffer is + * non-NULL, this function will also free the transfer buffer using the + * standard system memory allocator (e.g. free()). + * + * It is legal to call this function with a NULL transfer. In this case, + * the function will simply return safely. + * + * It is not legal to free an active transfer (one which has been submitted + * and has not yet completed). + * + * \param transfer the transfer to free + */ +void API_EXPORTED libusb_free_transfer(struct libusb_transfer *transfer) +{ + struct usbi_transfer *itransfer; + size_t priv_size; + unsigned char *ptr; + + if (!transfer) + return; + + usbi_dbg(TRANSFER_CTX(transfer), "transfer %p", transfer); + if (transfer->flags & LIBUSB_TRANSFER_FREE_BUFFER) + free(transfer->buffer); + + itransfer = LIBUSB_TRANSFER_TO_USBI_TRANSFER(transfer); + usbi_mutex_destroy(&itransfer->lock); + + priv_size = PTR_ALIGN(usbi_backend.transfer_priv_size); + ptr = (unsigned char *)itransfer - priv_size; + assert(ptr == itransfer->priv); + free(ptr); +} + +/* iterates through the flying transfers, and rearms the timer based on the + * next upcoming timeout. + * must be called with flying_list locked. + * returns 0 on success or a LIBUSB_ERROR code on failure. + */ +#ifdef HAVE_OS_TIMER +static int arm_timer_for_next_timeout(struct libusb_context *ctx) +{ + struct usbi_transfer *itransfer; + + if (!usbi_using_timer(ctx)) + return 0; + + for_each_transfer(ctx, itransfer) { + struct timespec *cur_ts = &itransfer->timeout; + + /* if we've reached transfers of infinite timeout, then we have no + * arming to do */ + if (!TIMESPEC_IS_SET(cur_ts)) + break; + + /* act on first transfer that has not already been handled */ + if (!(itransfer->timeout_flags & (USBI_TRANSFER_TIMEOUT_HANDLED | USBI_TRANSFER_OS_HANDLES_TIMEOUT))) { + usbi_dbg(ctx, "next timeout originally %ums", USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer)->timeout); + return usbi_arm_timer(&ctx->timer, cur_ts); + } + } + + usbi_dbg(ctx, "no timeouts, disarming timer"); + return usbi_disarm_timer(&ctx->timer); +} +#else +static inline int arm_timer_for_next_timeout(struct libusb_context *ctx) +{ + UNUSED(ctx); + return 0; +} +#endif + +/* add a transfer to the (timeout-sorted) active transfers list. + * This function will return non 0 if fails to update the timer, + * in which case the transfer is *not* on the flying_transfers list. */ +static int add_to_flying_list(struct usbi_transfer *itransfer) +{ + struct usbi_transfer *cur; + struct timespec *timeout = &itransfer->timeout; + struct libusb_context *ctx = ITRANSFER_CTX(itransfer); + int r = 0; + int first = 1; + + calculate_timeout(itransfer); + + /* if we have no other flying transfers, start the list with this one */ + if (list_empty(&ctx->flying_transfers)) { + list_add(&itransfer->list, &ctx->flying_transfers); + goto out; + } + + /* if we have infinite timeout, append to end of list */ + if (!TIMESPEC_IS_SET(timeout)) { + list_add_tail(&itransfer->list, &ctx->flying_transfers); + /* first is irrelevant in this case */ + goto out; + } + + /* otherwise, find appropriate place in list */ + for_each_transfer(ctx, cur) { + /* find first timeout that occurs after the transfer in question */ + struct timespec *cur_ts = &cur->timeout; + + if (!TIMESPEC_IS_SET(cur_ts) || TIMESPEC_CMP(cur_ts, timeout, >)) { + list_add_tail(&itransfer->list, &cur->list); + goto out; + } + first = 0; + } + /* first is 0 at this stage (list not empty) */ + + /* otherwise we need to be inserted at the end */ + list_add_tail(&itransfer->list, &ctx->flying_transfers); +out: +#ifdef HAVE_OS_TIMER + if (first && usbi_using_timer(ctx) && TIMESPEC_IS_SET(timeout)) { + /* if this transfer has the lowest timeout of all active transfers, + * rearm the timer with this transfer's timeout */ + usbi_dbg(ctx, "arm timer for timeout in %ums (first in line)", + USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer)->timeout); + r = usbi_arm_timer(&ctx->timer, timeout); + } +#else + UNUSED(first); +#endif + + if (r) + list_del(&itransfer->list); + + return r; +} + +/* remove a transfer from the active transfers list. + * This function will *always* remove the transfer from the + * flying_transfers list. It will return a LIBUSB_ERROR code + * if it fails to update the timer for the next timeout. */ +static int remove_from_flying_list(struct usbi_transfer *itransfer) +{ + struct libusb_context *ctx = ITRANSFER_CTX(itransfer); + int rearm_timer; + int r = 0; + + usbi_mutex_lock(&ctx->flying_transfers_lock); + rearm_timer = (TIMESPEC_IS_SET(&itransfer->timeout) && + list_first_entry(&ctx->flying_transfers, struct usbi_transfer, list) == itransfer); + list_del(&itransfer->list); + if (rearm_timer) + r = arm_timer_for_next_timeout(ctx); + usbi_mutex_unlock(&ctx->flying_transfers_lock); + + return r; +} + +/** \ingroup libusb_asyncio + * Submit a transfer. This function will fire off the USB transfer and then + * return immediately. + * + * \param transfer the transfer to submit + * \returns 0 on success + * \returns LIBUSB_ERROR_NO_DEVICE if the device has been disconnected + * \returns LIBUSB_ERROR_BUSY if the transfer has already been submitted. + * \returns LIBUSB_ERROR_NOT_SUPPORTED if the transfer flags are not supported + * by the operating system. + * \returns LIBUSB_ERROR_INVALID_PARAM if the transfer size is larger than + * the operating system and/or hardware can support (see \ref asynclimits) + * \returns another LIBUSB_ERROR code on other failure + */ +int API_EXPORTED libusb_submit_transfer(struct libusb_transfer *transfer) +{ + struct usbi_transfer *itransfer = + LIBUSB_TRANSFER_TO_USBI_TRANSFER(transfer); + struct libusb_context *ctx = TRANSFER_CTX(transfer); + int r; + + usbi_dbg(ctx, "transfer %p", transfer); + + /* + * Important note on locking, this function takes / releases locks + * in the following order: + * take flying_transfers_lock + * take itransfer->lock + * clear transfer + * add to flying_transfers list + * release flying_transfers_lock + * submit transfer + * release itransfer->lock + * if submit failed: + * take flying_transfers_lock + * remove from flying_transfers list + * release flying_transfers_lock + * + * Note that it takes locks in the order a-b and then releases them + * in the same order a-b. This is somewhat unusual but not wrong, + * release order is not important as long as *all* locks are released + * before re-acquiring any locks. + * + * This means that the ordering of first releasing itransfer->lock + * and then re-acquiring the flying_transfers_list on error is + * important and must not be changed! + * + * This is done this way because when we take both locks we must always + * take flying_transfers_lock first to avoid ab-ba style deadlocks with + * the timeout handling and usbi_handle_disconnect paths. + * + * And we cannot release itransfer->lock before the submission is + * complete otherwise timeout handling for transfers with short + * timeouts may run before submission. + */ + usbi_mutex_lock(&ctx->flying_transfers_lock); + usbi_mutex_lock(&itransfer->lock); + if (itransfer->state_flags & USBI_TRANSFER_IN_FLIGHT) { + usbi_mutex_unlock(&ctx->flying_transfers_lock); + usbi_mutex_unlock(&itransfer->lock); + return LIBUSB_ERROR_BUSY; + } + itransfer->transferred = 0; + itransfer->state_flags = 0; + itransfer->timeout_flags = 0; + r = add_to_flying_list(itransfer); + if (r) { + usbi_mutex_unlock(&ctx->flying_transfers_lock); + usbi_mutex_unlock(&itransfer->lock); + return r; + } + /* + * We must release the flying transfers lock here, because with + * some backends the submit_transfer method is synchronous. + */ + usbi_mutex_unlock(&ctx->flying_transfers_lock); + + r = usbi_backend.submit_transfer(itransfer); + if (r == LIBUSB_SUCCESS) { + itransfer->state_flags |= USBI_TRANSFER_IN_FLIGHT; + /* keep a reference to this device */ + libusb_ref_device(transfer->dev_handle->dev); + } + usbi_mutex_unlock(&itransfer->lock); + + if (r != LIBUSB_SUCCESS) + remove_from_flying_list(itransfer); + + return r; +} + +/** \ingroup libusb_asyncio + * Asynchronously cancel a previously submitted transfer. + * This function returns immediately, but this does not indicate cancellation + * is complete. Your callback function will be invoked at some later time + * with a transfer status of + * \ref libusb_transfer_status::LIBUSB_TRANSFER_CANCELLED + * "LIBUSB_TRANSFER_CANCELLED." + * + * \param transfer the transfer to cancel + * \returns 0 on success + * \returns LIBUSB_ERROR_NOT_FOUND if the transfer is not in progress, + * already complete, or already cancelled. + * \returns a LIBUSB_ERROR code on failure + */ +int API_EXPORTED libusb_cancel_transfer(struct libusb_transfer *transfer) +{ + struct usbi_transfer *itransfer = + LIBUSB_TRANSFER_TO_USBI_TRANSFER(transfer); + struct libusb_context *ctx = ITRANSFER_CTX(itransfer); + int r; + + usbi_dbg(ctx, "transfer %p", transfer ); + usbi_mutex_lock(&itransfer->lock); + if (!(itransfer->state_flags & USBI_TRANSFER_IN_FLIGHT) + || (itransfer->state_flags & USBI_TRANSFER_CANCELLING)) { + r = LIBUSB_ERROR_NOT_FOUND; + goto out; + } + r = usbi_backend.cancel_transfer(itransfer); + if (r < 0) { + if (r != LIBUSB_ERROR_NOT_FOUND && + r != LIBUSB_ERROR_NO_DEVICE) + usbi_err(ctx, "cancel transfer failed error %d", r); + else + usbi_dbg(ctx, "cancel transfer failed error %d", r); + + if (r == LIBUSB_ERROR_NO_DEVICE) + itransfer->state_flags |= USBI_TRANSFER_DEVICE_DISAPPEARED; + } + + itransfer->state_flags |= USBI_TRANSFER_CANCELLING; + +out: + usbi_mutex_unlock(&itransfer->lock); + return r; +} + +/** \ingroup libusb_asyncio + * Set a transfers bulk stream id. Note users are advised to use + * libusb_fill_bulk_stream_transfer() instead of calling this function + * directly. + * + * Since version 1.0.19, \ref LIBUSB_API_VERSION >= 0x01000103 + * + * \param transfer the transfer to set the stream id for + * \param stream_id the stream id to set + * \see libusb_alloc_streams() + */ +void API_EXPORTED libusb_transfer_set_stream_id( + struct libusb_transfer *transfer, uint32_t stream_id) +{ + struct usbi_transfer *itransfer = + LIBUSB_TRANSFER_TO_USBI_TRANSFER(transfer); + + itransfer->stream_id = stream_id; +} + +/** \ingroup libusb_asyncio + * Get a transfers bulk stream id. + * + * Since version 1.0.19, \ref LIBUSB_API_VERSION >= 0x01000103 + * + * \param transfer the transfer to get the stream id for + * \returns the stream id for the transfer + */ +uint32_t API_EXPORTED libusb_transfer_get_stream_id( + struct libusb_transfer *transfer) +{ + struct usbi_transfer *itransfer = + LIBUSB_TRANSFER_TO_USBI_TRANSFER(transfer); + + return itransfer->stream_id; +} + +/* Handle completion of a transfer (completion might be an error condition). + * This will invoke the user-supplied callback function, which may end up + * freeing the transfer. Therefore you cannot use the transfer structure + * after calling this function, and you should free all backend-specific + * data before calling it. + * Do not call this function with the usbi_transfer lock held. User-specified + * callback functions may attempt to directly resubmit the transfer, which + * will attempt to take the lock. */ +int usbi_handle_transfer_completion(struct usbi_transfer *itransfer, + enum libusb_transfer_status status) +{ + struct libusb_transfer *transfer = + USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer); + struct libusb_device_handle *dev_handle = transfer->dev_handle; + struct libusb_context *ctx = ITRANSFER_CTX(itransfer); + uint8_t flags; + int r; + + r = remove_from_flying_list(itransfer); + if (r < 0) + usbi_err(ctx, "failed to set timer for next timeout"); + + usbi_mutex_lock(&itransfer->lock); + itransfer->state_flags &= ~USBI_TRANSFER_IN_FLIGHT; + usbi_mutex_unlock(&itransfer->lock); + + if (status == LIBUSB_TRANSFER_COMPLETED + && transfer->flags & LIBUSB_TRANSFER_SHORT_NOT_OK) { + int rqlen = transfer->length; + if (transfer->type == LIBUSB_TRANSFER_TYPE_CONTROL) + rqlen -= LIBUSB_CONTROL_SETUP_SIZE; + if (rqlen != itransfer->transferred) { + usbi_dbg(ctx, "interpreting short transfer as error"); + status = LIBUSB_TRANSFER_ERROR; + } + } + + flags = transfer->flags; + transfer->status = status; + transfer->actual_length = itransfer->transferred; + usbi_dbg(ctx, "transfer %p has callback %p", transfer, transfer->callback); + if (transfer->callback) + transfer->callback(transfer); + /* transfer might have been freed by the above call, do not use from + * this point. */ + if (flags & LIBUSB_TRANSFER_FREE_TRANSFER) + libusb_free_transfer(transfer); + libusb_unref_device(dev_handle->dev); + return r; +} + +/* Similar to usbi_handle_transfer_completion() but exclusively for transfers + * that were asynchronously cancelled. The same concerns w.r.t. freeing of + * transfers exist here. + * Do not call this function with the usbi_transfer lock held. User-specified + * callback functions may attempt to directly resubmit the transfer, which + * will attempt to take the lock. */ +int usbi_handle_transfer_cancellation(struct usbi_transfer *itransfer) +{ + struct libusb_context *ctx = ITRANSFER_CTX(itransfer); + uint8_t timed_out; + + usbi_mutex_lock(&ctx->flying_transfers_lock); + timed_out = itransfer->timeout_flags & USBI_TRANSFER_TIMED_OUT; + usbi_mutex_unlock(&ctx->flying_transfers_lock); + + /* if the URB was cancelled due to timeout, report timeout to the user */ + if (timed_out) { + usbi_dbg(ctx, "detected timeout cancellation"); + return usbi_handle_transfer_completion(itransfer, LIBUSB_TRANSFER_TIMED_OUT); + } + + /* otherwise its a normal async cancel */ + return usbi_handle_transfer_completion(itransfer, LIBUSB_TRANSFER_CANCELLED); +} + +/* Add a completed transfer to the completed_transfers list of the + * context and signal the event. The backend's handle_transfer_completion() + * function will be called the next time an event handler runs. */ +void usbi_signal_transfer_completion(struct usbi_transfer *itransfer) +{ + libusb_device_handle *dev_handle = USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer)->dev_handle; + + if (dev_handle) { + struct libusb_context *ctx = HANDLE_CTX(dev_handle); + unsigned int event_flags; + + usbi_mutex_lock(&ctx->event_data_lock); + event_flags = ctx->event_flags; + ctx->event_flags |= USBI_EVENT_TRANSFER_COMPLETED; + list_add_tail(&itransfer->completed_list, &ctx->completed_transfers); + if (!event_flags) + usbi_signal_event(&ctx->event); + usbi_mutex_unlock(&ctx->event_data_lock); + } +} + +/** \ingroup libusb_poll + * Attempt to acquire the event handling lock. This lock is used to ensure that + * only one thread is monitoring libusb event sources at any one time. + * + * You only need to use this lock if you are developing an application + * which calls poll() or select() on libusb's file descriptors directly. + * If you stick to libusb's event handling loop functions (e.g. + * libusb_handle_events()) then you do not need to be concerned with this + * locking. + * + * While holding this lock, you are trusted to actually be handling events. + * If you are no longer handling events, you must call libusb_unlock_events() + * as soon as possible. + * + * \param ctx the context to operate on, or NULL for the default context + * \returns 0 if the lock was obtained successfully + * \returns 1 if the lock was not obtained (i.e. another thread holds the lock) + * \ref libusb_mtasync + */ +int API_EXPORTED libusb_try_lock_events(libusb_context *ctx) +{ + int r; + unsigned int ru; + + ctx = usbi_get_context(ctx); + + /* is someone else waiting to close a device? if so, don't let this thread + * start event handling */ + usbi_mutex_lock(&ctx->event_data_lock); + ru = ctx->device_close; + usbi_mutex_unlock(&ctx->event_data_lock); + if (ru) { + usbi_dbg(ctx, "someone else is closing a device"); + return 1; + } + + r = usbi_mutex_trylock(&ctx->events_lock); + if (!r) + return 1; + + ctx->event_handler_active = 1; + return 0; +} + +/** \ingroup libusb_poll + * Acquire the event handling lock, blocking until successful acquisition if + * it is contended. This lock is used to ensure that only one thread is + * monitoring libusb event sources at any one time. + * + * You only need to use this lock if you are developing an application + * which calls poll() or select() on libusb's file descriptors directly. + * If you stick to libusb's event handling loop functions (e.g. + * libusb_handle_events()) then you do not need to be concerned with this + * locking. + * + * While holding this lock, you are trusted to actually be handling events. + * If you are no longer handling events, you must call libusb_unlock_events() + * as soon as possible. + * + * \param ctx the context to operate on, or NULL for the default context + * \ref libusb_mtasync + */ +void API_EXPORTED libusb_lock_events(libusb_context *ctx) +{ + ctx = usbi_get_context(ctx); + usbi_mutex_lock(&ctx->events_lock); + ctx->event_handler_active = 1; +} + +/** \ingroup libusb_poll + * Release the lock previously acquired with libusb_try_lock_events() or + * libusb_lock_events(). Releasing this lock will wake up any threads blocked + * on libusb_wait_for_event(). + * + * \param ctx the context to operate on, or NULL for the default context + * \ref libusb_mtasync + */ +void API_EXPORTED libusb_unlock_events(libusb_context *ctx) +{ + ctx = usbi_get_context(ctx); + ctx->event_handler_active = 0; + usbi_mutex_unlock(&ctx->events_lock); + + /* FIXME: perhaps we should be a bit more efficient by not broadcasting + * the availability of the events lock when we are modifying pollfds + * (check ctx->device_close)? */ + usbi_mutex_lock(&ctx->event_waiters_lock); + usbi_cond_broadcast(&ctx->event_waiters_cond); + usbi_mutex_unlock(&ctx->event_waiters_lock); +} + +/** \ingroup libusb_poll + * Determine if it is still OK for this thread to be doing event handling. + * + * Sometimes, libusb needs to temporarily pause all event handlers, and this + * is the function you should use before polling file descriptors to see if + * this is the case. + * + * If this function instructs your thread to give up the events lock, you + * should just continue the usual logic that is documented in \ref libusb_mtasync. + * On the next iteration, your thread will fail to obtain the events lock, + * and will hence become an event waiter. + * + * This function should be called while the events lock is held: you don't + * need to worry about the results of this function if your thread is not + * the current event handler. + * + * \param ctx the context to operate on, or NULL for the default context + * \returns 1 if event handling can start or continue + * \returns 0 if this thread must give up the events lock + * \ref fullstory "Multi-threaded I/O: the full story" + */ +int API_EXPORTED libusb_event_handling_ok(libusb_context *ctx) +{ + unsigned int r; + + ctx = usbi_get_context(ctx); + + /* is someone else waiting to close a device? if so, don't let this thread + * continue event handling */ + usbi_mutex_lock(&ctx->event_data_lock); + r = ctx->device_close; + usbi_mutex_unlock(&ctx->event_data_lock); + if (r) { + usbi_dbg(ctx, "someone else is closing a device"); + return 0; + } + + return 1; +} + + +/** \ingroup libusb_poll + * Determine if an active thread is handling events (i.e. if anyone is holding + * the event handling lock). + * + * \param ctx the context to operate on, or NULL for the default context + * \returns 1 if a thread is handling events + * \returns 0 if there are no threads currently handling events + * \ref libusb_mtasync + */ +int API_EXPORTED libusb_event_handler_active(libusb_context *ctx) +{ + unsigned int r; + + ctx = usbi_get_context(ctx); + + /* is someone else waiting to close a device? if so, don't let this thread + * start event handling -- indicate that event handling is happening */ + usbi_mutex_lock(&ctx->event_data_lock); + r = ctx->device_close; + usbi_mutex_unlock(&ctx->event_data_lock); + if (r) { + usbi_dbg(ctx, "someone else is closing a device"); + return 1; + } + + return ctx->event_handler_active; +} + +/** \ingroup libusb_poll + * Interrupt any active thread that is handling events. This is mainly useful + * for interrupting a dedicated event handling thread when an application + * wishes to call libusb_exit(). + * + * Since version 1.0.21, \ref LIBUSB_API_VERSION >= 0x01000105 + * + * \param ctx the context to operate on, or NULL for the default context + * \ref libusb_mtasync + */ +void API_EXPORTED libusb_interrupt_event_handler(libusb_context *ctx) +{ + unsigned int event_flags; + + usbi_dbg(ctx, " "); + + ctx = usbi_get_context(ctx); + usbi_mutex_lock(&ctx->event_data_lock); + + event_flags = ctx->event_flags; + ctx->event_flags |= USBI_EVENT_USER_INTERRUPT; + if (!event_flags) + usbi_signal_event(&ctx->event); + + usbi_mutex_unlock(&ctx->event_data_lock); +} + +/** \ingroup libusb_poll + * Acquire the event waiters lock. This lock is designed to be obtained under + * the situation where you want to be aware when events are completed, but + * some other thread is event handling so calling libusb_handle_events() is not + * allowed. + * + * You then obtain this lock, re-check that another thread is still handling + * events, then call libusb_wait_for_event(). + * + * You only need to use this lock if you are developing an application + * which calls poll() or select() on libusb's file descriptors directly, + * <b>and</b> may potentially be handling events from 2 threads simultaneously. + * If you stick to libusb's event handling loop functions (e.g. + * libusb_handle_events()) then you do not need to be concerned with this + * locking. + * + * \param ctx the context to operate on, or NULL for the default context + * \ref libusb_mtasync + */ +void API_EXPORTED libusb_lock_event_waiters(libusb_context *ctx) +{ + ctx = usbi_get_context(ctx); + usbi_mutex_lock(&ctx->event_waiters_lock); +} + +/** \ingroup libusb_poll + * Release the event waiters lock. + * \param ctx the context to operate on, or NULL for the default context + * \ref libusb_mtasync + */ +void API_EXPORTED libusb_unlock_event_waiters(libusb_context *ctx) +{ + ctx = usbi_get_context(ctx); + usbi_mutex_unlock(&ctx->event_waiters_lock); +} + +/** \ingroup libusb_poll + * Wait for another thread to signal completion of an event. Must be called + * with the event waiters lock held, see libusb_lock_event_waiters(). + * + * This function will block until any of the following conditions are met: + * -# The timeout expires + * -# A transfer completes + * -# A thread releases the event handling lock through libusb_unlock_events() + * + * Condition 1 is obvious. Condition 2 unblocks your thread <em>after</em> + * the callback for the transfer has completed. Condition 3 is important + * because it means that the thread that was previously handling events is no + * longer doing so, so if any events are to complete, another thread needs to + * step up and start event handling. + * + * This function releases the event waiters lock before putting your thread + * to sleep, and reacquires the lock as it is being woken up. + * + * \param ctx the context to operate on, or NULL for the default context + * \param tv maximum timeout for this blocking function. A NULL value + * indicates unlimited timeout. + * \returns 0 after a transfer completes or another thread stops event handling + * \returns 1 if the timeout expired + * \returns LIBUSB_ERROR_INVALID_PARAM if timeval is invalid + * \ref libusb_mtasync + */ +int API_EXPORTED libusb_wait_for_event(libusb_context *ctx, struct timeval *tv) +{ + int r; + + ctx = usbi_get_context(ctx); + if (!tv) { + usbi_cond_wait(&ctx->event_waiters_cond, &ctx->event_waiters_lock); + return 0; + } + + if (!TIMEVAL_IS_VALID(tv)) + return LIBUSB_ERROR_INVALID_PARAM; + + r = usbi_cond_timedwait(&ctx->event_waiters_cond, + &ctx->event_waiters_lock, tv); + if (r < 0) + return r == LIBUSB_ERROR_TIMEOUT; + + return 0; +} + +static void handle_timeout(struct usbi_transfer *itransfer) +{ + struct libusb_transfer *transfer = + USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer); + int r; + + itransfer->timeout_flags |= USBI_TRANSFER_TIMEOUT_HANDLED; + r = libusb_cancel_transfer(transfer); + if (r == LIBUSB_SUCCESS) + itransfer->timeout_flags |= USBI_TRANSFER_TIMED_OUT; + else + usbi_warn(TRANSFER_CTX(transfer), + "async cancel failed %d", r); +} + +static void handle_timeouts_locked(struct libusb_context *ctx) +{ + struct timespec systime; + struct usbi_transfer *itransfer; + + if (list_empty(&ctx->flying_transfers)) + return; + + /* get current time */ + usbi_get_monotonic_time(&systime); + + /* iterate through flying transfers list, finding all transfers that + * have expired timeouts */ + for_each_transfer(ctx, itransfer) { + struct timespec *cur_ts = &itransfer->timeout; + + /* if we've reached transfers of infinite timeout, we're all done */ + if (!TIMESPEC_IS_SET(cur_ts)) + return; + + /* ignore timeouts we've already handled */ + if (itransfer->timeout_flags & (USBI_TRANSFER_TIMEOUT_HANDLED | USBI_TRANSFER_OS_HANDLES_TIMEOUT)) + continue; + + /* if transfer has non-expired timeout, nothing more to do */ + if (TIMESPEC_CMP(cur_ts, &systime, >)) + return; + + /* otherwise, we've got an expired timeout to handle */ + handle_timeout(itransfer); + } +} + +static void handle_timeouts(struct libusb_context *ctx) +{ + ctx = usbi_get_context(ctx); + usbi_mutex_lock(&ctx->flying_transfers_lock); + handle_timeouts_locked(ctx); + usbi_mutex_unlock(&ctx->flying_transfers_lock); +} + +static int handle_event_trigger(struct libusb_context *ctx) +{ + struct list_head hotplug_msgs; + int hotplug_event = 0; + int r = 0; + + usbi_dbg(ctx, "event triggered"); + + list_init(&hotplug_msgs); + + /* take the the event data lock while processing events */ + usbi_mutex_lock(&ctx->event_data_lock); + + /* check if someone modified the event sources */ + if (ctx->event_flags & USBI_EVENT_EVENT_SOURCES_MODIFIED) + usbi_dbg(ctx, "someone updated the event sources"); + + if (ctx->event_flags & USBI_EVENT_USER_INTERRUPT) { + usbi_dbg(ctx, "someone purposefully interrupted"); + ctx->event_flags &= ~USBI_EVENT_USER_INTERRUPT; + } + + if (ctx->event_flags & USBI_EVENT_HOTPLUG_CB_DEREGISTERED) { + usbi_dbg(ctx, "someone unregistered a hotplug cb"); + ctx->event_flags &= ~USBI_EVENT_HOTPLUG_CB_DEREGISTERED; + hotplug_event = 1; + } + + /* check if someone is closing a device */ + if (ctx->event_flags & USBI_EVENT_DEVICE_CLOSE) + usbi_dbg(ctx, "someone is closing a device"); + + /* check for any pending hotplug messages */ + if (ctx->event_flags & USBI_EVENT_HOTPLUG_MSG_PENDING) { + usbi_dbg(ctx, "hotplug message received"); + ctx->event_flags &= ~USBI_EVENT_HOTPLUG_MSG_PENDING; + hotplug_event = 1; + assert(!list_empty(&ctx->hotplug_msgs)); + list_cut(&hotplug_msgs, &ctx->hotplug_msgs); + } + + /* complete any pending transfers */ + if (ctx->event_flags & USBI_EVENT_TRANSFER_COMPLETED) { + struct usbi_transfer *itransfer, *tmp; + struct list_head completed_transfers; + + assert(!list_empty(&ctx->completed_transfers)); + list_cut(&completed_transfers, &ctx->completed_transfers); + usbi_mutex_unlock(&ctx->event_data_lock); + + __for_each_completed_transfer_safe(&completed_transfers, itransfer, tmp) { + list_del(&itransfer->completed_list); + r = usbi_backend.handle_transfer_completion(itransfer); + if (r) { + usbi_err(ctx, "backend handle_transfer_completion failed with error %d", r); + break; + } + } + + usbi_mutex_lock(&ctx->event_data_lock); + if (!list_empty(&completed_transfers)) { + /* an error occurred, put the remaining transfers back on the list */ + list_splice_front(&completed_transfers, &ctx->completed_transfers); + } else if (list_empty(&ctx->completed_transfers)) { + ctx->event_flags &= ~USBI_EVENT_TRANSFER_COMPLETED; + } + } + + /* if no further pending events, clear the event */ + if (!ctx->event_flags) + usbi_clear_event(&ctx->event); + + usbi_mutex_unlock(&ctx->event_data_lock); + + /* process the hotplug events, if any */ + if (hotplug_event) + usbi_hotplug_process(ctx, &hotplug_msgs); + + return r; +} + +#ifdef HAVE_OS_TIMER +static int handle_timer_trigger(struct libusb_context *ctx) +{ + int r; + + usbi_mutex_lock(&ctx->flying_transfers_lock); + + /* process the timeout that just happened */ + handle_timeouts_locked(ctx); + + /* arm for next timeout */ + r = arm_timer_for_next_timeout(ctx); + + usbi_mutex_unlock(&ctx->flying_transfers_lock); + + return r; +} +#endif + +/* do the actual event handling. assumes that no other thread is concurrently + * doing the same thing. */ +static int handle_events(struct libusb_context *ctx, struct timeval *tv) +{ + struct usbi_reported_events reported_events; + int r, timeout_ms; + + /* prevent attempts to recursively handle events (e.g. calling into + * libusb_handle_events() from within a hotplug or transfer callback) */ + if (usbi_handling_events(ctx)) + return LIBUSB_ERROR_BUSY; + + /* only reallocate the event source data when the list of event sources has + * been modified since the last handle_events(), otherwise reuse them to + * save the additional overhead */ + usbi_mutex_lock(&ctx->event_data_lock); + if (ctx->event_flags & USBI_EVENT_EVENT_SOURCES_MODIFIED) { + usbi_dbg(ctx, "event sources modified, reallocating event data"); + + /* free anything removed since we last ran */ + cleanup_removed_event_sources(ctx); + + r = usbi_alloc_event_data(ctx); + if (r) { + usbi_mutex_unlock(&ctx->event_data_lock); + return r; + } + + /* reset the flag now that we have the updated list */ + ctx->event_flags &= ~USBI_EVENT_EVENT_SOURCES_MODIFIED; + + /* if no further pending events, clear the event so that we do + * not immediately return from the wait function */ + if (!ctx->event_flags) + usbi_clear_event(&ctx->event); + } + usbi_mutex_unlock(&ctx->event_data_lock); + + timeout_ms = (int)(tv->tv_sec * 1000) + (tv->tv_usec / 1000); + + /* round up to next millisecond */ + if (tv->tv_usec % 1000) + timeout_ms++; + + reported_events.event_bits = 0; + + usbi_start_event_handling(ctx); + + r = usbi_wait_for_events(ctx, &reported_events, timeout_ms); + if (r != LIBUSB_SUCCESS) { + if (r == LIBUSB_ERROR_TIMEOUT) { + handle_timeouts(ctx); + r = LIBUSB_SUCCESS; + } + goto done; + } + + if (reported_events.event_triggered) { + r = handle_event_trigger(ctx); + if (r) { + /* return error code */ + goto done; + } + } + +#ifdef HAVE_OS_TIMER + if (reported_events.timer_triggered) { + r = handle_timer_trigger(ctx); + if (r) { + /* return error code */ + goto done; + } + } +#endif + + if (!reported_events.num_ready) + goto done; + + r = usbi_backend.handle_events(ctx, reported_events.event_data, + reported_events.event_data_count, reported_events.num_ready); + if (r) + usbi_err(ctx, "backend handle_events failed with error %d", r); + +done: + usbi_end_event_handling(ctx); + return r; +} + +/* returns the smallest of: + * 1. timeout of next URB + * 2. user-supplied timeout + * returns 1 if there is an already-expired timeout, otherwise returns 0 + * and populates out + */ +static int get_next_timeout(libusb_context *ctx, struct timeval *tv, + struct timeval *out) +{ + struct timeval timeout; + int r = libusb_get_next_timeout(ctx, &timeout); + if (r) { + /* timeout already expired? */ + if (!timerisset(&timeout)) + return 1; + + /* choose the smallest of next URB timeout or user specified timeout */ + if (timercmp(&timeout, tv, <)) + *out = timeout; + else + *out = *tv; + } else { + *out = *tv; + } + return 0; +} + +/** \ingroup libusb_poll + * Handle any pending events. + * + * libusb determines "pending events" by checking if any timeouts have expired + * and by checking the set of file descriptors for activity. + * + * If a zero timeval is passed, this function will handle any already-pending + * events and then immediately return in non-blocking style. + * + * If a non-zero timeval is passed and no events are currently pending, this + * function will block waiting for events to handle up until the specified + * timeout. If an event arrives or a signal is raised, this function will + * return early. + * + * If the parameter completed is not NULL then <em>after obtaining the event + * handling lock</em> this function will return immediately if the integer + * pointed to is not 0. This allows for race free waiting for the completion + * of a specific transfer. + * + * \param ctx the context to operate on, or NULL for the default context + * \param tv the maximum time to block waiting for events, or an all zero + * timeval struct for non-blocking mode + * \param completed pointer to completion integer to check, or NULL + * \returns 0 on success + * \returns LIBUSB_ERROR_INVALID_PARAM if timeval is invalid + * \returns another LIBUSB_ERROR code on other failure + * \ref libusb_mtasync + */ +int API_EXPORTED libusb_handle_events_timeout_completed(libusb_context *ctx, + struct timeval *tv, int *completed) +{ + int r; + struct timeval poll_timeout; + + if (!TIMEVAL_IS_VALID(tv)) + return LIBUSB_ERROR_INVALID_PARAM; + + ctx = usbi_get_context(ctx); + r = get_next_timeout(ctx, tv, &poll_timeout); + if (r) { + /* timeout already expired */ + handle_timeouts(ctx); + return 0; + } + +retry: + if (libusb_try_lock_events(ctx) == 0) { + if (completed == NULL || !*completed) { + /* we obtained the event lock: do our own event handling */ + usbi_dbg(ctx, "doing our own event handling"); + r = handle_events(ctx, &poll_timeout); + } + libusb_unlock_events(ctx); + return r; + } + + /* another thread is doing event handling. wait for thread events that + * notify event completion. */ + libusb_lock_event_waiters(ctx); + + if (completed && *completed) + goto already_done; + + if (!libusb_event_handler_active(ctx)) { + /* we hit a race: whoever was event handling earlier finished in the + * time it took us to reach this point. try the cycle again. */ + libusb_unlock_event_waiters(ctx); + usbi_dbg(ctx, "event handler was active but went away, retrying"); + goto retry; + } + + usbi_dbg(ctx, "another thread is doing event handling"); + r = libusb_wait_for_event(ctx, &poll_timeout); + +already_done: + libusb_unlock_event_waiters(ctx); + + if (r < 0) + return r; + else if (r == 1) + handle_timeouts(ctx); + return 0; +} + +/** \ingroup libusb_poll + * Handle any pending events + * + * Like libusb_handle_events_timeout_completed(), but without the completed + * parameter, calling this function is equivalent to calling + * libusb_handle_events_timeout_completed() with a NULL completed parameter. + * + * This function is kept primarily for backwards compatibility. + * All new code should call libusb_handle_events_completed() or + * libusb_handle_events_timeout_completed() to avoid race conditions. + * + * \param ctx the context to operate on, or NULL for the default context + * \param tv the maximum time to block waiting for events, or an all zero + * timeval struct for non-blocking mode + * \returns 0 on success, or a LIBUSB_ERROR code on failure + */ +int API_EXPORTED libusb_handle_events_timeout(libusb_context *ctx, + struct timeval *tv) +{ + return libusb_handle_events_timeout_completed(ctx, tv, NULL); +} + +/** \ingroup libusb_poll + * Handle any pending events in blocking mode. There is currently a timeout + * hard-coded at 60 seconds but we plan to make it unlimited in future. For + * finer control over whether this function is blocking or non-blocking, or + * for control over the timeout, use libusb_handle_events_timeout_completed() + * instead. + * + * This function is kept primarily for backwards compatibility. + * All new code should call libusb_handle_events_completed() or + * libusb_handle_events_timeout_completed() to avoid race conditions. + * + * \param ctx the context to operate on, or NULL for the default context + * \returns 0 on success, or a LIBUSB_ERROR code on failure + */ +int API_EXPORTED libusb_handle_events(libusb_context *ctx) +{ + struct timeval tv; + tv.tv_sec = 60; + tv.tv_usec = 0; + return libusb_handle_events_timeout_completed(ctx, &tv, NULL); +} + +/** \ingroup libusb_poll + * Handle any pending events in blocking mode. + * + * Like libusb_handle_events(), with the addition of a completed parameter + * to allow for race free waiting for the completion of a specific transfer. + * + * See libusb_handle_events_timeout_completed() for details on the completed + * parameter. + * + * \param ctx the context to operate on, or NULL for the default context + * \param completed pointer to completion integer to check, or NULL + * \returns 0 on success, or a LIBUSB_ERROR code on failure + * \ref libusb_mtasync + */ +int API_EXPORTED libusb_handle_events_completed(libusb_context *ctx, + int *completed) +{ + struct timeval tv; + tv.tv_sec = 60; + tv.tv_usec = 0; + return libusb_handle_events_timeout_completed(ctx, &tv, completed); +} + +/** \ingroup libusb_poll + * Handle any pending events by polling file descriptors, without checking if + * any other threads are already doing so. Must be called with the event lock + * held, see libusb_lock_events(). + * + * This function is designed to be called under the situation where you have + * taken the event lock and are calling poll()/select() directly on libusb's + * file descriptors (as opposed to using libusb_handle_events() or similar). + * You detect events on libusb's descriptors, so you then call this function + * with a zero timeout value (while still holding the event lock). + * + * \param ctx the context to operate on, or NULL for the default context + * \param tv the maximum time to block waiting for events, or zero for + * non-blocking mode + * \returns 0 on success + * \returns LIBUSB_ERROR_INVALID_PARAM if timeval is invalid + * \returns another LIBUSB_ERROR code on other failure + * \ref libusb_mtasync + */ +int API_EXPORTED libusb_handle_events_locked(libusb_context *ctx, + struct timeval *tv) +{ + int r; + struct timeval poll_timeout; + + if (!TIMEVAL_IS_VALID(tv)) + return LIBUSB_ERROR_INVALID_PARAM; + + ctx = usbi_get_context(ctx); + r = get_next_timeout(ctx, tv, &poll_timeout); + if (r) { + /* timeout already expired */ + handle_timeouts(ctx); + return 0; + } + + return handle_events(ctx, &poll_timeout); +} + +/** \ingroup libusb_poll + * Determines whether your application must apply special timing considerations + * when monitoring libusb's file descriptors. + * + * This function is only useful for applications which retrieve and poll + * libusb's file descriptors in their own main loop (\ref libusb_pollmain). + * + * Ordinarily, libusb's event handler needs to be called into at specific + * moments in time (in addition to times when there is activity on the file + * descriptor set). The usual approach is to use libusb_get_next_timeout() + * to learn about when the next timeout occurs, and to adjust your + * poll()/select() timeout accordingly so that you can make a call into the + * library at that time. + * + * Some platforms supported by libusb do not come with this baggage - any + * events relevant to timing will be represented by activity on the file + * descriptor set, and libusb_get_next_timeout() will always return 0. + * This function allows you to detect whether you are running on such a + * platform. + * + * Since v1.0.5. + * + * \param ctx the context to operate on, or NULL for the default context + * \returns 0 if you must call into libusb at times determined by + * libusb_get_next_timeout(), or 1 if all timeout events are handled internally + * or through regular activity on the file descriptors. + * \ref libusb_pollmain "Polling libusb file descriptors for event handling" + */ +int API_EXPORTED libusb_pollfds_handle_timeouts(libusb_context *ctx) +{ + ctx = usbi_get_context(ctx); + return usbi_using_timer(ctx); +} + +/** \ingroup libusb_poll + * Determine the next internal timeout that libusb needs to handle. You only + * need to use this function if you are calling poll() or select() or similar + * on libusb's file descriptors yourself - you do not need to use it if you + * are calling libusb_handle_events() or a variant directly. + * + * You should call this function in your main loop in order to determine how + * long to wait for select() or poll() to return results. libusb needs to be + * called into at this timeout, so you should use it as an upper bound on + * your select() or poll() call. + * + * When the timeout has expired, call into libusb_handle_events_timeout() + * (perhaps in non-blocking mode) so that libusb can handle the timeout. + * + * This function may return 1 (success) and an all-zero timeval. If this is + * the case, it indicates that libusb has a timeout that has already expired + * so you should call libusb_handle_events_timeout() or similar immediately. + * A return code of 0 indicates that there are no pending timeouts. + * + * On some platforms, this function will always returns 0 (no pending + * timeouts). See \ref polltime. + * + * \param ctx the context to operate on, or NULL for the default context + * \param tv output location for a relative time against the current + * clock in which libusb must be called into in order to process timeout events + * \returns 0 if there are no pending timeouts, 1 if a timeout was returned, + * or LIBUSB_ERROR_OTHER on failure + */ +int API_EXPORTED libusb_get_next_timeout(libusb_context *ctx, + struct timeval *tv) +{ + struct usbi_transfer *itransfer; + struct timespec systime; + struct timespec next_timeout = { 0, 0 }; + + ctx = usbi_get_context(ctx); + if (usbi_using_timer(ctx)) + return 0; + + usbi_mutex_lock(&ctx->flying_transfers_lock); + if (list_empty(&ctx->flying_transfers)) { + usbi_mutex_unlock(&ctx->flying_transfers_lock); + usbi_dbg(ctx, "no URBs, no timeout!"); + return 0; + } + + /* find next transfer which hasn't already been processed as timed out */ + for_each_transfer(ctx, itransfer) { + if (itransfer->timeout_flags & (USBI_TRANSFER_TIMEOUT_HANDLED | USBI_TRANSFER_OS_HANDLES_TIMEOUT)) + continue; + + /* if we've reached transfers of infinite timeout, we're done looking */ + if (!TIMESPEC_IS_SET(&itransfer->timeout)) + break; + + next_timeout = itransfer->timeout; + break; + } + usbi_mutex_unlock(&ctx->flying_transfers_lock); + + if (!TIMESPEC_IS_SET(&next_timeout)) { + usbi_dbg(ctx, "no URB with timeout or all handled by OS; no timeout!"); + return 0; + } + + usbi_get_monotonic_time(&systime); + + if (!TIMESPEC_CMP(&systime, &next_timeout, <)) { + usbi_dbg(ctx, "first timeout already expired"); + timerclear(tv); + } else { + TIMESPEC_SUB(&next_timeout, &systime, &next_timeout); + TIMESPEC_TO_TIMEVAL(tv, &next_timeout); + usbi_dbg(ctx, "next timeout in %ld.%06lds", (long)tv->tv_sec, (long)tv->tv_usec); + } + + return 1; +} + +/** \ingroup libusb_poll + * Register notification functions for file descriptor additions/removals. + * These functions will be invoked for every new or removed file descriptor + * that libusb uses as an event source. + * + * To remove notifiers, pass NULL values for the function pointers. + * + * Note that file descriptors may have been added even before you register + * these notifiers (e.g. at libusb_init() time). + * + * Additionally, note that the removal notifier may be called during + * libusb_exit() (e.g. when it is closing file descriptors that were opened + * and added to the poll set at libusb_init() time). If you don't want this, + * remove the notifiers immediately before calling libusb_exit(). + * + * \param ctx the context to operate on, or NULL for the default context + * \param added_cb pointer to function for addition notifications + * \param removed_cb pointer to function for removal notifications + * \param user_data User data to be passed back to callbacks (useful for + * passing context information) + */ +void API_EXPORTED libusb_set_pollfd_notifiers(libusb_context *ctx, + libusb_pollfd_added_cb added_cb, libusb_pollfd_removed_cb removed_cb, + void *user_data) +{ +#if !defined(PLATFORM_WINDOWS) + ctx = usbi_get_context(ctx); + ctx->fd_added_cb = added_cb; + ctx->fd_removed_cb = removed_cb; + ctx->fd_cb_user_data = user_data; +#else + usbi_err(ctx, "external polling of libusb's internal event sources " \ + "is not yet supported on Windows"); + UNUSED(added_cb); + UNUSED(removed_cb); + UNUSED(user_data); +#endif +} + +/* + * Interrupt the iteration of the event handling thread, so that it picks + * up the event source change. Callers of this function must hold the event_data_lock. + */ +static void usbi_event_source_notification(struct libusb_context *ctx) +{ + unsigned int event_flags; + + /* Record that there is a new poll fd. + * Only signal an event if there are no prior pending events. */ + event_flags = ctx->event_flags; + ctx->event_flags |= USBI_EVENT_EVENT_SOURCES_MODIFIED; + if (!event_flags) + usbi_signal_event(&ctx->event); +} + +/* Add an event source to the list of event sources to be monitored. + * poll_events should be specified as a bitmask of events passed to poll(), e.g. + * POLLIN and/or POLLOUT. */ +int usbi_add_event_source(struct libusb_context *ctx, usbi_os_handle_t os_handle, short poll_events) +{ + struct usbi_event_source *ievent_source = malloc(sizeof(*ievent_source)); + + if (!ievent_source) + return LIBUSB_ERROR_NO_MEM; + + usbi_dbg(ctx, "add " USBI_OS_HANDLE_FORMAT_STRING " events %d", os_handle, poll_events); + ievent_source->data.os_handle = os_handle; + ievent_source->data.poll_events = poll_events; + usbi_mutex_lock(&ctx->event_data_lock); + list_add_tail(&ievent_source->list, &ctx->event_sources); + usbi_event_source_notification(ctx); + usbi_mutex_unlock(&ctx->event_data_lock); + +#if !defined(PLATFORM_WINDOWS) + if (ctx->fd_added_cb) + ctx->fd_added_cb(os_handle, poll_events, ctx->fd_cb_user_data); +#endif + + return 0; +} + +/* Remove an event source from the list of event sources to be monitored. */ +void usbi_remove_event_source(struct libusb_context *ctx, usbi_os_handle_t os_handle) +{ + struct usbi_event_source *ievent_source; + int found = 0; + + usbi_dbg(ctx, "remove " USBI_OS_HANDLE_FORMAT_STRING, os_handle); + usbi_mutex_lock(&ctx->event_data_lock); + for_each_event_source(ctx, ievent_source) { + if (ievent_source->data.os_handle == os_handle) { + found = 1; + break; + } + } + + if (!found) { + usbi_dbg(ctx, "couldn't find " USBI_OS_HANDLE_FORMAT_STRING " to remove", os_handle); + usbi_mutex_unlock(&ctx->event_data_lock); + return; + } + + list_del(&ievent_source->list); + list_add_tail(&ievent_source->list, &ctx->removed_event_sources); + usbi_event_source_notification(ctx); + usbi_mutex_unlock(&ctx->event_data_lock); + +#if !defined(PLATFORM_WINDOWS) + if (ctx->fd_removed_cb) + ctx->fd_removed_cb(os_handle, ctx->fd_cb_user_data); +#endif +} + +/** \ingroup libusb_poll + * Retrieve a list of file descriptors that should be polled by your main loop + * as libusb event sources. + * + * The returned list is NULL-terminated and should be freed with libusb_free_pollfds() + * when done. The actual list contents must not be touched. + * + * As file descriptors are a Unix-specific concept, this function is not + * available on Windows and will always return NULL. + * + * \param ctx the context to operate on, or NULL for the default context + * \returns a NULL-terminated list of libusb_pollfd structures + * \returns NULL on error + * \returns NULL on platforms where the functionality is not available + */ +DEFAULT_VISIBILITY +const struct libusb_pollfd ** LIBUSB_CALL libusb_get_pollfds( + libusb_context *ctx) +{ +#if !defined(PLATFORM_WINDOWS) + struct libusb_pollfd **ret = NULL; + struct usbi_event_source *ievent_source; + size_t i; + + static_assert(sizeof(struct usbi_event_source_data) == sizeof(struct libusb_pollfd), + "mismatch between usbi_event_source_data and libusb_pollfd sizes"); + + ctx = usbi_get_context(ctx); + + usbi_mutex_lock(&ctx->event_data_lock); + + i = 0; + for_each_event_source(ctx, ievent_source) + i++; + + ret = calloc(i + 1, sizeof(struct libusb_pollfd *)); + if (!ret) + goto out; + + i = 0; + for_each_event_source(ctx, ievent_source) + ret[i++] = (struct libusb_pollfd *)ievent_source; + +out: + usbi_mutex_unlock(&ctx->event_data_lock); + return (const struct libusb_pollfd **)ret; +#else + usbi_err(ctx, "external polling of libusb's internal event sources " \ + "is not yet supported on Windows"); + return NULL; +#endif +} + +/** \ingroup libusb_poll + * Free a list of libusb_pollfd structures. This should be called for all + * pollfd lists allocated with libusb_get_pollfds(). + * + * Since version 1.0.20, \ref LIBUSB_API_VERSION >= 0x01000104 + * + * It is legal to call this function with a NULL pollfd list. In this case, + * the function will simply do nothing. + * + * \param pollfds the list of libusb_pollfd structures to free + */ +void API_EXPORTED libusb_free_pollfds(const struct libusb_pollfd **pollfds) +{ +#if !defined(PLATFORM_WINDOWS) + free((void *)pollfds); +#else + UNUSED(pollfds); +#endif +} + +/* Backends may call this from handle_events to report disconnection of a + * device. This function ensures transfers get cancelled appropriately. + * Callers of this function must hold the events_lock. + */ +void usbi_handle_disconnect(struct libusb_device_handle *dev_handle) +{ + struct libusb_context *ctx = HANDLE_CTX(dev_handle); + struct usbi_transfer *cur; + struct usbi_transfer *to_cancel; + + usbi_dbg(ctx, "device %d.%d", + dev_handle->dev->bus_number, dev_handle->dev->device_address); + + /* terminate all pending transfers with the LIBUSB_TRANSFER_NO_DEVICE + * status code. + * + * when we find a transfer for this device on the list, there are two + * possible scenarios: + * 1. the transfer is currently in-flight, in which case we terminate the + * transfer here + * 2. the transfer has been added to the flying transfer list by + * libusb_submit_transfer, has failed to submit and + * libusb_submit_transfer is waiting for us to release the + * flying_transfers_lock to remove it, so we ignore it + */ + + while (1) { + to_cancel = NULL; + usbi_mutex_lock(&ctx->flying_transfers_lock); + for_each_transfer(ctx, cur) { + if (USBI_TRANSFER_TO_LIBUSB_TRANSFER(cur)->dev_handle == dev_handle) { + usbi_mutex_lock(&cur->lock); + if (cur->state_flags & USBI_TRANSFER_IN_FLIGHT) + to_cancel = cur; + usbi_mutex_unlock(&cur->lock); + + if (to_cancel) + break; + } + } + usbi_mutex_unlock(&ctx->flying_transfers_lock); + + if (!to_cancel) + break; + + usbi_dbg(ctx, "cancelling transfer %p from disconnect", + USBI_TRANSFER_TO_LIBUSB_TRANSFER(to_cancel)); + + usbi_mutex_lock(&to_cancel->lock); + usbi_backend.clear_transfer_priv(to_cancel); + usbi_mutex_unlock(&to_cancel->lock); + usbi_handle_transfer_completion(to_cancel, LIBUSB_TRANSFER_NO_DEVICE); + } +} diff --git a/Radio/HW/RtlSdr/libusb/libusb.h b/Radio/HW/RtlSdr/libusb/libusb.h new file mode 100644 index 0000000..61cacc9 --- /dev/null +++ b/Radio/HW/RtlSdr/libusb/libusb.h @@ -0,0 +1,2135 @@ +/* + * Public libusb header file + * Copyright © 2001 Johannes Erdfelt <johannes@erdfelt.com> + * Copyright © 2007-2008 Daniel Drake <dsd@gentoo.org> + * Copyright © 2012 Pete Batard <pete@akeo.ie> + * Copyright © 2012-2018 Nathan Hjelm <hjelmn@cs.unm.edu> + * Copyright © 2014-2020 Chris Dickens <christopher.a.dickens@gmail.com> + * For more information, please visit: http://libusb.info + * + * 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 + */ + +#ifndef LIBUSB_H +#define LIBUSB_H + +#if defined(_MSC_VER) +/* on MS environments, the inline keyword is available in C++ only */ +#if !defined(__cplusplus) +#define inline __inline +#endif +/* ssize_t is also not available */ +#ifndef _SSIZE_T_DEFINED +#define _SSIZE_T_DEFINED +#include <basetsd.h> +typedef SSIZE_T ssize_t; +#endif /* _SSIZE_T_DEFINED */ +#endif /* _MSC_VER */ + +#include <limits.h> +#include <stdint.h> +#include <sys/types.h> +#if !defined(_MSC_VER) +#include <sys/time.h> +#endif +#include <time.h> + +#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) +#define ZERO_SIZED_ARRAY /* [] - valid C99 code */ +#else +#define ZERO_SIZED_ARRAY 0 /* [0] - non-standard, but usually working code */ +#endif /* __STDC_VERSION__ */ + +/* 'interface' might be defined as a macro on Windows, so we need to + * undefine it so as not to break the current libusb API, because + * libusb_config_descriptor has an 'interface' member + * As this can be problematic if you include windows.h after libusb.h + * in your sources, we force windows.h to be included first. */ +#if defined(_WIN32) || defined(__CYGWIN__) +#include <windows.h> +#if defined(interface) +#undef interface +#endif +#if !defined(__CYGWIN__) +#include <winsock.h> +#endif +#endif /* _WIN32 || __CYGWIN__ */ + +#if defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 5)) +#define LIBUSB_DEPRECATED_FOR(f) __attribute__ ((deprecated ("Use " #f " instead"))) +#elif defined(__GNUC__) && (__GNUC__ >= 3) +#define LIBUSB_DEPRECATED_FOR(f) __attribute__ ((deprecated)) +#else +#define LIBUSB_DEPRECATED_FOR(f) +#endif /* __GNUC__ */ + +#if defined(__GNUC__) +#define LIBUSB_PACKED __attribute__ ((packed)) +#else +#define LIBUSB_PACKED +#endif /* __GNUC__ */ + +/** \def LIBUSB_CALL + * \ingroup libusb_misc + * libusb's Windows calling convention. + * + * Under Windows, the selection of available compilers and configurations + * means that, unlike other platforms, there is not <em>one true calling + * convention</em> (calling convention: the manner in which parameters are + * passed to functions in the generated assembly code). + * + * Matching the Windows API itself, libusb uses the WINAPI convention (which + * translates to the <tt>stdcall</tt> convention) and guarantees that the + * library is compiled in this way. The public header file also includes + * appropriate annotations so that your own software will use the right + * convention, even if another convention is being used by default within + * your codebase. + * + * The one consideration that you must apply in your software is to mark + * all functions which you use as libusb callbacks with this LIBUSB_CALL + * annotation, so that they too get compiled for the correct calling + * convention. + * + * On non-Windows operating systems, this macro is defined as nothing. This + * means that you can apply it to your code without worrying about + * cross-platform compatibility. + */ +/* LIBUSB_CALL must be defined on both definition and declaration of libusb + * functions. You'd think that declaration would be enough, but cygwin will + * complain about conflicting types unless both are marked this way. + * The placement of this macro is important too; it must appear after the + * return type, before the function name. See internal documentation for + * API_EXPORTED. + */ +#if defined(_WIN32) || defined(__CYGWIN__) +#define LIBUSB_CALL WINAPI +#else +#define LIBUSB_CALL +#endif /* _WIN32 || __CYGWIN__ */ + +/** \def LIBUSB_API_VERSION + * \ingroup libusb_misc + * libusb's API version. + * + * Since version 1.0.13, to help with feature detection, libusb defines + * a LIBUSB_API_VERSION macro that gets increased every time there is a + * significant change to the API, such as the introduction of a new call, + * the definition of a new macro/enum member, or any other element that + * libusb applications may want to detect at compilation time. + * + * The macro is typically used in an application as follows: + * \code + * #if defined(LIBUSB_API_VERSION) && (LIBUSB_API_VERSION >= 0x01001234) + * // Use one of the newer features from the libusb API + * #endif + * \endcode + * + * Internally, LIBUSB_API_VERSION is defined as follows: + * (libusb major << 24) | (libusb minor << 16) | (16 bit incremental) + */ +#define LIBUSB_API_VERSION 0x01000109 + +/* The following is kept for compatibility, but will be deprecated in the future */ +#define LIBUSBX_API_VERSION LIBUSB_API_VERSION + +#if defined(__cplusplus) +extern "C" { +#endif + +/** + * \ingroup libusb_misc + * Convert a 16-bit value from host-endian to little-endian format. On + * little endian systems, this function does nothing. On big endian systems, + * the bytes are swapped. + * \param x the host-endian value to convert + * \returns the value in little-endian byte order + */ +static inline uint16_t libusb_cpu_to_le16(const uint16_t x) +{ + union { + uint8_t b8[2]; + uint16_t b16; + } _tmp; + _tmp.b8[1] = (uint8_t) (x >> 8); + _tmp.b8[0] = (uint8_t) (x & 0xff); + return _tmp.b16; +} + +/** \def libusb_le16_to_cpu + * \ingroup libusb_misc + * Convert a 16-bit value from little-endian to host-endian format. On + * little endian systems, this function does nothing. On big endian systems, + * the bytes are swapped. + * \param x the little-endian value to convert + * \returns the value in host-endian byte order + */ +#define libusb_le16_to_cpu libusb_cpu_to_le16 + +/* standard USB stuff */ + +/** \ingroup libusb_desc + * Device and/or Interface Class codes */ +enum libusb_class_code { + /** In the context of a \ref libusb_device_descriptor "device descriptor", + * this bDeviceClass value indicates that each interface specifies its + * own class information and all interfaces operate independently. + */ + LIBUSB_CLASS_PER_INTERFACE = 0x00, + + /** Audio class */ + LIBUSB_CLASS_AUDIO = 0x01, + + /** Communications class */ + LIBUSB_CLASS_COMM = 0x02, + + /** Human Interface Device class */ + LIBUSB_CLASS_HID = 0x03, + + /** Physical */ + LIBUSB_CLASS_PHYSICAL = 0x05, + + /** Image class */ + LIBUSB_CLASS_IMAGE = 0x06, + LIBUSB_CLASS_PTP = 0x06, /* legacy name from libusb-0.1 usb.h */ + + /** Printer class */ + LIBUSB_CLASS_PRINTER = 0x07, + + /** Mass storage class */ + LIBUSB_CLASS_MASS_STORAGE = 0x08, + + /** Hub class */ + LIBUSB_CLASS_HUB = 0x09, + + /** Data class */ + LIBUSB_CLASS_DATA = 0x0a, + + /** Smart Card */ + LIBUSB_CLASS_SMART_CARD = 0x0b, + + /** Content Security */ + LIBUSB_CLASS_CONTENT_SECURITY = 0x0d, + + /** Video */ + LIBUSB_CLASS_VIDEO = 0x0e, + + /** Personal Healthcare */ + LIBUSB_CLASS_PERSONAL_HEALTHCARE = 0x0f, + + /** Diagnostic Device */ + LIBUSB_CLASS_DIAGNOSTIC_DEVICE = 0xdc, + + /** Wireless class */ + LIBUSB_CLASS_WIRELESS = 0xe0, + + /** Miscellaneous class */ + LIBUSB_CLASS_MISCELLANEOUS = 0xef, + + /** Application class */ + LIBUSB_CLASS_APPLICATION = 0xfe, + + /** Class is vendor-specific */ + LIBUSB_CLASS_VENDOR_SPEC = 0xff +}; + +/** \ingroup libusb_desc + * Descriptor types as defined by the USB specification. */ +enum libusb_descriptor_type { + /** Device descriptor. See libusb_device_descriptor. */ + LIBUSB_DT_DEVICE = 0x01, + + /** Configuration descriptor. See libusb_config_descriptor. */ + LIBUSB_DT_CONFIG = 0x02, + + /** String descriptor */ + LIBUSB_DT_STRING = 0x03, + + /** Interface descriptor. See libusb_interface_descriptor. */ + LIBUSB_DT_INTERFACE = 0x04, + + /** Endpoint descriptor. See libusb_endpoint_descriptor. */ + LIBUSB_DT_ENDPOINT = 0x05, + + /** BOS descriptor */ + LIBUSB_DT_BOS = 0x0f, + + /** Device Capability descriptor */ + LIBUSB_DT_DEVICE_CAPABILITY = 0x10, + + /** HID descriptor */ + LIBUSB_DT_HID = 0x21, + + /** HID report descriptor */ + LIBUSB_DT_REPORT = 0x22, + + /** Physical descriptor */ + LIBUSB_DT_PHYSICAL = 0x23, + + /** Hub descriptor */ + LIBUSB_DT_HUB = 0x29, + + /** SuperSpeed Hub descriptor */ + LIBUSB_DT_SUPERSPEED_HUB = 0x2a, + + /** SuperSpeed Endpoint Companion descriptor */ + LIBUSB_DT_SS_ENDPOINT_COMPANION = 0x30 +}; + +/* Descriptor sizes per descriptor type */ +#define LIBUSB_DT_DEVICE_SIZE 18 +#define LIBUSB_DT_CONFIG_SIZE 9 +#define LIBUSB_DT_INTERFACE_SIZE 9 +#define LIBUSB_DT_ENDPOINT_SIZE 7 +#define LIBUSB_DT_ENDPOINT_AUDIO_SIZE 9 /* Audio extension */ +#define LIBUSB_DT_HUB_NONVAR_SIZE 7 +#define LIBUSB_DT_SS_ENDPOINT_COMPANION_SIZE 6 +#define LIBUSB_DT_BOS_SIZE 5 +#define LIBUSB_DT_DEVICE_CAPABILITY_SIZE 3 + +/* BOS descriptor sizes */ +#define LIBUSB_BT_USB_2_0_EXTENSION_SIZE 7 +#define LIBUSB_BT_SS_USB_DEVICE_CAPABILITY_SIZE 10 +#define LIBUSB_BT_CONTAINER_ID_SIZE 20 + +/* We unwrap the BOS => define its max size */ +#define LIBUSB_DT_BOS_MAX_SIZE \ + (LIBUSB_DT_BOS_SIZE + \ + LIBUSB_BT_USB_2_0_EXTENSION_SIZE + \ + LIBUSB_BT_SS_USB_DEVICE_CAPABILITY_SIZE + \ + LIBUSB_BT_CONTAINER_ID_SIZE) + +#define LIBUSB_ENDPOINT_ADDRESS_MASK 0x0f /* in bEndpointAddress */ +#define LIBUSB_ENDPOINT_DIR_MASK 0x80 + +/** \ingroup libusb_desc + * Endpoint direction. Values for bit 7 of the + * \ref libusb_endpoint_descriptor::bEndpointAddress "endpoint address" scheme. + */ +enum libusb_endpoint_direction { + /** Out: host-to-device */ + LIBUSB_ENDPOINT_OUT = 0x00, + + /** In: device-to-host */ + LIBUSB_ENDPOINT_IN = 0x80 +}; + +#define LIBUSB_TRANSFER_TYPE_MASK 0x03 /* in bmAttributes */ + +/** \ingroup libusb_desc + * Endpoint transfer type. Values for bits 0:1 of the + * \ref libusb_endpoint_descriptor::bmAttributes "endpoint attributes" field. + */ +enum libusb_endpoint_transfer_type { + /** Control endpoint */ + LIBUSB_ENDPOINT_TRANSFER_TYPE_CONTROL = 0x0, + + /** Isochronous endpoint */ + LIBUSB_ENDPOINT_TRANSFER_TYPE_ISOCHRONOUS = 0x1, + + /** Bulk endpoint */ + LIBUSB_ENDPOINT_TRANSFER_TYPE_BULK = 0x2, + + /** Interrupt endpoint */ + LIBUSB_ENDPOINT_TRANSFER_TYPE_INTERRUPT = 0x3 +}; + +/** \ingroup libusb_misc + * Standard requests, as defined in table 9-5 of the USB 3.0 specifications */ +enum libusb_standard_request { + /** Request status of the specific recipient */ + LIBUSB_REQUEST_GET_STATUS = 0x00, + + /** Clear or disable a specific feature */ + LIBUSB_REQUEST_CLEAR_FEATURE = 0x01, + + /* 0x02 is reserved */ + + /** Set or enable a specific feature */ + LIBUSB_REQUEST_SET_FEATURE = 0x03, + + /* 0x04 is reserved */ + + /** Set device address for all future accesses */ + LIBUSB_REQUEST_SET_ADDRESS = 0x05, + + /** Get the specified descriptor */ + LIBUSB_REQUEST_GET_DESCRIPTOR = 0x06, + + /** Used to update existing descriptors or add new descriptors */ + LIBUSB_REQUEST_SET_DESCRIPTOR = 0x07, + + /** Get the current device configuration value */ + LIBUSB_REQUEST_GET_CONFIGURATION = 0x08, + + /** Set device configuration */ + LIBUSB_REQUEST_SET_CONFIGURATION = 0x09, + + /** Return the selected alternate setting for the specified interface */ + LIBUSB_REQUEST_GET_INTERFACE = 0x0a, + + /** Select an alternate interface for the specified interface */ + LIBUSB_REQUEST_SET_INTERFACE = 0x0b, + + /** Set then report an endpoint's synchronization frame */ + LIBUSB_REQUEST_SYNCH_FRAME = 0x0c, + + /** Sets both the U1 and U2 Exit Latency */ + LIBUSB_REQUEST_SET_SEL = 0x30, + + /** Delay from the time a host transmits a packet to the time it is + * received by the device. */ + LIBUSB_SET_ISOCH_DELAY = 0x31 +}; + +/** \ingroup libusb_misc + * Request type bits of the + * \ref libusb_control_setup::bmRequestType "bmRequestType" field in control + * transfers. */ +enum libusb_request_type { + /** Standard */ + LIBUSB_REQUEST_TYPE_STANDARD = (0x00 << 5), + + /** Class */ + LIBUSB_REQUEST_TYPE_CLASS = (0x01 << 5), + + /** Vendor */ + LIBUSB_REQUEST_TYPE_VENDOR = (0x02 << 5), + + /** Reserved */ + LIBUSB_REQUEST_TYPE_RESERVED = (0x03 << 5) +}; + +/** \ingroup libusb_misc + * Recipient bits of the + * \ref libusb_control_setup::bmRequestType "bmRequestType" field in control + * transfers. Values 4 through 31 are reserved. */ +enum libusb_request_recipient { + /** Device */ + LIBUSB_RECIPIENT_DEVICE = 0x00, + + /** Interface */ + LIBUSB_RECIPIENT_INTERFACE = 0x01, + + /** Endpoint */ + LIBUSB_RECIPIENT_ENDPOINT = 0x02, + + /** Other */ + LIBUSB_RECIPIENT_OTHER = 0x03 +}; + +#define LIBUSB_ISO_SYNC_TYPE_MASK 0x0c + +/** \ingroup libusb_desc + * Synchronization type for isochronous endpoints. Values for bits 2:3 of the + * \ref libusb_endpoint_descriptor::bmAttributes "bmAttributes" field in + * libusb_endpoint_descriptor. + */ +enum libusb_iso_sync_type { + /** No synchronization */ + LIBUSB_ISO_SYNC_TYPE_NONE = 0x0, + + /** Asynchronous */ + LIBUSB_ISO_SYNC_TYPE_ASYNC = 0x1, + + /** Adaptive */ + LIBUSB_ISO_SYNC_TYPE_ADAPTIVE = 0x2, + + /** Synchronous */ + LIBUSB_ISO_SYNC_TYPE_SYNC = 0x3 +}; + +#define LIBUSB_ISO_USAGE_TYPE_MASK 0x30 + +/** \ingroup libusb_desc + * Usage type for isochronous endpoints. Values for bits 4:5 of the + * \ref libusb_endpoint_descriptor::bmAttributes "bmAttributes" field in + * libusb_endpoint_descriptor. + */ +enum libusb_iso_usage_type { + /** Data endpoint */ + LIBUSB_ISO_USAGE_TYPE_DATA = 0x0, + + /** Feedback endpoint */ + LIBUSB_ISO_USAGE_TYPE_FEEDBACK = 0x1, + + /** Implicit feedback Data endpoint */ + LIBUSB_ISO_USAGE_TYPE_IMPLICIT = 0x2 +}; + +/** \ingroup libusb_desc + * Supported speeds (wSpeedSupported) bitfield. Indicates what + * speeds the device supports. + */ +enum libusb_supported_speed { + /** Low speed operation supported (1.5MBit/s). */ + LIBUSB_LOW_SPEED_OPERATION = (1 << 0), + + /** Full speed operation supported (12MBit/s). */ + LIBUSB_FULL_SPEED_OPERATION = (1 << 1), + + /** High speed operation supported (480MBit/s). */ + LIBUSB_HIGH_SPEED_OPERATION = (1 << 2), + + /** Superspeed operation supported (5000MBit/s). */ + LIBUSB_SUPER_SPEED_OPERATION = (1 << 3) +}; + +/** \ingroup libusb_desc + * Masks for the bits of the + * \ref libusb_usb_2_0_extension_descriptor::bmAttributes "bmAttributes" field + * of the USB 2.0 Extension descriptor. + */ +enum libusb_usb_2_0_extension_attributes { + /** Supports Link Power Management (LPM) */ + LIBUSB_BM_LPM_SUPPORT = (1 << 1) +}; + +/** \ingroup libusb_desc + * Masks for the bits of the + * \ref libusb_ss_usb_device_capability_descriptor::bmAttributes "bmAttributes" field + * field of the SuperSpeed USB Device Capability descriptor. + */ +enum libusb_ss_usb_device_capability_attributes { + /** Supports Latency Tolerance Messages (LTM) */ + LIBUSB_BM_LTM_SUPPORT = (1 << 1) +}; + +/** \ingroup libusb_desc + * USB capability types + */ +enum libusb_bos_type { + /** Wireless USB device capability */ + LIBUSB_BT_WIRELESS_USB_DEVICE_CAPABILITY = 0x01, + + /** USB 2.0 extensions */ + LIBUSB_BT_USB_2_0_EXTENSION = 0x02, + + /** SuperSpeed USB device capability */ + LIBUSB_BT_SS_USB_DEVICE_CAPABILITY = 0x03, + + /** Container ID type */ + LIBUSB_BT_CONTAINER_ID = 0x04 +}; + +/** \ingroup libusb_desc + * A structure representing the standard USB device descriptor. This + * descriptor is documented in section 9.6.1 of the USB 3.0 specification. + * All multiple-byte fields are represented in host-endian format. + */ +struct libusb_device_descriptor { + /** Size of this descriptor (in bytes) */ + uint8_t bLength; + + /** Descriptor type. Will have value + * \ref libusb_descriptor_type::LIBUSB_DT_DEVICE LIBUSB_DT_DEVICE in this + * context. */ + uint8_t bDescriptorType; + + /** USB specification release number in binary-coded decimal. A value of + * 0x0200 indicates USB 2.0, 0x0110 indicates USB 1.1, etc. */ + uint16_t bcdUSB; + + /** USB-IF class code for the device. See \ref libusb_class_code. */ + uint8_t bDeviceClass; + + /** USB-IF subclass code for the device, qualified by the bDeviceClass + * value */ + uint8_t bDeviceSubClass; + + /** USB-IF protocol code for the device, qualified by the bDeviceClass and + * bDeviceSubClass values */ + uint8_t bDeviceProtocol; + + /** Maximum packet size for endpoint 0 */ + uint8_t bMaxPacketSize0; + + /** USB-IF vendor ID */ + uint16_t idVendor; + + /** USB-IF product ID */ + uint16_t idProduct; + + /** Device release number in binary-coded decimal */ + uint16_t bcdDevice; + + /** Index of string descriptor describing manufacturer */ + uint8_t iManufacturer; + + /** Index of string descriptor describing product */ + uint8_t iProduct; + + /** Index of string descriptor containing device serial number */ + uint8_t iSerialNumber; + + /** Number of possible configurations */ + uint8_t bNumConfigurations; +}; + +/** \ingroup libusb_desc + * A structure representing the standard USB endpoint descriptor. This + * descriptor is documented in section 9.6.6 of the USB 3.0 specification. + * All multiple-byte fields are represented in host-endian format. + */ +struct libusb_endpoint_descriptor { + /** Size of this descriptor (in bytes) */ + uint8_t bLength; + + /** Descriptor type. Will have value + * \ref libusb_descriptor_type::LIBUSB_DT_ENDPOINT LIBUSB_DT_ENDPOINT in + * this context. */ + uint8_t bDescriptorType; + + /** The address of the endpoint described by this descriptor. Bits 0:3 are + * the endpoint number. Bits 4:6 are reserved. Bit 7 indicates direction, + * see \ref libusb_endpoint_direction. */ + uint8_t bEndpointAddress; + + /** Attributes which apply to the endpoint when it is configured using + * the bConfigurationValue. Bits 0:1 determine the transfer type and + * correspond to \ref libusb_endpoint_transfer_type. Bits 2:3 are only used + * for isochronous endpoints and correspond to \ref libusb_iso_sync_type. + * Bits 4:5 are also only used for isochronous endpoints and correspond to + * \ref libusb_iso_usage_type. Bits 6:7 are reserved. */ + uint8_t bmAttributes; + + /** Maximum packet size this endpoint is capable of sending/receiving. */ + uint16_t wMaxPacketSize; + + /** Interval for polling endpoint for data transfers. */ + uint8_t bInterval; + + /** For audio devices only: the rate at which synchronization feedback + * is provided. */ + uint8_t bRefresh; + + /** For audio devices only: the address if the synch endpoint */ + uint8_t bSynchAddress; + + /** Extra descriptors. If libusb encounters unknown endpoint descriptors, + * it will store them here, should you wish to parse them. */ + const unsigned char *extra; + + /** Length of the extra descriptors, in bytes. Must be non-negative. */ + int extra_length; +}; + +/** \ingroup libusb_desc + * A structure representing the standard USB interface descriptor. This + * descriptor is documented in section 9.6.5 of the USB 3.0 specification. + * All multiple-byte fields are represented in host-endian format. + */ +struct libusb_interface_descriptor { + /** Size of this descriptor (in bytes) */ + uint8_t bLength; + + /** Descriptor type. Will have value + * \ref libusb_descriptor_type::LIBUSB_DT_INTERFACE LIBUSB_DT_INTERFACE + * in this context. */ + uint8_t bDescriptorType; + + /** Number of this interface */ + uint8_t bInterfaceNumber; + + /** Value used to select this alternate setting for this interface */ + uint8_t bAlternateSetting; + + /** Number of endpoints used by this interface (excluding the control + * endpoint). */ + uint8_t bNumEndpoints; + + /** USB-IF class code for this interface. See \ref libusb_class_code. */ + uint8_t bInterfaceClass; + + /** USB-IF subclass code for this interface, qualified by the + * bInterfaceClass value */ + uint8_t bInterfaceSubClass; + + /** USB-IF protocol code for this interface, qualified by the + * bInterfaceClass and bInterfaceSubClass values */ + uint8_t bInterfaceProtocol; + + /** Index of string descriptor describing this interface */ + uint8_t iInterface; + + /** Array of endpoint descriptors. This length of this array is determined + * by the bNumEndpoints field. */ + const struct libusb_endpoint_descriptor *endpoint; + + /** Extra descriptors. If libusb encounters unknown interface descriptors, + * it will store them here, should you wish to parse them. */ + const unsigned char *extra; + + /** Length of the extra descriptors, in bytes. Must be non-negative. */ + int extra_length; +}; + +/** \ingroup libusb_desc + * A collection of alternate settings for a particular USB interface. + */ +struct libusb_interface { + /** Array of interface descriptors. The length of this array is determined + * by the num_altsetting field. */ + const struct libusb_interface_descriptor *altsetting; + + /** The number of alternate settings that belong to this interface. + * Must be non-negative. */ + int num_altsetting; +}; + +/** \ingroup libusb_desc + * A structure representing the standard USB configuration descriptor. This + * descriptor is documented in section 9.6.3 of the USB 3.0 specification. + * All multiple-byte fields are represented in host-endian format. + */ +struct libusb_config_descriptor { + /** Size of this descriptor (in bytes) */ + uint8_t bLength; + + /** Descriptor type. Will have value + * \ref libusb_descriptor_type::LIBUSB_DT_CONFIG LIBUSB_DT_CONFIG + * in this context. */ + uint8_t bDescriptorType; + + /** Total length of data returned for this configuration */ + uint16_t wTotalLength; + + /** Number of interfaces supported by this configuration */ + uint8_t bNumInterfaces; + + /** Identifier value for this configuration */ + uint8_t bConfigurationValue; + + /** Index of string descriptor describing this configuration */ + uint8_t iConfiguration; + + /** Configuration characteristics */ + uint8_t bmAttributes; + + /** Maximum power consumption of the USB device from this bus in this + * configuration when the device is fully operation. Expressed in units + * of 2 mA when the device is operating in high-speed mode and in units + * of 8 mA when the device is operating in super-speed mode. */ + uint8_t MaxPower; + + /** Array of interfaces supported by this configuration. The length of + * this array is determined by the bNumInterfaces field. */ + const struct libusb_interface *interface; + + /** Extra descriptors. If libusb encounters unknown configuration + * descriptors, it will store them here, should you wish to parse them. */ + const unsigned char *extra; + + /** Length of the extra descriptors, in bytes. Must be non-negative. */ + int extra_length; +}; + +/** \ingroup libusb_desc + * A structure representing the superspeed endpoint companion + * descriptor. This descriptor is documented in section 9.6.7 of + * the USB 3.0 specification. All multiple-byte fields are represented in + * host-endian format. + */ +struct libusb_ss_endpoint_companion_descriptor { + /** Size of this descriptor (in bytes) */ + uint8_t bLength; + + /** Descriptor type. Will have value + * \ref libusb_descriptor_type::LIBUSB_DT_SS_ENDPOINT_COMPANION in + * this context. */ + uint8_t bDescriptorType; + + /** The maximum number of packets the endpoint can send or + * receive as part of a burst. */ + uint8_t bMaxBurst; + + /** In bulk EP: bits 4:0 represents the maximum number of + * streams the EP supports. In isochronous EP: bits 1:0 + * represents the Mult - a zero based value that determines + * the maximum number of packets within a service interval */ + uint8_t bmAttributes; + + /** The total number of bytes this EP will transfer every + * service interval. Valid only for periodic EPs. */ + uint16_t wBytesPerInterval; +}; + +/** \ingroup libusb_desc + * A generic representation of a BOS Device Capability descriptor. It is + * advised to check bDevCapabilityType and call the matching + * libusb_get_*_descriptor function to get a structure fully matching the type. + */ +struct libusb_bos_dev_capability_descriptor { + /** Size of this descriptor (in bytes) */ + uint8_t bLength; + + /** Descriptor type. Will have value + * \ref libusb_descriptor_type::LIBUSB_DT_DEVICE_CAPABILITY + * LIBUSB_DT_DEVICE_CAPABILITY in this context. */ + uint8_t bDescriptorType; + + /** Device Capability type */ + uint8_t bDevCapabilityType; + + /** Device Capability data (bLength - 3 bytes) */ + uint8_t dev_capability_data[ZERO_SIZED_ARRAY]; +}; + +/** \ingroup libusb_desc + * A structure representing the Binary Device Object Store (BOS) descriptor. + * This descriptor is documented in section 9.6.2 of the USB 3.0 specification. + * All multiple-byte fields are represented in host-endian format. + */ +struct libusb_bos_descriptor { + /** Size of this descriptor (in bytes) */ + uint8_t bLength; + + /** Descriptor type. Will have value + * \ref libusb_descriptor_type::LIBUSB_DT_BOS LIBUSB_DT_BOS + * in this context. */ + uint8_t bDescriptorType; + + /** Length of this descriptor and all of its sub descriptors */ + uint16_t wTotalLength; + + /** The number of separate device capability descriptors in + * the BOS */ + uint8_t bNumDeviceCaps; + + /** bNumDeviceCap Device Capability Descriptors */ + struct libusb_bos_dev_capability_descriptor *dev_capability[ZERO_SIZED_ARRAY]; +}; + +/** \ingroup libusb_desc + * A structure representing the USB 2.0 Extension descriptor + * This descriptor is documented in section 9.6.2.1 of the USB 3.0 specification. + * All multiple-byte fields are represented in host-endian format. + */ +struct libusb_usb_2_0_extension_descriptor { + /** Size of this descriptor (in bytes) */ + uint8_t bLength; + + /** Descriptor type. Will have value + * \ref libusb_descriptor_type::LIBUSB_DT_DEVICE_CAPABILITY + * LIBUSB_DT_DEVICE_CAPABILITY in this context. */ + uint8_t bDescriptorType; + + /** Capability type. Will have value + * \ref libusb_capability_type::LIBUSB_BT_USB_2_0_EXTENSION + * LIBUSB_BT_USB_2_0_EXTENSION in this context. */ + uint8_t bDevCapabilityType; + + /** Bitmap encoding of supported device level features. + * A value of one in a bit location indicates a feature is + * supported; a value of zero indicates it is not supported. + * See \ref libusb_usb_2_0_extension_attributes. */ + uint32_t bmAttributes; +}; + +/** \ingroup libusb_desc + * A structure representing the SuperSpeed USB Device Capability descriptor + * This descriptor is documented in section 9.6.2.2 of the USB 3.0 specification. + * All multiple-byte fields are represented in host-endian format. + */ +struct libusb_ss_usb_device_capability_descriptor { + /** Size of this descriptor (in bytes) */ + uint8_t bLength; + + /** Descriptor type. Will have value + * \ref libusb_descriptor_type::LIBUSB_DT_DEVICE_CAPABILITY + * LIBUSB_DT_DEVICE_CAPABILITY in this context. */ + uint8_t bDescriptorType; + + /** Capability type. Will have value + * \ref libusb_capability_type::LIBUSB_BT_SS_USB_DEVICE_CAPABILITY + * LIBUSB_BT_SS_USB_DEVICE_CAPABILITY in this context. */ + uint8_t bDevCapabilityType; + + /** Bitmap encoding of supported device level features. + * A value of one in a bit location indicates a feature is + * supported; a value of zero indicates it is not supported. + * See \ref libusb_ss_usb_device_capability_attributes. */ + uint8_t bmAttributes; + + /** Bitmap encoding of the speed supported by this device when + * operating in SuperSpeed mode. See \ref libusb_supported_speed. */ + uint16_t wSpeedSupported; + + /** The lowest speed at which all the functionality supported + * by the device is available to the user. For example if the + * device supports all its functionality when connected at + * full speed and above then it sets this value to 1. */ + uint8_t bFunctionalitySupport; + + /** U1 Device Exit Latency. */ + uint8_t bU1DevExitLat; + + /** U2 Device Exit Latency. */ + uint16_t bU2DevExitLat; +}; + +/** \ingroup libusb_desc + * A structure representing the Container ID descriptor. + * This descriptor is documented in section 9.6.2.3 of the USB 3.0 specification. + * All multiple-byte fields, except UUIDs, are represented in host-endian format. + */ +struct libusb_container_id_descriptor { + /** Size of this descriptor (in bytes) */ + uint8_t bLength; + + /** Descriptor type. Will have value + * \ref libusb_descriptor_type::LIBUSB_DT_DEVICE_CAPABILITY + * LIBUSB_DT_DEVICE_CAPABILITY in this context. */ + uint8_t bDescriptorType; + + /** Capability type. Will have value + * \ref libusb_capability_type::LIBUSB_BT_CONTAINER_ID + * LIBUSB_BT_CONTAINER_ID in this context. */ + uint8_t bDevCapabilityType; + + /** Reserved field */ + uint8_t bReserved; + + /** 128 bit UUID */ + uint8_t ContainerID[16]; +}; + +/** \ingroup libusb_asyncio + * Setup packet for control transfers. */ +#if defined(_MSC_VER) || defined(__WATCOMC__) +#pragma pack(push, 1) +#endif +struct libusb_control_setup { + /** Request type. Bits 0:4 determine recipient, see + * \ref libusb_request_recipient. Bits 5:6 determine type, see + * \ref libusb_request_type. Bit 7 determines data transfer direction, see + * \ref libusb_endpoint_direction. + */ + uint8_t bmRequestType; + + /** Request. If the type bits of bmRequestType are equal to + * \ref libusb_request_type::LIBUSB_REQUEST_TYPE_STANDARD + * "LIBUSB_REQUEST_TYPE_STANDARD" then this field refers to + * \ref libusb_standard_request. For other cases, use of this field is + * application-specific. */ + uint8_t bRequest; + + /** Value. Varies according to request */ + uint16_t wValue; + + /** Index. Varies according to request, typically used to pass an index + * or offset */ + uint16_t wIndex; + + /** Number of bytes to transfer */ + uint16_t wLength; +} LIBUSB_PACKED; +#if defined(_MSC_VER) || defined(__WATCOMC__) +#pragma pack(pop) +#endif + +#define LIBUSB_CONTROL_SETUP_SIZE (sizeof(struct libusb_control_setup)) + +/* libusb */ + +struct libusb_context; +struct libusb_device; +struct libusb_device_handle; + +/** \ingroup libusb_lib + * Structure providing the version of the libusb runtime + */ +struct libusb_version { + /** Library major version. */ + const uint16_t major; + + /** Library minor version. */ + const uint16_t minor; + + /** Library micro version. */ + const uint16_t micro; + + /** Library nano version. */ + const uint16_t nano; + + /** Library release candidate suffix string, e.g. "-rc4". */ + const char *rc; + + /** For ABI compatibility only. */ + const char *describe; +}; + +/** \ingroup libusb_lib + * Structure representing a libusb session. The concept of individual libusb + * sessions allows for your program to use two libraries (or dynamically + * load two modules) which both independently use libusb. This will prevent + * interference between the individual libusb users - for example + * libusb_set_option() will not affect the other user of the library, and + * libusb_exit() will not destroy resources that the other user is still + * using. + * + * Sessions are created by libusb_init() and destroyed through libusb_exit(). + * If your application is guaranteed to only ever include a single libusb + * user (i.e. you), you do not have to worry about contexts: pass NULL in + * every function call where a context is required, and the default context + * will be used. Note that libusb_set_option(NULL, ...) is special, and adds + * an option to a list of default options for new contexts. + * + * For more information, see \ref libusb_contexts. + */ +typedef struct libusb_context libusb_context; + +/** \ingroup libusb_dev + * Structure representing a USB device detected on the system. This is an + * opaque type for which you are only ever provided with a pointer, usually + * originating from libusb_get_device_list() or libusb_hotplug_register_callback(). + * + * Certain operations can be performed on a device, but in order to do any + * I/O you will have to first obtain a device handle using libusb_open(). + * + * Devices are reference counted with libusb_ref_device() and + * libusb_unref_device(), and are freed when the reference count reaches 0. + * New devices presented by libusb_get_device_list() have a reference count of + * 1, and libusb_free_device_list() can optionally decrease the reference count + * on all devices in the list. libusb_open() adds another reference which is + * later destroyed by libusb_close(). + */ +typedef struct libusb_device libusb_device; + + +/** \ingroup libusb_dev + * Structure representing a handle on a USB device. This is an opaque type for + * which you are only ever provided with a pointer, usually originating from + * libusb_open(). + * + * A device handle is used to perform I/O and other operations. When finished + * with a device handle, you should call libusb_close(). + */ +typedef struct libusb_device_handle libusb_device_handle; + +/** \ingroup libusb_dev + * Speed codes. Indicates the speed at which the device is operating. + */ +enum libusb_speed { + /** The OS doesn't report or know the device speed. */ + LIBUSB_SPEED_UNKNOWN = 0, + + /** The device is operating at low speed (1.5MBit/s). */ + LIBUSB_SPEED_LOW = 1, + + /** The device is operating at full speed (12MBit/s). */ + LIBUSB_SPEED_FULL = 2, + + /** The device is operating at high speed (480MBit/s). */ + LIBUSB_SPEED_HIGH = 3, + + /** The device is operating at super speed (5000MBit/s). */ + LIBUSB_SPEED_SUPER = 4, + + /** The device is operating at super speed plus (10000MBit/s). */ + LIBUSB_SPEED_SUPER_PLUS = 5 +}; + +/** \ingroup libusb_misc + * Error codes. Most libusb functions return 0 on success or one of these + * codes on failure. + * You can call libusb_error_name() to retrieve a string representation of an + * error code or libusb_strerror() to get an end-user suitable description of + * an error code. + */ +enum libusb_error { + /** Success (no error) */ + LIBUSB_SUCCESS = 0, + + /** Input/output error */ + LIBUSB_ERROR_IO = -1, + + /** Invalid parameter */ + LIBUSB_ERROR_INVALID_PARAM = -2, + + /** Access denied (insufficient permissions) */ + LIBUSB_ERROR_ACCESS = -3, + + /** No such device (it may have been disconnected) */ + LIBUSB_ERROR_NO_DEVICE = -4, + + /** Entity not found */ + LIBUSB_ERROR_NOT_FOUND = -5, + + /** Resource busy */ + LIBUSB_ERROR_BUSY = -6, + + /** Operation timed out */ + LIBUSB_ERROR_TIMEOUT = -7, + + /** Overflow */ + LIBUSB_ERROR_OVERFLOW = -8, + + /** Pipe error */ + LIBUSB_ERROR_PIPE = -9, + + /** System call interrupted (perhaps due to signal) */ + LIBUSB_ERROR_INTERRUPTED = -10, + + /** Insufficient memory */ + LIBUSB_ERROR_NO_MEM = -11, + + /** Operation not supported or unimplemented on this platform */ + LIBUSB_ERROR_NOT_SUPPORTED = -12, + + /* NB: Remember to update LIBUSB_ERROR_COUNT below as well as the + message strings in strerror.c when adding new error codes here. */ + + /** Other error */ + LIBUSB_ERROR_OTHER = -99 +}; + +/* Total number of error codes in enum libusb_error */ +#define LIBUSB_ERROR_COUNT 14 + +/** \ingroup libusb_asyncio + * Transfer type */ +enum libusb_transfer_type { + /** Control transfer */ + LIBUSB_TRANSFER_TYPE_CONTROL = 0U, + + /** Isochronous transfer */ + LIBUSB_TRANSFER_TYPE_ISOCHRONOUS = 1U, + + /** Bulk transfer */ + LIBUSB_TRANSFER_TYPE_BULK = 2U, + + /** Interrupt transfer */ + LIBUSB_TRANSFER_TYPE_INTERRUPT = 3U, + + /** Bulk stream transfer */ + LIBUSB_TRANSFER_TYPE_BULK_STREAM = 4U +}; + +/** \ingroup libusb_asyncio + * Transfer status codes */ +enum libusb_transfer_status { + /** Transfer completed without error. Note that this does not indicate + * that the entire amount of requested data was transferred. */ + LIBUSB_TRANSFER_COMPLETED, + + /** Transfer failed */ + LIBUSB_TRANSFER_ERROR, + + /** Transfer timed out */ + LIBUSB_TRANSFER_TIMED_OUT, + + /** Transfer was cancelled */ + LIBUSB_TRANSFER_CANCELLED, + + /** For bulk/interrupt endpoints: halt condition detected (endpoint + * stalled). For control endpoints: control request not supported. */ + LIBUSB_TRANSFER_STALL, + + /** Device was disconnected */ + LIBUSB_TRANSFER_NO_DEVICE, + + /** Device sent more data than requested */ + LIBUSB_TRANSFER_OVERFLOW + + /* NB! Remember to update libusb_error_name() + when adding new status codes here. */ +}; + +/** \ingroup libusb_asyncio + * libusb_transfer.flags values */ +enum libusb_transfer_flags { + /** Report short frames as errors */ + LIBUSB_TRANSFER_SHORT_NOT_OK = (1U << 0), + + /** Automatically free() transfer buffer during libusb_free_transfer(). + * Note that buffers allocated with libusb_dev_mem_alloc() should not + * be attempted freed in this way, since free() is not an appropriate + * way to release such memory. */ + LIBUSB_TRANSFER_FREE_BUFFER = (1U << 1), + + /** Automatically call libusb_free_transfer() after callback returns. + * If this flag is set, it is illegal to call libusb_free_transfer() + * from your transfer callback, as this will result in a double-free + * when this flag is acted upon. */ + LIBUSB_TRANSFER_FREE_TRANSFER = (1U << 2), + + /** Terminate transfers that are a multiple of the endpoint's + * wMaxPacketSize with an extra zero length packet. This is useful + * when a device protocol mandates that each logical request is + * terminated by an incomplete packet (i.e. the logical requests are + * not separated by other means). + * + * This flag only affects host-to-device transfers to bulk and interrupt + * endpoints. In other situations, it is ignored. + * + * This flag only affects transfers with a length that is a multiple of + * the endpoint's wMaxPacketSize. On transfers of other lengths, this + * flag has no effect. Therefore, if you are working with a device that + * needs a ZLP whenever the end of the logical request falls on a packet + * boundary, then it is sensible to set this flag on <em>every</em> + * transfer (you do not have to worry about only setting it on transfers + * that end on the boundary). + * + * This flag is currently only supported on Linux. + * On other systems, libusb_submit_transfer() will return + * LIBUSB_ERROR_NOT_SUPPORTED for every transfer where this flag is set. + * + * Available since libusb-1.0.9. + */ + LIBUSB_TRANSFER_ADD_ZERO_PACKET = (1U << 3) +}; + +/** \ingroup libusb_asyncio + * Isochronous packet descriptor. */ +struct libusb_iso_packet_descriptor { + /** Length of data to request in this packet */ + unsigned int length; + + /** Amount of data that was actually transferred */ + unsigned int actual_length; + + /** Status code for this packet */ + enum libusb_transfer_status status; +}; + +struct libusb_transfer; + +/** \ingroup libusb_asyncio + * Asynchronous transfer callback function type. When submitting asynchronous + * transfers, you pass a pointer to a callback function of this type via the + * \ref libusb_transfer::callback "callback" member of the libusb_transfer + * structure. libusb will call this function later, when the transfer has + * completed or failed. See \ref libusb_asyncio for more information. + * \param transfer The libusb_transfer struct the callback function is being + * notified about. + */ +typedef void (LIBUSB_CALL *libusb_transfer_cb_fn)(struct libusb_transfer *transfer); + +/** \ingroup libusb_asyncio + * The generic USB transfer structure. The user populates this structure and + * then submits it in order to request a transfer. After the transfer has + * completed, the library populates the transfer with the results and passes + * it back to the user. + */ +struct libusb_transfer { + /** Handle of the device that this transfer will be submitted to */ + libusb_device_handle *dev_handle; + + /** A bitwise OR combination of \ref libusb_transfer_flags. */ + uint8_t flags; + + /** Address of the endpoint where this transfer will be sent. */ + unsigned char endpoint; + + /** Type of the transfer from \ref libusb_transfer_type */ + unsigned char type; + + /** Timeout for this transfer in milliseconds. A value of 0 indicates no + * timeout. */ + unsigned int timeout; + + /** The status of the transfer. Read-only, and only for use within + * transfer callback function. + * + * If this is an isochronous transfer, this field may read COMPLETED even + * if there were errors in the frames. Use the + * \ref libusb_iso_packet_descriptor::status "status" field in each packet + * to determine if errors occurred. */ + enum libusb_transfer_status status; + + /** Length of the data buffer. Must be non-negative. */ + int length; + + /** Actual length of data that was transferred. Read-only, and only for + * use within transfer callback function. Not valid for isochronous + * endpoint transfers. */ + int actual_length; + + /** Callback function. This will be invoked when the transfer completes, + * fails, or is cancelled. */ + libusb_transfer_cb_fn callback; + + /** User context data. Useful for associating specific data to a transfer + * that can be accessed from within the callback function. + * + * This field may be set manually or is taken as the `user_data` parameter + * of the following functions: + * - libusb_fill_bulk_transfer() + * - libusb_fill_bulk_stream_transfer() + * - libusb_fill_control_transfer() + * - libusb_fill_interrupt_transfer() + * - libusb_fill_iso_transfer() */ + void *user_data; + + /** Data buffer */ + unsigned char *buffer; + + /** Number of isochronous packets. Only used for I/O with isochronous + * endpoints. Must be non-negative. */ + int num_iso_packets; + + /** Isochronous packet descriptors, for isochronous transfers only. */ + struct libusb_iso_packet_descriptor iso_packet_desc[ZERO_SIZED_ARRAY]; +}; + +/** \ingroup libusb_misc + * Capabilities supported by an instance of libusb on the current running + * platform. Test if the loaded library supports a given capability by calling + * \ref libusb_has_capability(). + */ +enum libusb_capability { + /** The libusb_has_capability() API is available. */ + LIBUSB_CAP_HAS_CAPABILITY = 0x0000U, + + /** Hotplug support is available on this platform. */ + LIBUSB_CAP_HAS_HOTPLUG = 0x0001U, + + /** The library can access HID devices without requiring user intervention. + * Note that before being able to actually access an HID device, you may + * still have to call additional libusb functions such as + * \ref libusb_detach_kernel_driver(). */ + LIBUSB_CAP_HAS_HID_ACCESS = 0x0100U, + + /** The library supports detaching of the default USB driver, using + * \ref libusb_detach_kernel_driver(), if one is set by the OS kernel */ + LIBUSB_CAP_SUPPORTS_DETACH_KERNEL_DRIVER = 0x0101U +}; + +/** \ingroup libusb_lib + * Log message levels. + */ +enum libusb_log_level { + /** (0) : No messages ever emitted by the library (default) */ + LIBUSB_LOG_LEVEL_NONE = 0, + + /** (1) : Error messages are emitted */ + LIBUSB_LOG_LEVEL_ERROR = 1, + + /** (2) : Warning and error messages are emitted */ + LIBUSB_LOG_LEVEL_WARNING = 2, + + /** (3) : Informational, warning and error messages are emitted */ + LIBUSB_LOG_LEVEL_INFO = 3, + + /** (4) : All messages are emitted */ + LIBUSB_LOG_LEVEL_DEBUG = 4 +}; + +/** \ingroup libusb_lib + * Log callback mode. + * + * Since version 1.0.23, \ref LIBUSB_API_VERSION >= 0x01000107 + * + * \see libusb_set_log_cb() + */ +enum libusb_log_cb_mode { + /** Callback function handling all log messages. */ + LIBUSB_LOG_CB_GLOBAL = (1 << 0), + + /** Callback function handling context related log messages. */ + LIBUSB_LOG_CB_CONTEXT = (1 << 1) +}; + +/** \ingroup libusb_lib + * Callback function for handling log messages. + * \param ctx the context which is related to the log message, or NULL if it + * is a global log message + * \param level the log level, see \ref libusb_log_level for a description + * \param str the log message + * + * Since version 1.0.23, \ref LIBUSB_API_VERSION >= 0x01000107 + * + * \see libusb_set_log_cb() + */ +typedef void (LIBUSB_CALL *libusb_log_cb)(libusb_context *ctx, + enum libusb_log_level level, const char *str); + +int LIBUSB_CALL libusb_init(libusb_context **ctx); +void LIBUSB_CALL libusb_exit(libusb_context *ctx); +LIBUSB_DEPRECATED_FOR(libusb_set_option) +void LIBUSB_CALL libusb_set_debug(libusb_context *ctx, int level); +void LIBUSB_CALL libusb_set_log_cb(libusb_context *ctx, libusb_log_cb cb, int mode); +const struct libusb_version * LIBUSB_CALL libusb_get_version(void); +int LIBUSB_CALL libusb_has_capability(uint32_t capability); +const char * LIBUSB_CALL libusb_error_name(int errcode); +int LIBUSB_CALL libusb_setlocale(const char *locale); +const char * LIBUSB_CALL libusb_strerror(int errcode); + +ssize_t LIBUSB_CALL libusb_get_device_list(libusb_context *ctx, + libusb_device ***list); +void LIBUSB_CALL libusb_free_device_list(libusb_device **list, + int unref_devices); +libusb_device * LIBUSB_CALL libusb_ref_device(libusb_device *dev); +void LIBUSB_CALL libusb_unref_device(libusb_device *dev); + +int LIBUSB_CALL libusb_get_configuration(libusb_device_handle *dev, + int *config); +int LIBUSB_CALL libusb_get_device_descriptor(libusb_device *dev, + struct libusb_device_descriptor *desc); +int LIBUSB_CALL libusb_get_active_config_descriptor(libusb_device *dev, + struct libusb_config_descriptor **config); +int LIBUSB_CALL libusb_get_config_descriptor(libusb_device *dev, + uint8_t config_index, struct libusb_config_descriptor **config); +int LIBUSB_CALL libusb_get_config_descriptor_by_value(libusb_device *dev, + uint8_t bConfigurationValue, struct libusb_config_descriptor **config); +void LIBUSB_CALL libusb_free_config_descriptor( + struct libusb_config_descriptor *config); +int LIBUSB_CALL libusb_get_ss_endpoint_companion_descriptor( + libusb_context *ctx, + const struct libusb_endpoint_descriptor *endpoint, + struct libusb_ss_endpoint_companion_descriptor **ep_comp); +void LIBUSB_CALL libusb_free_ss_endpoint_companion_descriptor( + struct libusb_ss_endpoint_companion_descriptor *ep_comp); +int LIBUSB_CALL libusb_get_bos_descriptor(libusb_device_handle *dev_handle, + struct libusb_bos_descriptor **bos); +void LIBUSB_CALL libusb_free_bos_descriptor(struct libusb_bos_descriptor *bos); +int LIBUSB_CALL libusb_get_usb_2_0_extension_descriptor( + libusb_context *ctx, + struct libusb_bos_dev_capability_descriptor *dev_cap, + struct libusb_usb_2_0_extension_descriptor **usb_2_0_extension); +void LIBUSB_CALL libusb_free_usb_2_0_extension_descriptor( + struct libusb_usb_2_0_extension_descriptor *usb_2_0_extension); +int LIBUSB_CALL libusb_get_ss_usb_device_capability_descriptor( + libusb_context *ctx, + struct libusb_bos_dev_capability_descriptor *dev_cap, + struct libusb_ss_usb_device_capability_descriptor **ss_usb_device_cap); +void LIBUSB_CALL libusb_free_ss_usb_device_capability_descriptor( + struct libusb_ss_usb_device_capability_descriptor *ss_usb_device_cap); +int LIBUSB_CALL libusb_get_container_id_descriptor(libusb_context *ctx, + struct libusb_bos_dev_capability_descriptor *dev_cap, + struct libusb_container_id_descriptor **container_id); +void LIBUSB_CALL libusb_free_container_id_descriptor( + struct libusb_container_id_descriptor *container_id); +uint8_t LIBUSB_CALL libusb_get_bus_number(libusb_device *dev); +uint8_t LIBUSB_CALL libusb_get_port_number(libusb_device *dev); +int LIBUSB_CALL libusb_get_port_numbers(libusb_device *dev, uint8_t *port_numbers, int port_numbers_len); +LIBUSB_DEPRECATED_FOR(libusb_get_port_numbers) +int LIBUSB_CALL libusb_get_port_path(libusb_context *ctx, libusb_device *dev, uint8_t *path, uint8_t path_length); +libusb_device * LIBUSB_CALL libusb_get_parent(libusb_device *dev); +uint8_t LIBUSB_CALL libusb_get_device_address(libusb_device *dev); +int LIBUSB_CALL libusb_get_device_speed(libusb_device *dev); +int LIBUSB_CALL libusb_get_max_packet_size(libusb_device *dev, + unsigned char endpoint); +int LIBUSB_CALL libusb_get_max_iso_packet_size(libusb_device *dev, + unsigned char endpoint); + +int LIBUSB_CALL libusb_wrap_sys_device(libusb_context *ctx, intptr_t sys_dev, libusb_device_handle **dev_handle); +int LIBUSB_CALL libusb_open(libusb_device *dev, libusb_device_handle **dev_handle); +void LIBUSB_CALL libusb_close(libusb_device_handle *dev_handle); +libusb_device * LIBUSB_CALL libusb_get_device(libusb_device_handle *dev_handle); + +int LIBUSB_CALL libusb_set_configuration(libusb_device_handle *dev_handle, + int configuration); +int LIBUSB_CALL libusb_claim_interface(libusb_device_handle *dev_handle, + int interface_number); +int LIBUSB_CALL libusb_release_interface(libusb_device_handle *dev_handle, + int interface_number); + +libusb_device_handle * LIBUSB_CALL libusb_open_device_with_vid_pid( + libusb_context *ctx, uint16_t vendor_id, uint16_t product_id); + +int LIBUSB_CALL libusb_set_interface_alt_setting(libusb_device_handle *dev_handle, + int interface_number, int alternate_setting); +int LIBUSB_CALL libusb_clear_halt(libusb_device_handle *dev_handle, + unsigned char endpoint); +int LIBUSB_CALL libusb_reset_device(libusb_device_handle *dev_handle); + +int LIBUSB_CALL libusb_alloc_streams(libusb_device_handle *dev_handle, + uint32_t num_streams, unsigned char *endpoints, int num_endpoints); +int LIBUSB_CALL libusb_free_streams(libusb_device_handle *dev_handle, + unsigned char *endpoints, int num_endpoints); + +unsigned char * LIBUSB_CALL libusb_dev_mem_alloc(libusb_device_handle *dev_handle, + size_t length); +int LIBUSB_CALL libusb_dev_mem_free(libusb_device_handle *dev_handle, + unsigned char *buffer, size_t length); + +int LIBUSB_CALL libusb_kernel_driver_active(libusb_device_handle *dev_handle, + int interface_number); +int LIBUSB_CALL libusb_detach_kernel_driver(libusb_device_handle *dev_handle, + int interface_number); +int LIBUSB_CALL libusb_attach_kernel_driver(libusb_device_handle *dev_handle, + int interface_number); +int LIBUSB_CALL libusb_set_auto_detach_kernel_driver( + libusb_device_handle *dev_handle, int enable); + +/* async I/O */ + +/** \ingroup libusb_asyncio + * Get the data section of a control transfer. This convenience function is here + * to remind you that the data does not start until 8 bytes into the actual + * buffer, as the setup packet comes first. + * + * Calling this function only makes sense from a transfer callback function, + * or situations where you have already allocated a suitably sized buffer at + * transfer->buffer. + * + * \param transfer a transfer + * \returns pointer to the first byte of the data section + */ +static inline unsigned char *libusb_control_transfer_get_data( + struct libusb_transfer *transfer) +{ + return transfer->buffer + LIBUSB_CONTROL_SETUP_SIZE; +} + +/** \ingroup libusb_asyncio + * Get the control setup packet of a control transfer. This convenience + * function is here to remind you that the control setup occupies the first + * 8 bytes of the transfer data buffer. + * + * Calling this function only makes sense from a transfer callback function, + * or situations where you have already allocated a suitably sized buffer at + * transfer->buffer. + * + * \param transfer a transfer + * \returns a casted pointer to the start of the transfer data buffer + */ +static inline struct libusb_control_setup *libusb_control_transfer_get_setup( + struct libusb_transfer *transfer) +{ + return (struct libusb_control_setup *)(void *)transfer->buffer; +} + +/** \ingroup libusb_asyncio + * Helper function to populate the setup packet (first 8 bytes of the data + * buffer) for a control transfer. The wIndex, wValue and wLength values should + * be given in host-endian byte order. + * + * \param buffer buffer to output the setup packet into + * This pointer must be aligned to at least 2 bytes boundary. + * \param bmRequestType see the + * \ref libusb_control_setup::bmRequestType "bmRequestType" field of + * \ref libusb_control_setup + * \param bRequest see the + * \ref libusb_control_setup::bRequest "bRequest" field of + * \ref libusb_control_setup + * \param wValue see the + * \ref libusb_control_setup::wValue "wValue" field of + * \ref libusb_control_setup + * \param wIndex see the + * \ref libusb_control_setup::wIndex "wIndex" field of + * \ref libusb_control_setup + * \param wLength see the + * \ref libusb_control_setup::wLength "wLength" field of + * \ref libusb_control_setup + */ +static inline void libusb_fill_control_setup(unsigned char *buffer, + uint8_t bmRequestType, uint8_t bRequest, uint16_t wValue, uint16_t wIndex, + uint16_t wLength) +{ + struct libusb_control_setup *setup = (struct libusb_control_setup *)(void *)buffer; + setup->bmRequestType = bmRequestType; + setup->bRequest = bRequest; + setup->wValue = libusb_cpu_to_le16(wValue); + setup->wIndex = libusb_cpu_to_le16(wIndex); + setup->wLength = libusb_cpu_to_le16(wLength); +} + +struct libusb_transfer * LIBUSB_CALL libusb_alloc_transfer(int iso_packets); +int LIBUSB_CALL libusb_submit_transfer(struct libusb_transfer *transfer); +int LIBUSB_CALL libusb_cancel_transfer(struct libusb_transfer *transfer); +void LIBUSB_CALL libusb_free_transfer(struct libusb_transfer *transfer); +void LIBUSB_CALL libusb_transfer_set_stream_id( + struct libusb_transfer *transfer, uint32_t stream_id); +uint32_t LIBUSB_CALL libusb_transfer_get_stream_id( + struct libusb_transfer *transfer); + +/** \ingroup libusb_asyncio + * Helper function to populate the required \ref libusb_transfer fields + * for a control transfer. + * + * If you pass a transfer buffer to this function, the first 8 bytes will + * be interpreted as a control setup packet, and the wLength field will be + * used to automatically populate the \ref libusb_transfer::length "length" + * field of the transfer. Therefore the recommended approach is: + * -# Allocate a suitably sized data buffer (including space for control setup) + * -# Call libusb_fill_control_setup() + * -# If this is a host-to-device transfer with a data stage, put the data + * in place after the setup packet + * -# Call this function + * -# Call libusb_submit_transfer() + * + * It is also legal to pass a NULL buffer to this function, in which case this + * function will not attempt to populate the length field. Remember that you + * must then populate the buffer and length fields later. + * + * \param transfer the transfer to populate + * \param dev_handle handle of the device that will handle the transfer + * \param buffer data buffer. If provided, this function will interpret the + * first 8 bytes as a setup packet and infer the transfer length from that. + * This pointer must be aligned to at least 2 bytes boundary. + * \param callback callback function to be invoked on transfer completion + * \param user_data user data to pass to callback function + * \param timeout timeout for the transfer in milliseconds + */ +static inline void libusb_fill_control_transfer( + struct libusb_transfer *transfer, libusb_device_handle *dev_handle, + unsigned char *buffer, libusb_transfer_cb_fn callback, void *user_data, + unsigned int timeout) +{ + struct libusb_control_setup *setup = (struct libusb_control_setup *)(void *)buffer; + transfer->dev_handle = dev_handle; + transfer->endpoint = 0; + transfer->type = LIBUSB_TRANSFER_TYPE_CONTROL; + transfer->timeout = timeout; + transfer->buffer = buffer; + if (setup) + transfer->length = (int) (LIBUSB_CONTROL_SETUP_SIZE + + libusb_le16_to_cpu(setup->wLength)); + transfer->user_data = user_data; + transfer->callback = callback; +} + +/** \ingroup libusb_asyncio + * Helper function to populate the required \ref libusb_transfer fields + * for a bulk transfer. + * + * \param transfer the transfer to populate + * \param dev_handle handle of the device that will handle the transfer + * \param endpoint address of the endpoint where this transfer will be sent + * \param buffer data buffer + * \param length length of data buffer + * \param callback callback function to be invoked on transfer completion + * \param user_data user data to pass to callback function + * \param timeout timeout for the transfer in milliseconds + */ +static inline void libusb_fill_bulk_transfer(struct libusb_transfer *transfer, + libusb_device_handle *dev_handle, unsigned char endpoint, + unsigned char *buffer, int length, libusb_transfer_cb_fn callback, + void *user_data, unsigned int timeout) +{ + transfer->dev_handle = dev_handle; + transfer->endpoint = endpoint; + transfer->type = LIBUSB_TRANSFER_TYPE_BULK; + transfer->timeout = timeout; + transfer->buffer = buffer; + transfer->length = length; + transfer->user_data = user_data; + transfer->callback = callback; +} + +/** \ingroup libusb_asyncio + * Helper function to populate the required \ref libusb_transfer fields + * for a bulk transfer using bulk streams. + * + * Since version 1.0.19, \ref LIBUSB_API_VERSION >= 0x01000103 + * + * \param transfer the transfer to populate + * \param dev_handle handle of the device that will handle the transfer + * \param endpoint address of the endpoint where this transfer will be sent + * \param stream_id bulk stream id for this transfer + * \param buffer data buffer + * \param length length of data buffer + * \param callback callback function to be invoked on transfer completion + * \param user_data user data to pass to callback function + * \param timeout timeout for the transfer in milliseconds + */ +static inline void libusb_fill_bulk_stream_transfer( + struct libusb_transfer *transfer, libusb_device_handle *dev_handle, + unsigned char endpoint, uint32_t stream_id, + unsigned char *buffer, int length, libusb_transfer_cb_fn callback, + void *user_data, unsigned int timeout) +{ + libusb_fill_bulk_transfer(transfer, dev_handle, endpoint, buffer, + length, callback, user_data, timeout); + transfer->type = LIBUSB_TRANSFER_TYPE_BULK_STREAM; + libusb_transfer_set_stream_id(transfer, stream_id); +} + +/** \ingroup libusb_asyncio + * Helper function to populate the required \ref libusb_transfer fields + * for an interrupt transfer. + * + * \param transfer the transfer to populate + * \param dev_handle handle of the device that will handle the transfer + * \param endpoint address of the endpoint where this transfer will be sent + * \param buffer data buffer + * \param length length of data buffer + * \param callback callback function to be invoked on transfer completion + * \param user_data user data to pass to callback function + * \param timeout timeout for the transfer in milliseconds + */ +static inline void libusb_fill_interrupt_transfer( + struct libusb_transfer *transfer, libusb_device_handle *dev_handle, + unsigned char endpoint, unsigned char *buffer, int length, + libusb_transfer_cb_fn callback, void *user_data, unsigned int timeout) +{ + transfer->dev_handle = dev_handle; + transfer->endpoint = endpoint; + transfer->type = LIBUSB_TRANSFER_TYPE_INTERRUPT; + transfer->timeout = timeout; + transfer->buffer = buffer; + transfer->length = length; + transfer->user_data = user_data; + transfer->callback = callback; +} + +/** \ingroup libusb_asyncio + * Helper function to populate the required \ref libusb_transfer fields + * for an isochronous transfer. + * + * \param transfer the transfer to populate + * \param dev_handle handle of the device that will handle the transfer + * \param endpoint address of the endpoint where this transfer will be sent + * \param buffer data buffer + * \param length length of data buffer + * \param num_iso_packets the number of isochronous packets + * \param callback callback function to be invoked on transfer completion + * \param user_data user data to pass to callback function + * \param timeout timeout for the transfer in milliseconds + */ +static inline void libusb_fill_iso_transfer(struct libusb_transfer *transfer, + libusb_device_handle *dev_handle, unsigned char endpoint, + unsigned char *buffer, int length, int num_iso_packets, + libusb_transfer_cb_fn callback, void *user_data, unsigned int timeout) +{ + transfer->dev_handle = dev_handle; + transfer->endpoint = endpoint; + transfer->type = LIBUSB_TRANSFER_TYPE_ISOCHRONOUS; + transfer->timeout = timeout; + transfer->buffer = buffer; + transfer->length = length; + transfer->num_iso_packets = num_iso_packets; + transfer->user_data = user_data; + transfer->callback = callback; +} + +/** \ingroup libusb_asyncio + * Convenience function to set the length of all packets in an isochronous + * transfer, based on the num_iso_packets field in the transfer structure. + * + * \param transfer a transfer + * \param length the length to set in each isochronous packet descriptor + * \see libusb_get_max_packet_size() + */ +static inline void libusb_set_iso_packet_lengths( + struct libusb_transfer *transfer, unsigned int length) +{ + int i; + + for (i = 0; i < transfer->num_iso_packets; i++) + transfer->iso_packet_desc[i].length = length; +} + +/** \ingroup libusb_asyncio + * Convenience function to locate the position of an isochronous packet + * within the buffer of an isochronous transfer. + * + * This is a thorough function which loops through all preceding packets, + * accumulating their lengths to find the position of the specified packet. + * Typically you will assign equal lengths to each packet in the transfer, + * and hence the above method is sub-optimal. You may wish to use + * libusb_get_iso_packet_buffer_simple() instead. + * + * \param transfer a transfer + * \param packet the packet to return the address of + * \returns the base address of the packet buffer inside the transfer buffer, + * or NULL if the packet does not exist. + * \see libusb_get_iso_packet_buffer_simple() + */ +static inline unsigned char *libusb_get_iso_packet_buffer( + struct libusb_transfer *transfer, unsigned int packet) +{ + int i; + size_t offset = 0; + int _packet; + + /* oops..slight bug in the API. packet is an unsigned int, but we use + * signed integers almost everywhere else. range-check and convert to + * signed to avoid compiler warnings. FIXME for libusb-2. */ + if (packet > INT_MAX) + return NULL; + _packet = (int) packet; + + if (_packet >= transfer->num_iso_packets) + return NULL; + + for (i = 0; i < _packet; i++) + offset += transfer->iso_packet_desc[i].length; + + return transfer->buffer + offset; +} + +/** \ingroup libusb_asyncio + * Convenience function to locate the position of an isochronous packet + * within the buffer of an isochronous transfer, for transfers where each + * packet is of identical size. + * + * This function relies on the assumption that every packet within the transfer + * is of identical size to the first packet. Calculating the location of + * the packet buffer is then just a simple calculation: + * <tt>buffer + (packet_size * packet)</tt> + * + * Do not use this function on transfers other than those that have identical + * packet lengths for each packet. + * + * \param transfer a transfer + * \param packet the packet to return the address of + * \returns the base address of the packet buffer inside the transfer buffer, + * or NULL if the packet does not exist. + * \see libusb_get_iso_packet_buffer() + */ +static inline unsigned char *libusb_get_iso_packet_buffer_simple( + struct libusb_transfer *transfer, unsigned int packet) +{ + int _packet; + + /* oops..slight bug in the API. packet is an unsigned int, but we use + * signed integers almost everywhere else. range-check and convert to + * signed to avoid compiler warnings. FIXME for libusb-2. */ + if (packet > INT_MAX) + return NULL; + _packet = (int) packet; + + if (_packet >= transfer->num_iso_packets) + return NULL; + + return transfer->buffer + ((int) transfer->iso_packet_desc[0].length * _packet); +} + +/* sync I/O */ + +int LIBUSB_CALL libusb_control_transfer(libusb_device_handle *dev_handle, + uint8_t request_type, uint8_t bRequest, uint16_t wValue, uint16_t wIndex, + unsigned char *data, uint16_t wLength, unsigned int timeout); + +int LIBUSB_CALL libusb_bulk_transfer(libusb_device_handle *dev_handle, + unsigned char endpoint, unsigned char *data, int length, + int *actual_length, unsigned int timeout); + +int LIBUSB_CALL libusb_interrupt_transfer(libusb_device_handle *dev_handle, + unsigned char endpoint, unsigned char *data, int length, + int *actual_length, unsigned int timeout); + +/** \ingroup libusb_desc + * Retrieve a descriptor from the default control pipe. + * This is a convenience function which formulates the appropriate control + * message to retrieve the descriptor. + * + * \param dev_handle a device handle + * \param desc_type the descriptor type, see \ref libusb_descriptor_type + * \param desc_index the index of the descriptor to retrieve + * \param data output buffer for descriptor + * \param length size of data buffer + * \returns number of bytes returned in data, or LIBUSB_ERROR code on failure + */ +static inline int libusb_get_descriptor(libusb_device_handle *dev_handle, + uint8_t desc_type, uint8_t desc_index, unsigned char *data, int length) +{ + return libusb_control_transfer(dev_handle, LIBUSB_ENDPOINT_IN, + LIBUSB_REQUEST_GET_DESCRIPTOR, (uint16_t) ((desc_type << 8) | desc_index), + 0, data, (uint16_t) length, 1000); +} + +/** \ingroup libusb_desc + * Retrieve a descriptor from a device. + * This is a convenience function which formulates the appropriate control + * message to retrieve the descriptor. The string returned is Unicode, as + * detailed in the USB specifications. + * + * \param dev_handle a device handle + * \param desc_index the index of the descriptor to retrieve + * \param langid the language ID for the string descriptor + * \param data output buffer for descriptor + * \param length size of data buffer + * \returns number of bytes returned in data, or LIBUSB_ERROR code on failure + * \see libusb_get_string_descriptor_ascii() + */ +static inline int libusb_get_string_descriptor(libusb_device_handle *dev_handle, + uint8_t desc_index, uint16_t langid, unsigned char *data, int length) +{ + return libusb_control_transfer(dev_handle, LIBUSB_ENDPOINT_IN, + LIBUSB_REQUEST_GET_DESCRIPTOR, (uint16_t)((LIBUSB_DT_STRING << 8) | desc_index), + langid, data, (uint16_t) length, 1000); +} + +int LIBUSB_CALL libusb_get_string_descriptor_ascii(libusb_device_handle *dev_handle, + uint8_t desc_index, unsigned char *data, int length); + +/* polling and timeouts */ + +int LIBUSB_CALL libusb_try_lock_events(libusb_context *ctx); +void LIBUSB_CALL libusb_lock_events(libusb_context *ctx); +void LIBUSB_CALL libusb_unlock_events(libusb_context *ctx); +int LIBUSB_CALL libusb_event_handling_ok(libusb_context *ctx); +int LIBUSB_CALL libusb_event_handler_active(libusb_context *ctx); +void LIBUSB_CALL libusb_interrupt_event_handler(libusb_context *ctx); +void LIBUSB_CALL libusb_lock_event_waiters(libusb_context *ctx); +void LIBUSB_CALL libusb_unlock_event_waiters(libusb_context *ctx); +int LIBUSB_CALL libusb_wait_for_event(libusb_context *ctx, struct timeval *tv); + +int LIBUSB_CALL libusb_handle_events_timeout(libusb_context *ctx, + struct timeval *tv); +int LIBUSB_CALL libusb_handle_events_timeout_completed(libusb_context *ctx, + struct timeval *tv, int *completed); +int LIBUSB_CALL libusb_handle_events(libusb_context *ctx); +int LIBUSB_CALL libusb_handle_events_completed(libusb_context *ctx, int *completed); +int LIBUSB_CALL libusb_handle_events_locked(libusb_context *ctx, + struct timeval *tv); +int LIBUSB_CALL libusb_pollfds_handle_timeouts(libusb_context *ctx); +int LIBUSB_CALL libusb_get_next_timeout(libusb_context *ctx, + struct timeval *tv); + +/** \ingroup libusb_poll + * File descriptor for polling + */ +struct libusb_pollfd { + /** Numeric file descriptor */ + int fd; + + /** Event flags to poll for from <poll.h>. POLLIN indicates that you + * should monitor this file descriptor for becoming ready to read from, + * and POLLOUT indicates that you should monitor this file descriptor for + * nonblocking write readiness. */ + short events; +}; + +/** \ingroup libusb_poll + * Callback function, invoked when a new file descriptor should be added + * to the set of file descriptors monitored for events. + * \param fd the new file descriptor + * \param events events to monitor for, see \ref libusb_pollfd for a + * description + * \param user_data User data pointer specified in + * libusb_set_pollfd_notifiers() call + * \see libusb_set_pollfd_notifiers() + */ +typedef void (LIBUSB_CALL *libusb_pollfd_added_cb)(int fd, short events, + void *user_data); + +/** \ingroup libusb_poll + * Callback function, invoked when a file descriptor should be removed from + * the set of file descriptors being monitored for events. After returning + * from this callback, do not use that file descriptor again. + * \param fd the file descriptor to stop monitoring + * \param user_data User data pointer specified in + * libusb_set_pollfd_notifiers() call + * \see libusb_set_pollfd_notifiers() + */ +typedef void (LIBUSB_CALL *libusb_pollfd_removed_cb)(int fd, void *user_data); + +const struct libusb_pollfd ** LIBUSB_CALL libusb_get_pollfds( + libusb_context *ctx); +void LIBUSB_CALL libusb_free_pollfds(const struct libusb_pollfd **pollfds); +void LIBUSB_CALL libusb_set_pollfd_notifiers(libusb_context *ctx, + libusb_pollfd_added_cb added_cb, libusb_pollfd_removed_cb removed_cb, + void *user_data); + +/** \ingroup libusb_hotplug + * Callback handle. + * + * Callbacks handles are generated by libusb_hotplug_register_callback() + * and can be used to deregister callbacks. Callback handles are unique + * per libusb_context and it is safe to call libusb_hotplug_deregister_callback() + * on an already deregistered callback. + * + * Since version 1.0.16, \ref LIBUSB_API_VERSION >= 0x01000102 + * + * For more information, see \ref libusb_hotplug. + */ +typedef int libusb_hotplug_callback_handle; + +/** \ingroup libusb_hotplug + * + * Since version 1.0.16, \ref LIBUSB_API_VERSION >= 0x01000102 + * + * Hotplug events */ +typedef enum { + /** A device has been plugged in and is ready to use */ + LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED = (1 << 0), + + /** A device has left and is no longer available. + * It is the user's responsibility to call libusb_close on any handle associated with a disconnected device. + * It is safe to call libusb_get_device_descriptor on a device that has left */ + LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT = (1 << 1) +} libusb_hotplug_event; + +/** \ingroup libusb_hotplug + * + * Since version 1.0.16, \ref LIBUSB_API_VERSION >= 0x01000102 + * + * Hotplug flags */ +typedef enum { + /** Arm the callback and fire it for all matching currently attached devices. */ + LIBUSB_HOTPLUG_ENUMERATE = (1 << 0) +} libusb_hotplug_flag; + +/** \ingroup libusb_hotplug + * Convenience macro when not using any flags */ +#define LIBUSB_HOTPLUG_NO_FLAGS 0 + +/** \ingroup libusb_hotplug + * Wildcard matching for hotplug events */ +#define LIBUSB_HOTPLUG_MATCH_ANY -1 + +/** \ingroup libusb_hotplug + * Hotplug callback function type. When requesting hotplug event notifications, + * you pass a pointer to a callback function of this type. + * + * This callback may be called by an internal event thread and as such it is + * recommended the callback do minimal processing before returning. + * + * libusb will call this function later, when a matching event had happened on + * a matching device. See \ref libusb_hotplug for more information. + * + * It is safe to call either libusb_hotplug_register_callback() or + * libusb_hotplug_deregister_callback() from within a callback function. + * + * Since version 1.0.16, \ref LIBUSB_API_VERSION >= 0x01000102 + * + * \param ctx context of this notification + * \param device libusb_device this event occurred on + * \param event event that occurred + * \param user_data user data provided when this callback was registered + * \returns bool whether this callback is finished processing events. + * returning 1 will cause this callback to be deregistered + */ +typedef int (LIBUSB_CALL *libusb_hotplug_callback_fn)(libusb_context *ctx, + libusb_device *device, libusb_hotplug_event event, void *user_data); + +/** \ingroup libusb_hotplug + * Register a hotplug callback function + * + * Register a callback with the libusb_context. The callback will fire + * when a matching event occurs on a matching device. The callback is + * armed until either it is deregistered with libusb_hotplug_deregister_callback() + * or the supplied callback returns 1 to indicate it is finished processing events. + * + * If the \ref LIBUSB_HOTPLUG_ENUMERATE is passed the callback will be + * called with a \ref LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED for all devices + * already plugged into the machine. Note that libusb modifies its internal + * device list from a separate thread, while calling hotplug callbacks from + * libusb_handle_events(), so it is possible for a device to already be present + * on, or removed from, its internal device list, while the hotplug callbacks + * still need to be dispatched. This means that when using \ref + * LIBUSB_HOTPLUG_ENUMERATE, your callback may be called twice for the arrival + * of the same device, once from libusb_hotplug_register_callback() and once + * from libusb_handle_events(); and/or your callback may be called for the + * removal of a device for which an arrived call was never made. + * + * Since version 1.0.16, \ref LIBUSB_API_VERSION >= 0x01000102 + * + * \param[in] ctx context to register this callback with + * \param[in] events bitwise or of hotplug events that will trigger this callback. + * See \ref libusb_hotplug_event + * \param[in] flags bitwise or of hotplug flags that affect registration. + * See \ref libusb_hotplug_flag + * \param[in] vendor_id the vendor id to match or \ref LIBUSB_HOTPLUG_MATCH_ANY + * \param[in] product_id the product id to match or \ref LIBUSB_HOTPLUG_MATCH_ANY + * \param[in] dev_class the device class to match or \ref LIBUSB_HOTPLUG_MATCH_ANY + * \param[in] cb_fn the function to be invoked on a matching event/device + * \param[in] user_data user data to pass to the callback function + * \param[out] callback_handle pointer to store the handle of the allocated callback (can be NULL) + * \returns LIBUSB_SUCCESS on success LIBUSB_ERROR code on failure + */ +int LIBUSB_CALL libusb_hotplug_register_callback(libusb_context *ctx, + int events, int flags, + int vendor_id, int product_id, int dev_class, + libusb_hotplug_callback_fn cb_fn, void *user_data, + libusb_hotplug_callback_handle *callback_handle); + +/** \ingroup libusb_hotplug + * Deregisters a hotplug callback. + * + * Deregister a callback from a libusb_context. This function is safe to call from within + * a hotplug callback. + * + * Since version 1.0.16, \ref LIBUSB_API_VERSION >= 0x01000102 + * + * \param[in] ctx context this callback is registered with + * \param[in] callback_handle the handle of the callback to deregister + */ +void LIBUSB_CALL libusb_hotplug_deregister_callback(libusb_context *ctx, + libusb_hotplug_callback_handle callback_handle); + +/** \ingroup libusb_hotplug + * Gets the user_data associated with a hotplug callback. + * + * Since version v1.0.24 \ref LIBUSB_API_VERSION >= 0x01000108 + * + * \param[in] ctx context this callback is registered with + * \param[in] callback_handle the handle of the callback to get the user_data of + */ +void * LIBUSB_CALL libusb_hotplug_get_user_data(libusb_context *ctx, + libusb_hotplug_callback_handle callback_handle); + +/** \ingroup libusb_lib + * Available option values for libusb_set_option(). + */ +enum libusb_option { + /** Set the log message verbosity. + * + * The default level is LIBUSB_LOG_LEVEL_NONE, which means no messages are ever + * printed. If you choose to increase the message verbosity level, ensure + * that your application does not close the stderr file descriptor. + * + * You are advised to use level LIBUSB_LOG_LEVEL_WARNING. libusb is conservative + * with its message logging and most of the time, will only log messages that + * explain error conditions and other oddities. This will help you debug + * your software. + * + * If the LIBUSB_DEBUG environment variable was set when libusb was + * initialized, this function does nothing: the message verbosity is fixed + * to the value in the environment variable. + * + * If libusb was compiled without any message logging, this function does + * nothing: you'll never get any messages. + * + * If libusb was compiled with verbose debug message logging, this function + * does nothing: you'll always get messages from all levels. + */ + LIBUSB_OPTION_LOG_LEVEL = 0, + + /** Use the UsbDk backend for a specific context, if available. + * + * This option should be set immediately after calling libusb_init(), otherwise + * unspecified behavior may occur. + * + * Only valid on Windows. + */ + LIBUSB_OPTION_USE_USBDK = 1, + + /** Do not scan for devices + * + * With this option set, libusb will skip scanning devices in + * libusb_init(). Must be set before calling libusb_init(). + * + * Hotplug functionality will also be deactivated. + * + * The option is useful in combination with libusb_wrap_sys_device(), + * which can access a device directly without prior device scanning. + * + * This is typically needed on Android, where access to USB devices + * is limited. + * + * For LIBUSB_API_VERSION 0x01000108 it was called LIBUSB_OPTION_WEAK_AUTHORITY + * + * Only valid on Linux. + */ + LIBUSB_OPTION_NO_DEVICE_DISCOVERY = 2, + +#define LIBUSB_OPTION_WEAK_AUTHORITY LIBUSB_OPTION_NO_DEVICE_DISCOVERY + + LIBUSB_OPTION_MAX = 3 +}; + +int LIBUSB_CALL libusb_set_option(libusb_context *ctx, enum libusb_option option, ...); + +#if defined(__cplusplus) +} +#endif + +#endif diff --git a/Radio/HW/RtlSdr/libusb/libusbi.h b/Radio/HW/RtlSdr/libusb/libusbi.h new file mode 100644 index 0000000..6f38828 --- /dev/null +++ b/Radio/HW/RtlSdr/libusb/libusbi.h @@ -0,0 +1,1483 @@ +/* + * Internal header for libusb + * Copyright © 2007-2009 Daniel Drake <dsd@gentoo.org> + * Copyright © 2001 Johannes Erdfelt <johannes@erdfelt.com> + * Copyright © 2019 Nathan Hjelm <hjelmn@cs.umm.edu> + * Copyright © 2019-2020 Google LLC. All rights reserved. + * Copyright © 2020 Chris Dickens <christopher.a.dickens@gmail.com> + * + * 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 + */ + +#ifndef LIBUSBI_H +#define LIBUSBI_H + +#include "config.h" + +#include <assert.h> +#include <inttypes.h> +#include <stdarg.h> +#include <stddef.h> +#include <stdlib.h> +#ifdef HAVE_SYS_TIME_H +#include <sys/time.h> +#endif + +#include "libusb.h" + +/* Not all C standard library headers define static_assert in assert.h + * Additionally, Visual Studio treats static_assert as a keyword. + */ +#if !defined(__cplusplus) && !defined(static_assert) && !defined(_MSC_VER) +#define static_assert(cond, msg) _Static_assert(cond, msg) +#endif + +#ifdef NDEBUG +#define ASSERT_EQ(expression, value) (void)expression +#define ASSERT_NE(expression, value) (void)expression +#else +#define ASSERT_EQ(expression, value) assert(expression == value) +#define ASSERT_NE(expression, value) assert(expression != value) +#endif + +#define container_of(ptr, type, member) \ + ((type *)((uintptr_t)(ptr) - (uintptr_t)offsetof(type, member))) + +#ifndef ARRAYSIZE +#define ARRAYSIZE(array) (sizeof(array) / sizeof(array[0])) +#endif + +#ifndef CLAMP +#define CLAMP(val, min, max) \ + ((val) < (min) ? (min) : ((val) > (max) ? (max) : (val))) +#endif + +#ifndef MIN +#define MIN(a, b) ((a) < (b) ? (a) : (b)) +#endif + +#ifndef MAX +#define MAX(a, b) ((a) > (b) ? (a) : (b)) +#endif + +/* The following is used to silence warnings for unused variables */ +#if defined(UNREFERENCED_PARAMETER) +#define UNUSED(var) UNREFERENCED_PARAMETER(var) +#else +#define UNUSED(var) do { (void)(var); } while(0) +#endif + +/* Macro to align a value up to the next multiple of the size of a pointer */ +#define PTR_ALIGN(v) \ + (((v) + (sizeof(void *) - 1)) & ~(sizeof(void *) - 1)) + +/* Atomic operations + * + * Useful for reference counting or when accessing a value without a lock + * + * The following atomic operations are defined: + * usbi_atomic_load() - Atomically read a variable's value + * usbi_atomic_store() - Atomically write a new value value to a variable + * usbi_atomic_inc() - Atomically increment a variable's value and return the new value + * usbi_atomic_dec() - Atomically decrement a variable's value and return the new value + * + * All of these operations are ordered with each other, thus the effects of + * any one operation is guaranteed to be seen by any other operation. + */ +#ifdef _MSC_VER +typedef volatile LONG usbi_atomic_t; +#define usbi_atomic_load(a) (*(a)) +#define usbi_atomic_store(a, v) (*(a)) = (v) +#define usbi_atomic_inc(a) InterlockedIncrement((a)) +#define usbi_atomic_dec(a) InterlockedDecrement((a)) +#else +#include <stdatomic.h> +typedef atomic_long usbi_atomic_t; +#define usbi_atomic_load(a) atomic_load((a)) +#define usbi_atomic_store(a, v) atomic_store((a), (v)) +#define usbi_atomic_inc(a) (atomic_fetch_add((a), 1) + 1) +#define usbi_atomic_dec(a) (atomic_fetch_add((a), -1) - 1) +#endif + +/* Internal abstractions for event handling and thread synchronization */ +#if defined(PLATFORM_POSIX) +#include "os/events_posix.h" +#include "os/threads_posix.h" +#elif defined(PLATFORM_WINDOWS) +#include "os/events_windows.h" +#include "os/threads_windows.h" +#endif + +/* Inside the libusb code, mark all public functions as follows: + * return_type API_EXPORTED function_name(params) { ... } + * But if the function returns a pointer, mark it as follows: + * DEFAULT_VISIBILITY return_type * LIBUSB_CALL function_name(params) { ... } + * In the libusb public header, mark all declarations as: + * return_type LIBUSB_CALL function_name(params); + */ +#define API_EXPORTED LIBUSB_CALL DEFAULT_VISIBILITY + +#ifdef __cplusplus +extern "C" { +#endif + +#define USB_MAXENDPOINTS 32 +#define USB_MAXINTERFACES 32 +#define USB_MAXCONFIG 8 + +/* Backend specific capabilities */ +#define USBI_CAP_HAS_HID_ACCESS 0x00010000 +#define USBI_CAP_SUPPORTS_DETACH_KERNEL_DRIVER 0x00020000 + +/* Maximum number of bytes in a log line */ +#define USBI_MAX_LOG_LEN 1024 +/* Terminator for log lines */ +#define USBI_LOG_LINE_END "\n" + +struct list_head { + struct list_head *prev, *next; +}; + +/* Get an entry from the list + * ptr - the address of this list_head element in "type" + * type - the data type that contains "member" + * member - the list_head element in "type" + */ +#define list_entry(ptr, type, member) \ + container_of(ptr, type, member) + +#define list_first_entry(ptr, type, member) \ + list_entry((ptr)->next, type, member) + +#define list_next_entry(ptr, type, member) \ + list_entry((ptr)->member.next, type, member) + +/* Get each entry from a list + * pos - A structure pointer has a "member" element + * head - list head + * member - the list_head element in "pos" + * type - the type of the first parameter + */ +#define list_for_each_entry(pos, head, member, type) \ + for (pos = list_first_entry(head, type, member); \ + &pos->member != (head); \ + pos = list_next_entry(pos, type, member)) + +#define list_for_each_entry_safe(pos, n, head, member, type) \ + for (pos = list_first_entry(head, type, member), \ + n = list_next_entry(pos, type, member); \ + &pos->member != (head); \ + pos = n, n = list_next_entry(n, type, member)) + +/* Helper macros to iterate over a list. The structure pointed + * to by "pos" must have a list_head member named "list". + */ +#define for_each_helper(pos, head, type) \ + list_for_each_entry(pos, head, list, type) + +#define for_each_safe_helper(pos, n, head, type) \ + list_for_each_entry_safe(pos, n, head, list, type) + +#define list_empty(entry) ((entry)->next == (entry)) + +static inline void list_init(struct list_head *entry) +{ + entry->prev = entry->next = entry; +} + +static inline void list_add(struct list_head *entry, struct list_head *head) +{ + entry->next = head->next; + entry->prev = head; + + head->next->prev = entry; + head->next = entry; +} + +static inline void list_add_tail(struct list_head *entry, + struct list_head *head) +{ + entry->next = head; + entry->prev = head->prev; + + head->prev->next = entry; + head->prev = entry; +} + +static inline void list_del(struct list_head *entry) +{ + entry->next->prev = entry->prev; + entry->prev->next = entry->next; + entry->next = entry->prev = NULL; +} + +static inline void list_cut(struct list_head *list, struct list_head *head) +{ + if (list_empty(head)) { + list_init(list); + return; + } + + list->next = head->next; + list->next->prev = list; + list->prev = head->prev; + list->prev->next = list; + + list_init(head); +} + +static inline void list_splice_front(struct list_head *list, struct list_head *head) +{ + list->next->prev = head; + list->prev->next = head->next; + head->next->prev = list->prev; + head->next = list->next; +} + +static inline void *usbi_reallocf(void *ptr, size_t size) +{ + void *ret = realloc(ptr, size); + + if (!ret) + free(ptr); + return ret; +} + +#if !defined(USEC_PER_SEC) +#define USEC_PER_SEC 1000000L +#endif + +#if !defined(NSEC_PER_SEC) +#define NSEC_PER_SEC 1000000000L +#endif + +#define TIMEVAL_IS_VALID(tv) \ + ((tv)->tv_sec >= 0 && \ + (tv)->tv_usec >= 0 && (tv)->tv_usec < USEC_PER_SEC) + +#define TIMESPEC_IS_SET(ts) ((ts)->tv_sec || (ts)->tv_nsec) +#define TIMESPEC_CLEAR(ts) (ts)->tv_sec = (ts)->tv_nsec = 0 +#define TIMESPEC_CMP(a, b, CMP) \ + (((a)->tv_sec == (b)->tv_sec) \ + ? ((a)->tv_nsec CMP (b)->tv_nsec) \ + : ((a)->tv_sec CMP (b)->tv_sec)) +#define TIMESPEC_SUB(a, b, result) \ + do { \ + (result)->tv_sec = (a)->tv_sec - (b)->tv_sec; \ + (result)->tv_nsec = (a)->tv_nsec - (b)->tv_nsec; \ + if ((result)->tv_nsec < 0L) { \ + --(result)->tv_sec; \ + (result)->tv_nsec += NSEC_PER_SEC; \ + } \ + } while (0) + +#if defined(PLATFORM_WINDOWS) +#define TIMEVAL_TV_SEC_TYPE long +#else +#define TIMEVAL_TV_SEC_TYPE time_t +#endif + +/* Some platforms don't have this define */ +#ifndef TIMESPEC_TO_TIMEVAL +#define TIMESPEC_TO_TIMEVAL(tv, ts) \ + do { \ + (tv)->tv_sec = (TIMEVAL_TV_SEC_TYPE) (ts)->tv_sec; \ + (tv)->tv_usec = (ts)->tv_nsec / 1000L; \ + } while (0) +#endif + +#ifdef ENABLE_LOGGING + +#if defined(_MSC_VER) && (_MSC_VER < 1900) +#include <stdio.h> +#define snprintf usbi_snprintf +#define vsnprintf usbi_vsnprintf +int usbi_snprintf(char *dst, size_t size, const char *format, ...); +int usbi_vsnprintf(char *dst, size_t size, const char *format, va_list args); +#define LIBUSB_PRINTF_WIN32 +#endif /* defined(_MSC_VER) && (_MSC_VER < 1900) */ + +void usbi_log(struct libusb_context *ctx, enum libusb_log_level level, + const char *function, const char *format, ...) PRINTF_FORMAT(4, 5); + +#define _usbi_log(ctx, level, ...) usbi_log(ctx, level, __func__, __VA_ARGS__) + +#define usbi_err(ctx, ...) _usbi_log(ctx, LIBUSB_LOG_LEVEL_ERROR, __VA_ARGS__) +#define usbi_warn(ctx, ...) _usbi_log(ctx, LIBUSB_LOG_LEVEL_WARNING, __VA_ARGS__) +#define usbi_info(ctx, ...) _usbi_log(ctx, LIBUSB_LOG_LEVEL_INFO, __VA_ARGS__) +#define usbi_dbg(ctx ,...) _usbi_log(ctx, LIBUSB_LOG_LEVEL_DEBUG, __VA_ARGS__) + +#else /* ENABLE_LOGGING */ + +#define usbi_err(ctx, ...) UNUSED(ctx) +#define usbi_warn(ctx, ...) UNUSED(ctx) +#define usbi_info(ctx, ...) UNUSED(ctx) +#define usbi_dbg(ctx, ...) do {} while (0) + +#endif /* ENABLE_LOGGING */ + +#define DEVICE_CTX(dev) ((dev)->ctx) +#define HANDLE_CTX(handle) (DEVICE_CTX((handle)->dev)) +#define TRANSFER_CTX(transfer) (HANDLE_CTX((transfer)->dev_handle)) +#define ITRANSFER_CTX(itransfer) \ + (TRANSFER_CTX(USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer))) + +#define IS_EPIN(ep) (0 != ((ep) & LIBUSB_ENDPOINT_IN)) +#define IS_EPOUT(ep) (!IS_EPIN(ep)) +#define IS_XFERIN(xfer) (0 != ((xfer)->endpoint & LIBUSB_ENDPOINT_IN)) +#define IS_XFEROUT(xfer) (!IS_XFERIN(xfer)) + +struct libusb_context { +#if defined(ENABLE_LOGGING) && !defined(ENABLE_DEBUG_LOGGING) + enum libusb_log_level debug; + int debug_fixed; + libusb_log_cb log_handler; +#endif + + /* used for signalling occurrence of an internal event. */ + usbi_event_t event; + +#ifdef HAVE_OS_TIMER + /* used for timeout handling, if supported by OS. + * this timer is maintained to trigger on the next pending timeout */ + usbi_timer_t timer; +#endif + + struct list_head usb_devs; + usbi_mutex_t usb_devs_lock; + + /* A list of open handles. Backends are free to traverse this if required. + */ + struct list_head open_devs; + usbi_mutex_t open_devs_lock; + + /* A list of registered hotplug callbacks */ + struct list_head hotplug_cbs; + libusb_hotplug_callback_handle next_hotplug_cb_handle; + usbi_mutex_t hotplug_cbs_lock; + + /* A flag to indicate that the context is ready for hotplug notifications */ + usbi_atomic_t hotplug_ready; + + /* this is a list of in-flight transfer handles, sorted by timeout + * expiration. URBs to timeout the soonest are placed at the beginning of + * the list, URBs that will time out later are placed after, and urbs with + * infinite timeout are always placed at the very end. */ + struct list_head flying_transfers; + /* Note paths taking both this and usbi_transfer->lock must always + * take this lock first */ + usbi_mutex_t flying_transfers_lock; + +#if !defined(PLATFORM_WINDOWS) + /* user callbacks for pollfd changes */ + libusb_pollfd_added_cb fd_added_cb; + libusb_pollfd_removed_cb fd_removed_cb; + void *fd_cb_user_data; +#endif + + /* ensures that only one thread is handling events at any one time */ + usbi_mutex_t events_lock; + + /* used to see if there is an active thread doing event handling */ + int event_handler_active; + + /* A thread-local storage key to track which thread is performing event + * handling */ + usbi_tls_key_t event_handling_key; + + /* used to wait for event completion in threads other than the one that is + * event handling */ + usbi_mutex_t event_waiters_lock; + usbi_cond_t event_waiters_cond; + + /* A lock to protect internal context event data. */ + usbi_mutex_t event_data_lock; + + /* A bitmask of flags that are set to indicate specific events that need to + * be handled. Protected by event_data_lock. */ + unsigned int event_flags; + + /* A counter that is set when we want to interrupt and prevent event handling, + * in order to safely close a device. Protected by event_data_lock. */ + unsigned int device_close; + + /* A list of currently active event sources. Protected by event_data_lock. */ + struct list_head event_sources; + + /* A list of event sources that have been removed since the last time + * event sources were waited on. Protected by event_data_lock. */ + struct list_head removed_event_sources; + + /* A pointer and count to platform-specific data used for monitoring event + * sources. Only accessed during event handling. */ + void *event_data; + unsigned int event_data_cnt; + + /* A list of pending hotplug messages. Protected by event_data_lock. */ + struct list_head hotplug_msgs; + + /* A list of pending completed transfers. Protected by event_data_lock. */ + struct list_head completed_transfers; + + struct list_head list; +}; + +extern struct libusb_context *usbi_default_context; + +extern struct list_head active_contexts_list; +extern usbi_mutex_static_t active_contexts_lock; + +static inline struct libusb_context *usbi_get_context(struct libusb_context *ctx) +{ + return ctx ? ctx : usbi_default_context; +} + +enum usbi_event_flags { + /* The list of event sources has been modified */ + USBI_EVENT_EVENT_SOURCES_MODIFIED = 1U << 0, + + /* The user has interrupted the event handler */ + USBI_EVENT_USER_INTERRUPT = 1U << 1, + + /* A hotplug callback deregistration is pending */ + USBI_EVENT_HOTPLUG_CB_DEREGISTERED = 1U << 2, + + /* One or more hotplug messages are pending */ + USBI_EVENT_HOTPLUG_MSG_PENDING = 1U << 3, + + /* One or more completed transfers are pending */ + USBI_EVENT_TRANSFER_COMPLETED = 1U << 4, + + /* A device is in the process of being closed */ + USBI_EVENT_DEVICE_CLOSE = 1U << 5, +}; + +/* Macros for managing event handling state */ +static inline int usbi_handling_events(struct libusb_context *ctx) +{ + return usbi_tls_key_get(ctx->event_handling_key) != NULL; +} + +static inline void usbi_start_event_handling(struct libusb_context *ctx) +{ + usbi_tls_key_set(ctx->event_handling_key, ctx); +} + +static inline void usbi_end_event_handling(struct libusb_context *ctx) +{ + usbi_tls_key_set(ctx->event_handling_key, NULL); +} + +struct libusb_device { + usbi_atomic_t refcnt; + + struct libusb_context *ctx; + struct libusb_device *parent_dev; + + uint8_t bus_number; + uint8_t port_number; + uint8_t device_address; + enum libusb_speed speed; + + struct list_head list; + unsigned long session_data; + + struct libusb_device_descriptor device_descriptor; + usbi_atomic_t attached; +}; + +struct libusb_device_handle { + /* lock protects claimed_interfaces */ + usbi_mutex_t lock; + unsigned long claimed_interfaces; + + struct list_head list; + struct libusb_device *dev; + int auto_detach_kernel_driver; +}; + +/* Function called by backend during device initialization to convert + * multi-byte fields in the device descriptor to host-endian format. + */ +static inline void usbi_localize_device_descriptor(struct libusb_device_descriptor *desc) +{ + desc->bcdUSB = libusb_le16_to_cpu(desc->bcdUSB); + desc->idVendor = libusb_le16_to_cpu(desc->idVendor); + desc->idProduct = libusb_le16_to_cpu(desc->idProduct); + desc->bcdDevice = libusb_le16_to_cpu(desc->bcdDevice); +} + +#ifdef HAVE_CLOCK_GETTIME +static inline void usbi_get_monotonic_time(struct timespec *tp) +{ + ASSERT_EQ(clock_gettime(CLOCK_MONOTONIC, tp), 0); +} +static inline void usbi_get_real_time(struct timespec *tp) +{ + ASSERT_EQ(clock_gettime(CLOCK_REALTIME, tp), 0); +} +#else +/* If the platform doesn't provide the clock_gettime() function, the backend + * must provide its own clock implementations. Two clock functions are + * required: + * + * usbi_get_monotonic_time(): returns the time since an unspecified starting + * point (usually boot) that is monotonically + * increasing. + * usbi_get_real_time(): returns the time since system epoch. + */ +void usbi_get_monotonic_time(struct timespec *tp); +void usbi_get_real_time(struct timespec *tp); +#endif + +/* in-memory transfer layout: + * + * 1. os private data + * 2. struct usbi_transfer + * 3. struct libusb_transfer (which includes iso packets) [variable size] + * + * from a libusb_transfer, you can get the usbi_transfer by rewinding the + * appropriate number of bytes. + */ + +struct usbi_transfer { + int num_iso_packets; + struct list_head list; + struct list_head completed_list; + struct timespec timeout; + int transferred; + uint32_t stream_id; + uint32_t state_flags; /* Protected by usbi_transfer->lock */ + uint32_t timeout_flags; /* Protected by the flying_stransfers_lock */ + + /* this lock is held during libusb_submit_transfer() and + * libusb_cancel_transfer() (allowing the OS backend to prevent duplicate + * cancellation, submission-during-cancellation, etc). the OS backend + * should also take this lock in the handle_events path, to prevent the user + * cancelling the transfer from another thread while you are processing + * its completion (presumably there would be races within your OS backend + * if this were possible). + * Note paths taking both this and the flying_transfers_lock must + * always take the flying_transfers_lock first */ + usbi_mutex_t lock; + + void *priv; +}; + +enum usbi_transfer_state_flags { + /* Transfer successfully submitted by backend */ + USBI_TRANSFER_IN_FLIGHT = 1U << 0, + + /* Cancellation was requested via libusb_cancel_transfer() */ + USBI_TRANSFER_CANCELLING = 1U << 1, + + /* Operation on the transfer failed because the device disappeared */ + USBI_TRANSFER_DEVICE_DISAPPEARED = 1U << 2, +}; + +enum usbi_transfer_timeout_flags { + /* Set by backend submit_transfer() if the OS handles timeout */ + USBI_TRANSFER_OS_HANDLES_TIMEOUT = 1U << 0, + + /* The transfer timeout has been handled */ + USBI_TRANSFER_TIMEOUT_HANDLED = 1U << 1, + + /* The transfer timeout was successfully processed */ + USBI_TRANSFER_TIMED_OUT = 1U << 2, +}; + +#define USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer) \ + ((struct libusb_transfer *) \ + ((unsigned char *)(itransfer) \ + + PTR_ALIGN(sizeof(struct usbi_transfer)))) +#define LIBUSB_TRANSFER_TO_USBI_TRANSFER(transfer) \ + ((struct usbi_transfer *) \ + ((unsigned char *)(transfer) \ + - PTR_ALIGN(sizeof(struct usbi_transfer)))) + +#ifdef _MSC_VER +#pragma pack(push, 1) +#endif + +/* All standard descriptors have these 2 fields in common */ +struct usbi_descriptor_header { + uint8_t bLength; + uint8_t bDescriptorType; +} LIBUSB_PACKED; + +struct usbi_device_descriptor { + uint8_t bLength; + uint8_t bDescriptorType; + uint16_t bcdUSB; + uint8_t bDeviceClass; + uint8_t bDeviceSubClass; + uint8_t bDeviceProtocol; + uint8_t bMaxPacketSize0; + uint16_t idVendor; + uint16_t idProduct; + uint16_t bcdDevice; + uint8_t iManufacturer; + uint8_t iProduct; + uint8_t iSerialNumber; + uint8_t bNumConfigurations; +} LIBUSB_PACKED; + +struct usbi_configuration_descriptor { + uint8_t bLength; + uint8_t bDescriptorType; + uint16_t wTotalLength; + uint8_t bNumInterfaces; + uint8_t bConfigurationValue; + uint8_t iConfiguration; + uint8_t bmAttributes; + uint8_t bMaxPower; +} LIBUSB_PACKED; + +struct usbi_interface_descriptor { + uint8_t bLength; + uint8_t bDescriptorType; + uint8_t bInterfaceNumber; + uint8_t bAlternateSetting; + uint8_t bNumEndpoints; + uint8_t bInterfaceClass; + uint8_t bInterfaceSubClass; + uint8_t bInterfaceProtocol; + uint8_t iInterface; +} LIBUSB_PACKED; + +struct usbi_string_descriptor { + uint8_t bLength; + uint8_t bDescriptorType; + uint16_t wData[ZERO_SIZED_ARRAY]; +} LIBUSB_PACKED; + +struct usbi_bos_descriptor { + uint8_t bLength; + uint8_t bDescriptorType; + uint16_t wTotalLength; + uint8_t bNumDeviceCaps; +} LIBUSB_PACKED; + +#ifdef _MSC_VER +#pragma pack(pop) +#endif + +union usbi_config_desc_buf { + struct usbi_configuration_descriptor desc; + uint8_t buf[LIBUSB_DT_CONFIG_SIZE]; + uint16_t align; /* Force 2-byte alignment */ +}; + +union usbi_string_desc_buf { + struct usbi_string_descriptor desc; + uint8_t buf[255]; /* Some devices choke on size > 255 */ + uint16_t align; /* Force 2-byte alignment */ +}; + +union usbi_bos_desc_buf { + struct usbi_bos_descriptor desc; + uint8_t buf[LIBUSB_DT_BOS_SIZE]; + uint16_t align; /* Force 2-byte alignment */ +}; + +enum usbi_hotplug_flags { + /* This callback is interested in device arrivals */ + USBI_HOTPLUG_DEVICE_ARRIVED = LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED, + + /* This callback is interested in device removals */ + USBI_HOTPLUG_DEVICE_LEFT = LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT, + + /* IMPORTANT: The values for the below entries must start *after* + * the highest value of the above entries!!! + */ + + /* The vendor_id field is valid for matching */ + USBI_HOTPLUG_VENDOR_ID_VALID = (1U << 3), + + /* The product_id field is valid for matching */ + USBI_HOTPLUG_PRODUCT_ID_VALID = (1U << 4), + + /* The dev_class field is valid for matching */ + USBI_HOTPLUG_DEV_CLASS_VALID = (1U << 5), + + /* This callback has been unregistered and needs to be freed */ + USBI_HOTPLUG_NEEDS_FREE = (1U << 6), +}; + +struct usbi_hotplug_callback { + /* Flags that control how this callback behaves */ + uint8_t flags; + + /* Vendor ID to match (if flags says this is valid) */ + uint16_t vendor_id; + + /* Product ID to match (if flags says this is valid) */ + uint16_t product_id; + + /* Device class to match (if flags says this is valid) */ + uint8_t dev_class; + + /* Callback function to invoke for matching event/device */ + libusb_hotplug_callback_fn cb; + + /* Handle for this callback (used to match on deregister) */ + libusb_hotplug_callback_handle handle; + + /* User data that will be passed to the callback function */ + void *user_data; + + /* List this callback is registered in (ctx->hotplug_cbs) */ + struct list_head list; +}; + +struct usbi_hotplug_message { + /* The hotplug event that occurred */ + libusb_hotplug_event event; + + /* The device for which this hotplug event occurred */ + struct libusb_device *device; + + /* List this message is contained in (ctx->hotplug_msgs) */ + struct list_head list; +}; + +/* shared data and functions */ + +void usbi_hotplug_init(struct libusb_context *ctx); +void usbi_hotplug_exit(struct libusb_context *ctx); +void usbi_hotplug_notification(struct libusb_context *ctx, struct libusb_device *dev, + libusb_hotplug_event event); +void usbi_hotplug_process(struct libusb_context *ctx, struct list_head *hotplug_msgs); + +int usbi_io_init(struct libusb_context *ctx); +void usbi_io_exit(struct libusb_context *ctx); + +struct libusb_device *usbi_alloc_device(struct libusb_context *ctx, + unsigned long session_id); +struct libusb_device *usbi_get_device_by_session_id(struct libusb_context *ctx, + unsigned long session_id); +int usbi_sanitize_device(struct libusb_device *dev); +void usbi_handle_disconnect(struct libusb_device_handle *dev_handle); + +int usbi_handle_transfer_completion(struct usbi_transfer *itransfer, + enum libusb_transfer_status status); +int usbi_handle_transfer_cancellation(struct usbi_transfer *itransfer); +void usbi_signal_transfer_completion(struct usbi_transfer *itransfer); + +void usbi_connect_device(struct libusb_device *dev); +void usbi_disconnect_device(struct libusb_device *dev); + +struct usbi_event_source { + struct usbi_event_source_data { + usbi_os_handle_t os_handle; + short poll_events; + } data; + struct list_head list; +}; + +int usbi_add_event_source(struct libusb_context *ctx, usbi_os_handle_t os_handle, + short poll_events); +void usbi_remove_event_source(struct libusb_context *ctx, usbi_os_handle_t os_handle); + +struct usbi_option { + int is_set; + union { + int ival; + } arg; +}; + +/* OS event abstraction */ + +int usbi_create_event(usbi_event_t *event); +void usbi_destroy_event(usbi_event_t *event); +void usbi_signal_event(usbi_event_t *event); +void usbi_clear_event(usbi_event_t *event); + +#ifdef HAVE_OS_TIMER +int usbi_create_timer(usbi_timer_t *timer); +void usbi_destroy_timer(usbi_timer_t *timer); +int usbi_arm_timer(usbi_timer_t *timer, const struct timespec *timeout); +int usbi_disarm_timer(usbi_timer_t *timer); +#endif + +static inline int usbi_using_timer(struct libusb_context *ctx) +{ +#ifdef HAVE_OS_TIMER + return usbi_timer_valid(&ctx->timer); +#else + UNUSED(ctx); + return 0; +#endif +} + +struct usbi_reported_events { + union { + struct { + unsigned int event_triggered:1; +#ifdef HAVE_OS_TIMER + unsigned int timer_triggered:1; +#endif + }; + unsigned int event_bits; + }; + void *event_data; + unsigned int event_data_count; + unsigned int num_ready; +}; + +int usbi_alloc_event_data(struct libusb_context *ctx); +int usbi_wait_for_events(struct libusb_context *ctx, + struct usbi_reported_events *reported_events, int timeout_ms); + +/* accessor functions for structure private data */ + +static inline void *usbi_get_context_priv(struct libusb_context *ctx) +{ + return (unsigned char *)ctx + PTR_ALIGN(sizeof(*ctx)); +} + +static inline void *usbi_get_device_priv(struct libusb_device *dev) +{ + return (unsigned char *)dev + PTR_ALIGN(sizeof(*dev)); +} + +static inline void *usbi_get_device_handle_priv(struct libusb_device_handle *dev_handle) +{ + return (unsigned char *)dev_handle + PTR_ALIGN(sizeof(*dev_handle)); +} + +static inline void *usbi_get_transfer_priv(struct usbi_transfer *itransfer) +{ + return itransfer->priv; +} + +/* device discovery */ + +/* we traverse usbfs without knowing how many devices we are going to find. + * so we create this discovered_devs model which is similar to a linked-list + * which grows when required. it can be freed once discovery has completed, + * eliminating the need for a list node in the libusb_device structure + * itself. */ +struct discovered_devs { + size_t len; + size_t capacity; + struct libusb_device *devices[ZERO_SIZED_ARRAY]; +}; + +struct discovered_devs *discovered_devs_append( + struct discovered_devs *discdevs, struct libusb_device *dev); + +/* OS abstraction */ + +/* This is the interface that OS backends need to implement. + * All fields are mandatory, except ones explicitly noted as optional. */ +struct usbi_os_backend { + /* A human-readable name for your backend, e.g. "Linux usbfs" */ + const char *name; + + /* Binary mask for backend specific capabilities */ + uint32_t caps; + + /* Perform initialization of your backend. You might use this function + * to determine specific capabilities of the system, allocate required + * data structures for later, etc. + * + * This function is called when a libusb user initializes the library + * prior to use. Mutual exclusion with other init and exit calls is + * guaranteed when this function is called. + * + * Return 0 on success, or a LIBUSB_ERROR code on failure. + */ + int (*init)(struct libusb_context *ctx); + + /* Deinitialization. Optional. This function should destroy anything + * that was set up by init. + * + * This function is called when the user deinitializes the library. + * Mutual exclusion with other init and exit calls is guaranteed when + * this function is called. + */ + void (*exit)(struct libusb_context *ctx); + + /* Set a backend-specific option. Optional. + * + * This function is called when the user calls libusb_set_option() and + * the option is not handled by the core library. + * + * Return 0 on success, or a LIBUSB_ERROR code on failure. + */ + int (*set_option)(struct libusb_context *ctx, enum libusb_option option, + va_list args); + + /* Enumerate all the USB devices on the system, returning them in a list + * of discovered devices. + * + * Your implementation should enumerate all devices on the system, + * regardless of whether they have been seen before or not. + * + * When you have found a device, compute a session ID for it. The session + * ID should uniquely represent that particular device for that particular + * connection session since boot (i.e. if you disconnect and reconnect a + * device immediately after, it should be assigned a different session ID). + * If your OS cannot provide a unique session ID as described above, + * presenting a session ID of (bus_number << 8 | device_address) should + * be sufficient. Bus numbers and device addresses wrap and get reused, + * but that is an unlikely case. + * + * After computing a session ID for a device, call + * usbi_get_device_by_session_id(). This function checks if libusb already + * knows about the device, and if so, it provides you with a reference + * to a libusb_device structure for it. + * + * If usbi_get_device_by_session_id() returns NULL, it is time to allocate + * a new device structure for the device. Call usbi_alloc_device() to + * obtain a new libusb_device structure with reference count 1. Populate + * the bus_number and device_address attributes of the new device, and + * perform any other internal backend initialization you need to do. At + * this point, you should be ready to provide device descriptors and so + * on through the get_*_descriptor functions. Finally, call + * usbi_sanitize_device() to perform some final sanity checks on the + * device. Assuming all of the above succeeded, we can now continue. + * If any of the above failed, remember to unreference the device that + * was returned by usbi_alloc_device(). + * + * At this stage we have a populated libusb_device structure (either one + * that was found earlier, or one that we have just allocated and + * populated). This can now be added to the discovered devices list + * using discovered_devs_append(). Note that discovered_devs_append() + * may reallocate the list, returning a new location for it, and also + * note that reallocation can fail. Your backend should handle these + * error conditions appropriately. + * + * This function should not generate any bus I/O and should not block. + * If I/O is required (e.g. reading the active configuration value), it is + * OK to ignore these suggestions :) + * + * This function is executed when the user wishes to retrieve a list + * of USB devices connected to the system. + * + * If the backend has hotplug support, this function is not used! + * + * Return 0 on success, or a LIBUSB_ERROR code on failure. + */ + int (*get_device_list)(struct libusb_context *ctx, + struct discovered_devs **discdevs); + + /* Apps which were written before hotplug support, may listen for + * hotplug events on their own and call libusb_get_device_list on + * device addition. In this case libusb_get_device_list will likely + * return a list without the new device in there, as the hotplug + * event thread will still be busy enumerating the device, which may + * take a while, or may not even have seen the event yet. + * + * To avoid this libusb_get_device_list will call this optional + * function for backends with hotplug support before copying + * ctx->usb_devs to the user. In this function the backend should + * ensure any pending hotplug events are fully processed before + * returning. + * + * Optional, should be implemented by backends with hotplug support. + */ + void (*hotplug_poll)(void); + + /* Wrap a platform-specific device handle for I/O and other USB + * operations. The device handle is preallocated for you. + * + * Your backend should allocate any internal resources required for I/O + * and other operations so that those operations can happen (hopefully) + * without hiccup. This is also a good place to inform libusb that it + * should monitor certain file descriptors related to this device - + * see the usbi_add_event_source() function. + * + * Your backend should also initialize the device structure + * (dev_handle->dev), which is NULL at the beginning of the call. + * + * This function should not generate any bus I/O and should not block. + * + * This function is called when the user attempts to wrap an existing + * platform-specific device handle for a device. + * + * Return: + * - 0 on success + * - LIBUSB_ERROR_ACCESS if the user has insufficient permissions + * - another LIBUSB_ERROR code on other failure + * + * Do not worry about freeing the handle on failed open, the upper layers + * do this for you. + */ + int (*wrap_sys_device)(struct libusb_context *ctx, + struct libusb_device_handle *dev_handle, intptr_t sys_dev); + + /* Open a device for I/O and other USB operations. The device handle + * is preallocated for you, you can retrieve the device in question + * through handle->dev. + * + * Your backend should allocate any internal resources required for I/O + * and other operations so that those operations can happen (hopefully) + * without hiccup. This is also a good place to inform libusb that it + * should monitor certain file descriptors related to this device - + * see the usbi_add_event_source() function. + * + * This function should not generate any bus I/O and should not block. + * + * This function is called when the user attempts to obtain a device + * handle for a device. + * + * Return: + * - 0 on success + * - LIBUSB_ERROR_ACCESS if the user has insufficient permissions + * - LIBUSB_ERROR_NO_DEVICE if the device has been disconnected since + * discovery + * - another LIBUSB_ERROR code on other failure + * + * Do not worry about freeing the handle on failed open, the upper layers + * do this for you. + */ + int (*open)(struct libusb_device_handle *dev_handle); + + /* Close a device such that the handle cannot be used again. Your backend + * should destroy any resources that were allocated in the open path. + * This may also be a good place to call usbi_remove_event_source() to + * inform libusb of any event sources associated with this device that + * should no longer be monitored. + * + * This function is called when the user closes a device handle. + */ + void (*close)(struct libusb_device_handle *dev_handle); + + /* Get the ACTIVE configuration descriptor for a device. + * + * The descriptor should be retrieved from memory, NOT via bus I/O to the + * device. This means that you may have to cache it in a private structure + * during get_device_list enumeration. You may also have to keep track + * of which configuration is active when the user changes it. + * + * This function is expected to write len bytes of data into buffer, which + * is guaranteed to be big enough. If you can only do a partial write, + * return an error code. + * + * This function is expected to return the descriptor in bus-endian format + * (LE). + * + * Return: + * - 0 on success + * - LIBUSB_ERROR_NOT_FOUND if the device is in unconfigured state + * - another LIBUSB_ERROR code on other failure + */ + int (*get_active_config_descriptor)(struct libusb_device *device, + void *buffer, size_t len); + + /* Get a specific configuration descriptor for a device. + * + * The descriptor should be retrieved from memory, NOT via bus I/O to the + * device. This means that you may have to cache it in a private structure + * during get_device_list enumeration. + * + * The requested descriptor is expressed as a zero-based index (i.e. 0 + * indicates that we are requesting the first descriptor). The index does + * not (necessarily) equal the bConfigurationValue of the configuration + * being requested. + * + * This function is expected to write len bytes of data into buffer, which + * is guaranteed to be big enough. If you can only do a partial write, + * return an error code. + * + * This function is expected to return the descriptor in bus-endian format + * (LE). + * + * Return the length read on success or a LIBUSB_ERROR code on failure. + */ + int (*get_config_descriptor)(struct libusb_device *device, + uint8_t config_index, void *buffer, size_t len); + + /* Like get_config_descriptor but then by bConfigurationValue instead + * of by index. + * + * Optional, if not present the core will call get_config_descriptor + * for all configs until it finds the desired bConfigurationValue. + * + * Returns a pointer to the raw-descriptor in *buffer, this memory + * is valid as long as device is valid. + * + * Returns the length of the returned raw-descriptor on success, + * or a LIBUSB_ERROR code on failure. + */ + int (*get_config_descriptor_by_value)(struct libusb_device *device, + uint8_t bConfigurationValue, void **buffer); + + /* Get the bConfigurationValue for the active configuration for a device. + * Optional. This should only be implemented if you can retrieve it from + * cache (don't generate I/O). + * + * If you cannot retrieve this from cache, either do not implement this + * function, or return LIBUSB_ERROR_NOT_SUPPORTED. This will cause + * libusb to retrieve the information through a standard control transfer. + * + * This function must be non-blocking. + * Return: + * - 0 on success + * - LIBUSB_ERROR_NO_DEVICE if the device has been disconnected since it + * was opened + * - LIBUSB_ERROR_NOT_SUPPORTED if the value cannot be retrieved without + * blocking + * - another LIBUSB_ERROR code on other failure. + */ + int (*get_configuration)(struct libusb_device_handle *dev_handle, uint8_t *config); + + /* Set the active configuration for a device. + * + * A configuration value of -1 should put the device in unconfigured state. + * + * This function can block. + * + * Return: + * - 0 on success + * - LIBUSB_ERROR_NOT_FOUND if the configuration does not exist + * - LIBUSB_ERROR_BUSY if interfaces are currently claimed (and hence + * configuration cannot be changed) + * - LIBUSB_ERROR_NO_DEVICE if the device has been disconnected since it + * was opened + * - another LIBUSB_ERROR code on other failure. + */ + int (*set_configuration)(struct libusb_device_handle *dev_handle, int config); + + /* Claim an interface. When claimed, the application can then perform + * I/O to an interface's endpoints. + * + * This function should not generate any bus I/O and should not block. + * Interface claiming is a logical operation that simply ensures that + * no other drivers/applications are using the interface, and after + * claiming, no other drivers/applications can use the interface because + * we now "own" it. + * + * Return: + * - 0 on success + * - LIBUSB_ERROR_NOT_FOUND if the interface does not exist + * - LIBUSB_ERROR_BUSY if the interface is in use by another driver/app + * - LIBUSB_ERROR_NO_DEVICE if the device has been disconnected since it + * was opened + * - another LIBUSB_ERROR code on other failure + */ + int (*claim_interface)(struct libusb_device_handle *dev_handle, uint8_t interface_number); + + /* Release a previously claimed interface. + * + * This function should also generate a SET_INTERFACE control request, + * resetting the alternate setting of that interface to 0. It's OK for + * this function to block as a result. + * + * You will only ever be asked to release an interface which was + * successfully claimed earlier. + * + * Return: + * - 0 on success + * - LIBUSB_ERROR_NO_DEVICE if the device has been disconnected since it + * was opened + * - another LIBUSB_ERROR code on other failure + */ + int (*release_interface)(struct libusb_device_handle *dev_handle, uint8_t interface_number); + + /* Set the alternate setting for an interface. + * + * You will only ever be asked to set the alternate setting for an + * interface which was successfully claimed earlier. + * + * It's OK for this function to block. + * + * Return: + * - 0 on success + * - LIBUSB_ERROR_NOT_FOUND if the alternate setting does not exist + * - LIBUSB_ERROR_NO_DEVICE if the device has been disconnected since it + * was opened + * - another LIBUSB_ERROR code on other failure + */ + int (*set_interface_altsetting)(struct libusb_device_handle *dev_handle, + uint8_t interface_number, uint8_t altsetting); + + /* Clear a halt/stall condition on an endpoint. + * + * It's OK for this function to block. + * + * Return: + * - 0 on success + * - LIBUSB_ERROR_NOT_FOUND if the endpoint does not exist + * - LIBUSB_ERROR_NO_DEVICE if the device has been disconnected since it + * was opened + * - another LIBUSB_ERROR code on other failure + */ + int (*clear_halt)(struct libusb_device_handle *dev_handle, + unsigned char endpoint); + + /* Perform a USB port reset to reinitialize a device. Optional. + * + * If possible, the device handle should still be usable after the reset + * completes, assuming that the device descriptors did not change during + * reset and all previous interface state can be restored. + * + * If something changes, or you cannot easily locate/verify the reset + * device, return LIBUSB_ERROR_NOT_FOUND. This prompts the application + * to close the old handle and re-enumerate the device. + * + * Return: + * - 0 on success + * - LIBUSB_ERROR_NOT_FOUND if re-enumeration is required, or if the device + * has been disconnected since it was opened + * - another LIBUSB_ERROR code on other failure + */ + int (*reset_device)(struct libusb_device_handle *dev_handle); + + /* Alloc num_streams usb3 bulk streams on the passed in endpoints */ + int (*alloc_streams)(struct libusb_device_handle *dev_handle, + uint32_t num_streams, unsigned char *endpoints, int num_endpoints); + + /* Free usb3 bulk streams allocated with alloc_streams */ + int (*free_streams)(struct libusb_device_handle *dev_handle, + unsigned char *endpoints, int num_endpoints); + + /* Allocate persistent DMA memory for the given device, suitable for + * zerocopy. May return NULL on failure. Optional to implement. + */ + void *(*dev_mem_alloc)(struct libusb_device_handle *handle, size_t len); + + /* Free memory allocated by dev_mem_alloc. */ + int (*dev_mem_free)(struct libusb_device_handle *handle, void *buffer, + size_t len); + + /* Determine if a kernel driver is active on an interface. Optional. + * + * The presence of a kernel driver on an interface indicates that any + * calls to claim_interface would fail with the LIBUSB_ERROR_BUSY code. + * + * Return: + * - 0 if no driver is active + * - 1 if a driver is active + * - LIBUSB_ERROR_NO_DEVICE if the device has been disconnected since it + * was opened + * - another LIBUSB_ERROR code on other failure + */ + int (*kernel_driver_active)(struct libusb_device_handle *dev_handle, + uint8_t interface_number); + + /* Detach a kernel driver from an interface. Optional. + * + * After detaching a kernel driver, the interface should be available + * for claim. + * + * Return: + * - 0 on success + * - LIBUSB_ERROR_NOT_FOUND if no kernel driver was active + * - LIBUSB_ERROR_INVALID_PARAM if the interface does not exist + * - LIBUSB_ERROR_NO_DEVICE if the device has been disconnected since it + * was opened + * - another LIBUSB_ERROR code on other failure + */ + int (*detach_kernel_driver)(struct libusb_device_handle *dev_handle, + uint8_t interface_number); + + /* Attach a kernel driver to an interface. Optional. + * + * Reattach a kernel driver to the device. + * + * Return: + * - 0 on success + * - LIBUSB_ERROR_NOT_FOUND if no kernel driver was active + * - LIBUSB_ERROR_INVALID_PARAM if the interface does not exist + * - LIBUSB_ERROR_NO_DEVICE if the device has been disconnected since it + * was opened + * - LIBUSB_ERROR_BUSY if a program or driver has claimed the interface, + * preventing reattachment + * - another LIBUSB_ERROR code on other failure + */ + int (*attach_kernel_driver)(struct libusb_device_handle *dev_handle, + uint8_t interface_number); + + /* Destroy a device. Optional. + * + * This function is called when the last reference to a device is + * destroyed. It should free any resources allocated in the get_device_list + * path. + */ + void (*destroy_device)(struct libusb_device *dev); + + /* Submit a transfer. Your implementation should take the transfer, + * morph it into whatever form your platform requires, and submit it + * asynchronously. + * + * This function must not block. + * + * This function gets called with the flying_transfers_lock locked! + * + * Return: + * - 0 on success + * - LIBUSB_ERROR_NO_DEVICE if the device has been disconnected + * - another LIBUSB_ERROR code on other failure + */ + int (*submit_transfer)(struct usbi_transfer *itransfer); + + /* Cancel a previously submitted transfer. + * + * This function must not block. The transfer cancellation must complete + * later, resulting in a call to usbi_handle_transfer_cancellation() + * from the context of handle_events. + */ + int (*cancel_transfer)(struct usbi_transfer *itransfer); + + /* Clear a transfer as if it has completed or cancelled, but do not + * report any completion/cancellation to the library. You should free + * all private data from the transfer as if you were just about to report + * completion or cancellation. + * + * This function might seem a bit out of place. It is used when libusb + * detects a disconnected device - it calls this function for all pending + * transfers before reporting completion (with the disconnect code) to + * the user. Maybe we can improve upon this internal interface in future. + */ + void (*clear_transfer_priv)(struct usbi_transfer *itransfer); + + /* Handle any pending events on event sources. Optional. + * + * Provide this function when event sources directly indicate device + * or transfer activity. If your backend does not have such event sources, + * implement the handle_transfer_completion function below. + * + * This involves monitoring any active transfers and processing their + * completion or cancellation. + * + * The function is passed a pointer that represents platform-specific + * data for monitoring event sources (size count). This data is to be + * (re)allocated as necessary when event sources are modified. + * The num_ready parameter indicates the number of event sources that + * have reported events. This should be enough information for you to + * determine which actions need to be taken on the currently active + * transfers. + * + * For any cancelled transfers, call usbi_handle_transfer_cancellation(). + * For completed transfers, call usbi_handle_transfer_completion(). + * For control/bulk/interrupt transfers, populate the "transferred" + * element of the appropriate usbi_transfer structure before calling the + * above functions. For isochronous transfers, populate the status and + * transferred fields of the iso packet descriptors of the transfer. + * + * This function should also be able to detect disconnection of the + * device, reporting that situation with usbi_handle_disconnect(). + * + * When processing an event related to a transfer, you probably want to + * take usbi_transfer.lock to prevent races. See the documentation for + * the usbi_transfer structure. + * + * Return 0 on success, or a LIBUSB_ERROR code on failure. + */ + int (*handle_events)(struct libusb_context *ctx, + void *event_data, unsigned int count, unsigned int num_ready); + + /* Handle transfer completion. Optional. + * + * Provide this function when there are no event sources available that + * directly indicate device or transfer activity. If your backend does + * have such event sources, implement the handle_events function above. + * + * Your backend must tell the library when a transfer has completed by + * calling usbi_signal_transfer_completion(). You should store any private + * information about the transfer and its completion status in the transfer's + * private backend data. + * + * During event handling, this function will be called on each transfer for + * which usbi_signal_transfer_completion() was called. + * + * For any cancelled transfers, call usbi_handle_transfer_cancellation(). + * For completed transfers, call usbi_handle_transfer_completion(). + * For control/bulk/interrupt transfers, populate the "transferred" + * element of the appropriate usbi_transfer structure before calling the + * above functions. For isochronous transfers, populate the status and + * transferred fields of the iso packet descriptors of the transfer. + * + * Return 0 on success, or a LIBUSB_ERROR code on failure. + */ + int (*handle_transfer_completion)(struct usbi_transfer *itransfer); + + /* Number of bytes to reserve for per-context private backend data. + * This private data area is accessible by calling + * usbi_get_context_priv() on the libusb_context instance. + */ + size_t context_priv_size; + + /* Number of bytes to reserve for per-device private backend data. + * This private data area is accessible by calling + * usbi_get_device_priv() on the libusb_device instance. + */ + size_t device_priv_size; + + /* Number of bytes to reserve for per-handle private backend data. + * This private data area is accessible by calling + * usbi_get_device_handle_priv() on the libusb_device_handle instance. + */ + size_t device_handle_priv_size; + + /* Number of bytes to reserve for per-transfer private backend data. + * This private data area is accessible by calling + * usbi_get_transfer_priv() on the usbi_transfer instance. + */ + size_t transfer_priv_size; +}; + +extern const struct usbi_os_backend usbi_backend; + +#define for_each_context(c) \ + for_each_helper(c, &active_contexts_list, struct libusb_context) + +#define for_each_device(ctx, d) \ + for_each_helper(d, &(ctx)->usb_devs, struct libusb_device) + +#define for_each_device_safe(ctx, d, n) \ + for_each_safe_helper(d, n, &(ctx)->usb_devs, struct libusb_device) + +#define for_each_open_device(ctx, h) \ + for_each_helper(h, &(ctx)->open_devs, struct libusb_device_handle) + +#define __for_each_transfer(list, t) \ + for_each_helper(t, (list), struct usbi_transfer) + +#define for_each_transfer(ctx, t) \ + __for_each_transfer(&(ctx)->flying_transfers, t) + +#define __for_each_transfer_safe(list, t, n) \ + for_each_safe_helper(t, n, (list), struct usbi_transfer) + +#define for_each_transfer_safe(ctx, t, n) \ + __for_each_transfer_safe(&(ctx)->flying_transfers, t, n) + +#define __for_each_completed_transfer_safe(list, t, n) \ + list_for_each_entry_safe(t, n, (list), completed_list, struct usbi_transfer) + +#define for_each_event_source(ctx, e) \ + for_each_helper(e, &(ctx)->event_sources, struct usbi_event_source) + +#define for_each_removed_event_source(ctx, e) \ + for_each_helper(e, &(ctx)->removed_event_sources, struct usbi_event_source) + +#define for_each_removed_event_source_safe(ctx, e, n) \ + for_each_safe_helper(e, n, &(ctx)->removed_event_sources, struct usbi_event_source) + +#define for_each_hotplug_cb(ctx, c) \ + for_each_helper(c, &(ctx)->hotplug_cbs, struct usbi_hotplug_callback) + +#define for_each_hotplug_cb_safe(ctx, c, n) \ + for_each_safe_helper(c, n, &(ctx)->hotplug_cbs, struct usbi_hotplug_callback) + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/Radio/HW/RtlSdr/libusb/make.mk b/Radio/HW/RtlSdr/libusb/make.mk new file mode 100644 index 0000000..acf8f12 --- /dev/null +++ b/Radio/HW/RtlSdr/libusb/make.mk @@ -0,0 +1,22 @@ +DIR=src + +SRC_UTILS_PRE += $(wildcard $(DIR)/os/*.c) +SRC_UTILS += $(wildcard $(DIR)/*.c) +OBJ_UTILS += $(SRC_UTILS:.c=.o) +OBJ_UTILS_PRE += $(SRC_UTILS_PRE:.c=.o) +LDFLAGS_UTILS = +INCLUDE = -I./include -I./src +CFLAGS = -fPIC + +build-src: src-pre + echo $(SRC_UTILS_PRE) + echo $(SRC_UTILS) + +$(DIR)-pre: $(OBJ_UTILS_PRE) $(OBJ_UTILS) + +$(DIR)/os/%.o: $(DIR)/os/%.c + $(CC) $(CFLAGS) $(INCLUDE) -c $< -o $(BUILD_DIR)$@ + +$(DIR)/%.o: $(DIR)/%.c + echo "B" + $(CC) $(CFLAGS) $(INCLUDE) -c $< -o $(BUILD_DIR)$@ $(LDFLAGS_UTILS)
\ No newline at end of file diff --git a/Radio/HW/RtlSdr/libusb/os/darwin_usb.c b/Radio/HW/RtlSdr/libusb/os/darwin_usb.c new file mode 100644 index 0000000..903422c --- /dev/null +++ b/Radio/HW/RtlSdr/libusb/os/darwin_usb.c @@ -0,0 +1,2611 @@ +/* -*- Mode: C; indent-tabs-mode:nil -*- */ +/* + * darwin backend for libusb 1.0 + * Copyright © 2008-2021 Nathan Hjelm <hjelmn@cs.unm.edu> + * Copyright © 2019-2021 Google LLC. All rights reserved. + * + * 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 <config.h> +#include <assert.h> +#include <time.h> +#include <ctype.h> +#include <pthread.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/types.h> +#include <unistd.h> +#include <fcntl.h> +#include <sys/sysctl.h> + +#include <mach/clock.h> +#include <mach/clock_types.h> +#include <mach/mach_host.h> +#include <mach/mach_port.h> + +/* Suppress warnings about the use of the deprecated objc_registerThreadWithCollector + * function. Its use is also conditionalized to only older deployment targets. */ +#define OBJC_SILENCE_GC_DEPRECATIONS 1 + +/* Default timeout to 10s for reenumerate. This is needed because USBDeviceReEnumerate + * does not return error status on macOS. */ +#define DARWIN_REENUMERATE_TIMEOUT_US 10000000 + +#include <AvailabilityMacros.h> +#if MAC_OS_X_VERSION_MIN_REQUIRED >= 1060 && MAC_OS_X_VERSION_MIN_REQUIRED < 101200 + #include <objc/objc-auto.h> +#endif + +#include "darwin_usb.h" + +static int init_count = 0; + +/* Both kIOMasterPortDefault or kIOMainPortDefault are synonyms for 0. */ +static const mach_port_t darwin_default_master_port = 0; + +/* async event thread */ +static pthread_mutex_t libusb_darwin_at_mutex = PTHREAD_MUTEX_INITIALIZER; +static pthread_cond_t libusb_darwin_at_cond = PTHREAD_COND_INITIALIZER; + +#if !defined(HAVE_CLOCK_GETTIME) +static clock_serv_t clock_realtime; +static clock_serv_t clock_monotonic; +#endif + +#define LIBUSB_DARWIN_STARTUP_FAILURE ((CFRunLoopRef) -1) + +static CFRunLoopRef libusb_darwin_acfl = NULL; /* event cf loop */ +static CFRunLoopSourceRef libusb_darwin_acfls = NULL; /* shutdown signal for event cf loop */ + +static usbi_mutex_t darwin_cached_devices_lock = PTHREAD_MUTEX_INITIALIZER; +static struct list_head darwin_cached_devices; +static const char *darwin_device_class = "IOUSBDevice"; + +#define DARWIN_CACHED_DEVICE(a) (((struct darwin_device_priv *)usbi_get_device_priv((a)))->dev) + +/* async event thread */ +static pthread_t libusb_darwin_at; + +static int darwin_get_config_descriptor(struct libusb_device *dev, uint8_t config_index, void *buffer, size_t len); +static int darwin_claim_interface(struct libusb_device_handle *dev_handle, uint8_t iface); +static int darwin_release_interface(struct libusb_device_handle *dev_handle, uint8_t iface); +static int darwin_reenumerate_device(struct libusb_device_handle *dev_handle, bool capture); +static int darwin_clear_halt(struct libusb_device_handle *dev_handle, unsigned char endpoint); +static int darwin_reset_device(struct libusb_device_handle *dev_handle); +static void darwin_async_io_callback (void *refcon, IOReturn result, void *arg0); + +static enum libusb_error darwin_scan_devices(struct libusb_context *ctx); +static enum libusb_error process_new_device (struct libusb_context *ctx, struct darwin_cached_device *cached_device, + UInt64 old_session_id); + +static enum libusb_error darwin_get_cached_device(struct libusb_context *ctx, io_service_t service, struct darwin_cached_device **cached_out, + UInt64 *old_session_id); + +#if defined(ENABLE_LOGGING) +static const char *darwin_error_str (IOReturn result) { + static char string_buffer[50]; + switch (result) { + case kIOReturnSuccess: + return "no error"; + case kIOReturnNotOpen: + return "device not opened for exclusive access"; + case kIOReturnNoDevice: + return "no connection to an IOService"; + case kIOUSBNoAsyncPortErr: + return "no async port has been opened for interface"; + case kIOReturnExclusiveAccess: + return "another process has device opened for exclusive access"; + case kIOUSBPipeStalled: +#if defined(kUSBHostReturnPipeStalled) + case kUSBHostReturnPipeStalled: +#endif + return "pipe is stalled"; + case kIOReturnError: + return "could not establish a connection to the Darwin kernel"; + case kIOUSBTransactionTimeout: + return "transaction timed out"; + case kIOReturnBadArgument: + return "invalid argument"; + case kIOReturnAborted: + return "transaction aborted"; + case kIOReturnNotResponding: + return "device not responding"; + case kIOReturnOverrun: + return "data overrun"; + case kIOReturnCannotWire: + return "physical memory can not be wired down"; + case kIOReturnNoResources: + return "out of resources"; + case kIOUSBHighSpeedSplitError: + return "high speed split error"; + case kIOUSBUnknownPipeErr: + return "pipe ref not recognized"; + default: + snprintf(string_buffer, sizeof(string_buffer), "unknown error (0x%x)", result); + return string_buffer; + } +} +#endif + +static enum libusb_error darwin_to_libusb (IOReturn result) { + switch (result) { + case kIOReturnUnderrun: + case kIOReturnSuccess: + return LIBUSB_SUCCESS; + case kIOReturnNotOpen: + case kIOReturnNoDevice: + return LIBUSB_ERROR_NO_DEVICE; + case kIOReturnExclusiveAccess: + return LIBUSB_ERROR_ACCESS; + case kIOUSBPipeStalled: +#if defined(kUSBHostReturnPipeStalled) + case kUSBHostReturnPipeStalled: +#endif + return LIBUSB_ERROR_PIPE; + case kIOReturnBadArgument: + return LIBUSB_ERROR_INVALID_PARAM; + case kIOUSBTransactionTimeout: + return LIBUSB_ERROR_TIMEOUT; + case kIOUSBUnknownPipeErr: + return LIBUSB_ERROR_NOT_FOUND; + case kIOReturnNotResponding: + case kIOReturnAborted: + case kIOReturnError: + case kIOUSBNoAsyncPortErr: + default: + return LIBUSB_ERROR_OTHER; + } +} + +/* this function must be called with the darwin_cached_devices_lock held */ +static void darwin_deref_cached_device(struct darwin_cached_device *cached_dev) { + cached_dev->refcount--; + /* free the device and remove it from the cache */ + if (0 == cached_dev->refcount) { + list_del(&cached_dev->list); + + if (cached_dev->device) { + (*(cached_dev->device))->Release(cached_dev->device); + cached_dev->device = NULL; + } + IOObjectRelease (cached_dev->service); + free (cached_dev); + } +} + +static void darwin_ref_cached_device(struct darwin_cached_device *cached_dev) { + cached_dev->refcount++; +} + +static int ep_to_pipeRef(struct libusb_device_handle *dev_handle, uint8_t ep, uint8_t *pipep, uint8_t *ifcp, struct darwin_interface **interface_out) { + struct darwin_device_handle_priv *priv = usbi_get_device_handle_priv(dev_handle); + + /* current interface */ + struct darwin_interface *cInterface; + + uint8_t i, iface; + + struct libusb_context *ctx = HANDLE_CTX(dev_handle); + + usbi_dbg (ctx, "converting ep address 0x%02x to pipeRef and interface", ep); + + for (iface = 0 ; iface < USB_MAXINTERFACES ; iface++) { + cInterface = &priv->interfaces[iface]; + + if (dev_handle->claimed_interfaces & (1U << iface)) { + for (i = 0 ; i < cInterface->num_endpoints ; i++) { + if (cInterface->endpoint_addrs[i] == ep) { + *pipep = i + 1; + + if (ifcp) + *ifcp = iface; + + if (interface_out) + *interface_out = cInterface; + + usbi_dbg (ctx, "pipe %d on interface %d matches", *pipep, iface); + return LIBUSB_SUCCESS; + } + } + } + } + + /* No pipe found with the correct endpoint address */ + usbi_warn (HANDLE_CTX(dev_handle), "no pipeRef found with endpoint address 0x%02x.", ep); + + return LIBUSB_ERROR_NOT_FOUND; +} + +static IOReturn usb_setup_device_iterator (io_iterator_t *deviceIterator, UInt32 location) { + CFMutableDictionaryRef matchingDict = IOServiceMatching(darwin_device_class); + + if (!matchingDict) + return kIOReturnError; + + if (location) { + CFMutableDictionaryRef propertyMatchDict = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, + &kCFTypeDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks); + + /* there are no unsigned CFNumber types so treat the value as signed. the OS seems to do this + internally (CFNumberType of locationID is kCFNumberSInt32Type) */ + CFTypeRef locationCF = CFNumberCreate (NULL, kCFNumberSInt32Type, &location); + + if (propertyMatchDict && locationCF) { + CFDictionarySetValue (propertyMatchDict, CFSTR(kUSBDevicePropertyLocationID), locationCF); + CFDictionarySetValue (matchingDict, CFSTR(kIOPropertyMatchKey), propertyMatchDict); + } + /* else we can still proceed as long as the caller accounts for the possibility of other devices in the iterator */ + + /* release our references as per the Create Rule */ + if (propertyMatchDict) + CFRelease (propertyMatchDict); + if (locationCF) + CFRelease (locationCF); + } + + return IOServiceGetMatchingServices(darwin_default_master_port, matchingDict, deviceIterator); +} + +/* Returns 1 on success, 0 on failure. */ +static bool get_ioregistry_value_number (io_service_t service, CFStringRef property, CFNumberType type, void *p) { + CFTypeRef cfNumber = IORegistryEntryCreateCFProperty (service, property, kCFAllocatorDefault, 0); + Boolean success = 0; + + if (cfNumber) { + if (CFGetTypeID(cfNumber) == CFNumberGetTypeID()) { + success = CFNumberGetValue(cfNumber, type, p); + } + + CFRelease (cfNumber); + } + + return (success != 0); +} + +/* Returns 1 on success, 0 on failure. */ +static bool get_ioregistry_value_data (io_service_t service, CFStringRef property, ssize_t size, void *p) { + CFTypeRef cfData = IORegistryEntryCreateCFProperty (service, property, kCFAllocatorDefault, 0); + bool success = false; + + if (cfData) { + if (CFGetTypeID (cfData) == CFDataGetTypeID ()) { + CFIndex length = CFDataGetLength (cfData); + if (length < size) { + size = length; + } + + CFDataGetBytes (cfData, CFRangeMake(0, size), p); + success = true; + } + + CFRelease (cfData); + } + + return success; +} + +static usb_device_t **darwin_device_from_service (struct libusb_context *ctx, io_service_t service) +{ + io_cf_plugin_ref_t *plugInInterface = NULL; + usb_device_t **device; + IOReturn kresult; + SInt32 score; + const int max_retries = 5; + + /* The IOCreatePlugInInterfaceForService function might consistently return + an "out of resources" error with certain USB devices the first time we run + it. The reason is still unclear, but retrying fixes the problem */ + for (int count = 0; count < max_retries; count++) { + kresult = IOCreatePlugInInterfaceForService(service, kIOUSBDeviceUserClientTypeID, + kIOCFPlugInInterfaceID, &plugInInterface, + &score); + if (kIOReturnSuccess == kresult && plugInInterface) { + break; + } + + usbi_dbg (ctx, "set up plugin for service retry: %s", darwin_error_str (kresult)); + + /* sleep for a little while before trying again */ + nanosleep(&(struct timespec){.tv_sec = 0, .tv_nsec = 1000}, NULL); + } + + if (kIOReturnSuccess != kresult || !plugInInterface) { + usbi_dbg (ctx, "could not set up plugin for service: %s", darwin_error_str (kresult)); + return NULL; + } + + (void)(*plugInInterface)->QueryInterface(plugInInterface, CFUUIDGetUUIDBytes(DeviceInterfaceID), + (LPVOID)&device); + /* Use release instead of IODestroyPlugInInterface to avoid stopping IOServices associated with this device */ + (*plugInInterface)->Release (plugInInterface); + + return device; +} + +static void darwin_devices_attached (void *ptr, io_iterator_t add_devices) { + UNUSED(ptr); + struct darwin_cached_device *cached_device; + UInt64 old_session_id; + struct libusb_context *ctx; + io_service_t service; + int ret; + + usbi_mutex_lock(&active_contexts_lock); + + while ((service = IOIteratorNext(add_devices))) { + ret = darwin_get_cached_device (NULL, service, &cached_device, &old_session_id); + if (ret < 0 || !cached_device->can_enumerate) { + continue; + } + + /* add this device to each active context's device list */ + for_each_context(ctx) { + process_new_device (ctx, cached_device, old_session_id); + } + + if (cached_device->in_reenumerate) { + usbi_dbg (NULL, "cached device in reset state. reset complete..."); + cached_device->in_reenumerate = false; + } + + IOObjectRelease(service); + } + + usbi_mutex_unlock(&active_contexts_lock); +} + +static void darwin_devices_detached (void *ptr, io_iterator_t rem_devices) { + UNUSED(ptr); + struct libusb_device *dev = NULL; + struct libusb_context *ctx; + struct darwin_cached_device *old_device; + + io_service_t device; + UInt64 session, locationID; + int ret; + + usbi_mutex_lock(&active_contexts_lock); + + while ((device = IOIteratorNext (rem_devices)) != 0) { + bool is_reenumerating = false; + + /* get the location from the i/o registry */ + ret = get_ioregistry_value_number (device, CFSTR("sessionID"), kCFNumberSInt64Type, &session); + (void) get_ioregistry_value_number (device, CFSTR("locationID"), kCFNumberSInt32Type, &locationID); + IOObjectRelease (device); + if (!ret) + continue; + + /* we need to match darwin_ref_cached_device call made in darwin_get_cached_device function + otherwise no cached device will ever get freed */ + usbi_mutex_lock(&darwin_cached_devices_lock); + list_for_each_entry(old_device, &darwin_cached_devices, list, struct darwin_cached_device) { + if (old_device->session == session) { + if (old_device->in_reenumerate) { + /* device is re-enumerating. do not dereference the device at this time. libusb_reset_device() + * will deref if needed. */ + usbi_dbg (NULL, "detected device detached due to re-enumeration. sessionID: 0x%" PRIx64 ", locationID: 0x%" PRIx64, + session, locationID); + + /* the device object is no longer usable so go ahead and release it */ + if (old_device->device) { + (*(old_device->device))->Release(old_device->device); + old_device->device = NULL; + } + + is_reenumerating = true; + } else { + darwin_deref_cached_device (old_device); + } + + break; + } + } + + usbi_mutex_unlock(&darwin_cached_devices_lock); + if (is_reenumerating) { + continue; + } + + for_each_context(ctx) { + usbi_dbg (ctx, "notifying context %p of device disconnect", ctx); + + dev = usbi_get_device_by_session_id(ctx, (unsigned long) session); + if (dev) { + /* signal the core that this device has been disconnected. the core will tear down this device + when the reference count reaches 0 */ + usbi_disconnect_device(dev); + libusb_unref_device(dev); + } + } + } + + usbi_mutex_unlock(&active_contexts_lock); +} + +static void darwin_hotplug_poll (void) +{ + /* not sure if 1 ms will be too long/short but it should work ok */ + mach_timespec_t timeout = {.tv_sec = 0, .tv_nsec = 1000000ul}; + + /* since a kernel thread may notify the IOIterators used for + * hotplug notification we can't just clear the iterators. + * instead just wait until all IOService providers are quiet */ + (void) IOKitWaitQuiet (darwin_default_master_port, &timeout); +} + +static void darwin_clear_iterator (io_iterator_t iter) { + io_service_t device; + + while ((device = IOIteratorNext (iter)) != 0) + IOObjectRelease (device); +} + +static void darwin_fail_startup(void) { + pthread_mutex_lock (&libusb_darwin_at_mutex); + libusb_darwin_acfl = LIBUSB_DARWIN_STARTUP_FAILURE; + pthread_cond_signal (&libusb_darwin_at_cond); + pthread_mutex_unlock (&libusb_darwin_at_mutex); + pthread_exit (NULL); +} + +static void *darwin_event_thread_main (void *arg0) { + IOReturn kresult; + struct libusb_context *ctx = (struct libusb_context *)arg0; + CFRunLoopRef runloop; + CFRunLoopSourceRef libusb_shutdown_cfsource; + CFRunLoopSourceContext libusb_shutdown_cfsourcectx; + +#if MAC_OS_X_VERSION_MIN_REQUIRED >= 1060 + /* Set this thread's name, so it can be seen in the debugger + and crash reports. */ + pthread_setname_np ("org.libusb.device-hotplug"); +#endif + +#if MAC_OS_X_VERSION_MIN_REQUIRED >= 1060 && MAC_OS_X_VERSION_MIN_REQUIRED < 101200 + /* Tell the Objective-C garbage collector about this thread. + This is required because, unlike NSThreads, pthreads are + not automatically registered. Although we don't use + Objective-C, we use CoreFoundation, which does. + Garbage collection support was entirely removed in 10.12, + so don't bother there. */ + objc_registerThreadWithCollector(); +#endif + + /* hotplug (device arrival/removal) sources */ + CFRunLoopSourceRef libusb_notification_cfsource; + io_notification_port_t libusb_notification_port; + io_iterator_t libusb_rem_device_iterator; + io_iterator_t libusb_add_device_iterator; + + usbi_dbg (ctx, "creating hotplug event source"); + + runloop = CFRunLoopGetCurrent (); + CFRetain (runloop); + + /* add the shutdown cfsource to the run loop */ + memset(&libusb_shutdown_cfsourcectx, 0, sizeof(libusb_shutdown_cfsourcectx)); + libusb_shutdown_cfsourcectx.info = runloop; + libusb_shutdown_cfsourcectx.perform = (void (*)(void *))CFRunLoopStop; + libusb_shutdown_cfsource = CFRunLoopSourceCreate(NULL, 0, &libusb_shutdown_cfsourcectx); + CFRunLoopAddSource(runloop, libusb_shutdown_cfsource, kCFRunLoopDefaultMode); + + /* add the notification port to the run loop */ + libusb_notification_port = IONotificationPortCreate (darwin_default_master_port); + libusb_notification_cfsource = IONotificationPortGetRunLoopSource (libusb_notification_port); + CFRunLoopAddSource(runloop, libusb_notification_cfsource, kCFRunLoopDefaultMode); + + /* create notifications for removed devices */ + kresult = IOServiceAddMatchingNotification (libusb_notification_port, kIOTerminatedNotification, + IOServiceMatching(darwin_device_class), + darwin_devices_detached, + ctx, &libusb_rem_device_iterator); + + if (kresult != kIOReturnSuccess) { + usbi_err (ctx, "could not add hotplug event source: %s", darwin_error_str (kresult)); + CFRelease (libusb_shutdown_cfsource); + CFRelease (runloop); + darwin_fail_startup (); + } + + /* create notifications for attached devices */ + kresult = IOServiceAddMatchingNotification(libusb_notification_port, kIOFirstMatchNotification, + IOServiceMatching(darwin_device_class), + darwin_devices_attached, + ctx, &libusb_add_device_iterator); + + if (kresult != kIOReturnSuccess) { + usbi_err (ctx, "could not add hotplug event source: %s", darwin_error_str (kresult)); + CFRelease (libusb_shutdown_cfsource); + CFRelease (runloop); + darwin_fail_startup (); + } + + /* arm notifiers */ + darwin_clear_iterator (libusb_rem_device_iterator); + darwin_clear_iterator (libusb_add_device_iterator); + + usbi_dbg (ctx, "darwin event thread ready to receive events"); + + /* signal the main thread that the hotplug runloop has been created. */ + pthread_mutex_lock (&libusb_darwin_at_mutex); + libusb_darwin_acfl = runloop; + libusb_darwin_acfls = libusb_shutdown_cfsource; + pthread_cond_signal (&libusb_darwin_at_cond); + pthread_mutex_unlock (&libusb_darwin_at_mutex); + + /* run the runloop */ + CFRunLoopRun(); + + usbi_dbg (ctx, "darwin event thread exiting"); + + /* signal the main thread that the hotplug runloop has finished. */ + pthread_mutex_lock (&libusb_darwin_at_mutex); + libusb_darwin_acfls = NULL; + libusb_darwin_acfl = NULL; + pthread_cond_signal (&libusb_darwin_at_cond); + pthread_mutex_unlock (&libusb_darwin_at_mutex); + + /* remove the notification cfsource */ + CFRunLoopRemoveSource(runloop, libusb_notification_cfsource, kCFRunLoopDefaultMode); + + /* remove the shutdown cfsource */ + CFRunLoopRemoveSource(runloop, libusb_shutdown_cfsource, kCFRunLoopDefaultMode); + + /* delete notification port */ + IONotificationPortDestroy (libusb_notification_port); + + /* delete iterators */ + IOObjectRelease (libusb_rem_device_iterator); + IOObjectRelease (libusb_add_device_iterator); + + CFRelease (libusb_shutdown_cfsource); + CFRelease (runloop); + + pthread_exit (NULL); +} + +/* cleanup function to destroy cached devices */ +static void darwin_cleanup_devices(void) { + struct darwin_cached_device *dev, *next; + + list_for_each_entry_safe(dev, next, &darwin_cached_devices, list, struct darwin_cached_device) { + darwin_deref_cached_device(dev); + } +} + +static int darwin_init(struct libusb_context *ctx) { + bool first_init; + int rc; + + first_init = (1 == ++init_count); + + do { + if (first_init) { + if (NULL == darwin_cached_devices.next) { + list_init (&darwin_cached_devices); + } + assert(list_empty(&darwin_cached_devices)); +#if !defined(HAVE_CLOCK_GETTIME) + /* create the clocks that will be used if clock_gettime() is not available */ + host_name_port_t host_self; + + host_self = mach_host_self(); + host_get_clock_service(host_self, CALENDAR_CLOCK, &clock_realtime); + host_get_clock_service(host_self, SYSTEM_CLOCK, &clock_monotonic); + mach_port_deallocate(mach_task_self(), host_self); +#endif + } + + rc = darwin_scan_devices (ctx); + if (LIBUSB_SUCCESS != rc) + break; + + if (first_init) { + rc = pthread_create (&libusb_darwin_at, NULL, darwin_event_thread_main, ctx); + if (0 != rc) { + usbi_err (ctx, "could not create event thread, error %d", rc); + rc = LIBUSB_ERROR_OTHER; + break; + } + + pthread_mutex_lock (&libusb_darwin_at_mutex); + while (!libusb_darwin_acfl) + pthread_cond_wait (&libusb_darwin_at_cond, &libusb_darwin_at_mutex); + if (libusb_darwin_acfl == LIBUSB_DARWIN_STARTUP_FAILURE) { + libusb_darwin_acfl = NULL; + rc = LIBUSB_ERROR_OTHER; + } + pthread_mutex_unlock (&libusb_darwin_at_mutex); + + if (0 != rc) + pthread_join (libusb_darwin_at, NULL); + } + } while (0); + + if (LIBUSB_SUCCESS != rc) { + if (first_init) { + darwin_cleanup_devices (); +#if !defined(HAVE_CLOCK_GETTIME) + mach_port_deallocate(mach_task_self(), clock_realtime); + mach_port_deallocate(mach_task_self(), clock_monotonic); +#endif + } + --init_count; + } + + return rc; +} + +static void darwin_exit (struct libusb_context *ctx) { + UNUSED(ctx); + + if (0 == --init_count) { + /* stop the event runloop and wait for the thread to terminate. */ + pthread_mutex_lock (&libusb_darwin_at_mutex); + CFRunLoopSourceSignal (libusb_darwin_acfls); + CFRunLoopWakeUp (libusb_darwin_acfl); + while (libusb_darwin_acfl) + pthread_cond_wait (&libusb_darwin_at_cond, &libusb_darwin_at_mutex); + pthread_mutex_unlock (&libusb_darwin_at_mutex); + pthread_join (libusb_darwin_at, NULL); + + darwin_cleanup_devices (); + +#if !defined(HAVE_CLOCK_GETTIME) + mach_port_deallocate(mach_task_self(), clock_realtime); + mach_port_deallocate(mach_task_self(), clock_monotonic); +#endif + } +} + +static int get_configuration_index (struct libusb_device *dev, UInt8 config_value) { + struct darwin_cached_device *priv = DARWIN_CACHED_DEVICE(dev); + UInt8 i, numConfig; + IOUSBConfigurationDescriptorPtr desc; + IOReturn kresult; + + /* is there a simpler way to determine the index? */ + kresult = (*(priv->device))->GetNumberOfConfigurations (priv->device, &numConfig); + if (kresult != kIOReturnSuccess) + return darwin_to_libusb (kresult); + + for (i = 0 ; i < numConfig ; i++) { + (*(priv->device))->GetConfigurationDescriptorPtr (priv->device, i, &desc); + + if (desc->bConfigurationValue == config_value) + return i; + } + + /* configuration not found */ + return LIBUSB_ERROR_NOT_FOUND; +} + +static int darwin_get_active_config_descriptor(struct libusb_device *dev, void *buffer, size_t len) { + struct darwin_cached_device *priv = DARWIN_CACHED_DEVICE(dev); + int config_index; + + if (0 == priv->active_config) + return LIBUSB_ERROR_NOT_FOUND; + + config_index = get_configuration_index (dev, priv->active_config); + if (config_index < 0) + return config_index; + + assert(config_index >= 0 && config_index <= UINT8_MAX); + return darwin_get_config_descriptor (dev, (UInt8)config_index, buffer, len); +} + +static int darwin_get_config_descriptor(struct libusb_device *dev, uint8_t config_index, void *buffer, size_t len) { + struct darwin_cached_device *priv = DARWIN_CACHED_DEVICE(dev); + IOUSBConfigurationDescriptorPtr desc; + IOReturn kresult; + int ret; + + if (!priv || !priv->device) + return LIBUSB_ERROR_OTHER; + + kresult = (*priv->device)->GetConfigurationDescriptorPtr (priv->device, config_index, &desc); + if (kresult == kIOReturnSuccess) { + /* copy descriptor */ + if (libusb_le16_to_cpu(desc->wTotalLength) < len) + len = libusb_le16_to_cpu(desc->wTotalLength); + + memmove (buffer, desc, len); + } + + ret = darwin_to_libusb (kresult); + if (ret != LIBUSB_SUCCESS) + return ret; + + return (int) len; +} + +/* check whether the os has configured the device */ +static enum libusb_error darwin_check_configuration (struct libusb_context *ctx, struct darwin_cached_device *dev) { + usb_device_t **darwin_device = dev->device; + + IOUSBConfigurationDescriptorPtr configDesc; + IOUSBFindInterfaceRequest request; + IOReturn kresult; + io_iterator_t interface_iterator; + io_service_t firstInterface; + + if (dev->dev_descriptor.bNumConfigurations < 1) { + usbi_err (ctx, "device has no configurations"); + return LIBUSB_ERROR_OTHER; /* no configurations at this speed so we can't use it */ + } + + /* checking the configuration of a root hub simulation takes ~1 s in 10.11. the device is + not usable anyway */ + if (0x05ac == libusb_le16_to_cpu (dev->dev_descriptor.idVendor) && + 0x8005 == libusb_le16_to_cpu (dev->dev_descriptor.idProduct)) { + usbi_dbg (ctx, "ignoring configuration on root hub simulation"); + dev->active_config = 0; + return LIBUSB_SUCCESS; + } + + /* find the first configuration */ + kresult = (*darwin_device)->GetConfigurationDescriptorPtr (darwin_device, 0, &configDesc); + dev->first_config = (kIOReturnSuccess == kresult) ? configDesc->bConfigurationValue : 1; + + /* check if the device is already configured. there is probably a better way than iterating over the + to accomplish this (the trick is we need to avoid a call to GetConfigurations since buggy devices + might lock up on the device request) */ + + /* Setup the Interface Request */ + request.bInterfaceClass = kIOUSBFindInterfaceDontCare; + request.bInterfaceSubClass = kIOUSBFindInterfaceDontCare; + request.bInterfaceProtocol = kIOUSBFindInterfaceDontCare; + request.bAlternateSetting = kIOUSBFindInterfaceDontCare; + + kresult = (*(darwin_device))->CreateInterfaceIterator(darwin_device, &request, &interface_iterator); + if (kresult != kIOReturnSuccess) + return darwin_to_libusb (kresult); + + /* iterate once */ + firstInterface = IOIteratorNext(interface_iterator); + + /* done with the interface iterator */ + IOObjectRelease(interface_iterator); + + if (firstInterface) { + IOObjectRelease (firstInterface); + + /* device is configured */ + if (dev->dev_descriptor.bNumConfigurations == 1) + /* to avoid problems with some devices get the configurations value from the configuration descriptor */ + dev->active_config = dev->first_config; + else + /* devices with more than one configuration should work with GetConfiguration */ + (*darwin_device)->GetConfiguration (darwin_device, &dev->active_config); + } else + /* not configured */ + dev->active_config = 0; + + usbi_dbg (ctx, "active config: %u, first config: %u", dev->active_config, dev->first_config); + + return LIBUSB_SUCCESS; +} + +static IOReturn darwin_request_descriptor (usb_device_t **device, UInt8 desc, UInt8 desc_index, void *buffer, size_t buffer_size) { + IOUSBDevRequestTO req; + + assert(buffer_size <= UINT16_MAX); + + memset (buffer, 0, buffer_size); + + /* Set up request for descriptor/ */ + req.bmRequestType = USBmakebmRequestType(kUSBIn, kUSBStandard, kUSBDevice); + req.bRequest = kUSBRqGetDescriptor; + req.wValue = (UInt16)(desc << 8); + req.wIndex = desc_index; + req.wLength = (UInt16)buffer_size; + req.pData = buffer; + req.noDataTimeout = 20; + req.completionTimeout = 100; + + return (*device)->DeviceRequestTO (device, &req); +} + +static enum libusb_error darwin_cache_device_descriptor (struct libusb_context *ctx, struct darwin_cached_device *dev) { + usb_device_t **device = dev->device; + int retries = 1; + long delay = 30000; // microseconds + int unsuspended = 0, try_unsuspend = 1, try_reconfigure = 1; + int is_open = 0; + IOReturn ret = 0, ret2; + UInt8 bDeviceClass; + UInt16 idProduct, idVendor; + + dev->can_enumerate = 0; + + (*device)->GetDeviceClass (device, &bDeviceClass); + (*device)->GetDeviceProduct (device, &idProduct); + (*device)->GetDeviceVendor (device, &idVendor); + + /* According to Apple's documentation the device must be open for DeviceRequest but we may not be able to open some + * devices and Apple's USB Prober doesn't bother to open the device before issuing a descriptor request. Still, + * to follow the spec as closely as possible, try opening the device */ + is_open = ((*device)->USBDeviceOpenSeize(device) == kIOReturnSuccess); + + do { + /**** retrieve device descriptor ****/ + ret = darwin_request_descriptor (device, kUSBDeviceDesc, 0, &dev->dev_descriptor, sizeof(dev->dev_descriptor)); + + if (kIOReturnOverrun == ret && kUSBDeviceDesc == dev->dev_descriptor.bDescriptorType) + /* received an overrun error but we still received a device descriptor */ + ret = kIOReturnSuccess; + + if (kIOUSBVendorIDAppleComputer == idVendor) { + /* NTH: don't bother retrying or unsuspending Apple devices */ + break; + } + + if (kIOReturnSuccess == ret && (0 == dev->dev_descriptor.bNumConfigurations || + 0 == dev->dev_descriptor.bcdUSB)) { + /* work around for incorrectly configured devices */ + if (try_reconfigure && is_open) { + usbi_dbg(ctx, "descriptor appears to be invalid. resetting configuration before trying again..."); + + /* set the first configuration */ + (*device)->SetConfiguration(device, 1); + + /* don't try to reconfigure again */ + try_reconfigure = 0; + } + + ret = kIOUSBPipeStalled; + } + + if (kIOReturnSuccess != ret && is_open && try_unsuspend) { + /* device may be suspended. unsuspend it and try again */ +#if DeviceVersion >= 320 + UInt32 info = 0; + + /* IOUSBFamily 320+ provides a way to detect device suspension but earlier versions do not */ + (void)(*device)->GetUSBDeviceInformation (device, &info); + + /* note that the device was suspended */ + if (info & (1U << kUSBInformationDeviceIsSuspendedBit) || 0 == info) + try_unsuspend = 1; +#endif + + if (try_unsuspend) { + /* try to unsuspend the device */ + ret2 = (*device)->USBDeviceSuspend (device, 0); + if (kIOReturnSuccess != ret2) { + /* prevent log spew from poorly behaving devices. this indicates the + os actually had trouble communicating with the device */ + usbi_dbg(ctx, "could not retrieve device descriptor. failed to unsuspend: %s",darwin_error_str(ret2)); + } else + unsuspended = 1; + + try_unsuspend = 0; + } + } + + if (kIOReturnSuccess != ret) { + usbi_dbg(ctx, "kernel responded with code: 0x%08x. sleeping for %ld ms before trying again", ret, delay/1000); + /* sleep for a little while before trying again */ + nanosleep(&(struct timespec){delay / 1000000, (delay * 1000) % 1000000000}, NULL); + } + } while (kIOReturnSuccess != ret && retries--); + + if (unsuspended) + /* resuspend the device */ + (void)(*device)->USBDeviceSuspend (device, 1); + + if (is_open) + (void) (*device)->USBDeviceClose (device); + + if (ret != kIOReturnSuccess) { + /* a debug message was already printed out for this error */ + if (LIBUSB_CLASS_HUB == bDeviceClass) + usbi_dbg (ctx, "could not retrieve device descriptor %.4x:%.4x: %s (%x). skipping device", + idVendor, idProduct, darwin_error_str (ret), ret); + else + usbi_warn (ctx, "could not retrieve device descriptor %.4x:%.4x: %s (%x). skipping device", + idVendor, idProduct, darwin_error_str (ret), ret); + return darwin_to_libusb (ret); + } + + /* catch buggy hubs (which appear to be virtual). Apple's own USB prober has problems with these devices. */ + if (libusb_le16_to_cpu (dev->dev_descriptor.idProduct) != idProduct) { + /* not a valid device */ + usbi_warn (NULL, "idProduct from iokit (%04x) does not match idProduct in descriptor (%04x). skipping device", + idProduct, libusb_le16_to_cpu (dev->dev_descriptor.idProduct)); + return LIBUSB_ERROR_NO_DEVICE; + } + + usbi_dbg (ctx, "cached device descriptor:"); + usbi_dbg (ctx, " bDescriptorType: 0x%02x", dev->dev_descriptor.bDescriptorType); + usbi_dbg (ctx, " bcdUSB: 0x%04x", libusb_le16_to_cpu (dev->dev_descriptor.bcdUSB)); + usbi_dbg (ctx, " bDeviceClass: 0x%02x", dev->dev_descriptor.bDeviceClass); + usbi_dbg (ctx, " bDeviceSubClass: 0x%02x", dev->dev_descriptor.bDeviceSubClass); + usbi_dbg (ctx, " bDeviceProtocol: 0x%02x", dev->dev_descriptor.bDeviceProtocol); + usbi_dbg (ctx, " bMaxPacketSize0: 0x%02x", dev->dev_descriptor.bMaxPacketSize0); + usbi_dbg (ctx, " idVendor: 0x%04x", libusb_le16_to_cpu (dev->dev_descriptor.idVendor)); + usbi_dbg (ctx, " idProduct: 0x%04x", libusb_le16_to_cpu (dev->dev_descriptor.idProduct)); + usbi_dbg (ctx, " bcdDevice: 0x%04x", libusb_le16_to_cpu (dev->dev_descriptor.bcdDevice)); + usbi_dbg (ctx, " iManufacturer: 0x%02x", dev->dev_descriptor.iManufacturer); + usbi_dbg (ctx, " iProduct: 0x%02x", dev->dev_descriptor.iProduct); + usbi_dbg (ctx, " iSerialNumber: 0x%02x", dev->dev_descriptor.iSerialNumber); + usbi_dbg (ctx, " bNumConfigurations: 0x%02x", dev->dev_descriptor.bNumConfigurations); + + dev->can_enumerate = 1; + + return LIBUSB_SUCCESS; +} + +/* Returns 1 on success, 0 on failure. */ +static bool get_device_port (io_service_t service, UInt8 *port) { + IOReturn kresult; + io_service_t parent; + bool ret = false; + + if (get_ioregistry_value_number (service, CFSTR("PortNum"), kCFNumberSInt8Type, port)) { + return true; + } + + kresult = IORegistryEntryGetParentEntry (service, kIOServicePlane, &parent); + if (kIOReturnSuccess == kresult) { + ret = get_ioregistry_value_data (parent, CFSTR("port"), 1, port); + IOObjectRelease (parent); + } + + return ret; +} + +/* Returns 1 on success, 0 on failure. */ +static bool get_device_parent_sessionID(io_service_t service, UInt64 *parent_sessionID) { + IOReturn kresult; + io_service_t parent; + + /* Walk up the tree in the IOService plane until we find a parent that has a sessionID */ + parent = service; + while((kresult = IORegistryEntryGetParentEntry (parent, kIOUSBPlane, &parent)) == kIOReturnSuccess) { + if (get_ioregistry_value_number (parent, CFSTR("sessionID"), kCFNumberSInt64Type, parent_sessionID)) { + /* Success */ + return true; + } + } + + /* We ran out of parents */ + return false; +} + +static enum libusb_error darwin_get_cached_device(struct libusb_context *ctx, io_service_t service, struct darwin_cached_device **cached_out, + UInt64 *old_session_id) { + struct darwin_cached_device *new_device; + UInt64 sessionID = 0, parent_sessionID = 0; + UInt32 locationID = 0; + enum libusb_error ret = LIBUSB_SUCCESS; + usb_device_t **device; + UInt8 port = 0; + + /* assuming sessionID != 0 normally (never seen it be 0) */ + *old_session_id = 0; + *cached_out = NULL; + + /* get some info from the io registry */ + (void) get_ioregistry_value_number (service, CFSTR("sessionID"), kCFNumberSInt64Type, &sessionID); + (void) get_ioregistry_value_number (service, CFSTR("locationID"), kCFNumberSInt32Type, &locationID); + if (!get_device_port (service, &port)) { + usbi_dbg(ctx, "could not get connected port number"); + } + + usbi_dbg(ctx, "finding cached device for sessionID 0x%" PRIx64, sessionID); + + if (get_device_parent_sessionID(service, &parent_sessionID)) { + usbi_dbg(ctx, "parent sessionID: 0x%" PRIx64, parent_sessionID); + } + + usbi_mutex_lock(&darwin_cached_devices_lock); + do { + list_for_each_entry(new_device, &darwin_cached_devices, list, struct darwin_cached_device) { + usbi_dbg(ctx, "matching sessionID/locationID 0x%" PRIx64 "/0x%x against cached device with sessionID/locationID 0x%" PRIx64 "/0x%x", + sessionID, locationID, new_device->session, new_device->location); + if (new_device->location == locationID && new_device->in_reenumerate) { + usbi_dbg (ctx, "found cached device with matching location that is being re-enumerated"); + *old_session_id = new_device->session; + break; + } + + if (new_device->session == sessionID) { + usbi_dbg(ctx, "using cached device for device"); + *cached_out = new_device; + break; + } + } + + if (*cached_out) + break; + + usbi_dbg(ctx, "caching new device with sessionID 0x%" PRIx64, sessionID); + + device = darwin_device_from_service (ctx, service); + if (!device) { + ret = LIBUSB_ERROR_NO_DEVICE; + break; + } + + if (!(*old_session_id)) { + new_device = calloc (1, sizeof (*new_device)); + if (!new_device) { + ret = LIBUSB_ERROR_NO_MEM; + break; + } + + /* add this device to the cached device list */ + list_add(&new_device->list, &darwin_cached_devices); + + (*device)->GetDeviceAddress (device, (USBDeviceAddress *)&new_device->address); + + /* keep a reference to this device */ + darwin_ref_cached_device(new_device); + + (*device)->GetLocationID (device, &new_device->location); + new_device->port = port; + new_device->parent_session = parent_sessionID; + } else { + /* release the ref to old device's service */ + IOObjectRelease (new_device->service); + } + + /* keep track of devices regardless of if we successfully enumerate them to + prevent them from being enumerated multiple times */ + *cached_out = new_device; + + new_device->session = sessionID; + new_device->device = device; + new_device->service = service; + + /* retain the service */ + IOObjectRetain (service); + + /* cache the device descriptor */ + ret = darwin_cache_device_descriptor(ctx, new_device); + if (ret) + break; + + if (new_device->can_enumerate) { + snprintf(new_device->sys_path, 20, "%03i-%04x-%04x-%02x-%02x", new_device->address, + libusb_le16_to_cpu (new_device->dev_descriptor.idVendor), + libusb_le16_to_cpu (new_device->dev_descriptor.idProduct), + new_device->dev_descriptor.bDeviceClass, new_device->dev_descriptor.bDeviceSubClass); + } + } while (0); + + usbi_mutex_unlock(&darwin_cached_devices_lock); + + return ret; +} + +static enum libusb_error process_new_device (struct libusb_context *ctx, struct darwin_cached_device *cached_device, + UInt64 old_session_id) { + struct darwin_device_priv *priv; + struct libusb_device *dev = NULL; + UInt8 devSpeed; + enum libusb_error ret = LIBUSB_SUCCESS; + + do { + /* check current active configuration (and cache the first configuration value-- + which may be used by claim_interface) */ + ret = darwin_check_configuration (ctx, cached_device); + if (ret) + break; + + if (0 != old_session_id) { + usbi_dbg (ctx, "re-using existing device from context %p for with session 0x%" PRIx64 " new session 0x%" PRIx64, + ctx, old_session_id, cached_device->session); + /* save the libusb device before the session id is updated */ + dev = usbi_get_device_by_session_id (ctx, (unsigned long) old_session_id); + } + + if (!dev) { + usbi_dbg (ctx, "allocating new device in context %p for with session 0x%" PRIx64, + ctx, cached_device->session); + + dev = usbi_alloc_device(ctx, (unsigned long) cached_device->session); + if (!dev) { + return LIBUSB_ERROR_NO_MEM; + } + + priv = usbi_get_device_priv(dev); + + priv->dev = cached_device; + darwin_ref_cached_device (priv->dev); + dev->port_number = cached_device->port; + /* the location ID encodes the path to the device. the top byte of the location ID contains the bus number + (numbered from 0). the remaining bytes can be used to construct the device tree for that bus. */ + dev->bus_number = cached_device->location >> 24; + assert(cached_device->address <= UINT8_MAX); + dev->device_address = (uint8_t)cached_device->address; + } else { + priv = usbi_get_device_priv(dev); + } + + static_assert(sizeof(dev->device_descriptor) == sizeof(cached_device->dev_descriptor), + "mismatch between libusb and IOKit device descriptor sizes"); + memcpy(&dev->device_descriptor, &cached_device->dev_descriptor, LIBUSB_DT_DEVICE_SIZE); + usbi_localize_device_descriptor(&dev->device_descriptor); + dev->session_data = cached_device->session; + + if (NULL != dev->parent_dev) { + libusb_unref_device(dev->parent_dev); + dev->parent_dev = NULL; + } + + if (cached_device->parent_session > 0) { + dev->parent_dev = usbi_get_device_by_session_id (ctx, (unsigned long) cached_device->parent_session); + } + + (*(priv->dev->device))->GetDeviceSpeed (priv->dev->device, &devSpeed); + + switch (devSpeed) { + case kUSBDeviceSpeedLow: dev->speed = LIBUSB_SPEED_LOW; break; + case kUSBDeviceSpeedFull: dev->speed = LIBUSB_SPEED_FULL; break; + case kUSBDeviceSpeedHigh: dev->speed = LIBUSB_SPEED_HIGH; break; +#if MAC_OS_X_VERSION_MAX_ALLOWED >= 1080 + case kUSBDeviceSpeedSuper: dev->speed = LIBUSB_SPEED_SUPER; break; +#endif +#if MAC_OS_X_VERSION_MAX_ALLOWED >= 101200 + case kUSBDeviceSpeedSuperPlus: dev->speed = LIBUSB_SPEED_SUPER_PLUS; break; +#endif + default: + usbi_warn (ctx, "Got unknown device speed %d", devSpeed); + } + + ret = usbi_sanitize_device (dev); + if (ret < 0) + break; + + usbi_dbg (ctx, "found device with address %d port = %d parent = %p at %p", dev->device_address, + dev->port_number, (void *) dev->parent_dev, priv->dev->sys_path); + + } while (0); + + if (!cached_device->in_reenumerate && 0 == ret) { + usbi_connect_device (dev); + } else { + libusb_unref_device (dev); + } + + return ret; +} + +static enum libusb_error darwin_scan_devices(struct libusb_context *ctx) { + struct darwin_cached_device *cached_device; + UInt64 old_session_id; + io_iterator_t deviceIterator; + io_service_t service; + IOReturn kresult; + int ret; + + kresult = usb_setup_device_iterator (&deviceIterator, 0); + if (kresult != kIOReturnSuccess) + return darwin_to_libusb (kresult); + + while ((service = IOIteratorNext (deviceIterator))) { + ret = darwin_get_cached_device (ctx, service, &cached_device, &old_session_id); + if (ret < 0 || !cached_device->can_enumerate) { + continue; + } + + (void) process_new_device (ctx, cached_device, old_session_id); + + IOObjectRelease(service); + } + + IOObjectRelease(deviceIterator); + + return LIBUSB_SUCCESS; +} + +static int darwin_open (struct libusb_device_handle *dev_handle) { + struct darwin_device_handle_priv *priv = usbi_get_device_handle_priv(dev_handle); + struct darwin_cached_device *dpriv = DARWIN_CACHED_DEVICE(dev_handle->dev); + IOReturn kresult; + + if (0 == dpriv->open_count) { + /* try to open the device */ + kresult = (*(dpriv->device))->USBDeviceOpenSeize (dpriv->device); + if (kresult != kIOReturnSuccess) { + usbi_warn (HANDLE_CTX (dev_handle), "USBDeviceOpen: %s", darwin_error_str(kresult)); + + if (kIOReturnExclusiveAccess != kresult) { + return darwin_to_libusb (kresult); + } + + /* it is possible to perform some actions on a device that is not open so do not return an error */ + priv->is_open = false; + } else { + priv->is_open = true; + } + + /* create async event source */ + kresult = (*(dpriv->device))->CreateDeviceAsyncEventSource (dpriv->device, &priv->cfSource); + if (kresult != kIOReturnSuccess) { + usbi_err (HANDLE_CTX (dev_handle), "CreateDeviceAsyncEventSource: %s", darwin_error_str(kresult)); + + if (priv->is_open) { + (*(dpriv->device))->USBDeviceClose (dpriv->device); + } + + priv->is_open = false; + + return darwin_to_libusb (kresult); + } + + CFRetain (libusb_darwin_acfl); + + /* add the cfSource to the async run loop */ + CFRunLoopAddSource(libusb_darwin_acfl, priv->cfSource, kCFRunLoopCommonModes); + } + + /* device opened successfully */ + dpriv->open_count++; + + usbi_dbg (HANDLE_CTX(dev_handle), "device open for access"); + + return 0; +} + +static void darwin_close (struct libusb_device_handle *dev_handle) { + struct darwin_device_handle_priv *priv = usbi_get_device_handle_priv(dev_handle); + struct darwin_cached_device *dpriv = DARWIN_CACHED_DEVICE(dev_handle->dev); + IOReturn kresult; + int i; + + if (dpriv->open_count == 0) { + /* something is probably very wrong if this is the case */ + usbi_err (HANDLE_CTX (dev_handle), "Close called on a device that was not open!"); + return; + } + + dpriv->open_count--; + if (NULL == dpriv->device) { + usbi_warn (HANDLE_CTX (dev_handle), "darwin_close device missing IOService"); + return; + } + + /* make sure all interfaces are released */ + for (i = 0 ; i < USB_MAXINTERFACES ; i++) + if (dev_handle->claimed_interfaces & (1U << i)) + libusb_release_interface (dev_handle, i); + + if (0 == dpriv->open_count) { + /* delete the device's async event source */ + if (priv->cfSource) { + CFRunLoopRemoveSource (libusb_darwin_acfl, priv->cfSource, kCFRunLoopDefaultMode); + CFRelease (priv->cfSource); + priv->cfSource = NULL; + CFRelease (libusb_darwin_acfl); + } + + if (priv->is_open) { + /* close the device */ + kresult = (*(dpriv->device))->USBDeviceClose(dpriv->device); + if (kresult != kIOReturnSuccess) { + /* Log the fact that we had a problem closing the file, however failing a + * close isn't really an error, so return success anyway */ + usbi_warn (HANDLE_CTX (dev_handle), "USBDeviceClose: %s", darwin_error_str(kresult)); + } + } + } +} + +static int darwin_get_configuration(struct libusb_device_handle *dev_handle, uint8_t *config) { + struct darwin_cached_device *dpriv = DARWIN_CACHED_DEVICE(dev_handle->dev); + + *config = dpriv->active_config; + + return LIBUSB_SUCCESS; +} + +static enum libusb_error darwin_set_configuration(struct libusb_device_handle *dev_handle, int config) { + struct darwin_cached_device *dpriv = DARWIN_CACHED_DEVICE(dev_handle->dev); + IOReturn kresult; + uint8_t i; + + if (config == -1) + config = 0; + + /* Setting configuration will invalidate the interface, so we need + to reclaim it. First, dispose of existing interfaces, if any. */ + for (i = 0 ; i < USB_MAXINTERFACES ; i++) + if (dev_handle->claimed_interfaces & (1U << i)) + darwin_release_interface (dev_handle, i); + + kresult = (*(dpriv->device))->SetConfiguration (dpriv->device, (UInt8)config); + if (kresult != kIOReturnSuccess) + return darwin_to_libusb (kresult); + + /* Reclaim any interfaces. */ + for (i = 0 ; i < USB_MAXINTERFACES ; i++) + if (dev_handle->claimed_interfaces & (1U << i)) + darwin_claim_interface (dev_handle, i); + + dpriv->active_config = (UInt8)config; + + return LIBUSB_SUCCESS; +} + +static IOReturn darwin_get_interface (usb_device_t **darwin_device, uint8_t ifc, io_service_t *usbInterfacep) { + IOUSBFindInterfaceRequest request; + IOReturn kresult; + io_iterator_t interface_iterator; + UInt8 bInterfaceNumber; + bool ret; + + *usbInterfacep = IO_OBJECT_NULL; + + /* Setup the Interface Request */ + request.bInterfaceClass = kIOUSBFindInterfaceDontCare; + request.bInterfaceSubClass = kIOUSBFindInterfaceDontCare; + request.bInterfaceProtocol = kIOUSBFindInterfaceDontCare; + request.bAlternateSetting = kIOUSBFindInterfaceDontCare; + + kresult = (*(darwin_device))->CreateInterfaceIterator(darwin_device, &request, &interface_iterator); + if (kresult != kIOReturnSuccess) + return kresult; + + while ((*usbInterfacep = IOIteratorNext(interface_iterator))) { + /* find the interface number */ + ret = get_ioregistry_value_number (*usbInterfacep, CFSTR("bInterfaceNumber"), kCFNumberSInt8Type, + &bInterfaceNumber); + + if (ret && bInterfaceNumber == ifc) { + break; + } + + (void) IOObjectRelease (*usbInterfacep); + } + + /* done with the interface iterator */ + IOObjectRelease(interface_iterator); + + return kIOReturnSuccess; +} + +static enum libusb_error get_endpoints (struct libusb_device_handle *dev_handle, uint8_t iface) { + struct darwin_device_handle_priv *priv = usbi_get_device_handle_priv(dev_handle); + + /* current interface */ + struct darwin_interface *cInterface = &priv->interfaces[iface]; + + IOReturn kresult; + + UInt8 numep, direction, number; + UInt8 dont_care1, dont_care3; + UInt16 dont_care2; + int rc; + struct libusb_context *ctx = HANDLE_CTX (dev_handle); + + + usbi_dbg (ctx, "building table of endpoints."); + + /* retrieve the total number of endpoints on this interface */ + kresult = (*(cInterface->interface))->GetNumEndpoints(cInterface->interface, &numep); + if (kresult != kIOReturnSuccess) { + usbi_err (ctx, "can't get number of endpoints for interface: %s", darwin_error_str(kresult)); + return darwin_to_libusb (kresult); + } + + /* iterate through pipe references */ + for (UInt8 i = 1 ; i <= numep ; i++) { + kresult = (*(cInterface->interface))->GetPipeProperties(cInterface->interface, i, &direction, &number, &dont_care1, + &dont_care2, &dont_care3); + + if (kresult != kIOReturnSuccess) { + /* probably a buggy device. try to get the endpoint address from the descriptors */ + struct libusb_config_descriptor *config; + const struct libusb_endpoint_descriptor *endpoint_desc; + UInt8 alt_setting; + + kresult = (*(cInterface->interface))->GetAlternateSetting (cInterface->interface, &alt_setting); + if (kresult != kIOReturnSuccess) { + usbi_err (HANDLE_CTX (dev_handle), "can't get alternate setting for interface"); + return darwin_to_libusb (kresult); + } + + rc = libusb_get_active_config_descriptor (dev_handle->dev, &config); + if (LIBUSB_SUCCESS != rc) { + return rc; + } + + endpoint_desc = config->interface[iface].altsetting[alt_setting].endpoint + i - 1; + + cInterface->endpoint_addrs[i - 1] = endpoint_desc->bEndpointAddress; + } else { + cInterface->endpoint_addrs[i - 1] = (UInt8)(((kUSBIn == direction) << kUSBRqDirnShift) | (number & LIBUSB_ENDPOINT_ADDRESS_MASK)); + } + + usbi_dbg (ctx, "interface: %i pipe %i: dir: %i number: %i", iface, i, cInterface->endpoint_addrs[i - 1] >> kUSBRqDirnShift, + cInterface->endpoint_addrs[i - 1] & LIBUSB_ENDPOINT_ADDRESS_MASK); + } + + cInterface->num_endpoints = numep; + + return LIBUSB_SUCCESS; +} + +static int darwin_claim_interface(struct libusb_device_handle *dev_handle, uint8_t iface) { + struct darwin_cached_device *dpriv = DARWIN_CACHED_DEVICE(dev_handle->dev); + struct darwin_device_handle_priv *priv = usbi_get_device_handle_priv(dev_handle); + io_service_t usbInterface = IO_OBJECT_NULL; + IOReturn kresult; + enum libusb_error ret; + IOCFPlugInInterface **plugInInterface = NULL; + SInt32 score; + + /* current interface */ + struct darwin_interface *cInterface = &priv->interfaces[iface]; + + struct libusb_context *ctx = HANDLE_CTX (dev_handle); + + kresult = darwin_get_interface (dpriv->device, iface, &usbInterface); + if (kresult != kIOReturnSuccess) + return darwin_to_libusb (kresult); + + /* make sure we have an interface */ + if (!usbInterface && dpriv->first_config != 0) { + usbi_info (ctx, "no interface found; setting configuration: %d", dpriv->first_config); + + /* set the configuration */ + ret = darwin_set_configuration (dev_handle, (int) dpriv->first_config); + if (ret != LIBUSB_SUCCESS) { + usbi_err (ctx, "could not set configuration"); + return ret; + } + + kresult = darwin_get_interface (dpriv->device, iface, &usbInterface); + if (kresult != kIOReturnSuccess) { + usbi_err (ctx, "darwin_get_interface: %s", darwin_error_str(kresult)); + return darwin_to_libusb (kresult); + } + } + + if (!usbInterface) { + usbi_info (ctx, "interface not found"); + return LIBUSB_ERROR_NOT_FOUND; + } + + /* get an interface to the device's interface */ + kresult = IOCreatePlugInInterfaceForService (usbInterface, kIOUSBInterfaceUserClientTypeID, + kIOCFPlugInInterfaceID, &plugInInterface, &score); + + /* ignore release error */ + (void)IOObjectRelease (usbInterface); + + if (kresult != kIOReturnSuccess) { + usbi_err (ctx, "IOCreatePlugInInterfaceForService: %s", darwin_error_str(kresult)); + return darwin_to_libusb (kresult); + } + + if (!plugInInterface) { + usbi_err (ctx, "plugin interface not found"); + return LIBUSB_ERROR_NOT_FOUND; + } + + /* Do the actual claim */ + kresult = (*plugInInterface)->QueryInterface(plugInInterface, + CFUUIDGetUUIDBytes(InterfaceInterfaceID), + (LPVOID)&cInterface->interface); + /* We no longer need the intermediate plug-in */ + /* Use release instead of IODestroyPlugInInterface to avoid stopping IOServices associated with this device */ + (*plugInInterface)->Release (plugInInterface); + if (kresult != kIOReturnSuccess || !cInterface->interface) { + usbi_err (ctx, "QueryInterface: %s", darwin_error_str(kresult)); + return darwin_to_libusb (kresult); + } + + /* claim the interface */ + kresult = (*(cInterface->interface))->USBInterfaceOpen(cInterface->interface); + if (kresult != kIOReturnSuccess) { + usbi_info (ctx, "USBInterfaceOpen: %s", darwin_error_str(kresult)); + return darwin_to_libusb (kresult); + } + + /* update list of endpoints */ + ret = get_endpoints (dev_handle, iface); + if (ret) { + /* this should not happen */ + darwin_release_interface (dev_handle, iface); + usbi_err (ctx, "could not build endpoint table"); + return ret; + } + + cInterface->cfSource = NULL; + + /* create async event source */ + kresult = (*(cInterface->interface))->CreateInterfaceAsyncEventSource (cInterface->interface, &cInterface->cfSource); + if (kresult != kIOReturnSuccess) { + usbi_err (ctx, "could not create async event source"); + + /* can't continue without an async event source */ + (void)darwin_release_interface (dev_handle, iface); + + return darwin_to_libusb (kresult); + } + + /* add the cfSource to the async thread's run loop */ + CFRunLoopAddSource(libusb_darwin_acfl, cInterface->cfSource, kCFRunLoopDefaultMode); + + usbi_dbg (ctx, "interface opened"); + + return LIBUSB_SUCCESS; +} + +static int darwin_release_interface(struct libusb_device_handle *dev_handle, uint8_t iface) { + struct darwin_device_handle_priv *priv = usbi_get_device_handle_priv(dev_handle); + IOReturn kresult; + + /* current interface */ + struct darwin_interface *cInterface = &priv->interfaces[iface]; + + /* Check to see if an interface is open */ + if (!cInterface->interface) + return LIBUSB_SUCCESS; + + /* clean up endpoint data */ + cInterface->num_endpoints = 0; + + /* delete the interface's async event source */ + if (cInterface->cfSource) { + CFRunLoopRemoveSource (libusb_darwin_acfl, cInterface->cfSource, kCFRunLoopDefaultMode); + CFRelease (cInterface->cfSource); + cInterface->cfSource = NULL; + } + + kresult = (*(cInterface->interface))->USBInterfaceClose(cInterface->interface); + if (kresult != kIOReturnSuccess) + usbi_warn (HANDLE_CTX (dev_handle), "USBInterfaceClose: %s", darwin_error_str(kresult)); + + kresult = (*(cInterface->interface))->Release(cInterface->interface); + if (kresult != kIOReturnSuccess) + usbi_warn (HANDLE_CTX (dev_handle), "Release: %s", darwin_error_str(kresult)); + + cInterface->interface = (usb_interface_t **) IO_OBJECT_NULL; + + return darwin_to_libusb (kresult); +} + +static int check_alt_setting_and_clear_halt(struct libusb_device_handle *dev_handle, uint8_t altsetting, struct darwin_interface *cInterface) { + enum libusb_error ret; + IOReturn kresult; + uint8_t current_alt_setting; + + kresult = (*(cInterface->interface))->GetAlternateSetting (cInterface->interface, ¤t_alt_setting); + if (kresult == kIOReturnSuccess && altsetting != current_alt_setting) { + return LIBUSB_ERROR_PIPE; + } + + for (int i = 0 ; i < cInterface->num_endpoints ; i++) { + ret = darwin_clear_halt(dev_handle, cInterface->endpoint_addrs[i]); + if (LIBUSB_SUCCESS != ret) { + usbi_warn(HANDLE_CTX (dev_handle), "error clearing pipe halt for endpoint %d", i); + if (LIBUSB_ERROR_NOT_FOUND == ret) { + /* may need to re-open the interface */ + return ret; + } + } + } + + return LIBUSB_SUCCESS; +} + +static int darwin_set_interface_altsetting(struct libusb_device_handle *dev_handle, uint8_t iface, uint8_t altsetting) { + struct darwin_device_handle_priv *priv = usbi_get_device_handle_priv(dev_handle); + IOReturn kresult; + enum libusb_error ret; + + /* current interface */ + struct darwin_interface *cInterface = &priv->interfaces[iface]; + + if (!cInterface->interface) + return LIBUSB_ERROR_NO_DEVICE; + + kresult = (*(cInterface->interface))->SetAlternateInterface (cInterface->interface, altsetting); + if (kresult == kIOReturnSuccess) { + /* update the list of endpoints */ + ret = get_endpoints (dev_handle, iface); + if (ret) { + /* this should not happen */ + darwin_release_interface (dev_handle, iface); + usbi_err (HANDLE_CTX (dev_handle), "could not build endpoint table"); + } + return ret; + } + + usbi_warn (HANDLE_CTX (dev_handle), "SetAlternateInterface: %s", darwin_error_str(kresult)); + + ret = darwin_to_libusb(kresult); + if (ret != LIBUSB_ERROR_PIPE) { + return ret; + } + + /* If a device only supports a default setting for the specified interface, then a STALL + (kIOUSBPipeStalled) may be returned. Ref: USB 2.0 specs 9.4.10. + Mimic the behaviour in e.g. the Linux kernel: in such case, reset all endpoints + of the interface (as would have been done per 9.1.1.5) and return success. */ + + ret = check_alt_setting_and_clear_halt(dev_handle, altsetting, cInterface); + if (LIBUSB_ERROR_NOT_FOUND == ret) { + /* For some reason we need to reclaim the interface after the pipe error with some versions of macOS */ + ret = darwin_claim_interface (dev_handle, iface); + if (LIBUSB_SUCCESS != ret) { + darwin_release_interface (dev_handle, iface); + usbi_err (HANDLE_CTX (dev_handle), "could not reclaim interface: %s", darwin_error_str(kresult)); + } + ret = check_alt_setting_and_clear_halt(dev_handle, altsetting, cInterface); + } + + return ret; +} + +static int darwin_clear_halt(struct libusb_device_handle *dev_handle, unsigned char endpoint) { + /* current interface */ + struct darwin_interface *cInterface; + IOReturn kresult; + uint8_t pipeRef; + + /* determine the interface/endpoint to use */ + if (ep_to_pipeRef (dev_handle, endpoint, &pipeRef, NULL, &cInterface) != 0) { + usbi_err (HANDLE_CTX (dev_handle), "endpoint not found on any open interface"); + + return LIBUSB_ERROR_NOT_FOUND; + } + + /* newer versions of darwin support clearing additional bits on the device's endpoint */ + kresult = (*(cInterface->interface))->ClearPipeStallBothEnds(cInterface->interface, pipeRef); + if (kresult != kIOReturnSuccess) + usbi_warn (HANDLE_CTX (dev_handle), "ClearPipeStall: %s", darwin_error_str (kresult)); + + return darwin_to_libusb (kresult); +} + +static int darwin_restore_state (struct libusb_device_handle *dev_handle, int8_t active_config, + unsigned long claimed_interfaces) { + struct darwin_cached_device *dpriv = DARWIN_CACHED_DEVICE(dev_handle->dev); + struct darwin_device_handle_priv *priv = usbi_get_device_handle_priv(dev_handle); + int open_count = dpriv->open_count; + int ret; + + struct libusb_context *ctx = HANDLE_CTX (dev_handle); + + /* clear claimed interfaces temporarily */ + dev_handle->claimed_interfaces = 0; + + /* close and re-open the device */ + priv->is_open = false; + dpriv->open_count = 1; + + /* clean up open interfaces */ + (void) darwin_close (dev_handle); + + /* re-open the device */ + ret = darwin_open (dev_handle); + dpriv->open_count = open_count; + if (LIBUSB_SUCCESS != ret) { + /* could not restore configuration */ + return LIBUSB_ERROR_NOT_FOUND; + } + + if (dpriv->active_config != active_config) { + usbi_dbg (ctx, "darwin/restore_state: restoring configuration %d...", active_config); + + ret = darwin_set_configuration (dev_handle, active_config); + if (LIBUSB_SUCCESS != ret) { + usbi_dbg (ctx, "darwin/restore_state: could not restore configuration"); + return LIBUSB_ERROR_NOT_FOUND; + } + } + + usbi_dbg (ctx, "darwin/restore_state: reclaiming interfaces"); + + if (claimed_interfaces) { + for (uint8_t iface = 0 ; iface < USB_MAXINTERFACES ; ++iface) { + if (!(claimed_interfaces & (1U << iface))) { + continue; + } + + usbi_dbg (ctx, "darwin/restore_state: re-claiming interface %u", iface); + + ret = darwin_claim_interface (dev_handle, iface); + if (LIBUSB_SUCCESS != ret) { + usbi_dbg (ctx, "darwin/restore_state: could not claim interface %u", iface); + return LIBUSB_ERROR_NOT_FOUND; + } + + dev_handle->claimed_interfaces |= 1U << iface; + } + } + + usbi_dbg (ctx, "darwin/restore_state: device state restored"); + + return LIBUSB_SUCCESS; +} + +static int darwin_reenumerate_device (struct libusb_device_handle *dev_handle, bool capture) { + struct darwin_cached_device *dpriv = DARWIN_CACHED_DEVICE(dev_handle->dev); + unsigned long claimed_interfaces = dev_handle->claimed_interfaces; + int8_t active_config = dpriv->active_config; + UInt32 options = 0; + IOUSBDeviceDescriptor descriptor; + IOUSBConfigurationDescriptorPtr cached_configuration; + IOUSBConfigurationDescriptor *cached_configurations; + IOReturn kresult; + UInt8 i; + + struct libusb_context *ctx = HANDLE_CTX (dev_handle); + + if (dpriv->in_reenumerate) { + /* ack, two (or more) threads are trying to reset the device! abort! */ + return LIBUSB_ERROR_NOT_FOUND; + } + + dpriv->in_reenumerate = true; + + /* store copies of descriptors so they can be compared after the reset */ + memcpy (&descriptor, &dpriv->dev_descriptor, sizeof (descriptor)); + cached_configurations = alloca (sizeof (*cached_configurations) * descriptor.bNumConfigurations); + + for (i = 0 ; i < descriptor.bNumConfigurations ; ++i) { + (*(dpriv->device))->GetConfigurationDescriptorPtr (dpriv->device, i, &cached_configuration); + memcpy (cached_configurations + i, cached_configuration, sizeof (cached_configurations[i])); + } + + /* if we need to release capture */ + if (HAS_CAPTURE_DEVICE()) { + if (capture) { +#if MAC_OS_X_VERSION_MAX_ALLOWED >= 101000 + options |= kUSBReEnumerateCaptureDeviceMask; +#endif + } + } else { + capture = false; + } + + /* from macOS 10.11 ResetDevice no longer does anything so just use USBDeviceReEnumerate */ + kresult = (*(dpriv->device))->USBDeviceReEnumerate (dpriv->device, options); + if (kresult != kIOReturnSuccess) { + usbi_err (ctx, "USBDeviceReEnumerate: %s", darwin_error_str (kresult)); + dpriv->in_reenumerate = false; + return darwin_to_libusb (kresult); + } + + /* capture mode does not re-enumerate but it does require re-open */ + if (capture) { + usbi_dbg (ctx, "darwin/reenumerate_device: restoring state..."); + dpriv->in_reenumerate = false; + return darwin_restore_state (dev_handle, active_config, claimed_interfaces); + } + + usbi_dbg (ctx, "darwin/reenumerate_device: waiting for re-enumeration to complete..."); + + struct timespec start; + clock_gettime(CLOCK_MONOTONIC, &start); + + while (dpriv->in_reenumerate) { + struct timespec delay = {.tv_sec = 0, .tv_nsec = 1000}; + nanosleep (&delay, NULL); + + struct timespec now; + clock_gettime(CLOCK_MONOTONIC, &now); + UInt32 elapsed = (now.tv_sec - start.tv_sec) * 1000000 + (now.tv_nsec - start.tv_nsec) / 1000; + + if (elapsed >= DARWIN_REENUMERATE_TIMEOUT_US) { + usbi_err (ctx, "darwin/reenumerate_device: timeout waiting for reenumerate"); + dpriv->in_reenumerate = false; + return LIBUSB_ERROR_TIMEOUT; + } + } + + /* compare descriptors */ + usbi_dbg (ctx, "darwin/reenumerate_device: checking whether descriptors changed"); + + if (memcmp (&descriptor, &dpriv->dev_descriptor, sizeof (descriptor))) { + /* device descriptor changed. need to return not found. */ + usbi_dbg (ctx, "darwin/reenumerate_device: device descriptor changed"); + return LIBUSB_ERROR_NOT_FOUND; + } + + for (i = 0 ; i < descriptor.bNumConfigurations ; ++i) { + (void) (*(dpriv->device))->GetConfigurationDescriptorPtr (dpriv->device, i, &cached_configuration); + if (memcmp (cached_configuration, cached_configurations + i, sizeof (cached_configurations[i]))) { + usbi_dbg (ctx, "darwin/reenumerate_device: configuration descriptor %d changed", i); + return LIBUSB_ERROR_NOT_FOUND; + } + } + + usbi_dbg (ctx, "darwin/reenumerate_device: device reset complete. restoring state..."); + + return darwin_restore_state (dev_handle, active_config, claimed_interfaces); +} + +static int darwin_reset_device (struct libusb_device_handle *dev_handle) { + struct darwin_cached_device *dpriv = DARWIN_CACHED_DEVICE(dev_handle->dev); + IOReturn kresult; + + if (dpriv->capture_count > 0) { + /* we have to use ResetDevice as USBDeviceReEnumerate() loses the authorization for capture */ + kresult = (*(dpriv->device))->ResetDevice (dpriv->device); + return darwin_to_libusb (kresult); + } else { + return darwin_reenumerate_device (dev_handle, false); + } +} + +static io_service_t usb_find_interface_matching_location (const io_name_t class_name, UInt8 interface_number, UInt32 location) { + CFMutableDictionaryRef matchingDict = IOServiceMatching (class_name); + CFMutableDictionaryRef propertyMatchDict = CFDictionaryCreateMutable (kCFAllocatorDefault, 0, + &kCFTypeDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks); + CFTypeRef locationCF = CFNumberCreate (NULL, kCFNumberSInt32Type, &location); + CFTypeRef interfaceCF = CFNumberCreate (NULL, kCFNumberSInt8Type, &interface_number); + + CFDictionarySetValue (matchingDict, CFSTR(kIOPropertyMatchKey), propertyMatchDict); + CFDictionarySetValue (propertyMatchDict, CFSTR(kUSBDevicePropertyLocationID), locationCF); + CFDictionarySetValue (propertyMatchDict, CFSTR(kUSBHostMatchingPropertyInterfaceNumber), interfaceCF); + + CFRelease (interfaceCF); + CFRelease (locationCF); + CFRelease (propertyMatchDict); + + return IOServiceGetMatchingService (darwin_default_master_port, matchingDict); +} + +static int darwin_kernel_driver_active(struct libusb_device_handle *dev_handle, uint8_t interface) { + struct darwin_cached_device *dpriv = DARWIN_CACHED_DEVICE(dev_handle->dev); + io_service_t usb_interface, child = IO_OBJECT_NULL; + + /* locate the IO registry entry for this interface */ + usb_interface = usb_find_interface_matching_location (kIOUSBHostInterfaceClassName, interface, dpriv->location); + if (0 == usb_interface) { + /* check for the legacy class entry */ + usb_interface = usb_find_interface_matching_location (kIOUSBInterfaceClassName, interface, dpriv->location); + if (0 == usb_interface) { + return LIBUSB_ERROR_NOT_FOUND; + } + } + + /* if the IO object has a child entry in the IO Registry it has a kernel driver attached */ + (void) IORegistryEntryGetChildEntry (usb_interface, kIOServicePlane, &child); + IOObjectRelease (usb_interface); + if (IO_OBJECT_NULL != child) { + IOObjectRelease (child); + return 1; + } + + /* no driver */ + return 0; +} + +static void darwin_destroy_device(struct libusb_device *dev) { + struct darwin_device_priv *dpriv = usbi_get_device_priv(dev); + + if (dpriv->dev) { + /* need to hold the lock in case this is the last reference to the device */ + usbi_mutex_lock(&darwin_cached_devices_lock); + darwin_deref_cached_device (dpriv->dev); + dpriv->dev = NULL; + usbi_mutex_unlock(&darwin_cached_devices_lock); + } +} + +static int submit_bulk_transfer(struct usbi_transfer *itransfer) { + struct libusb_transfer *transfer = USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer); + + IOReturn ret; + uint8_t transferType; + uint8_t pipeRef; + uint16_t maxPacketSize; + + struct darwin_interface *cInterface; +#if InterfaceVersion >= 550 + IOUSBEndpointProperties pipeProperties = {.bVersion = kUSBEndpointPropertiesVersion3}; +#else + /* None of the values below are used in libusb for bulk transfers */ + uint8_t direction, number, interval; +#endif + + if (ep_to_pipeRef (transfer->dev_handle, transfer->endpoint, &pipeRef, NULL, &cInterface) != 0) { + usbi_err (TRANSFER_CTX (transfer), "endpoint not found on any open interface"); + + return LIBUSB_ERROR_NOT_FOUND; + } + +#if InterfaceVersion >= 550 + ret = (*(cInterface->interface))->GetPipePropertiesV3 (cInterface->interface, pipeRef, &pipeProperties); + + transferType = pipeProperties.bTransferType; + maxPacketSize = pipeProperties.wMaxPacketSize; +#else + ret = (*(cInterface->interface))->GetPipeProperties (cInterface->interface, pipeRef, &direction, &number, + &transferType, &maxPacketSize, &interval); +#endif + + if (ret) { + usbi_err (TRANSFER_CTX (transfer), "bulk transfer failed (dir = %s): %s (code = 0x%08x)", IS_XFERIN(transfer) ? "In" : "Out", + darwin_error_str(ret), ret); + return darwin_to_libusb (ret); + } + + if (0 != (transfer->length % maxPacketSize)) { + /* do not need a zero packet */ + transfer->flags &= ~LIBUSB_TRANSFER_ADD_ZERO_PACKET; + } + + /* submit the request */ + /* timeouts are unavailable on interrupt endpoints */ + if (transferType == kUSBInterrupt) { + if (IS_XFERIN(transfer)) + ret = (*(cInterface->interface))->ReadPipeAsync(cInterface->interface, pipeRef, transfer->buffer, + (UInt32)transfer->length, darwin_async_io_callback, itransfer); + else + ret = (*(cInterface->interface))->WritePipeAsync(cInterface->interface, pipeRef, transfer->buffer, + (UInt32)transfer->length, darwin_async_io_callback, itransfer); + } else { + itransfer->timeout_flags |= USBI_TRANSFER_OS_HANDLES_TIMEOUT; + + if (IS_XFERIN(transfer)) + ret = (*(cInterface->interface))->ReadPipeAsyncTO(cInterface->interface, pipeRef, transfer->buffer, + (UInt32)transfer->length, transfer->timeout, transfer->timeout, + darwin_async_io_callback, itransfer); + else + ret = (*(cInterface->interface))->WritePipeAsyncTO(cInterface->interface, pipeRef, transfer->buffer, + (UInt32)transfer->length, transfer->timeout, transfer->timeout, + darwin_async_io_callback, itransfer); + } + + if (ret) + usbi_err (TRANSFER_CTX (transfer), "bulk transfer failed (dir = %s): %s (code = 0x%08x)", IS_XFERIN(transfer) ? "In" : "Out", + darwin_error_str(ret), ret); + + return darwin_to_libusb (ret); +} + +#if InterfaceVersion >= 550 +static int submit_stream_transfer(struct usbi_transfer *itransfer) { + struct libusb_transfer *transfer = USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer); + struct darwin_interface *cInterface; + uint8_t pipeRef; + IOReturn ret; + + if (ep_to_pipeRef (transfer->dev_handle, transfer->endpoint, &pipeRef, NULL, &cInterface) != 0) { + usbi_err (TRANSFER_CTX (transfer), "endpoint not found on any open interface"); + + return LIBUSB_ERROR_NOT_FOUND; + } + + itransfer->timeout_flags |= USBI_TRANSFER_OS_HANDLES_TIMEOUT; + + if (IS_XFERIN(transfer)) + ret = (*(cInterface->interface))->ReadStreamsPipeAsyncTO(cInterface->interface, pipeRef, itransfer->stream_id, + transfer->buffer, (UInt32)transfer->length, transfer->timeout, + transfer->timeout, darwin_async_io_callback, itransfer); + else + ret = (*(cInterface->interface))->WriteStreamsPipeAsyncTO(cInterface->interface, pipeRef, itransfer->stream_id, + transfer->buffer, (UInt32)transfer->length, transfer->timeout, + transfer->timeout, darwin_async_io_callback, itransfer); + + if (ret) + usbi_err (TRANSFER_CTX (transfer), "bulk stream transfer failed (dir = %s): %s (code = 0x%08x)", IS_XFERIN(transfer) ? "In" : "Out", + darwin_error_str(ret), ret); + + return darwin_to_libusb (ret); +} +#endif + +static int submit_iso_transfer(struct usbi_transfer *itransfer) { + struct libusb_transfer *transfer = USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer); + struct darwin_transfer_priv *tpriv = usbi_get_transfer_priv(itransfer); + + IOReturn kresult; + uint8_t direction, number, interval, pipeRef, transferType; + uint16_t maxPacketSize; + UInt64 frame; + AbsoluteTime atTime; + int i; + + struct darwin_interface *cInterface; + + /* construct an array of IOUSBIsocFrames, reuse the old one if the sizes are the same */ + if (tpriv->num_iso_packets != transfer->num_iso_packets) { + free(tpriv->isoc_framelist); + tpriv->isoc_framelist = NULL; + } + + if (!tpriv->isoc_framelist) { + tpriv->num_iso_packets = transfer->num_iso_packets; + tpriv->isoc_framelist = (IOUSBIsocFrame*) calloc ((size_t)transfer->num_iso_packets, sizeof(IOUSBIsocFrame)); + if (!tpriv->isoc_framelist) + return LIBUSB_ERROR_NO_MEM; + } + + /* copy the frame list from the libusb descriptor (the structures differ only is member order) */ + for (i = 0 ; i < transfer->num_iso_packets ; i++) { + unsigned int length = transfer->iso_packet_desc[i].length; + assert(length <= UINT16_MAX); + tpriv->isoc_framelist[i].frReqCount = (UInt16)length; + } + + /* determine the interface/endpoint to use */ + if (ep_to_pipeRef (transfer->dev_handle, transfer->endpoint, &pipeRef, NULL, &cInterface) != 0) { + usbi_err (TRANSFER_CTX (transfer), "endpoint not found on any open interface"); + + return LIBUSB_ERROR_NOT_FOUND; + } + + /* determine the properties of this endpoint and the speed of the device */ + (*(cInterface->interface))->GetPipeProperties (cInterface->interface, pipeRef, &direction, &number, + &transferType, &maxPacketSize, &interval); + + /* Last but not least we need the bus frame number */ + kresult = (*(cInterface->interface))->GetBusFrameNumber(cInterface->interface, &frame, &atTime); + if (kresult != kIOReturnSuccess) { + usbi_err (TRANSFER_CTX (transfer), "failed to get bus frame number: %d", kresult); + free(tpriv->isoc_framelist); + tpriv->isoc_framelist = NULL; + + return darwin_to_libusb (kresult); + } + + (*(cInterface->interface))->GetPipeProperties (cInterface->interface, pipeRef, &direction, &number, + &transferType, &maxPacketSize, &interval); + + /* schedule for a frame a little in the future */ + frame += 4; + + if (cInterface->frames[transfer->endpoint] && frame < cInterface->frames[transfer->endpoint]) + frame = cInterface->frames[transfer->endpoint]; + + /* submit the request */ + if (IS_XFERIN(transfer)) + kresult = (*(cInterface->interface))->ReadIsochPipeAsync(cInterface->interface, pipeRef, transfer->buffer, frame, + (UInt32)transfer->num_iso_packets, tpriv->isoc_framelist, darwin_async_io_callback, + itransfer); + else + kresult = (*(cInterface->interface))->WriteIsochPipeAsync(cInterface->interface, pipeRef, transfer->buffer, frame, + (UInt32)transfer->num_iso_packets, tpriv->isoc_framelist, darwin_async_io_callback, + itransfer); + + if (LIBUSB_SPEED_FULL == transfer->dev_handle->dev->speed) + /* Full speed */ + cInterface->frames[transfer->endpoint] = frame + (UInt32)transfer->num_iso_packets * (1U << (interval - 1)); + else + /* High/super speed */ + cInterface->frames[transfer->endpoint] = frame + (UInt32)transfer->num_iso_packets * (1U << (interval - 1)) / 8; + + if (kresult != kIOReturnSuccess) { + usbi_err (TRANSFER_CTX (transfer), "isochronous transfer failed (dir: %s): %s", IS_XFERIN(transfer) ? "In" : "Out", + darwin_error_str(kresult)); + free (tpriv->isoc_framelist); + tpriv->isoc_framelist = NULL; + } + + return darwin_to_libusb (kresult); +} + +static int submit_control_transfer(struct usbi_transfer *itransfer) { + struct libusb_transfer *transfer = USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer); + struct libusb_control_setup *setup = (struct libusb_control_setup *) transfer->buffer; + struct darwin_cached_device *dpriv = DARWIN_CACHED_DEVICE(transfer->dev_handle->dev); + struct darwin_transfer_priv *tpriv = usbi_get_transfer_priv(itransfer); + + IOReturn kresult; + + memset(&tpriv->req, 0, sizeof(tpriv->req)); + + /* IOUSBDeviceInterface expects the request in cpu endianness */ + tpriv->req.bmRequestType = setup->bmRequestType; + tpriv->req.bRequest = setup->bRequest; + /* these values should be in bus order from libusb_fill_control_setup */ + tpriv->req.wValue = OSSwapLittleToHostInt16 (setup->wValue); + tpriv->req.wIndex = OSSwapLittleToHostInt16 (setup->wIndex); + tpriv->req.wLength = OSSwapLittleToHostInt16 (setup->wLength); + /* data is stored after the libusb control block */ + tpriv->req.pData = transfer->buffer + LIBUSB_CONTROL_SETUP_SIZE; + tpriv->req.completionTimeout = transfer->timeout; + tpriv->req.noDataTimeout = transfer->timeout; + + itransfer->timeout_flags |= USBI_TRANSFER_OS_HANDLES_TIMEOUT; + + /* all transfers in libusb-1.0 are async */ + + if (transfer->endpoint) { + struct darwin_interface *cInterface; + uint8_t pipeRef; + + if (ep_to_pipeRef (transfer->dev_handle, transfer->endpoint, &pipeRef, NULL, &cInterface) != 0) { + usbi_err (TRANSFER_CTX (transfer), "endpoint not found on any open interface"); + + return LIBUSB_ERROR_NOT_FOUND; + } + + kresult = (*(cInterface->interface))->ControlRequestAsyncTO (cInterface->interface, pipeRef, &(tpriv->req), darwin_async_io_callback, itransfer); + } else + /* control request on endpoint 0 */ + kresult = (*(dpriv->device))->DeviceRequestAsyncTO(dpriv->device, &(tpriv->req), darwin_async_io_callback, itransfer); + + if (kresult != kIOReturnSuccess) + usbi_err (TRANSFER_CTX (transfer), "control request failed: %s", darwin_error_str(kresult)); + + return darwin_to_libusb (kresult); +} + +static int darwin_submit_transfer(struct usbi_transfer *itransfer) { + struct libusb_transfer *transfer = USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer); + + switch (transfer->type) { + case LIBUSB_TRANSFER_TYPE_CONTROL: + return submit_control_transfer(itransfer); + case LIBUSB_TRANSFER_TYPE_BULK: + case LIBUSB_TRANSFER_TYPE_INTERRUPT: + return submit_bulk_transfer(itransfer); + case LIBUSB_TRANSFER_TYPE_ISOCHRONOUS: + return submit_iso_transfer(itransfer); + case LIBUSB_TRANSFER_TYPE_BULK_STREAM: +#if InterfaceVersion >= 550 + return submit_stream_transfer(itransfer); +#else + usbi_err (TRANSFER_CTX(transfer), "IOUSBFamily version does not support bulk stream transfers"); + return LIBUSB_ERROR_NOT_SUPPORTED; +#endif + default: + usbi_err (TRANSFER_CTX(transfer), "unknown endpoint type %d", transfer->type); + return LIBUSB_ERROR_INVALID_PARAM; + } +} + +static int cancel_control_transfer(struct usbi_transfer *itransfer) { + struct libusb_transfer *transfer = USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer); + struct darwin_cached_device *dpriv = DARWIN_CACHED_DEVICE(transfer->dev_handle->dev); + IOReturn kresult; + + usbi_warn (ITRANSFER_CTX (itransfer), "aborting all transactions control pipe"); + + if (!dpriv->device) + return LIBUSB_ERROR_NO_DEVICE; + + kresult = (*(dpriv->device))->USBDeviceAbortPipeZero (dpriv->device); + + return darwin_to_libusb (kresult); +} + +static int darwin_abort_transfers (struct usbi_transfer *itransfer) { + struct libusb_transfer *transfer = USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer); + struct darwin_cached_device *dpriv = DARWIN_CACHED_DEVICE(transfer->dev_handle->dev); + struct darwin_interface *cInterface; + uint8_t pipeRef, iface; + IOReturn kresult; + + struct libusb_context *ctx = ITRANSFER_CTX (itransfer); + + if (ep_to_pipeRef (transfer->dev_handle, transfer->endpoint, &pipeRef, &iface, &cInterface) != 0) { + usbi_err (ctx, "endpoint not found on any open interface"); + + return LIBUSB_ERROR_NOT_FOUND; + } + + if (!dpriv->device) + return LIBUSB_ERROR_NO_DEVICE; + + usbi_warn (ctx, "aborting all transactions on interface %d pipe %d", iface, pipeRef); + + /* abort transactions */ +#if InterfaceVersion >= 550 + if (LIBUSB_TRANSFER_TYPE_BULK_STREAM == transfer->type) + (*(cInterface->interface))->AbortStreamsPipe (cInterface->interface, pipeRef, itransfer->stream_id); + else +#endif + (*(cInterface->interface))->AbortPipe (cInterface->interface, pipeRef); + + usbi_dbg (ctx, "calling clear pipe stall to clear the data toggle bit"); + + /* newer versions of darwin support clearing additional bits on the device's endpoint */ + kresult = (*(cInterface->interface))->ClearPipeStallBothEnds(cInterface->interface, pipeRef); + + return darwin_to_libusb (kresult); +} + +static int darwin_cancel_transfer(struct usbi_transfer *itransfer) { + struct libusb_transfer *transfer = USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer); + + switch (transfer->type) { + case LIBUSB_TRANSFER_TYPE_CONTROL: + return cancel_control_transfer(itransfer); + case LIBUSB_TRANSFER_TYPE_BULK: + case LIBUSB_TRANSFER_TYPE_INTERRUPT: + case LIBUSB_TRANSFER_TYPE_ISOCHRONOUS: + return darwin_abort_transfers (itransfer); + default: + usbi_err (TRANSFER_CTX(transfer), "unknown endpoint type %d", transfer->type); + return LIBUSB_ERROR_INVALID_PARAM; + } +} + +static void darwin_async_io_callback (void *refcon, IOReturn result, void *arg0) { + struct usbi_transfer *itransfer = (struct usbi_transfer *)refcon; + struct libusb_transfer *transfer = USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer); + struct darwin_transfer_priv *tpriv = usbi_get_transfer_priv(itransfer); + + usbi_dbg (TRANSFER_CTX(transfer), "an async io operation has completed"); + + /* if requested write a zero packet */ + if (kIOReturnSuccess == result && IS_XFEROUT(transfer) && transfer->flags & LIBUSB_TRANSFER_ADD_ZERO_PACKET) { + struct darwin_interface *cInterface; + uint8_t pipeRef; + + (void) ep_to_pipeRef (transfer->dev_handle, transfer->endpoint, &pipeRef, NULL, &cInterface); + + (*(cInterface->interface))->WritePipe (cInterface->interface, pipeRef, transfer->buffer, 0); + } + + tpriv->result = result; + tpriv->size = (UInt32) (uintptr_t) arg0; + + /* signal the core that this transfer is complete */ + usbi_signal_transfer_completion(itransfer); +} + +static enum libusb_transfer_status darwin_transfer_status (struct usbi_transfer *itransfer, IOReturn result) { + if (itransfer->timeout_flags & USBI_TRANSFER_TIMED_OUT) + result = kIOUSBTransactionTimeout; + + struct libusb_context *ctx = ITRANSFER_CTX (itransfer); + + switch (result) { + case kIOReturnUnderrun: + case kIOReturnSuccess: + return LIBUSB_TRANSFER_COMPLETED; + case kIOReturnAborted: + return LIBUSB_TRANSFER_CANCELLED; + case kIOUSBPipeStalled: + usbi_dbg (ctx, "transfer error: pipe is stalled"); + return LIBUSB_TRANSFER_STALL; + case kIOReturnOverrun: + usbi_warn (ctx, "transfer error: data overrun"); + return LIBUSB_TRANSFER_OVERFLOW; + case kIOUSBTransactionTimeout: + usbi_warn (ctx, "transfer error: timed out"); + itransfer->timeout_flags |= USBI_TRANSFER_TIMED_OUT; + return LIBUSB_TRANSFER_TIMED_OUT; + default: + usbi_warn (ctx, "transfer error: %s (value = 0x%08x)", darwin_error_str (result), result); + return LIBUSB_TRANSFER_ERROR; + } +} + +static int darwin_handle_transfer_completion (struct usbi_transfer *itransfer) { + struct libusb_transfer *transfer = USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer); + struct darwin_transfer_priv *tpriv = usbi_get_transfer_priv(itransfer); + const unsigned char max_transfer_type = LIBUSB_TRANSFER_TYPE_BULK_STREAM; + const char *transfer_types[] = {"control", "isoc", "bulk", "interrupt", "bulk-stream", NULL}; + bool is_isoc = LIBUSB_TRANSFER_TYPE_ISOCHRONOUS == transfer->type; + struct libusb_context *ctx = ITRANSFER_CTX (itransfer); + + if (transfer->type > max_transfer_type) { + usbi_err (ctx, "unknown endpoint type %d", transfer->type); + return LIBUSB_ERROR_INVALID_PARAM; + } + + if (NULL == tpriv) { + usbi_err (ctx, "malformed request is missing transfer priv"); + return LIBUSB_ERROR_INVALID_PARAM; + } + + usbi_dbg (ctx, "handling transfer completion type %s with kernel status %d", transfer_types[transfer->type], tpriv->result); + + if (kIOReturnSuccess == tpriv->result || kIOReturnUnderrun == tpriv->result || kIOUSBTransactionTimeout == tpriv->result) { + if (is_isoc && tpriv->isoc_framelist) { + /* copy isochronous results back */ + + for (int i = 0; i < transfer->num_iso_packets ; i++) { + struct libusb_iso_packet_descriptor *lib_desc = &transfer->iso_packet_desc[i]; + lib_desc->status = darwin_transfer_status (itransfer, tpriv->isoc_framelist[i].frStatus); + lib_desc->actual_length = tpriv->isoc_framelist[i].frActCount; + } + } else if (!is_isoc) { + itransfer->transferred += tpriv->size; + } + } + + /* it is ok to handle cancelled transfers without calling usbi_handle_transfer_cancellation (we catch timeout transfers) */ + return usbi_handle_transfer_completion (itransfer, darwin_transfer_status (itransfer, tpriv->result)); +} + +#if !defined(HAVE_CLOCK_GETTIME) +void usbi_get_monotonic_time(struct timespec *tp) { + mach_timespec_t sys_time; + + /* use system boot time as reference for the monotonic clock */ + clock_get_time (clock_monotonic, &sys_time); + + tp->tv_sec = sys_time.tv_sec; + tp->tv_nsec = sys_time.tv_nsec; +} + +void usbi_get_real_time(struct timespec *tp) { + mach_timespec_t sys_time; + + /* CLOCK_REALTIME represents time since the epoch */ + clock_get_time (clock_realtime, &sys_time); + + tp->tv_sec = sys_time.tv_sec; + tp->tv_nsec = sys_time.tv_nsec; +} +#endif + +#if InterfaceVersion >= 550 +static int darwin_alloc_streams (struct libusb_device_handle *dev_handle, uint32_t num_streams, unsigned char *endpoints, + int num_endpoints) { + struct darwin_interface *cInterface; + UInt32 supportsStreams; + uint8_t pipeRef; + int rc, i; + + /* find the minimum number of supported streams on the endpoint list */ + for (i = 0 ; i < num_endpoints ; ++i) { + if (0 != (rc = ep_to_pipeRef (dev_handle, endpoints[i], &pipeRef, NULL, &cInterface))) { + return rc; + } + + (*(cInterface->interface))->SupportsStreams (cInterface->interface, pipeRef, &supportsStreams); + if (num_streams > supportsStreams) + num_streams = supportsStreams; + } + + /* it is an error if any endpoint in endpoints does not support streams */ + if (0 == num_streams) + return LIBUSB_ERROR_INVALID_PARAM; + + /* create the streams */ + for (i = 0 ; i < num_endpoints ; ++i) { + (void) ep_to_pipeRef (dev_handle, endpoints[i], &pipeRef, NULL, &cInterface); + + rc = (*(cInterface->interface))->CreateStreams (cInterface->interface, pipeRef, num_streams); + if (kIOReturnSuccess != rc) + return darwin_to_libusb(rc); + } + + assert(num_streams <= INT_MAX); + return (int)num_streams; +} + +static int darwin_free_streams (struct libusb_device_handle *dev_handle, unsigned char *endpoints, int num_endpoints) { + struct darwin_interface *cInterface; + UInt32 supportsStreams; + uint8_t pipeRef; + int rc; + + for (int i = 0 ; i < num_endpoints ; ++i) { + if (0 != (rc = ep_to_pipeRef (dev_handle, endpoints[i], &pipeRef, NULL, &cInterface))) + return rc; + + (*(cInterface->interface))->SupportsStreams (cInterface->interface, pipeRef, &supportsStreams); + if (0 == supportsStreams) + return LIBUSB_ERROR_INVALID_PARAM; + + rc = (*(cInterface->interface))->CreateStreams (cInterface->interface, pipeRef, 0); + if (kIOReturnSuccess != rc) + return darwin_to_libusb(rc); + } + + return LIBUSB_SUCCESS; +} +#endif + +#if InterfaceVersion >= 700 + +/* macOS APIs for getting entitlement values */ + +#if TARGET_OS_OSX +#include <Security/Security.h> +#else +typedef struct __SecTask *SecTaskRef; +extern SecTaskRef SecTaskCreateFromSelf(CFAllocatorRef allocator); +extern CFTypeRef SecTaskCopyValueForEntitlement(SecTaskRef task, CFStringRef entitlement, CFErrorRef *error); +#endif + +static bool darwin_has_capture_entitlements (void) { + SecTaskRef task; + CFTypeRef value; + bool entitled; + + task = SecTaskCreateFromSelf (kCFAllocatorDefault); + if (task == NULL) { + return false; + } + value = SecTaskCopyValueForEntitlement(task, CFSTR("com.apple.vm.device-access"), NULL); + CFRelease (task); + entitled = value && (CFGetTypeID (value) == CFBooleanGetTypeID ()) && CFBooleanGetValue (value); + if (value) { + CFRelease (value); + } + return entitled; +} + +static int darwin_reload_device (struct libusb_device_handle *dev_handle) { + struct darwin_cached_device *dpriv = DARWIN_CACHED_DEVICE(dev_handle->dev); + enum libusb_error err; + + usbi_mutex_lock(&darwin_cached_devices_lock); + (*(dpriv->device))->Release(dpriv->device); + dpriv->device = darwin_device_from_service (HANDLE_CTX (dev_handle), dpriv->service); + if (!dpriv->device) { + err = LIBUSB_ERROR_NO_DEVICE; + } else { + err = LIBUSB_SUCCESS; + } + usbi_mutex_unlock(&darwin_cached_devices_lock); + + return err; +} + +/* On macOS, we capture an entire device at once, not individual interfaces. */ + +static int darwin_detach_kernel_driver (struct libusb_device_handle *dev_handle, uint8_t interface) { + UNUSED(interface); + struct darwin_cached_device *dpriv = DARWIN_CACHED_DEVICE(dev_handle->dev); + IOReturn kresult; + enum libusb_error err; + struct libusb_context *ctx = HANDLE_CTX (dev_handle); + + if (HAS_CAPTURE_DEVICE()) { + } else { + return LIBUSB_ERROR_NOT_SUPPORTED; + } + + if (dpriv->capture_count == 0) { + usbi_dbg (ctx, "attempting to detach kernel driver from device"); + + if (darwin_has_capture_entitlements ()) { + /* request authorization */ + kresult = IOServiceAuthorize (dpriv->service, kIOServiceInteractionAllowed); + if (kresult != kIOReturnSuccess) { + usbi_warn (ctx, "IOServiceAuthorize: %s", darwin_error_str(kresult)); + return darwin_to_libusb (kresult); + } + + /* we need start() to be called again for authorization status to refresh */ + err = darwin_reload_device (dev_handle); + if (err != LIBUSB_SUCCESS) { + return err; + } + } else { + usbi_info (ctx, "no capture entitlements. may not be able to detach the kernel driver for this device"); + if (0 != geteuid()) { + usbi_warn (ctx, "USB device capture requires either an entitlement (com.apple.vm.device-access) or root privilege"); + return LIBUSB_ERROR_ACCESS; + } + } + + /* reset device to release existing drivers */ + err = darwin_reenumerate_device (dev_handle, true); + if (err != LIBUSB_SUCCESS) { + return err; + } + } + dpriv->capture_count++; + return LIBUSB_SUCCESS; +} + + +static int darwin_attach_kernel_driver (struct libusb_device_handle *dev_handle, uint8_t interface) { + UNUSED(interface); + struct darwin_cached_device *dpriv = DARWIN_CACHED_DEVICE(dev_handle->dev); + + if (HAS_CAPTURE_DEVICE()) { + } else { + return LIBUSB_ERROR_NOT_SUPPORTED; + } + + dpriv->capture_count--; + if (dpriv->capture_count > 0) { + return LIBUSB_SUCCESS; + } + + usbi_dbg (HANDLE_CTX (dev_handle), "reenumerating device for kernel driver attach"); + + /* reset device to attach kernel drivers */ + return darwin_reenumerate_device (dev_handle, false); +} + +static int darwin_capture_claim_interface(struct libusb_device_handle *dev_handle, uint8_t iface) { + enum libusb_error ret; + if (dev_handle->auto_detach_kernel_driver && darwin_kernel_driver_active(dev_handle, iface)) { + ret = darwin_detach_kernel_driver (dev_handle, iface); + if (ret != LIBUSB_SUCCESS) { + usbi_info (HANDLE_CTX (dev_handle), "failed to auto-detach the kernel driver for this device, ret=%d", ret); + } + } + + return darwin_claim_interface (dev_handle, iface); +} + +static int darwin_capture_release_interface(struct libusb_device_handle *dev_handle, uint8_t iface) { + enum libusb_error ret; + struct darwin_cached_device *dpriv = DARWIN_CACHED_DEVICE(dev_handle->dev); + + ret = darwin_release_interface (dev_handle, iface); + if (ret != LIBUSB_SUCCESS) { + return ret; + } + + if (dev_handle->auto_detach_kernel_driver && dpriv->capture_count > 0) { + ret = darwin_attach_kernel_driver (dev_handle, iface); + if (LIBUSB_SUCCESS != ret) { + usbi_info (HANDLE_CTX (dev_handle), "on attempt to reattach the kernel driver got ret=%d", ret); + } + /* ignore the error as the interface was successfully released */ + } + + return LIBUSB_SUCCESS; +} + +#endif + +const struct usbi_os_backend usbi_backend = { + .name = "Darwin", + .caps = USBI_CAP_SUPPORTS_DETACH_KERNEL_DRIVER, + .init = darwin_init, + .exit = darwin_exit, + .get_active_config_descriptor = darwin_get_active_config_descriptor, + .get_config_descriptor = darwin_get_config_descriptor, + .hotplug_poll = darwin_hotplug_poll, + + .open = darwin_open, + .close = darwin_close, + .get_configuration = darwin_get_configuration, + .set_configuration = darwin_set_configuration, + + .set_interface_altsetting = darwin_set_interface_altsetting, + .clear_halt = darwin_clear_halt, + .reset_device = darwin_reset_device, + +#if InterfaceVersion >= 550 + .alloc_streams = darwin_alloc_streams, + .free_streams = darwin_free_streams, +#endif + + .kernel_driver_active = darwin_kernel_driver_active, + +#if InterfaceVersion >= 700 + .detach_kernel_driver = darwin_detach_kernel_driver, + .attach_kernel_driver = darwin_attach_kernel_driver, + .claim_interface = darwin_capture_claim_interface, + .release_interface = darwin_capture_release_interface, +#else + .claim_interface = darwin_claim_interface, + .release_interface = darwin_release_interface, +#endif + + .destroy_device = darwin_destroy_device, + + .submit_transfer = darwin_submit_transfer, + .cancel_transfer = darwin_cancel_transfer, + + .handle_transfer_completion = darwin_handle_transfer_completion, + + .device_priv_size = sizeof(struct darwin_device_priv), + .device_handle_priv_size = sizeof(struct darwin_device_handle_priv), + .transfer_priv_size = sizeof(struct darwin_transfer_priv), +}; diff --git a/Radio/HW/RtlSdr/libusb/os/darwin_usb.h b/Radio/HW/RtlSdr/libusb/os/darwin_usb.h new file mode 100644 index 0000000..7b72fff --- /dev/null +++ b/Radio/HW/RtlSdr/libusb/os/darwin_usb.h @@ -0,0 +1,227 @@ +/* + * darwin backend for libusb 1.0 + * Copyright © 2008-2019 Nathan Hjelm <hjelmn@users.sourceforge.net> + * Copyright © 2019 Google LLC. All rights reserved. + * + * 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 + */ + +#if !defined(LIBUSB_DARWIN_H) +#define LIBUSB_DARWIN_H + +#include <stdbool.h> + +#include "libusbi.h" + +#include <IOKit/IOTypes.h> +#include <IOKit/IOCFBundle.h> +#include <IOKit/usb/IOUSBLib.h> +#include <IOKit/IOCFPlugIn.h> + +#if defined(HAVE_IOKIT_USB_IOUSBHOSTFAMILYDEFINITIONS_H) +#include <IOKit/usb/IOUSBHostFamilyDefinitions.h> +#endif + +/* IOUSBInterfaceInferface */ + +/* New in OS 10.12.0. */ +#if defined (kIOUSBInterfaceInterfaceID800) + +#define usb_interface_t IOUSBInterfaceInterface800 +#define InterfaceInterfaceID kIOUSBInterfaceInterfaceID800 +#define InterfaceVersion 800 + +/* New in OS 10.10.0. */ +#elif defined (kIOUSBInterfaceInterfaceID700) + +#define usb_interface_t IOUSBInterfaceInterface700 +#define InterfaceInterfaceID kIOUSBInterfaceInterfaceID700 +#define InterfaceVersion 700 + +/* New in OS 10.9.0. */ +#elif defined (kIOUSBInterfaceInterfaceID650) + +#define usb_interface_t IOUSBInterfaceInterface650 +#define InterfaceInterfaceID kIOUSBInterfaceInterfaceID650 +#define InterfaceVersion 650 + +/* New in OS 10.8.2 but can't test deployment target to that granularity, so round up. */ +#elif defined (kIOUSBInterfaceInterfaceID550) + +#define usb_interface_t IOUSBInterfaceInterface550 +#define InterfaceInterfaceID kIOUSBInterfaceInterfaceID550 +#define InterfaceVersion 550 + +/* New in OS 10.7.3 but can't test deployment target to that granularity, so round up. */ +#elif defined (kIOUSBInterfaceInterfaceID500) + +#define usb_interface_t IOUSBInterfaceInterface500 +#define InterfaceInterfaceID kIOUSBInterfaceInterfaceID500 +#define InterfaceVersion 500 + +/* New in OS 10.5.0. */ +#elif defined (kIOUSBInterfaceInterfaceID300) + +#define usb_interface_t IOUSBInterfaceInterface300 +#define InterfaceInterfaceID kIOUSBInterfaceInterfaceID300 +#define InterfaceVersion 300 + +/* New in OS 10.4.5 (or 10.4.6?) but can't test deployment target to that granularity, so round up. */ +#elif defined (kIOUSBInterfaceInterfaceID245) + +#define usb_interface_t IOUSBInterfaceInterface245 +#define InterfaceInterfaceID kIOUSBInterfaceInterfaceID245 +#define InterfaceVersion 245 + +/* New in OS 10.4.0. */ +#elif defined (kIOUSBInterfaceInterfaceID220) + +#define usb_interface_t IOUSBInterfaceInterface220 +#define InterfaceInterfaceID kIOUSBInterfaceInterfaceID220 +#define InterfaceVersion 220 + +#else + +#error "IOUSBFamily is too old. Please upgrade your SDK and/or deployment target" + +#endif + +/* IOUSBDeviceInterface */ + +/* New in OS 10.9.0. */ +#if defined (kIOUSBDeviceInterfaceID650) + +#define usb_device_t IOUSBDeviceInterface650 +#define DeviceInterfaceID kIOUSBDeviceInterfaceID650 +#define DeviceVersion 650 + +/* New in OS 10.7.3 but can't test deployment target to that granularity, so round up. */ +#elif defined (kIOUSBDeviceInterfaceID500) + +#define usb_device_t IOUSBDeviceInterface500 +#define DeviceInterfaceID kIOUSBDeviceInterfaceID500 +#define DeviceVersion 500 + +/* New in OS 10.5.4 but can't test deployment target to that granularity, so round up. */ +#elif defined (kIOUSBDeviceInterfaceID320) + +#define usb_device_t IOUSBDeviceInterface320 +#define DeviceInterfaceID kIOUSBDeviceInterfaceID320 +#define DeviceVersion 320 + +/* New in OS 10.5.0. */ +#elif defined (kIOUSBDeviceInterfaceID300) + +#define usb_device_t IOUSBDeviceInterface300 +#define DeviceInterfaceID kIOUSBDeviceInterfaceID300 +#define DeviceVersion 300 + +/* New in OS 10.4.5 (or 10.4.6?) but can't test deployment target to that granularity, so round up. */ +#elif defined (kIOUSBDeviceInterfaceID245) + +#define usb_device_t IOUSBDeviceInterface245 +#define DeviceInterfaceID kIOUSBDeviceInterfaceID245 +#define DeviceVersion 245 + +/* New in OS 10.2.3 but can't test deployment target to that granularity, so round up. */ +#elif defined (kIOUSBDeviceInterfaceID197) + +#define usb_device_t IOUSBDeviceInterface197 +#define DeviceInterfaceID kIOUSBDeviceInterfaceID197 +#define DeviceVersion 197 + +#else + +#error "IOUSBFamily is too old. Please upgrade your SDK and/or deployment target" + +#endif + +#if !defined(kIOUSBHostInterfaceClassName) +#define kIOUSBHostInterfaceClassName "IOUSBHostInterface" +#endif + +#if !defined(kUSBHostMatchingPropertyInterfaceNumber) +#define kUSBHostMatchingPropertyInterfaceNumber "bInterfaceNumber" +#endif + +#if !defined(IO_OBJECT_NULL) +#define IO_OBJECT_NULL ((io_object_t) 0) +#endif + +/* Testing availability */ +#ifndef __has_builtin + #define __has_builtin(x) 0 // Compatibility with non-clang compilers. +#endif +#if __has_builtin(__builtin_available) + #define HAS_CAPTURE_DEVICE() __builtin_available(macOS 10.10, *) +#else + #define HAS_CAPTURE_DEVICE() 0 +#endif + +typedef IOCFPlugInInterface *io_cf_plugin_ref_t; +typedef IONotificationPortRef io_notification_port_t; + +/* private structures */ +struct darwin_cached_device { + struct list_head list; + IOUSBDeviceDescriptor dev_descriptor; + UInt32 location; + UInt64 parent_session; + UInt64 session; + USBDeviceAddress address; + char sys_path[21]; + usb_device_t **device; + io_service_t service; + int open_count; + UInt8 first_config, active_config, port; + int can_enumerate; + int refcount; + bool in_reenumerate; + int capture_count; +}; + +struct darwin_device_priv { + struct darwin_cached_device *dev; +}; + +struct darwin_device_handle_priv { + bool is_open; + CFRunLoopSourceRef cfSource; + + struct darwin_interface { + usb_interface_t **interface; + uint8_t num_endpoints; + CFRunLoopSourceRef cfSource; + uint64_t frames[256]; + uint8_t endpoint_addrs[USB_MAXENDPOINTS]; + } interfaces[USB_MAXINTERFACES]; +}; + +struct darwin_transfer_priv { + /* Isoc */ + IOUSBIsocFrame *isoc_framelist; + int num_iso_packets; + + /* Control */ + IOUSBDevRequestTO req; + + /* Bulk */ + + /* Completion status */ + IOReturn result; + UInt32 size; +}; + +#endif diff --git a/Radio/HW/RtlSdr/libusb/os/events_posix.c b/Radio/HW/RtlSdr/libusb/os/events_posix.c new file mode 100644 index 0000000..715a2d5 --- /dev/null +++ b/Radio/HW/RtlSdr/libusb/os/events_posix.c @@ -0,0 +1,300 @@ +/* + * libusb event abstraction on POSIX platforms + * + * Copyright © 2020 Chris Dickens <christopher.a.dickens@gmail.com> + * + * 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 "libusbi.h" + +#include <errno.h> +#include <fcntl.h> +#ifdef HAVE_EVENTFD +#include <sys/eventfd.h> +#endif +#ifdef HAVE_TIMERFD +#include <sys/timerfd.h> +#endif +#include <unistd.h> + +#ifdef HAVE_EVENTFD +#define EVENT_READ_FD(e) ((e)->eventfd) +#define EVENT_WRITE_FD(e) ((e)->eventfd) +#else +#define EVENT_READ_FD(e) ((e)->pipefd[0]) +#define EVENT_WRITE_FD(e) ((e)->pipefd[1]) +#endif + +#ifdef HAVE_NFDS_T +typedef nfds_t usbi_nfds_t; +#else +typedef unsigned int usbi_nfds_t; +#endif + +int usbi_create_event(usbi_event_t *event) +{ +#ifdef HAVE_EVENTFD + event->eventfd = eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC); + if (event->eventfd == -1) { + usbi_err(NULL, "failed to create eventfd, errno=%d", errno); + return LIBUSB_ERROR_OTHER; + } + + return 0; +#else +#if defined(HAVE_PIPE2) + int ret = pipe2(event->pipefd, O_CLOEXEC); +#else + int ret = pipe(event->pipefd); +#endif + + if (ret != 0) { + usbi_err(NULL, "failed to create pipe, errno=%d", errno); + return LIBUSB_ERROR_OTHER; + } + +#if !defined(HAVE_PIPE2) && defined(FD_CLOEXEC) + ret = fcntl(event->pipefd[0], F_GETFD); + if (ret == -1) { + usbi_err(NULL, "failed to get pipe fd flags, errno=%d", errno); + goto err_close_pipe; + } + ret = fcntl(event->pipefd[0], F_SETFD, ret | FD_CLOEXEC); + if (ret == -1) { + usbi_err(NULL, "failed to set pipe fd flags, errno=%d", errno); + goto err_close_pipe; + } + + ret = fcntl(event->pipefd[1], F_GETFD); + if (ret == -1) { + usbi_err(NULL, "failed to get pipe fd flags, errno=%d", errno); + goto err_close_pipe; + } + ret = fcntl(event->pipefd[1], F_SETFD, ret | FD_CLOEXEC); + if (ret == -1) { + usbi_err(NULL, "failed to set pipe fd flags, errno=%d", errno); + goto err_close_pipe; + } +#endif + + ret = fcntl(event->pipefd[1], F_GETFL); + if (ret == -1) { + usbi_err(NULL, "failed to get pipe fd status flags, errno=%d", errno); + goto err_close_pipe; + } + ret = fcntl(event->pipefd[1], F_SETFL, ret | O_NONBLOCK); + if (ret == -1) { + usbi_err(NULL, "failed to set pipe fd status flags, errno=%d", errno); + goto err_close_pipe; + } + + return 0; + +err_close_pipe: + close(event->pipefd[1]); + close(event->pipefd[0]); + return LIBUSB_ERROR_OTHER; +#endif +} + +void usbi_destroy_event(usbi_event_t *event) +{ +#ifdef HAVE_EVENTFD + if (close(event->eventfd) == -1) + usbi_warn(NULL, "failed to close eventfd, errno=%d", errno); +#else + if (close(event->pipefd[1]) == -1) + usbi_warn(NULL, "failed to close pipe write end, errno=%d", errno); + if (close(event->pipefd[0]) == -1) + usbi_warn(NULL, "failed to close pipe read end, errno=%d", errno); +#endif +} + +void usbi_signal_event(usbi_event_t *event) +{ + uint64_t dummy = 1; + ssize_t r; + + r = write(EVENT_WRITE_FD(event), &dummy, sizeof(dummy)); + if (r != sizeof(dummy)) + usbi_warn(NULL, "event write failed"); +} + +void usbi_clear_event(usbi_event_t *event) +{ + uint64_t dummy; + ssize_t r; + + r = read(EVENT_READ_FD(event), &dummy, sizeof(dummy)); + if (r != sizeof(dummy)) + usbi_warn(NULL, "event read failed"); +} + +#ifdef HAVE_TIMERFD +int usbi_create_timer(usbi_timer_t *timer) +{ + timer->timerfd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK | TFD_CLOEXEC); + if (timer->timerfd == -1) { + usbi_warn(NULL, "failed to create timerfd, errno=%d", errno); + return LIBUSB_ERROR_OTHER; + } + + return 0; +} + +void usbi_destroy_timer(usbi_timer_t *timer) +{ + if (close(timer->timerfd) == -1) + usbi_warn(NULL, "failed to close timerfd, errno=%d", errno); +} + +int usbi_arm_timer(usbi_timer_t *timer, const struct timespec *timeout) +{ + const struct itimerspec it = { { 0, 0 }, { timeout->tv_sec, timeout->tv_nsec } }; + + if (timerfd_settime(timer->timerfd, TFD_TIMER_ABSTIME, &it, NULL) == -1) { + usbi_warn(NULL, "failed to arm timerfd, errno=%d", errno); + return LIBUSB_ERROR_OTHER; + } + + return 0; +} + +int usbi_disarm_timer(usbi_timer_t *timer) +{ + const struct itimerspec it = { { 0, 0 }, { 0, 0 } }; + + if (timerfd_settime(timer->timerfd, 0, &it, NULL) == -1) { + usbi_warn(NULL, "failed to disarm timerfd, errno=%d", errno); + return LIBUSB_ERROR_OTHER; + } + + return 0; +} +#endif + +int usbi_alloc_event_data(struct libusb_context *ctx) +{ + struct usbi_event_source *ievent_source; + struct pollfd *fds; + size_t i = 0; + + if (ctx->event_data) { + free(ctx->event_data); + ctx->event_data = NULL; + } + + ctx->event_data_cnt = 0; + for_each_event_source(ctx, ievent_source) + ctx->event_data_cnt++; + + fds = calloc(ctx->event_data_cnt, sizeof(*fds)); + if (!fds) + return LIBUSB_ERROR_NO_MEM; + + for_each_event_source(ctx, ievent_source) { + fds[i].fd = ievent_source->data.os_handle; + fds[i].events = ievent_source->data.poll_events; + i++; + } + + ctx->event_data = fds; + return 0; +} + +int usbi_wait_for_events(struct libusb_context *ctx, + struct usbi_reported_events *reported_events, int timeout_ms) +{ + struct pollfd *fds = ctx->event_data; + usbi_nfds_t nfds = (usbi_nfds_t)ctx->event_data_cnt; + int internal_fds, num_ready; + + usbi_dbg(ctx, "poll() %u fds with timeout in %dms", (unsigned int)nfds, timeout_ms); + num_ready = poll(fds, nfds, timeout_ms); + usbi_dbg(ctx, "poll() returned %d", num_ready); + if (num_ready == 0) { + if (usbi_using_timer(ctx)) + goto done; + return LIBUSB_ERROR_TIMEOUT; + } else if (num_ready == -1) { + if (errno == EINTR) + return LIBUSB_ERROR_INTERRUPTED; + usbi_err(ctx, "poll() failed, errno=%d", errno); + return LIBUSB_ERROR_IO; + } + + /* fds[0] is always the internal signalling event */ + if (fds[0].revents) { + reported_events->event_triggered = 1; + num_ready--; + } else { + reported_events->event_triggered = 0; + } + +#ifdef HAVE_OS_TIMER + /* on timer configurations, fds[1] is the timer */ + if (usbi_using_timer(ctx) && fds[1].revents) { + reported_events->timer_triggered = 1; + num_ready--; + } else { + reported_events->timer_triggered = 0; + } +#endif + + if (!num_ready) + goto done; + + /* the backend will never need to attempt to handle events on the + * library's internal file descriptors, so we determine how many are + * in use internally for this context and skip these when passing any + * remaining pollfds to the backend. */ + internal_fds = usbi_using_timer(ctx) ? 2 : 1; + fds += internal_fds; + nfds -= internal_fds; + + usbi_mutex_lock(&ctx->event_data_lock); + if (ctx->event_flags & USBI_EVENT_EVENT_SOURCES_MODIFIED) { + struct usbi_event_source *ievent_source; + + for_each_removed_event_source(ctx, ievent_source) { + usbi_nfds_t n; + + for (n = 0; n < nfds; n++) { + if (ievent_source->data.os_handle != fds[n].fd) + continue; + if (!fds[n].revents) + continue; + /* pollfd was removed between the creation of the fds array and + * here. remove triggered revent as it is no longer relevant. */ + usbi_dbg(ctx, "fd %d was removed, ignoring raised events", fds[n].fd); + fds[n].revents = 0; + num_ready--; + break; + } + } + } + usbi_mutex_unlock(&ctx->event_data_lock); + + if (num_ready) { + assert(num_ready > 0); + reported_events->event_data = fds; + reported_events->event_data_count = (unsigned int)nfds; + } + +done: + reported_events->num_ready = num_ready; + return LIBUSB_SUCCESS; +} diff --git a/Radio/HW/RtlSdr/libusb/os/events_posix.h b/Radio/HW/RtlSdr/libusb/os/events_posix.h new file mode 100644 index 0000000..d81b5c4 --- /dev/null +++ b/Radio/HW/RtlSdr/libusb/os/events_posix.h @@ -0,0 +1,59 @@ +/* + * libusb event abstraction on POSIX platforms + * + * Copyright © 2020 Chris Dickens <christopher.a.dickens@gmail.com> + * + * 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 + */ + +#ifndef LIBUSB_EVENTS_POSIX_H +#define LIBUSB_EVENTS_POSIX_H + +#include <poll.h> + +typedef int usbi_os_handle_t; +#define USBI_OS_HANDLE_FORMAT_STRING "fd %d" + +#ifdef HAVE_EVENTFD +typedef struct usbi_event { + int eventfd; +} usbi_event_t; +#define USBI_EVENT_OS_HANDLE(e) ((e)->eventfd) +#define USBI_EVENT_POLL_EVENTS POLLIN +#define USBI_INVALID_EVENT { -1 } +#else +typedef struct usbi_event { + int pipefd[2]; +} usbi_event_t; +#define USBI_EVENT_OS_HANDLE(e) ((e)->pipefd[0]) +#define USBI_EVENT_POLL_EVENTS POLLIN +#define USBI_INVALID_EVENT { { -1, -1 } } +#endif + +#ifdef HAVE_TIMERFD +#define HAVE_OS_TIMER 1 +typedef struct usbi_timer { + int timerfd; +} usbi_timer_t; +#define USBI_TIMER_OS_HANDLE(t) ((t)->timerfd) +#define USBI_TIMER_POLL_EVENTS POLLIN + +static inline int usbi_timer_valid(usbi_timer_t *timer) +{ + return timer->timerfd >= 0; +} +#endif + +#endif diff --git a/Radio/HW/RtlSdr/libusb/os/linux_udev.c b/Radio/HW/RtlSdr/libusb/os/linux_udev.c new file mode 100644 index 0000000..9ec9eb1 --- /dev/null +++ b/Radio/HW/RtlSdr/libusb/os/linux_udev.c @@ -0,0 +1,321 @@ +/* -*- Mode: C; c-basic-offset:8 ; indent-tabs-mode:t -*- */ +/* + * Linux usbfs backend for libusb + * Copyright (C) 2007-2009 Daniel Drake <dsd@gentoo.org> + * Copyright (c) 2001 Johannes Erdfelt <johannes@erdfelt.com> + * Copyright (c) 2012-2013 Nathan Hjelm <hjelmn@mac.com> + * + * 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 "libusbi.h" +#include "linux_usbfs.h" + +#include <errno.h> +#include <fcntl.h> +#include <libudev.h> +#include <poll.h> +#include <pthread.h> +#include <string.h> +#include <unistd.h> + +/* udev context */ +static struct udev *udev_ctx = NULL; +static int udev_monitor_fd = -1; +static usbi_event_t udev_control_event = USBI_INVALID_EVENT; +static struct udev_monitor *udev_monitor = NULL; +static pthread_t linux_event_thread; + +static void udev_hotplug_event(struct udev_device *udev_dev); +static void *linux_udev_event_thread_main(void *arg); + +int linux_udev_start_event_monitor(void) +{ + int r; + + assert(udev_ctx == NULL); + udev_ctx = udev_new(); + if (!udev_ctx) { + usbi_err(NULL, "could not create udev context"); + goto err; + } + + udev_monitor = udev_monitor_new_from_netlink(udev_ctx, "udev"); + if (!udev_monitor) { + usbi_err(NULL, "could not initialize udev monitor"); + goto err_free_ctx; + } + + r = udev_monitor_filter_add_match_subsystem_devtype(udev_monitor, "usb", "usb_device"); + if (r) { + usbi_err(NULL, "could not initialize udev monitor filter for \"usb\" subsystem"); + goto err_free_monitor; + } + + if (udev_monitor_enable_receiving(udev_monitor)) { + usbi_err(NULL, "failed to enable the udev monitor"); + goto err_free_monitor; + } + + udev_monitor_fd = udev_monitor_get_fd(udev_monitor); + +#if defined(FD_CLOEXEC) + /* Make sure the udev file descriptor is marked as CLOEXEC */ + r = fcntl(udev_monitor_fd, F_GETFD); + if (r == -1) { + usbi_err(NULL, "failed to get udev monitor fd flags, errno=%d", errno); + goto err_free_monitor; + } + if (!(r & FD_CLOEXEC)) { + if (fcntl(udev_monitor_fd, F_SETFD, r | FD_CLOEXEC) == -1) { + usbi_err(NULL, "failed to set udev monitor fd flags, errno=%d", errno); + goto err_free_monitor; + } + } +#endif + + /* Some older versions of udev are not non-blocking by default, + * so make sure this is set */ + r = fcntl(udev_monitor_fd, F_GETFL); + if (r == -1) { + usbi_err(NULL, "failed to get udev monitor fd status flags, errno=%d", errno); + goto err_free_monitor; + } + if (!(r & O_NONBLOCK)) { + if (fcntl(udev_monitor_fd, F_SETFL, r | O_NONBLOCK) == -1) { + usbi_err(NULL, "failed to set udev monitor fd status flags, errno=%d", errno); + goto err_free_monitor; + } + } + + r = usbi_create_event(&udev_control_event); + if (r) { + usbi_err(NULL, "failed to create udev control event"); + goto err_free_monitor; + } + + r = pthread_create(&linux_event_thread, NULL, linux_udev_event_thread_main, NULL); + if (r) { + usbi_err(NULL, "failed to create hotplug event thread (%d)", r); + goto err_destroy_event; + } + + return LIBUSB_SUCCESS; + +err_destroy_event: + usbi_destroy_event(&udev_control_event); + udev_control_event = (usbi_event_t)USBI_INVALID_EVENT; +err_free_monitor: + udev_monitor_unref(udev_monitor); + udev_monitor = NULL; + udev_monitor_fd = -1; +err_free_ctx: + udev_unref(udev_ctx); +err: + udev_ctx = NULL; + return LIBUSB_ERROR_OTHER; +} + +int linux_udev_stop_event_monitor(void) +{ + int r; + + assert(udev_ctx != NULL); + assert(udev_monitor != NULL); + assert(udev_monitor_fd != -1); + + /* Signal the control event and wait for the thread to exit */ + usbi_signal_event(&udev_control_event); + + r = pthread_join(linux_event_thread, NULL); + if (r) + usbi_warn(NULL, "failed to join hotplug event thread (%d)", r); + + usbi_destroy_event(&udev_control_event); + udev_control_event = (usbi_event_t)USBI_INVALID_EVENT; + + /* Release the udev monitor */ + udev_monitor_unref(udev_monitor); + udev_monitor = NULL; + udev_monitor_fd = -1; + + /* Clean up the udev context */ + udev_unref(udev_ctx); + udev_ctx = NULL; + + return LIBUSB_SUCCESS; +} + +static void *linux_udev_event_thread_main(void *arg) +{ + struct pollfd fds[] = { + { .fd = USBI_EVENT_OS_HANDLE(&udev_control_event), + .events = USBI_EVENT_POLL_EVENTS }, + { .fd = udev_monitor_fd, + .events = POLLIN }, + }; + struct udev_device *udev_dev; + int r; + + UNUSED(arg); + +#if defined(HAVE_PTHREAD_SETNAME_NP) + r = pthread_setname_np(pthread_self(), "libusb_event"); + if (r) + usbi_warn(NULL, "failed to set hotplug event thread name, error=%d", r); +#endif + + usbi_dbg(NULL, "udev event thread entering"); + + while (1) { + r = poll(fds, 2, -1); + if (r == -1) { + /* check for temporary failure */ + if (errno == EINTR) + continue; + usbi_err(NULL, "poll() failed, errno=%d", errno); + break; + } + if (fds[0].revents) { + /* activity on control event, exit */ + break; + } + if (fds[1].revents) { + usbi_mutex_static_lock(&linux_hotplug_lock); + udev_dev = udev_monitor_receive_device(udev_monitor); + if (udev_dev) + udev_hotplug_event(udev_dev); + usbi_mutex_static_unlock(&linux_hotplug_lock); + } + } + + usbi_dbg(NULL, "udev event thread exiting"); + + return NULL; +} + +static int udev_device_info(struct libusb_context *ctx, int detached, + struct udev_device *udev_dev, uint8_t *busnum, + uint8_t *devaddr, const char **sys_name) { + const char *dev_node; + + dev_node = udev_device_get_devnode(udev_dev); + if (!dev_node) { + return LIBUSB_ERROR_OTHER; + } + + *sys_name = udev_device_get_sysname(udev_dev); + if (!*sys_name) { + return LIBUSB_ERROR_OTHER; + } + + return linux_get_device_address(ctx, detached, busnum, devaddr, + dev_node, *sys_name, -1); +} + +static void udev_hotplug_event(struct udev_device *udev_dev) +{ + const char *udev_action; + const char *sys_name = NULL; + uint8_t busnum = 0, devaddr = 0; + int detached; + int r; + + do { + udev_action = udev_device_get_action(udev_dev); + if (!udev_action) { + break; + } + + detached = !strncmp(udev_action, "remove", 6); + + r = udev_device_info(NULL, detached, udev_dev, &busnum, &devaddr, &sys_name); + if (LIBUSB_SUCCESS != r) { + break; + } + + usbi_dbg(NULL, "udev hotplug event. action: %s.", udev_action); + + if (strncmp(udev_action, "add", 3) == 0) { + linux_hotplug_enumerate(busnum, devaddr, sys_name); + } else if (detached) { + linux_device_disconnected(busnum, devaddr); + } else if (strncmp(udev_action, "bind", 4) == 0) { + /* silently ignore "known unhandled" action */ + } else { + usbi_err(NULL, "ignoring udev action %s", udev_action); + } + } while (0); + + udev_device_unref(udev_dev); +} + +int linux_udev_scan_devices(struct libusb_context *ctx) +{ + struct udev_enumerate *enumerator; + struct udev_list_entry *devices, *entry; + struct udev_device *udev_dev; + const char *sys_name; + int r; + + assert(udev_ctx != NULL); + + enumerator = udev_enumerate_new(udev_ctx); + if (NULL == enumerator) { + usbi_err(ctx, "error creating udev enumerator"); + return LIBUSB_ERROR_OTHER; + } + + udev_enumerate_add_match_subsystem(enumerator, "usb"); + udev_enumerate_add_match_property(enumerator, "DEVTYPE", "usb_device"); + udev_enumerate_scan_devices(enumerator); + devices = udev_enumerate_get_list_entry(enumerator); + + entry = NULL; + udev_list_entry_foreach(entry, devices) { + const char *path = udev_list_entry_get_name(entry); + uint8_t busnum = 0, devaddr = 0; + + udev_dev = udev_device_new_from_syspath(udev_ctx, path); + + r = udev_device_info(ctx, 0, udev_dev, &busnum, &devaddr, &sys_name); + if (r) { + udev_device_unref(udev_dev); + continue; + } + + linux_enumerate_device(ctx, busnum, devaddr, sys_name); + udev_device_unref(udev_dev); + } + + udev_enumerate_unref(enumerator); + + return LIBUSB_SUCCESS; +} + +void linux_udev_hotplug_poll(void) +{ + struct udev_device *udev_dev; + + usbi_mutex_static_lock(&linux_hotplug_lock); + do { + udev_dev = udev_monitor_receive_device(udev_monitor); + if (udev_dev) { + usbi_dbg(NULL, "Handling hotplug event from hotplug_poll"); + udev_hotplug_event(udev_dev); + } + } while (udev_dev); + usbi_mutex_static_unlock(&linux_hotplug_lock); +} diff --git a/Radio/HW/RtlSdr/libusb/os/linux_usbfs.c b/Radio/HW/RtlSdr/libusb/os/linux_usbfs.c new file mode 100644 index 0000000..c300675 --- /dev/null +++ b/Radio/HW/RtlSdr/libusb/os/linux_usbfs.c @@ -0,0 +1,2807 @@ +/* -*- Mode: C; c-basic-offset:8 ; indent-tabs-mode:t -*- */ +/* + * Linux usbfs backend for libusb + * Copyright © 2007-2009 Daniel Drake <dsd@gentoo.org> + * Copyright © 2001 Johannes Erdfelt <johannes@erdfelt.com> + * Copyright © 2013 Nathan Hjelm <hjelmn@mac.com> + * Copyright © 2012-2013 Hans de Goede <hdegoede@redhat.com> + * Copyright © 2020 Chris Dickens <christopher.a.dickens@gmail.com> + * + * 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 "libusbi.h" +#include "linux_usbfs.h" + +#include <alloca.h> +#include <ctype.h> +#include <dirent.h> +#include <errno.h> +#include <fcntl.h> +#include <stdio.h> +#include <string.h> +#include <sys/ioctl.h> +#include <sys/mman.h> +#include <sys/utsname.h> +#include <sys/vfs.h> +#include <unistd.h> + +/* sysfs vs usbfs: + * opening a usbfs node causes the device to be resumed, so we attempt to + * avoid this during enumeration. + * + * sysfs allows us to read the kernel's in-memory copies of device descriptors + * and so forth, avoiding the need to open the device: + * - The binary "descriptors" file contains all config descriptors since + * 2.6.26, commit 217a9081d8e69026186067711131b77f0ce219ed + * - The binary "descriptors" file was added in 2.6.23, commit + * 69d42a78f935d19384d1f6e4f94b65bb162b36df, but it only contains the + * active config descriptors + * - The "busnum" file was added in 2.6.22, commit + * 83f7d958eab2fbc6b159ee92bf1493924e1d0f72 + * - The "devnum" file has been present since pre-2.6.18 + * - the "bConfigurationValue" file has been present since pre-2.6.18 + * + * If we have bConfigurationValue, busnum, and devnum, then we can determine + * the active configuration without having to open the usbfs node in RDWR mode. + * The busnum file is important as that is the only way we can relate sysfs + * devices to usbfs nodes. + * + * If we also have all descriptors, we can obtain the device descriptor and + * configuration without touching usbfs at all. + */ + +/* endianness for multi-byte fields: + * + * Descriptors exposed by usbfs have the multi-byte fields in the device + * descriptor as host endian. Multi-byte fields in the other descriptors are + * bus-endian. The kernel documentation says otherwise, but it is wrong. + * + * In sysfs all descriptors are bus-endian. + */ + +#define USBDEV_PATH "/dev" +#define USB_DEVTMPFS_PATH "/dev/bus/usb" + +/* use usbdev*.* device names in /dev instead of the usbfs bus directories */ +static int usbdev_names = 0; + +/* Linux has changed the maximum length of an individual isochronous packet + * over time. Initially this limit was 1,023 bytes, but Linux 2.6.18 + * (commit 3612242e527eb47ee4756b5350f8bdf791aa5ede) increased this value to + * 8,192 bytes to support higher bandwidth devices. Linux 3.10 + * (commit e2e2f0ea1c935edcf53feb4c4c8fdb4f86d57dd9) further increased this + * value to 49,152 bytes to support super speed devices. Linux 5.2 + * (commit 8a1dbc8d91d3d1602282c7e6b4222c7759c916fa) even further increased + * this value to 98,304 bytes to support super speed plus devices. + */ +static unsigned int max_iso_packet_len = 0; + +/* is sysfs available (mounted) ? */ +static int sysfs_available = -1; + +/* how many times have we initted (and not exited) ? */ +static int init_count = 0; + +/* have no authority to operate usb device directly */ +static int no_enumeration = 0; + +/* Serialize scan-devices, event-thread, and poll */ +usbi_mutex_static_t linux_hotplug_lock = USBI_MUTEX_INITIALIZER; + +static int linux_scan_devices(struct libusb_context *ctx); +static int detach_kernel_driver_and_claim(struct libusb_device_handle *, uint8_t); + +#if !defined(HAVE_LIBUDEV) +static int linux_default_scan_devices(struct libusb_context *ctx); +#endif + +struct kernel_version { + int major; + int minor; + int sublevel; +}; + +struct config_descriptor { + struct usbi_configuration_descriptor *desc; + size_t actual_len; +}; + +struct linux_device_priv { + char *sysfs_dir; + void *descriptors; + size_t descriptors_len; + struct config_descriptor *config_descriptors; + int active_config; /* cache val for !sysfs_available */ +}; + +struct linux_device_handle_priv { + int fd; + int fd_removed; + int fd_keep; + uint32_t caps; +}; + +enum reap_action { + NORMAL = 0, + /* submission failed after the first URB, so await cancellation/completion + * of all the others */ + SUBMIT_FAILED, + + /* cancelled by user or timeout */ + CANCELLED, + + /* completed multi-URB transfer in non-final URB */ + COMPLETED_EARLY, + + /* one or more urbs encountered a low-level error */ + ERROR, +}; + +struct linux_transfer_priv { + union { + struct usbfs_urb *urbs; + struct usbfs_urb **iso_urbs; + }; + + enum reap_action reap_action; + int num_urbs; + int num_retired; + enum libusb_transfer_status reap_status; + + /* next iso packet in user-supplied transfer to be populated */ + int iso_packet_offset; +}; + +static int dev_has_config0(struct libusb_device *dev) +{ + struct linux_device_priv *priv = usbi_get_device_priv(dev); + struct config_descriptor *config; + uint8_t idx; + + for (idx = 0; idx < dev->device_descriptor.bNumConfigurations; idx++) { + config = &priv->config_descriptors[idx]; + if (config->desc->bConfigurationValue == 0) + return 1; + } + + return 0; +} + +static int get_usbfs_fd(struct libusb_device *dev, mode_t mode, int silent) +{ + struct libusb_context *ctx = DEVICE_CTX(dev); + char path[24]; + int fd; + + if (usbdev_names) + sprintf(path, USBDEV_PATH "/usbdev%u.%u", + dev->bus_number, dev->device_address); + else + sprintf(path, USB_DEVTMPFS_PATH "/%03u/%03u", + dev->bus_number, dev->device_address); + + fd = open(path, mode | O_CLOEXEC); + if (fd != -1) + return fd; /* Success */ + + if (errno == ENOENT) { + const long delay_ms = 10L; + const struct timespec delay_ts = { 0L, delay_ms * 1000L * 1000L }; + + if (!silent) + usbi_err(ctx, "File doesn't exist, wait %ld ms and try again", delay_ms); + + /* Wait 10ms for USB device path creation.*/ + nanosleep(&delay_ts, NULL); + + fd = open(path, mode | O_CLOEXEC); + if (fd != -1) + return fd; /* Success */ + } + + if (!silent) { + usbi_err(ctx, "libusb couldn't open USB device %s, errno=%d", path, errno); + if (errno == EACCES && mode == O_RDWR) + usbi_err(ctx, "libusb requires write access to USB device nodes"); + } + + if (errno == EACCES) + return LIBUSB_ERROR_ACCESS; + if (errno == ENOENT) + return LIBUSB_ERROR_NO_DEVICE; + return LIBUSB_ERROR_IO; +} + +/* check dirent for a /dev/usbdev%d.%d name + * optionally return bus/device on success */ +static int is_usbdev_entry(const char *name, uint8_t *bus_p, uint8_t *dev_p) +{ + int busnum, devnum; + + if (sscanf(name, "usbdev%d.%d", &busnum, &devnum) != 2) + return 0; + if (busnum < 0 || busnum > UINT8_MAX || devnum < 0 || devnum > UINT8_MAX) { + usbi_dbg(NULL, "invalid usbdev format '%s'", name); + return 0; + } + + usbi_dbg(NULL, "found: %s", name); + if (bus_p) + *bus_p = (uint8_t)busnum; + if (dev_p) + *dev_p = (uint8_t)devnum; + return 1; +} + +static const char *find_usbfs_path(void) +{ + const char *path; + DIR *dir; + struct dirent *entry; + + path = USB_DEVTMPFS_PATH; + dir = opendir(path); + if (dir) { + while ((entry = readdir(dir))) { + if (entry->d_name[0] == '.') + continue; + + /* We assume if we find any files that it must be the right place */ + break; + } + + closedir(dir); + + if (entry) + return path; + } + + /* look for /dev/usbdev*.* if the normal place fails */ + path = USBDEV_PATH; + dir = opendir(path); + if (dir) { + while ((entry = readdir(dir))) { + if (entry->d_name[0] == '.') + continue; + + if (is_usbdev_entry(entry->d_name, NULL, NULL)) { + /* found one; that's enough */ + break; + } + } + + closedir(dir); + + if (entry) { + usbdev_names = 1; + return path; + } + } + +/* On udev based systems without any usb-devices /dev/bus/usb will not + * exist. So if we've not found anything and we're using udev for hotplug + * simply assume /dev/bus/usb rather then making libusb_init fail. + * Make the same assumption for Android where SELinux policies might block us + * from reading /dev on newer devices. */ +#if defined(HAVE_LIBUDEV) || defined(__ANDROID__) + return USB_DEVTMPFS_PATH; +#else + return NULL; +#endif +} + +static int get_kernel_version(struct libusb_context *ctx, + struct kernel_version *ver) +{ + struct utsname uts; + int atoms; + + if (uname(&uts) < 0) { + usbi_err(ctx, "uname failed, errno=%d", errno); + return -1; + } + + atoms = sscanf(uts.release, "%d.%d.%d", &ver->major, &ver->minor, &ver->sublevel); + if (atoms < 2) { + usbi_err(ctx, "failed to parse uname release '%s'", uts.release); + return -1; + } + + if (atoms < 3) + ver->sublevel = -1; + + usbi_dbg(ctx, "reported kernel version is %s", uts.release); + + return 0; +} + +static int kernel_version_ge(const struct kernel_version *ver, + int major, int minor, int sublevel) +{ + if (ver->major > major) + return 1; + else if (ver->major < major) + return 0; + + /* kmajor == major */ + if (ver->minor > minor) + return 1; + else if (ver->minor < minor) + return 0; + + /* kminor == minor */ + if (ver->sublevel == -1) + return sublevel == 0; + + return ver->sublevel >= sublevel; +} + +static int op_init(struct libusb_context *ctx) +{ + struct kernel_version kversion; + const char *usbfs_path; + int r; + + if (get_kernel_version(ctx, &kversion) < 0) + return LIBUSB_ERROR_OTHER; + + if (!kernel_version_ge(&kversion, 2, 6, 32)) { + usbi_err(ctx, "kernel version is too old (reported as %d.%d.%d)", + kversion.major, kversion.minor, + kversion.sublevel != -1 ? kversion.sublevel : 0); + return LIBUSB_ERROR_NOT_SUPPORTED; + } + + usbfs_path = find_usbfs_path(); + if (!usbfs_path) { + usbi_err(ctx, "could not find usbfs"); + return LIBUSB_ERROR_OTHER; + } + + usbi_dbg(ctx, "found usbfs at %s", usbfs_path); + + if (!max_iso_packet_len) { + if (kernel_version_ge(&kversion, 5, 2, 0)) + max_iso_packet_len = 98304; + else if (kernel_version_ge(&kversion, 3, 10, 0)) + max_iso_packet_len = 49152; + else + max_iso_packet_len = 8192; + } + + usbi_dbg(ctx, "max iso packet length is (likely) %u bytes", max_iso_packet_len); + + if (sysfs_available == -1) { + struct statfs statfsbuf; + + r = statfs(SYSFS_MOUNT_PATH, &statfsbuf); + if (r == 0 && statfsbuf.f_type == SYSFS_MAGIC) { + usbi_dbg(ctx, "sysfs is available"); + sysfs_available = 1; + } else { + usbi_warn(ctx, "sysfs not mounted"); + sysfs_available = 0; + } + } + + if (no_enumeration) { + return LIBUSB_SUCCESS; + } + + r = LIBUSB_SUCCESS; + if (init_count == 0) { + /* start up hotplug event handler */ + r = linux_start_event_monitor(); + } + if (r == LIBUSB_SUCCESS) { + r = linux_scan_devices(ctx); + if (r == LIBUSB_SUCCESS) + init_count++; + else if (init_count == 0) + linux_stop_event_monitor(); + } else { + usbi_err(ctx, "error starting hotplug event monitor"); + } + + return r; +} + +static void op_exit(struct libusb_context *ctx) +{ + UNUSED(ctx); + + if (no_enumeration) { + return; + } + + assert(init_count != 0); + if (!--init_count) { + /* tear down event handler */ + linux_stop_event_monitor(); + } +} + +static int op_set_option(struct libusb_context *ctx, enum libusb_option option, va_list ap) +{ + UNUSED(ctx); + UNUSED(ap); + + if (option == LIBUSB_OPTION_NO_DEVICE_DISCOVERY) { + usbi_dbg(ctx, "no enumeration will be performed"); + no_enumeration = 1; + return LIBUSB_SUCCESS; + } + + return LIBUSB_ERROR_NOT_SUPPORTED; +} + +static int linux_scan_devices(struct libusb_context *ctx) +{ + int ret; + + usbi_mutex_static_lock(&linux_hotplug_lock); + +#if defined(HAVE_LIBUDEV) + ret = linux_udev_scan_devices(ctx); +#else + ret = linux_default_scan_devices(ctx); +#endif + + usbi_mutex_static_unlock(&linux_hotplug_lock); + + return ret; +} + +static void op_hotplug_poll(void) +{ + linux_hotplug_poll(); +} + +static int open_sysfs_attr(struct libusb_context *ctx, + const char *sysfs_dir, const char *attr) +{ + char filename[256]; + int fd; + + snprintf(filename, sizeof(filename), SYSFS_DEVICE_PATH "/%s/%s", sysfs_dir, attr); + fd = open(filename, O_RDONLY | O_CLOEXEC); + if (fd < 0) { + if (errno == ENOENT) { + /* File doesn't exist. Assume the device has been + disconnected (see trac ticket #70). */ + return LIBUSB_ERROR_NO_DEVICE; + } + usbi_err(ctx, "open %s failed, errno=%d", filename, errno); + return LIBUSB_ERROR_IO; + } + + return fd; +} + +/* Note only suitable for attributes which always read >= 0, < 0 is error */ +static int read_sysfs_attr(struct libusb_context *ctx, + const char *sysfs_dir, const char *attr, int max_value, int *value_p) +{ + char buf[20], *endptr; + long value; + ssize_t r; + int fd; + + fd = open_sysfs_attr(ctx, sysfs_dir, attr); + if (fd < 0) + return fd; + + r = read(fd, buf, sizeof(buf) - 1); + if (r < 0) { + r = errno; + close(fd); + if (r == ENODEV) + return LIBUSB_ERROR_NO_DEVICE; + usbi_err(ctx, "attribute %s read failed, errno=%zd", attr, r); + return LIBUSB_ERROR_IO; + } + close(fd); + + if (r == 0) { + /* Certain attributes (e.g. bConfigurationValue) are not + * populated if the device is not configured. */ + *value_p = -1; + return 0; + } + + /* The kernel does *not* NUL-terminate the string, but every attribute + * should be terminated with a newline character. */ + if (!isdigit(buf[0])) { + usbi_err(ctx, "attribute %s doesn't have numeric value?", attr); + return LIBUSB_ERROR_IO; + } else if (buf[r - 1] != '\n') { + usbi_warn(ctx, "attribute %s doesn't end with newline?", attr); + } else { + /* Remove the terminating newline character */ + r--; + } + buf[r] = '\0'; + + errno = 0; + value = strtol(buf, &endptr, 10); + if (value < 0 || value > (long)max_value || errno) { + usbi_err(ctx, "attribute %s contains an invalid value: '%s'", attr, buf); + return LIBUSB_ERROR_INVALID_PARAM; + } else if (*endptr != '\0') { + /* Consider the value to be valid if the remainder is a '.' + * character followed by numbers. This occurs, for example, + * when reading the "speed" attribute for a low-speed device + * (e.g. "1.5") */ + if (*endptr == '.' && isdigit(*(endptr + 1))) { + endptr++; + while (isdigit(*endptr)) + endptr++; + } + if (*endptr != '\0') { + usbi_err(ctx, "attribute %s contains an invalid value: '%s'", attr, buf); + return LIBUSB_ERROR_INVALID_PARAM; + } + } + + *value_p = (int)value; + return 0; +} + +static int sysfs_scan_device(struct libusb_context *ctx, const char *devname) +{ + uint8_t busnum, devaddr; + int ret; + + ret = linux_get_device_address(ctx, 0, &busnum, &devaddr, NULL, devname, -1); + if (ret != LIBUSB_SUCCESS) + return ret; + + return linux_enumerate_device(ctx, busnum, devaddr, devname); +} + +/* read the bConfigurationValue for a device */ +static int sysfs_get_active_config(struct libusb_device *dev, int *config) +{ + struct linux_device_priv *priv = usbi_get_device_priv(dev); + + return read_sysfs_attr(DEVICE_CTX(dev), priv->sysfs_dir, "bConfigurationValue", + UINT8_MAX, config); +} + +int linux_get_device_address(struct libusb_context *ctx, int detached, + uint8_t *busnum, uint8_t *devaddr, const char *dev_node, + const char *sys_name, int fd) +{ + int sysfs_val; + int r; + + usbi_dbg(ctx, "getting address for device: %s detached: %d", sys_name, detached); + /* can't use sysfs to read the bus and device number if the + * device has been detached */ + if (!sysfs_available || detached || !sys_name) { + if (!dev_node && fd >= 0) { + char *fd_path = alloca(PATH_MAX); + char proc_path[32]; + + /* try to retrieve the device node from fd */ + sprintf(proc_path, "/proc/self/fd/%d", fd); + r = readlink(proc_path, fd_path, PATH_MAX - 1); + if (r > 0) { + fd_path[r] = '\0'; + dev_node = fd_path; + } + } + + if (!dev_node) + return LIBUSB_ERROR_OTHER; + + /* will this work with all supported kernel versions? */ + if (!strncmp(dev_node, "/dev/bus/usb", 12)) + sscanf(dev_node, "/dev/bus/usb/%hhu/%hhu", busnum, devaddr); + else + return LIBUSB_ERROR_OTHER; + + return LIBUSB_SUCCESS; + } + + usbi_dbg(ctx, "scan %s", sys_name); + + r = read_sysfs_attr(ctx, sys_name, "busnum", UINT8_MAX, &sysfs_val); + if (r < 0) + return r; + *busnum = (uint8_t)sysfs_val; + + r = read_sysfs_attr(ctx, sys_name, "devnum", UINT8_MAX, &sysfs_val); + if (r < 0) + return r; + *devaddr = (uint8_t)sysfs_val; + + usbi_dbg(ctx, "bus=%u dev=%u", *busnum, *devaddr); + + return LIBUSB_SUCCESS; +} + +/* Return offset of the next config descriptor */ +static int seek_to_next_config(struct libusb_context *ctx, + uint8_t *buffer, size_t len) +{ + struct usbi_descriptor_header *header; + int offset; + + /* Start seeking past the config descriptor */ + offset = LIBUSB_DT_CONFIG_SIZE; + buffer += LIBUSB_DT_CONFIG_SIZE; + len -= LIBUSB_DT_CONFIG_SIZE; + + while (len > 0) { + if (len < 2) { + usbi_err(ctx, "short descriptor read %zu/2", len); + return LIBUSB_ERROR_IO; + } + + header = (struct usbi_descriptor_header *)buffer; + if (header->bDescriptorType == LIBUSB_DT_CONFIG) + return offset; + + if (len < header->bLength) { + usbi_err(ctx, "bLength overflow by %zu bytes", + (size_t)header->bLength - len); + return LIBUSB_ERROR_IO; + } + + offset += header->bLength; + buffer += header->bLength; + len -= header->bLength; + } + + usbi_err(ctx, "config descriptor not found"); + return LIBUSB_ERROR_IO; +} + +static int parse_config_descriptors(struct libusb_device *dev) +{ + struct libusb_context *ctx = DEVICE_CTX(dev); + struct linux_device_priv *priv = usbi_get_device_priv(dev); + struct usbi_device_descriptor *device_desc; + uint8_t idx, num_configs; + uint8_t *buffer; + size_t remaining; + + device_desc = priv->descriptors; + num_configs = device_desc->bNumConfigurations; + + if (num_configs == 0) + return 0; /* no configurations? */ + + priv->config_descriptors = malloc(num_configs * sizeof(priv->config_descriptors[0])); + if (!priv->config_descriptors) + return LIBUSB_ERROR_NO_MEM; + + buffer = (uint8_t *)priv->descriptors + LIBUSB_DT_DEVICE_SIZE; + remaining = priv->descriptors_len - LIBUSB_DT_DEVICE_SIZE; + + for (idx = 0; idx < num_configs; idx++) { + struct usbi_configuration_descriptor *config_desc; + uint16_t config_len; + + if (remaining < LIBUSB_DT_CONFIG_SIZE) { + usbi_err(ctx, "short descriptor read %zu/%d", + remaining, LIBUSB_DT_CONFIG_SIZE); + return LIBUSB_ERROR_IO; + } + + config_desc = (struct usbi_configuration_descriptor *)buffer; + if (config_desc->bDescriptorType != LIBUSB_DT_CONFIG) { + usbi_err(ctx, "descriptor is not a config desc (type 0x%02x)", + config_desc->bDescriptorType); + return LIBUSB_ERROR_IO; + } else if (config_desc->bLength < LIBUSB_DT_CONFIG_SIZE) { + usbi_err(ctx, "invalid descriptor bLength %u", + config_desc->bLength); + return LIBUSB_ERROR_IO; + } + + config_len = libusb_le16_to_cpu(config_desc->wTotalLength); + if (config_len < LIBUSB_DT_CONFIG_SIZE) { + usbi_err(ctx, "invalid wTotalLength %u", config_len); + return LIBUSB_ERROR_IO; + } + + if (priv->sysfs_dir) { + /* + * In sysfs wTotalLength is ignored, instead the kernel returns a + * config descriptor with verified bLength fields, with descriptors + * with an invalid bLength removed. + */ + uint16_t sysfs_config_len; + int offset; + + if (num_configs > 1 && idx < num_configs - 1) { + offset = seek_to_next_config(ctx, buffer, remaining); + if (offset < 0) + return offset; + sysfs_config_len = (uint16_t)offset; + } else { + sysfs_config_len = (uint16_t)remaining; + } + + if (config_len != sysfs_config_len) { + usbi_warn(ctx, "config length mismatch wTotalLength %u real %u", + config_len, sysfs_config_len); + config_len = sysfs_config_len; + } + } else { + /* + * In usbfs the config descriptors are wTotalLength bytes apart, + * with any short reads from the device appearing as holes in the file. + */ + if (config_len > remaining) { + usbi_warn(ctx, "short descriptor read %zu/%u", remaining, config_len); + config_len = (uint16_t)remaining; + } + } + + if (config_desc->bConfigurationValue == 0) + usbi_warn(ctx, "device has configuration 0"); + + priv->config_descriptors[idx].desc = config_desc; + priv->config_descriptors[idx].actual_len = config_len; + + buffer += config_len; + remaining -= config_len; + } + + return LIBUSB_SUCCESS; +} + +static int op_get_config_descriptor_by_value(struct libusb_device *dev, + uint8_t value, void **buffer) +{ + struct linux_device_priv *priv = usbi_get_device_priv(dev); + struct config_descriptor *config; + uint8_t idx; + + for (idx = 0; idx < dev->device_descriptor.bNumConfigurations; idx++) { + config = &priv->config_descriptors[idx]; + if (config->desc->bConfigurationValue == value) { + *buffer = config->desc; + return (int)config->actual_len; + } + } + + return LIBUSB_ERROR_NOT_FOUND; +} + +static int op_get_active_config_descriptor(struct libusb_device *dev, + void *buffer, size_t len) +{ + struct linux_device_priv *priv = usbi_get_device_priv(dev); + void *config_desc; + int active_config; + int r; + + if (priv->sysfs_dir) { + r = sysfs_get_active_config(dev, &active_config); + if (r < 0) + return r; + } else { + /* Use cached bConfigurationValue */ + active_config = priv->active_config; + } + + if (active_config == -1) { + usbi_err(DEVICE_CTX(dev), "device unconfigured"); + return LIBUSB_ERROR_NOT_FOUND; + } + + r = op_get_config_descriptor_by_value(dev, (uint8_t)active_config, &config_desc); + if (r < 0) + return r; + + len = MIN(len, (size_t)r); + memcpy(buffer, config_desc, len); + return len; +} + +static int op_get_config_descriptor(struct libusb_device *dev, + uint8_t config_index, void *buffer, size_t len) +{ + struct linux_device_priv *priv = usbi_get_device_priv(dev); + struct config_descriptor *config; + + if (config_index >= dev->device_descriptor.bNumConfigurations) + return LIBUSB_ERROR_NOT_FOUND; + + config = &priv->config_descriptors[config_index]; + len = MIN(len, config->actual_len); + memcpy(buffer, config->desc, len); + return len; +} + +/* send a control message to retrieve active configuration */ +static int usbfs_get_active_config(struct libusb_device *dev, int fd) +{ + struct linux_device_priv *priv = usbi_get_device_priv(dev); + uint8_t active_config = 0; + int r; + + struct usbfs_ctrltransfer ctrl = { + .bmRequestType = LIBUSB_ENDPOINT_IN, + .bRequest = LIBUSB_REQUEST_GET_CONFIGURATION, + .wValue = 0, + .wIndex = 0, + .wLength = 1, + .timeout = 1000, + .data = &active_config + }; + + r = ioctl(fd, IOCTL_USBFS_CONTROL, &ctrl); + if (r < 0) { + if (errno == ENODEV) + return LIBUSB_ERROR_NO_DEVICE; + + /* we hit this error path frequently with buggy devices :( */ + usbi_warn(DEVICE_CTX(dev), "get configuration failed, errno=%d", errno); + + /* assume the current configuration is the first one if we have + * the configuration descriptors, otherwise treat the device + * as unconfigured. */ + if (priv->config_descriptors) + priv->active_config = (int)priv->config_descriptors[0].desc->bConfigurationValue; + else + priv->active_config = -1; + } else if (active_config == 0) { + if (dev_has_config0(dev)) { + /* some buggy devices have a configuration 0, but we're + * reaching into the corner of a corner case here. */ + priv->active_config = 0; + } else { + priv->active_config = -1; + } + } else { + priv->active_config = (int)active_config; + } + + return LIBUSB_SUCCESS; +} + +static enum libusb_speed usbfs_get_speed(struct libusb_context *ctx, int fd) +{ + int r; + + r = ioctl(fd, IOCTL_USBFS_GET_SPEED, NULL); + switch (r) { + case USBFS_SPEED_UNKNOWN: return LIBUSB_SPEED_UNKNOWN; + case USBFS_SPEED_LOW: return LIBUSB_SPEED_LOW; + case USBFS_SPEED_FULL: return LIBUSB_SPEED_FULL; + case USBFS_SPEED_HIGH: return LIBUSB_SPEED_HIGH; + case USBFS_SPEED_WIRELESS: return LIBUSB_SPEED_HIGH; + case USBFS_SPEED_SUPER: return LIBUSB_SPEED_SUPER; + case USBFS_SPEED_SUPER_PLUS: return LIBUSB_SPEED_SUPER_PLUS; + default: + usbi_warn(ctx, "Error getting device speed: %d", r); + } + + return LIBUSB_SPEED_UNKNOWN; +} + +static int initialize_device(struct libusb_device *dev, uint8_t busnum, + uint8_t devaddr, const char *sysfs_dir, int wrapped_fd) +{ + struct linux_device_priv *priv = usbi_get_device_priv(dev); + struct libusb_context *ctx = DEVICE_CTX(dev); + size_t alloc_len; + int fd, speed, r; + ssize_t nb; + + dev->bus_number = busnum; + dev->device_address = devaddr; + + if (sysfs_dir) { + priv->sysfs_dir = strdup(sysfs_dir); + if (!priv->sysfs_dir) + return LIBUSB_ERROR_NO_MEM; + + /* Note speed can contain 1.5, in this case read_sysfs_attr() + will stop parsing at the '.' and return 1 */ + if (read_sysfs_attr(ctx, sysfs_dir, "speed", INT_MAX, &speed) == 0) { + switch (speed) { + case 1: dev->speed = LIBUSB_SPEED_LOW; break; + case 12: dev->speed = LIBUSB_SPEED_FULL; break; + case 480: dev->speed = LIBUSB_SPEED_HIGH; break; + case 5000: dev->speed = LIBUSB_SPEED_SUPER; break; + case 10000: dev->speed = LIBUSB_SPEED_SUPER_PLUS; break; + default: + usbi_warn(ctx, "unknown device speed: %d Mbps", speed); + } + } + } else if (wrapped_fd >= 0) { + dev->speed = usbfs_get_speed(ctx, wrapped_fd); + } + + /* cache descriptors in memory */ + if (sysfs_dir) { + fd = open_sysfs_attr(ctx, sysfs_dir, "descriptors"); + } else if (wrapped_fd < 0) { + fd = get_usbfs_fd(dev, O_RDONLY, 0); + } else { + fd = wrapped_fd; + r = lseek(fd, 0, SEEK_SET); + if (r < 0) { + usbi_err(ctx, "lseek failed, errno=%d", errno); + return LIBUSB_ERROR_IO; + } + } + if (fd < 0) + return fd; + + alloc_len = 0; + do { + const size_t desc_read_length = 256; + uint8_t *read_ptr; + + alloc_len += desc_read_length; + priv->descriptors = usbi_reallocf(priv->descriptors, alloc_len); + if (!priv->descriptors) { + if (fd != wrapped_fd) + close(fd); + return LIBUSB_ERROR_NO_MEM; + } + read_ptr = (uint8_t *)priv->descriptors + priv->descriptors_len; + /* usbfs has holes in the file */ + if (!sysfs_dir) + memset(read_ptr, 0, desc_read_length); + nb = read(fd, read_ptr, desc_read_length); + if (nb < 0) { + usbi_err(ctx, "read descriptor failed, errno=%d", errno); + if (fd != wrapped_fd) + close(fd); + return LIBUSB_ERROR_IO; + } + priv->descriptors_len += (size_t)nb; + } while (priv->descriptors_len == alloc_len); + + if (fd != wrapped_fd) + close(fd); + + if (priv->descriptors_len < LIBUSB_DT_DEVICE_SIZE) { + usbi_err(ctx, "short descriptor read (%zu)", priv->descriptors_len); + return LIBUSB_ERROR_IO; + } + + r = parse_config_descriptors(dev); + if (r < 0) + return r; + + memcpy(&dev->device_descriptor, priv->descriptors, LIBUSB_DT_DEVICE_SIZE); + + if (sysfs_dir) { + /* sysfs descriptors are in bus-endian format */ + usbi_localize_device_descriptor(&dev->device_descriptor); + return LIBUSB_SUCCESS; + } + + /* cache active config */ + if (wrapped_fd < 0) + fd = get_usbfs_fd(dev, O_RDWR, 1); + else + fd = wrapped_fd; + if (fd < 0) { + /* cannot send a control message to determine the active + * config. just assume the first one is active. */ + usbi_warn(ctx, "Missing rw usbfs access; cannot determine " + "active configuration descriptor"); + if (priv->config_descriptors) + priv->active_config = (int)priv->config_descriptors[0].desc->bConfigurationValue; + else + priv->active_config = -1; /* No config dt */ + + return LIBUSB_SUCCESS; + } + + r = usbfs_get_active_config(dev, fd); + if (fd != wrapped_fd) + close(fd); + + return r; +} + +static int linux_get_parent_info(struct libusb_device *dev, const char *sysfs_dir) +{ + struct libusb_context *ctx = DEVICE_CTX(dev); + struct libusb_device *it; + char *parent_sysfs_dir, *tmp; + int ret, add_parent = 1; + + /* XXX -- can we figure out the topology when using usbfs? */ + if (!sysfs_dir || !strncmp(sysfs_dir, "usb", 3)) { + /* either using usbfs or finding the parent of a root hub */ + return LIBUSB_SUCCESS; + } + + parent_sysfs_dir = strdup(sysfs_dir); + if (!parent_sysfs_dir) + return LIBUSB_ERROR_NO_MEM; + + if ((tmp = strrchr(parent_sysfs_dir, '.')) || + (tmp = strrchr(parent_sysfs_dir, '-'))) { + dev->port_number = atoi(tmp + 1); + *tmp = '\0'; + } else { + usbi_warn(ctx, "Can not parse sysfs_dir: %s, no parent info", + parent_sysfs_dir); + free(parent_sysfs_dir); + return LIBUSB_SUCCESS; + } + + /* is the parent a root hub? */ + if (!strchr(parent_sysfs_dir, '-')) { + tmp = parent_sysfs_dir; + ret = asprintf(&parent_sysfs_dir, "usb%s", tmp); + free(tmp); + if (ret < 0) + return LIBUSB_ERROR_NO_MEM; + } + +retry: + /* find the parent in the context */ + usbi_mutex_lock(&ctx->usb_devs_lock); + for_each_device(ctx, it) { + struct linux_device_priv *priv = usbi_get_device_priv(it); + + if (priv->sysfs_dir) { + if (!strcmp(priv->sysfs_dir, parent_sysfs_dir)) { + dev->parent_dev = libusb_ref_device(it); + break; + } + } + } + usbi_mutex_unlock(&ctx->usb_devs_lock); + + if (!dev->parent_dev && add_parent) { + usbi_dbg(ctx, "parent_dev %s not enumerated yet, enumerating now", + parent_sysfs_dir); + sysfs_scan_device(ctx, parent_sysfs_dir); + add_parent = 0; + goto retry; + } + + usbi_dbg(ctx, "dev %p (%s) has parent %p (%s) port %u", dev, sysfs_dir, + dev->parent_dev, parent_sysfs_dir, dev->port_number); + + free(parent_sysfs_dir); + + return LIBUSB_SUCCESS; +} + +int linux_enumerate_device(struct libusb_context *ctx, + uint8_t busnum, uint8_t devaddr, const char *sysfs_dir) +{ + unsigned long session_id; + struct libusb_device *dev; + int r; + + /* FIXME: session ID is not guaranteed unique as addresses can wrap and + * will be reused. instead we should add a simple sysfs attribute with + * a session ID. */ + session_id = busnum << 8 | devaddr; + usbi_dbg(ctx, "busnum %u devaddr %u session_id %lu", busnum, devaddr, session_id); + + dev = usbi_get_device_by_session_id(ctx, session_id); + if (dev) { + /* device already exists in the context */ + usbi_dbg(ctx, "session_id %lu already exists", session_id); + libusb_unref_device(dev); + return LIBUSB_SUCCESS; + } + + usbi_dbg(ctx, "allocating new device for %u/%u (session %lu)", + busnum, devaddr, session_id); + dev = usbi_alloc_device(ctx, session_id); + if (!dev) + return LIBUSB_ERROR_NO_MEM; + + r = initialize_device(dev, busnum, devaddr, sysfs_dir, -1); + if (r < 0) + goto out; + r = usbi_sanitize_device(dev); + if (r < 0) + goto out; + + r = linux_get_parent_info(dev, sysfs_dir); + if (r < 0) + goto out; +out: + if (r < 0) + libusb_unref_device(dev); + else + usbi_connect_device(dev); + + return r; +} + +void linux_hotplug_enumerate(uint8_t busnum, uint8_t devaddr, const char *sys_name) +{ + struct libusb_context *ctx; + + usbi_mutex_static_lock(&active_contexts_lock); + for_each_context(ctx) { + linux_enumerate_device(ctx, busnum, devaddr, sys_name); + } + usbi_mutex_static_unlock(&active_contexts_lock); +} + +void linux_device_disconnected(uint8_t busnum, uint8_t devaddr) +{ + struct libusb_context *ctx; + struct libusb_device *dev; + unsigned long session_id = busnum << 8 | devaddr; + + usbi_mutex_static_lock(&active_contexts_lock); + for_each_context(ctx) { + dev = usbi_get_device_by_session_id(ctx, session_id); + if (dev) { + usbi_disconnect_device(dev); + libusb_unref_device(dev); + } else { + usbi_dbg(ctx, "device not found for session %lx", session_id); + } + } + usbi_mutex_static_unlock(&active_contexts_lock); +} + +#if !defined(HAVE_LIBUDEV) +static int parse_u8(const char *str, uint8_t *val_p) +{ + char *endptr; + long num; + + errno = 0; + num = strtol(str, &endptr, 10); + if (num < 0 || num > UINT8_MAX || errno) + return 0; + if (endptr == str || *endptr != '\0') + return 0; + + *val_p = (uint8_t)num; + return 1; +} + +/* open a bus directory and adds all discovered devices to the context */ +static int usbfs_scan_busdir(struct libusb_context *ctx, uint8_t busnum) +{ + DIR *dir; + char dirpath[20]; + struct dirent *entry; + int r = LIBUSB_ERROR_IO; + + sprintf(dirpath, USB_DEVTMPFS_PATH "/%03u", busnum); + usbi_dbg(ctx, "%s", dirpath); + dir = opendir(dirpath); + if (!dir) { + usbi_err(ctx, "opendir '%s' failed, errno=%d", dirpath, errno); + /* FIXME: should handle valid race conditions like hub unplugged + * during directory iteration - this is not an error */ + return r; + } + + while ((entry = readdir(dir))) { + uint8_t devaddr; + + if (entry->d_name[0] == '.') + continue; + + if (!parse_u8(entry->d_name, &devaddr)) { + usbi_dbg(ctx, "unknown dir entry %s", entry->d_name); + continue; + } + + if (linux_enumerate_device(ctx, busnum, devaddr, NULL)) { + usbi_dbg(ctx, "failed to enumerate dir entry %s", entry->d_name); + continue; + } + + r = 0; + } + + closedir(dir); + return r; +} + +static int usbfs_get_device_list(struct libusb_context *ctx) +{ + struct dirent *entry; + DIR *buses; + uint8_t busnum, devaddr; + int r = 0; + + if (usbdev_names) + buses = opendir(USBDEV_PATH); + else + buses = opendir(USB_DEVTMPFS_PATH); + + if (!buses) { + usbi_err(ctx, "opendir buses failed, errno=%d", errno); + return LIBUSB_ERROR_IO; + } + + while ((entry = readdir(buses))) { + if (entry->d_name[0] == '.') + continue; + + if (usbdev_names) { + if (!is_usbdev_entry(entry->d_name, &busnum, &devaddr)) + continue; + + r = linux_enumerate_device(ctx, busnum, devaddr, NULL); + if (r < 0) { + usbi_dbg(ctx, "failed to enumerate dir entry %s", entry->d_name); + continue; + } + } else { + if (!parse_u8(entry->d_name, &busnum)) { + usbi_dbg(ctx, "unknown dir entry %s", entry->d_name); + continue; + } + + r = usbfs_scan_busdir(ctx, busnum); + if (r < 0) + break; + } + } + + closedir(buses); + return r; + +} + +static int sysfs_get_device_list(struct libusb_context *ctx) +{ + DIR *devices = opendir(SYSFS_DEVICE_PATH); + struct dirent *entry; + int num_devices = 0; + int num_enumerated = 0; + + if (!devices) { + usbi_err(ctx, "opendir devices failed, errno=%d", errno); + return LIBUSB_ERROR_IO; + } + + while ((entry = readdir(devices))) { + if ((!isdigit(entry->d_name[0]) && strncmp(entry->d_name, "usb", 3)) + || strchr(entry->d_name, ':')) + continue; + + num_devices++; + + if (sysfs_scan_device(ctx, entry->d_name)) { + usbi_dbg(ctx, "failed to enumerate dir entry %s", entry->d_name); + continue; + } + + num_enumerated++; + } + + closedir(devices); + + /* successful if at least one device was enumerated or no devices were found */ + if (num_enumerated || !num_devices) + return LIBUSB_SUCCESS; + else + return LIBUSB_ERROR_IO; +} + +static int linux_default_scan_devices(struct libusb_context *ctx) +{ + /* we can retrieve device list and descriptors from sysfs or usbfs. + * sysfs is preferable, because if we use usbfs we end up resuming + * any autosuspended USB devices. however, sysfs is not available + * everywhere, so we need a usbfs fallback too. + */ + if (sysfs_available) + return sysfs_get_device_list(ctx); + else + return usbfs_get_device_list(ctx); +} +#endif + +static int initialize_handle(struct libusb_device_handle *handle, int fd) +{ + struct linux_device_handle_priv *hpriv = usbi_get_device_handle_priv(handle); + int r; + + hpriv->fd = fd; + + r = ioctl(fd, IOCTL_USBFS_GET_CAPABILITIES, &hpriv->caps); + if (r < 0) { + if (errno == ENOTTY) + usbi_dbg(HANDLE_CTX(handle), "getcap not available"); + else + usbi_err(HANDLE_CTX(handle), "getcap failed, errno=%d", errno); + hpriv->caps = USBFS_CAP_BULK_CONTINUATION; + } + + return usbi_add_event_source(HANDLE_CTX(handle), hpriv->fd, POLLOUT); +} + +static int op_wrap_sys_device(struct libusb_context *ctx, + struct libusb_device_handle *handle, intptr_t sys_dev) +{ + struct linux_device_handle_priv *hpriv = usbi_get_device_handle_priv(handle); + int fd = (int)sys_dev; + uint8_t busnum, devaddr; + struct usbfs_connectinfo ci; + struct libusb_device *dev; + int r; + + r = linux_get_device_address(ctx, 1, &busnum, &devaddr, NULL, NULL, fd); + if (r < 0) { + r = ioctl(fd, IOCTL_USBFS_CONNECTINFO, &ci); + if (r < 0) { + usbi_err(ctx, "connectinfo failed, errno=%d", errno); + return LIBUSB_ERROR_IO; + } + /* There is no ioctl to get the bus number. We choose 0 here + * as linux starts numbering buses from 1. */ + busnum = 0; + devaddr = ci.devnum; + } + + /* Session id is unused as we do not add the device to the list of + * connected devices. */ + usbi_dbg(ctx, "allocating new device for fd %d", fd); + dev = usbi_alloc_device(ctx, 0); + if (!dev) + return LIBUSB_ERROR_NO_MEM; + + r = initialize_device(dev, busnum, devaddr, NULL, fd); + if (r < 0) + goto out; + r = usbi_sanitize_device(dev); + if (r < 0) + goto out; + /* Consider the device as connected, but do not add it to the managed + * device list. */ + usbi_atomic_store(&dev->attached, 1); + handle->dev = dev; + + r = initialize_handle(handle, fd); + hpriv->fd_keep = 1; + +out: + if (r < 0) + libusb_unref_device(dev); + return r; +} + +static int op_open(struct libusb_device_handle *handle) +{ + int fd, r; + + fd = get_usbfs_fd(handle->dev, O_RDWR, 0); + if (fd < 0) { + if (fd == LIBUSB_ERROR_NO_DEVICE) { + /* device will still be marked as attached if hotplug monitor thread + * hasn't processed remove event yet */ + usbi_mutex_static_lock(&linux_hotplug_lock); + if (usbi_atomic_load(&handle->dev->attached)) { + usbi_dbg(HANDLE_CTX(handle), "open failed with no device, but device still attached"); + linux_device_disconnected(handle->dev->bus_number, + handle->dev->device_address); + } + usbi_mutex_static_unlock(&linux_hotplug_lock); + } + return fd; + } + + r = initialize_handle(handle, fd); + if (r < 0) + close(fd); + + return r; +} + +static void op_close(struct libusb_device_handle *dev_handle) +{ + struct linux_device_handle_priv *hpriv = usbi_get_device_handle_priv(dev_handle); + + /* fd may have already been removed by POLLERR condition in op_handle_events() */ + if (!hpriv->fd_removed) + usbi_remove_event_source(HANDLE_CTX(dev_handle), hpriv->fd); + if (!hpriv->fd_keep) + close(hpriv->fd); +} + +static int op_get_configuration(struct libusb_device_handle *handle, + uint8_t *config) +{ + struct linux_device_priv *priv = usbi_get_device_priv(handle->dev); + int active_config; + int r; + + if (priv->sysfs_dir) { + r = sysfs_get_active_config(handle->dev, &active_config); + } else { + struct linux_device_handle_priv *hpriv = usbi_get_device_handle_priv(handle); + + r = usbfs_get_active_config(handle->dev, hpriv->fd); + if (r == LIBUSB_SUCCESS) + active_config = priv->active_config; + } + if (r < 0) + return r; + + if (active_config == -1) { + usbi_warn(HANDLE_CTX(handle), "device unconfigured"); + active_config = 0; + } + + *config = (uint8_t)active_config; + + return 0; +} + +static int op_set_configuration(struct libusb_device_handle *handle, int config) +{ + struct linux_device_priv *priv = usbi_get_device_priv(handle->dev); + struct linux_device_handle_priv *hpriv = usbi_get_device_handle_priv(handle); + int fd = hpriv->fd; + int r = ioctl(fd, IOCTL_USBFS_SETCONFIGURATION, &config); + + if (r < 0) { + if (errno == EINVAL) + return LIBUSB_ERROR_NOT_FOUND; + else if (errno == EBUSY) + return LIBUSB_ERROR_BUSY; + else if (errno == ENODEV) + return LIBUSB_ERROR_NO_DEVICE; + + usbi_err(HANDLE_CTX(handle), "set configuration failed, errno=%d", errno); + return LIBUSB_ERROR_OTHER; + } + + /* if necessary, update our cached active config descriptor */ + if (!priv->sysfs_dir) { + if (config == 0 && !dev_has_config0(handle->dev)) + config = -1; + + priv->active_config = config; + } + + return LIBUSB_SUCCESS; +} + +static int claim_interface(struct libusb_device_handle *handle, unsigned int iface) +{ + struct linux_device_handle_priv *hpriv = usbi_get_device_handle_priv(handle); + int fd = hpriv->fd; + int r = ioctl(fd, IOCTL_USBFS_CLAIMINTERFACE, &iface); + + if (r < 0) { + if (errno == ENOENT) + return LIBUSB_ERROR_NOT_FOUND; + else if (errno == EBUSY) + return LIBUSB_ERROR_BUSY; + else if (errno == ENODEV) + return LIBUSB_ERROR_NO_DEVICE; + + usbi_err(HANDLE_CTX(handle), "claim interface failed, errno=%d", errno); + return LIBUSB_ERROR_OTHER; + } + return 0; +} + +static int release_interface(struct libusb_device_handle *handle, unsigned int iface) +{ + struct linux_device_handle_priv *hpriv = usbi_get_device_handle_priv(handle); + int fd = hpriv->fd; + int r = ioctl(fd, IOCTL_USBFS_RELEASEINTERFACE, &iface); + + if (r < 0) { + if (errno == ENODEV) + return LIBUSB_ERROR_NO_DEVICE; + + usbi_err(HANDLE_CTX(handle), "release interface failed, errno=%d", errno); + return LIBUSB_ERROR_OTHER; + } + return 0; +} + +static int op_set_interface(struct libusb_device_handle *handle, uint8_t interface, + uint8_t altsetting) +{ + struct linux_device_handle_priv *hpriv = usbi_get_device_handle_priv(handle); + int fd = hpriv->fd; + struct usbfs_setinterface setintf; + int r; + + setintf.interface = interface; + setintf.altsetting = altsetting; + r = ioctl(fd, IOCTL_USBFS_SETINTERFACE, &setintf); + if (r < 0) { + if (errno == EINVAL) + return LIBUSB_ERROR_NOT_FOUND; + else if (errno == ENODEV) + return LIBUSB_ERROR_NO_DEVICE; + + usbi_err(HANDLE_CTX(handle), "set interface failed, errno=%d", errno); + return LIBUSB_ERROR_OTHER; + } + + return 0; +} + +static int op_clear_halt(struct libusb_device_handle *handle, + unsigned char endpoint) +{ + struct linux_device_handle_priv *hpriv = usbi_get_device_handle_priv(handle); + int fd = hpriv->fd; + unsigned int _endpoint = endpoint; + int r = ioctl(fd, IOCTL_USBFS_CLEAR_HALT, &_endpoint); + + if (r < 0) { + if (errno == ENOENT) + return LIBUSB_ERROR_NOT_FOUND; + else if (errno == ENODEV) + return LIBUSB_ERROR_NO_DEVICE; + + usbi_err(HANDLE_CTX(handle), "clear halt failed, errno=%d", errno); + return LIBUSB_ERROR_OTHER; + } + + return 0; +} + +static int op_reset_device(struct libusb_device_handle *handle) +{ + struct linux_device_handle_priv *hpriv = usbi_get_device_handle_priv(handle); + int fd = hpriv->fd; + int r, ret = 0; + uint8_t i; + + /* Doing a device reset will cause the usbfs driver to get unbound + * from any interfaces it is bound to. By voluntarily unbinding + * the usbfs driver ourself, we stop the kernel from rebinding + * the interface after reset (which would end up with the interface + * getting bound to the in kernel driver if any). */ + for (i = 0; i < USB_MAXINTERFACES; i++) { + if (handle->claimed_interfaces & (1UL << i)) + release_interface(handle, i); + } + + usbi_mutex_lock(&handle->lock); + r = ioctl(fd, IOCTL_USBFS_RESET, NULL); + if (r < 0) { + if (errno == ENODEV) { + ret = LIBUSB_ERROR_NOT_FOUND; + goto out; + } + + usbi_err(HANDLE_CTX(handle), "reset failed, errno=%d", errno); + ret = LIBUSB_ERROR_OTHER; + goto out; + } + + /* And re-claim any interfaces which were claimed before the reset */ + for (i = 0; i < USB_MAXINTERFACES; i++) { + if (!(handle->claimed_interfaces & (1UL << i))) + continue; + /* + * A driver may have completed modprobing during + * IOCTL_USBFS_RESET, and bound itself as soon as + * IOCTL_USBFS_RESET released the device lock + */ + r = detach_kernel_driver_and_claim(handle, i); + if (r) { + usbi_warn(HANDLE_CTX(handle), "failed to re-claim interface %u after reset: %s", + i, libusb_error_name(r)); + handle->claimed_interfaces &= ~(1UL << i); + ret = LIBUSB_ERROR_NOT_FOUND; + } + } +out: + usbi_mutex_unlock(&handle->lock); + return ret; +} + +static int do_streams_ioctl(struct libusb_device_handle *handle, long req, + uint32_t num_streams, unsigned char *endpoints, int num_endpoints) +{ + struct linux_device_handle_priv *hpriv = usbi_get_device_handle_priv(handle); + int r, fd = hpriv->fd; + struct usbfs_streams *streams; + + if (num_endpoints > 30) /* Max 15 in + 15 out eps */ + return LIBUSB_ERROR_INVALID_PARAM; + + streams = malloc(sizeof(*streams) + num_endpoints); + if (!streams) + return LIBUSB_ERROR_NO_MEM; + + streams->num_streams = num_streams; + streams->num_eps = num_endpoints; + memcpy(streams->eps, endpoints, num_endpoints); + + r = ioctl(fd, req, streams); + + free(streams); + + if (r < 0) { + if (errno == ENOTTY) + return LIBUSB_ERROR_NOT_SUPPORTED; + else if (errno == EINVAL) + return LIBUSB_ERROR_INVALID_PARAM; + else if (errno == ENODEV) + return LIBUSB_ERROR_NO_DEVICE; + + usbi_err(HANDLE_CTX(handle), "streams-ioctl failed, errno=%d", errno); + return LIBUSB_ERROR_OTHER; + } + return r; +} + +static int op_alloc_streams(struct libusb_device_handle *handle, + uint32_t num_streams, unsigned char *endpoints, int num_endpoints) +{ + return do_streams_ioctl(handle, IOCTL_USBFS_ALLOC_STREAMS, + num_streams, endpoints, num_endpoints); +} + +static int op_free_streams(struct libusb_device_handle *handle, + unsigned char *endpoints, int num_endpoints) +{ + return do_streams_ioctl(handle, IOCTL_USBFS_FREE_STREAMS, 0, + endpoints, num_endpoints); +} + +static void *op_dev_mem_alloc(struct libusb_device_handle *handle, size_t len) +{ + struct linux_device_handle_priv *hpriv = usbi_get_device_handle_priv(handle); + void *buffer; + + buffer = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_SHARED, hpriv->fd, 0); + if (buffer == MAP_FAILED) { + usbi_err(HANDLE_CTX(handle), "alloc dev mem failed, errno=%d", errno); + return NULL; + } + return buffer; +} + +static int op_dev_mem_free(struct libusb_device_handle *handle, void *buffer, + size_t len) +{ + if (munmap(buffer, len) != 0) { + usbi_err(HANDLE_CTX(handle), "free dev mem failed, errno=%d", errno); + return LIBUSB_ERROR_OTHER; + } else { + return LIBUSB_SUCCESS; + } +} + +static int op_kernel_driver_active(struct libusb_device_handle *handle, + uint8_t interface) +{ + struct linux_device_handle_priv *hpriv = usbi_get_device_handle_priv(handle); + int fd = hpriv->fd; + struct usbfs_getdriver getdrv; + int r; + + getdrv.interface = interface; + r = ioctl(fd, IOCTL_USBFS_GETDRIVER, &getdrv); + if (r < 0) { + if (errno == ENODATA) + return 0; + else if (errno == ENODEV) + return LIBUSB_ERROR_NO_DEVICE; + + usbi_err(HANDLE_CTX(handle), "get driver failed, errno=%d", errno); + return LIBUSB_ERROR_OTHER; + } + + return strcmp(getdrv.driver, "usbfs") != 0; +} + +static int op_detach_kernel_driver(struct libusb_device_handle *handle, + uint8_t interface) +{ + struct linux_device_handle_priv *hpriv = usbi_get_device_handle_priv(handle); + int fd = hpriv->fd; + struct usbfs_ioctl command; + struct usbfs_getdriver getdrv; + int r; + + command.ifno = interface; + command.ioctl_code = IOCTL_USBFS_DISCONNECT; + command.data = NULL; + + getdrv.interface = interface; + r = ioctl(fd, IOCTL_USBFS_GETDRIVER, &getdrv); + if (r == 0 && !strcmp(getdrv.driver, "usbfs")) + return LIBUSB_ERROR_NOT_FOUND; + + r = ioctl(fd, IOCTL_USBFS_IOCTL, &command); + if (r < 0) { + if (errno == ENODATA) + return LIBUSB_ERROR_NOT_FOUND; + else if (errno == EINVAL) + return LIBUSB_ERROR_INVALID_PARAM; + else if (errno == ENODEV) + return LIBUSB_ERROR_NO_DEVICE; + + usbi_err(HANDLE_CTX(handle), "detach failed, errno=%d", errno); + return LIBUSB_ERROR_OTHER; + } + + return 0; +} + +static int op_attach_kernel_driver(struct libusb_device_handle *handle, + uint8_t interface) +{ + struct linux_device_handle_priv *hpriv = usbi_get_device_handle_priv(handle); + int fd = hpriv->fd; + struct usbfs_ioctl command; + int r; + + command.ifno = interface; + command.ioctl_code = IOCTL_USBFS_CONNECT; + command.data = NULL; + + r = ioctl(fd, IOCTL_USBFS_IOCTL, &command); + if (r < 0) { + if (errno == ENODATA) + return LIBUSB_ERROR_NOT_FOUND; + else if (errno == EINVAL) + return LIBUSB_ERROR_INVALID_PARAM; + else if (errno == ENODEV) + return LIBUSB_ERROR_NO_DEVICE; + else if (errno == EBUSY) + return LIBUSB_ERROR_BUSY; + + usbi_err(HANDLE_CTX(handle), "attach failed, errno=%d", errno); + return LIBUSB_ERROR_OTHER; + } else if (r == 0) { + return LIBUSB_ERROR_NOT_FOUND; + } + + return 0; +} + +static int detach_kernel_driver_and_claim(struct libusb_device_handle *handle, + uint8_t interface) +{ + struct linux_device_handle_priv *hpriv = usbi_get_device_handle_priv(handle); + struct usbfs_disconnect_claim dc; + int r, fd = hpriv->fd; + + dc.interface = interface; + strcpy(dc.driver, "usbfs"); + dc.flags = USBFS_DISCONNECT_CLAIM_EXCEPT_DRIVER; + r = ioctl(fd, IOCTL_USBFS_DISCONNECT_CLAIM, &dc); + if (r == 0) + return 0; + switch (errno) { + case ENOTTY: + break; + case EBUSY: + return LIBUSB_ERROR_BUSY; + case EINVAL: + return LIBUSB_ERROR_INVALID_PARAM; + case ENODEV: + return LIBUSB_ERROR_NO_DEVICE; + default: + usbi_err(HANDLE_CTX(handle), "disconnect-and-claim failed, errno=%d", errno); + return LIBUSB_ERROR_OTHER; + } + + /* Fallback code for kernels which don't support the + disconnect-and-claim ioctl */ + r = op_detach_kernel_driver(handle, interface); + if (r != 0 && r != LIBUSB_ERROR_NOT_FOUND) + return r; + + return claim_interface(handle, interface); +} + +static int op_claim_interface(struct libusb_device_handle *handle, uint8_t interface) +{ + if (handle->auto_detach_kernel_driver) + return detach_kernel_driver_and_claim(handle, interface); + else + return claim_interface(handle, interface); +} + +static int op_release_interface(struct libusb_device_handle *handle, uint8_t interface) +{ + int r; + + r = release_interface(handle, interface); + if (r) + return r; + + if (handle->auto_detach_kernel_driver) + op_attach_kernel_driver(handle, interface); + + return 0; +} + +static void op_destroy_device(struct libusb_device *dev) +{ + struct linux_device_priv *priv = usbi_get_device_priv(dev); + + free(priv->config_descriptors); + free(priv->descriptors); + free(priv->sysfs_dir); +} + +/* URBs are discarded in reverse order of submission to avoid races. */ +static int discard_urbs(struct usbi_transfer *itransfer, int first, int last_plus_one) +{ + struct libusb_transfer *transfer = + USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer); + struct linux_transfer_priv *tpriv = usbi_get_transfer_priv(itransfer); + struct linux_device_handle_priv *hpriv = + usbi_get_device_handle_priv(transfer->dev_handle); + int i, ret = 0; + struct usbfs_urb *urb; + + for (i = last_plus_one - 1; i >= first; i--) { + if (transfer->type == LIBUSB_TRANSFER_TYPE_ISOCHRONOUS) + urb = tpriv->iso_urbs[i]; + else + urb = &tpriv->urbs[i]; + + if (ioctl(hpriv->fd, IOCTL_USBFS_DISCARDURB, urb) == 0) + continue; + + if (errno == EINVAL) { + usbi_dbg(TRANSFER_CTX(transfer), "URB not found --> assuming ready to be reaped"); + if (i == (last_plus_one - 1)) + ret = LIBUSB_ERROR_NOT_FOUND; + } else if (errno == ENODEV) { + usbi_dbg(TRANSFER_CTX(transfer), "Device not found for URB --> assuming ready to be reaped"); + ret = LIBUSB_ERROR_NO_DEVICE; + } else { + usbi_warn(TRANSFER_CTX(transfer), "unrecognised discard errno %d", errno); + ret = LIBUSB_ERROR_OTHER; + } + } + return ret; +} + +static void free_iso_urbs(struct linux_transfer_priv *tpriv) +{ + int i; + + for (i = 0; i < tpriv->num_urbs; i++) { + struct usbfs_urb *urb = tpriv->iso_urbs[i]; + + if (!urb) + break; + free(urb); + } + + free(tpriv->iso_urbs); + tpriv->iso_urbs = NULL; +} + +static int submit_bulk_transfer(struct usbi_transfer *itransfer) +{ + struct libusb_transfer *transfer = + USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer); + struct linux_transfer_priv *tpriv = usbi_get_transfer_priv(itransfer); + struct linux_device_handle_priv *hpriv = + usbi_get_device_handle_priv(transfer->dev_handle); + struct usbfs_urb *urbs; + int is_out = IS_XFEROUT(transfer); + int bulk_buffer_len, use_bulk_continuation; + int num_urbs; + int last_urb_partial = 0; + int r; + int i; + + /* + * Older versions of usbfs place a 16kb limit on bulk URBs. We work + * around this by splitting large transfers into 16k blocks, and then + * submit all urbs at once. it would be simpler to submit one urb at + * a time, but there is a big performance gain doing it this way. + * + * Newer versions lift the 16k limit (USBFS_CAP_NO_PACKET_SIZE_LIM), + * using arbitrary large transfers can still be a bad idea though, as + * the kernel needs to allocate physical contiguous memory for this, + * which may fail for large buffers. + * + * The kernel solves this problem by splitting the transfer into + * blocks itself when the host-controller is scatter-gather capable + * (USBFS_CAP_BULK_SCATTER_GATHER), which most controllers are. + * + * Last, there is the issue of short-transfers when splitting, for + * short split-transfers to work reliable USBFS_CAP_BULK_CONTINUATION + * is needed, but this is not always available. + */ + if (hpriv->caps & USBFS_CAP_BULK_SCATTER_GATHER) { + /* Good! Just submit everything in one go */ + bulk_buffer_len = transfer->length ? transfer->length : 1; + use_bulk_continuation = 0; + } else if (hpriv->caps & USBFS_CAP_BULK_CONTINUATION) { + /* Split the transfers and use bulk-continuation to + avoid issues with short-transfers */ + bulk_buffer_len = MAX_BULK_BUFFER_LENGTH; + use_bulk_continuation = 1; + } else if (hpriv->caps & USBFS_CAP_NO_PACKET_SIZE_LIM) { + /* Don't split, assume the kernel can alloc the buffer + (otherwise the submit will fail with -ENOMEM) */ + bulk_buffer_len = transfer->length ? transfer->length : 1; + use_bulk_continuation = 0; + } else { + /* Bad, splitting without bulk-continuation, short transfers + which end before the last urb will not work reliable! */ + /* Note we don't warn here as this is "normal" on kernels < + 2.6.32 and not a problem for most applications */ + bulk_buffer_len = MAX_BULK_BUFFER_LENGTH; + use_bulk_continuation = 0; + } + + num_urbs = transfer->length / bulk_buffer_len; + + if (transfer->length == 0) { + num_urbs = 1; + } else if ((transfer->length % bulk_buffer_len) > 0) { + last_urb_partial = 1; + num_urbs++; + } + usbi_dbg(TRANSFER_CTX(transfer), "need %d urbs for new transfer with length %d", num_urbs, transfer->length); + urbs = calloc(num_urbs, sizeof(*urbs)); + if (!urbs) + return LIBUSB_ERROR_NO_MEM; + tpriv->urbs = urbs; + tpriv->num_urbs = num_urbs; + tpriv->num_retired = 0; + tpriv->reap_action = NORMAL; + tpriv->reap_status = LIBUSB_TRANSFER_COMPLETED; + + for (i = 0; i < num_urbs; i++) { + struct usbfs_urb *urb = &urbs[i]; + + urb->usercontext = itransfer; + switch (transfer->type) { + case LIBUSB_TRANSFER_TYPE_BULK: + urb->type = USBFS_URB_TYPE_BULK; + urb->stream_id = 0; + break; + case LIBUSB_TRANSFER_TYPE_BULK_STREAM: + urb->type = USBFS_URB_TYPE_BULK; + urb->stream_id = itransfer->stream_id; + break; + case LIBUSB_TRANSFER_TYPE_INTERRUPT: + urb->type = USBFS_URB_TYPE_INTERRUPT; + break; + } + urb->endpoint = transfer->endpoint; + urb->buffer = transfer->buffer + (i * bulk_buffer_len); + + /* don't set the short not ok flag for the last URB */ + if (use_bulk_continuation && !is_out && (i < num_urbs - 1)) + urb->flags = USBFS_URB_SHORT_NOT_OK; + + if (i == num_urbs - 1 && last_urb_partial) + urb->buffer_length = transfer->length % bulk_buffer_len; + else if (transfer->length == 0) + urb->buffer_length = 0; + else + urb->buffer_length = bulk_buffer_len; + + if (i > 0 && use_bulk_continuation) + urb->flags |= USBFS_URB_BULK_CONTINUATION; + + /* we have already checked that the flag is supported */ + if (is_out && i == num_urbs - 1 && + (transfer->flags & LIBUSB_TRANSFER_ADD_ZERO_PACKET)) + urb->flags |= USBFS_URB_ZERO_PACKET; + + r = ioctl(hpriv->fd, IOCTL_USBFS_SUBMITURB, urb); + if (r == 0) + continue; + + if (errno == ENODEV) { + r = LIBUSB_ERROR_NO_DEVICE; + } else if (errno == ENOMEM) { + r = LIBUSB_ERROR_NO_MEM; + } else { + usbi_err(TRANSFER_CTX(transfer), "submiturb failed, errno=%d", errno); + r = LIBUSB_ERROR_IO; + } + + /* if the first URB submission fails, we can simply free up and + * return failure immediately. */ + if (i == 0) { + usbi_dbg(TRANSFER_CTX(transfer), "first URB failed, easy peasy"); + free(urbs); + tpriv->urbs = NULL; + return r; + } + + /* if it's not the first URB that failed, the situation is a bit + * tricky. we may need to discard all previous URBs. there are + * complications: + * - discarding is asynchronous - discarded urbs will be reaped + * later. the user must not have freed the transfer when the + * discarded URBs are reaped, otherwise libusb will be using + * freed memory. + * - the earlier URBs may have completed successfully and we do + * not want to throw away any data. + * - this URB failing may be no error; EREMOTEIO means that + * this transfer simply didn't need all the URBs we submitted + * so, we report that the transfer was submitted successfully and + * in case of error we discard all previous URBs. later when + * the final reap completes we can report error to the user, + * or success if an earlier URB was completed successfully. + */ + tpriv->reap_action = errno == EREMOTEIO ? COMPLETED_EARLY : SUBMIT_FAILED; + + /* The URBs we haven't submitted yet we count as already + * retired. */ + tpriv->num_retired += num_urbs - i; + + /* If we completed short then don't try to discard. */ + if (tpriv->reap_action == COMPLETED_EARLY) + return 0; + + discard_urbs(itransfer, 0, i); + + usbi_dbg(TRANSFER_CTX(transfer), "reporting successful submission but waiting for %d " + "discards before reporting error", i); + return 0; + } + + return 0; +} + +static int submit_iso_transfer(struct usbi_transfer *itransfer) +{ + struct libusb_transfer *transfer = + USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer); + struct linux_transfer_priv *tpriv = usbi_get_transfer_priv(itransfer); + struct linux_device_handle_priv *hpriv = + usbi_get_device_handle_priv(transfer->dev_handle); + struct usbfs_urb **urbs; + int num_packets = transfer->num_iso_packets; + int num_packets_remaining; + int i, j; + int num_urbs; + unsigned int packet_len; + unsigned int total_len = 0; + unsigned char *urb_buffer = transfer->buffer; + + if (num_packets < 1) + return LIBUSB_ERROR_INVALID_PARAM; + + /* usbfs places arbitrary limits on iso URBs. this limit has changed + * at least three times, but we attempt to detect this limit during + * init and check it here. if the kernel rejects the request due to + * its size, we return an error indicating such to the user. + */ + for (i = 0; i < num_packets; i++) { + packet_len = transfer->iso_packet_desc[i].length; + + if (packet_len > max_iso_packet_len) { + usbi_warn(TRANSFER_CTX(transfer), + "iso packet length of %u bytes exceeds maximum of %u bytes", + packet_len, max_iso_packet_len); + return LIBUSB_ERROR_INVALID_PARAM; + } + + total_len += packet_len; + } + + if (transfer->length < (int)total_len) + return LIBUSB_ERROR_INVALID_PARAM; + + /* usbfs limits the number of iso packets per URB */ + num_urbs = (num_packets + (MAX_ISO_PACKETS_PER_URB - 1)) / MAX_ISO_PACKETS_PER_URB; + + usbi_dbg(TRANSFER_CTX(transfer), "need %d urbs for new transfer with length %d", num_urbs, transfer->length); + + urbs = calloc(num_urbs, sizeof(*urbs)); + if (!urbs) + return LIBUSB_ERROR_NO_MEM; + + tpriv->iso_urbs = urbs; + tpriv->num_urbs = num_urbs; + tpriv->num_retired = 0; + tpriv->reap_action = NORMAL; + tpriv->iso_packet_offset = 0; + + /* allocate + initialize each URB with the correct number of packets */ + num_packets_remaining = num_packets; + for (i = 0, j = 0; i < num_urbs; i++) { + int num_packets_in_urb = MIN(num_packets_remaining, MAX_ISO_PACKETS_PER_URB); + struct usbfs_urb *urb; + size_t alloc_size; + int k; + + alloc_size = sizeof(*urb) + + (num_packets_in_urb * sizeof(struct usbfs_iso_packet_desc)); + urb = calloc(1, alloc_size); + if (!urb) { + free_iso_urbs(tpriv); + return LIBUSB_ERROR_NO_MEM; + } + urbs[i] = urb; + + /* populate packet lengths */ + for (k = 0; k < num_packets_in_urb; j++, k++) { + packet_len = transfer->iso_packet_desc[j].length; + urb->buffer_length += packet_len; + urb->iso_frame_desc[k].length = packet_len; + } + + urb->usercontext = itransfer; + urb->type = USBFS_URB_TYPE_ISO; + /* FIXME: interface for non-ASAP data? */ + urb->flags = USBFS_URB_ISO_ASAP; + urb->endpoint = transfer->endpoint; + urb->number_of_packets = num_packets_in_urb; + urb->buffer = urb_buffer; + + urb_buffer += urb->buffer_length; + num_packets_remaining -= num_packets_in_urb; + } + + /* submit URBs */ + for (i = 0; i < num_urbs; i++) { + int r = ioctl(hpriv->fd, IOCTL_USBFS_SUBMITURB, urbs[i]); + + if (r == 0) + continue; + + if (errno == ENODEV) { + r = LIBUSB_ERROR_NO_DEVICE; + } else if (errno == EINVAL) { + usbi_warn(TRANSFER_CTX(transfer), "submiturb failed, transfer too large"); + r = LIBUSB_ERROR_INVALID_PARAM; + } else if (errno == EMSGSIZE) { + usbi_warn(TRANSFER_CTX(transfer), "submiturb failed, iso packet length too large"); + r = LIBUSB_ERROR_INVALID_PARAM; + } else { + usbi_err(TRANSFER_CTX(transfer), "submiturb failed, errno=%d", errno); + r = LIBUSB_ERROR_IO; + } + + /* if the first URB submission fails, we can simply free up and + * return failure immediately. */ + if (i == 0) { + usbi_dbg(TRANSFER_CTX(transfer), "first URB failed, easy peasy"); + free_iso_urbs(tpriv); + return r; + } + + /* if it's not the first URB that failed, the situation is a bit + * tricky. we must discard all previous URBs. there are + * complications: + * - discarding is asynchronous - discarded urbs will be reaped + * later. the user must not have freed the transfer when the + * discarded URBs are reaped, otherwise libusb will be using + * freed memory. + * - the earlier URBs may have completed successfully and we do + * not want to throw away any data. + * so, in this case we discard all the previous URBs BUT we report + * that the transfer was submitted successfully. then later when + * the final discard completes we can report error to the user. + */ + tpriv->reap_action = SUBMIT_FAILED; + + /* The URBs we haven't submitted yet we count as already + * retired. */ + tpriv->num_retired = num_urbs - i; + discard_urbs(itransfer, 0, i); + + usbi_dbg(TRANSFER_CTX(transfer), "reporting successful submission but waiting for %d " + "discards before reporting error", i); + return 0; + } + + return 0; +} + +static int submit_control_transfer(struct usbi_transfer *itransfer) +{ + struct linux_transfer_priv *tpriv = usbi_get_transfer_priv(itransfer); + struct libusb_transfer *transfer = + USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer); + struct linux_device_handle_priv *hpriv = + usbi_get_device_handle_priv(transfer->dev_handle); + struct usbfs_urb *urb; + int r; + + if (transfer->length - LIBUSB_CONTROL_SETUP_SIZE > MAX_CTRL_BUFFER_LENGTH) + return LIBUSB_ERROR_INVALID_PARAM; + + urb = calloc(1, sizeof(*urb)); + if (!urb) + return LIBUSB_ERROR_NO_MEM; + tpriv->urbs = urb; + tpriv->num_urbs = 1; + tpriv->reap_action = NORMAL; + + urb->usercontext = itransfer; + urb->type = USBFS_URB_TYPE_CONTROL; + urb->endpoint = transfer->endpoint; + urb->buffer = transfer->buffer; + urb->buffer_length = transfer->length; + + r = ioctl(hpriv->fd, IOCTL_USBFS_SUBMITURB, urb); + if (r < 0) { + free(urb); + tpriv->urbs = NULL; + if (errno == ENODEV) + return LIBUSB_ERROR_NO_DEVICE; + + usbi_err(TRANSFER_CTX(transfer), "submiturb failed, errno=%d", errno); + return LIBUSB_ERROR_IO; + } + return 0; +} + +static int op_submit_transfer(struct usbi_transfer *itransfer) +{ + struct libusb_transfer *transfer = + USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer); + + switch (transfer->type) { + case LIBUSB_TRANSFER_TYPE_CONTROL: + return submit_control_transfer(itransfer); + case LIBUSB_TRANSFER_TYPE_BULK: + case LIBUSB_TRANSFER_TYPE_BULK_STREAM: + return submit_bulk_transfer(itransfer); + case LIBUSB_TRANSFER_TYPE_INTERRUPT: + return submit_bulk_transfer(itransfer); + case LIBUSB_TRANSFER_TYPE_ISOCHRONOUS: + return submit_iso_transfer(itransfer); + default: + usbi_err(TRANSFER_CTX(transfer), "unknown transfer type %u", transfer->type); + return LIBUSB_ERROR_INVALID_PARAM; + } +} + +static int op_cancel_transfer(struct usbi_transfer *itransfer) +{ + struct linux_transfer_priv *tpriv = usbi_get_transfer_priv(itransfer); + struct libusb_transfer *transfer = + USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer); + int r; + + if (!tpriv->urbs) + return LIBUSB_ERROR_NOT_FOUND; + + r = discard_urbs(itransfer, 0, tpriv->num_urbs); + if (r != 0) + return r; + + switch (transfer->type) { + case LIBUSB_TRANSFER_TYPE_BULK: + case LIBUSB_TRANSFER_TYPE_BULK_STREAM: + if (tpriv->reap_action == ERROR) + break; + /* else, fall through */ + default: + tpriv->reap_action = CANCELLED; + } + + return 0; +} + +static void op_clear_transfer_priv(struct usbi_transfer *itransfer) +{ + struct libusb_transfer *transfer = + USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer); + struct linux_transfer_priv *tpriv = usbi_get_transfer_priv(itransfer); + + switch (transfer->type) { + case LIBUSB_TRANSFER_TYPE_CONTROL: + case LIBUSB_TRANSFER_TYPE_BULK: + case LIBUSB_TRANSFER_TYPE_BULK_STREAM: + case LIBUSB_TRANSFER_TYPE_INTERRUPT: + if (tpriv->urbs) { + free(tpriv->urbs); + tpriv->urbs = NULL; + } + break; + case LIBUSB_TRANSFER_TYPE_ISOCHRONOUS: + if (tpriv->iso_urbs) { + free_iso_urbs(tpriv); + tpriv->iso_urbs = NULL; + } + break; + default: + usbi_err(TRANSFER_CTX(transfer), "unknown transfer type %u", transfer->type); + } +} + +static int handle_bulk_completion(struct usbi_transfer *itransfer, + struct usbfs_urb *urb) +{ + struct linux_transfer_priv *tpriv = usbi_get_transfer_priv(itransfer); + struct libusb_transfer *transfer = USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer); + int urb_idx = urb - tpriv->urbs; + + usbi_mutex_lock(&itransfer->lock); + usbi_dbg(TRANSFER_CTX(transfer), "handling completion status %d of bulk urb %d/%d", urb->status, + urb_idx + 1, tpriv->num_urbs); + + tpriv->num_retired++; + + if (tpriv->reap_action != NORMAL) { + /* cancelled, submit_fail, or completed early */ + usbi_dbg(TRANSFER_CTX(transfer), "abnormal reap: urb status %d", urb->status); + + /* even though we're in the process of cancelling, it's possible that + * we may receive some data in these URBs that we don't want to lose. + * examples: + * 1. while the kernel is cancelling all the packets that make up an + * URB, a few of them might complete. so we get back a successful + * cancellation *and* some data. + * 2. we receive a short URB which marks the early completion condition, + * so we start cancelling the remaining URBs. however, we're too + * slow and another URB completes (or at least completes partially). + * (this can't happen since we always use BULK_CONTINUATION.) + * + * When this happens, our objectives are not to lose any "surplus" data, + * and also to stick it at the end of the previously-received data + * (closing any holes), so that libusb reports the total amount of + * transferred data and presents it in a contiguous chunk. + */ + if (urb->actual_length > 0) { + unsigned char *target = transfer->buffer + itransfer->transferred; + + usbi_dbg(TRANSFER_CTX(transfer), "received %d bytes of surplus data", urb->actual_length); + if (urb->buffer != target) { + usbi_dbg(TRANSFER_CTX(transfer), "moving surplus data from offset %zu to offset %zu", + (unsigned char *)urb->buffer - transfer->buffer, + target - transfer->buffer); + memmove(target, urb->buffer, urb->actual_length); + } + itransfer->transferred += urb->actual_length; + } + + if (tpriv->num_retired == tpriv->num_urbs) { + usbi_dbg(TRANSFER_CTX(transfer), "abnormal reap: last URB handled, reporting"); + if (tpriv->reap_action != COMPLETED_EARLY && + tpriv->reap_status == LIBUSB_TRANSFER_COMPLETED) + tpriv->reap_status = LIBUSB_TRANSFER_ERROR; + goto completed; + } + goto out_unlock; + } + + itransfer->transferred += urb->actual_length; + + /* Many of these errors can occur on *any* urb of a multi-urb + * transfer. When they do, we tear down the rest of the transfer. + */ + switch (urb->status) { + case 0: + break; + case -EREMOTEIO: /* short transfer */ + break; + case -ENOENT: /* cancelled */ + case -ECONNRESET: + break; + case -ENODEV: + case -ESHUTDOWN: + usbi_dbg(TRANSFER_CTX(transfer), "device removed"); + tpriv->reap_status = LIBUSB_TRANSFER_NO_DEVICE; + goto cancel_remaining; + case -EPIPE: + usbi_dbg(TRANSFER_CTX(transfer), "detected endpoint stall"); + if (tpriv->reap_status == LIBUSB_TRANSFER_COMPLETED) + tpriv->reap_status = LIBUSB_TRANSFER_STALL; + goto cancel_remaining; + case -EOVERFLOW: + /* overflow can only ever occur in the last urb */ + usbi_dbg(TRANSFER_CTX(transfer), "overflow, actual_length=%d", urb->actual_length); + if (tpriv->reap_status == LIBUSB_TRANSFER_COMPLETED) + tpriv->reap_status = LIBUSB_TRANSFER_OVERFLOW; + goto completed; + case -ETIME: + case -EPROTO: + case -EILSEQ: + case -ECOMM: + case -ENOSR: + usbi_dbg(TRANSFER_CTX(transfer), "low-level bus error %d", urb->status); + tpriv->reap_action = ERROR; + goto cancel_remaining; + default: + usbi_warn(ITRANSFER_CTX(itransfer), "unrecognised urb status %d", urb->status); + tpriv->reap_action = ERROR; + goto cancel_remaining; + } + + /* if we've reaped all urbs or we got less data than requested then we're + * done */ + if (tpriv->num_retired == tpriv->num_urbs) { + usbi_dbg(TRANSFER_CTX(transfer), "all URBs in transfer reaped --> complete!"); + goto completed; + } else if (urb->actual_length < urb->buffer_length) { + usbi_dbg(TRANSFER_CTX(transfer), "short transfer %d/%d --> complete!", + urb->actual_length, urb->buffer_length); + if (tpriv->reap_action == NORMAL) + tpriv->reap_action = COMPLETED_EARLY; + } else { + goto out_unlock; + } + +cancel_remaining: + if (tpriv->reap_action == ERROR && tpriv->reap_status == LIBUSB_TRANSFER_COMPLETED) + tpriv->reap_status = LIBUSB_TRANSFER_ERROR; + + if (tpriv->num_retired == tpriv->num_urbs) /* nothing to cancel */ + goto completed; + + /* cancel remaining urbs and wait for their completion before + * reporting results */ + discard_urbs(itransfer, urb_idx + 1, tpriv->num_urbs); + +out_unlock: + usbi_mutex_unlock(&itransfer->lock); + return 0; + +completed: + free(tpriv->urbs); + tpriv->urbs = NULL; + usbi_mutex_unlock(&itransfer->lock); + return tpriv->reap_action == CANCELLED ? + usbi_handle_transfer_cancellation(itransfer) : + usbi_handle_transfer_completion(itransfer, tpriv->reap_status); +} + +static int handle_iso_completion(struct usbi_transfer *itransfer, + struct usbfs_urb *urb) +{ + struct libusb_transfer *transfer = + USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer); + struct linux_transfer_priv *tpriv = usbi_get_transfer_priv(itransfer); + int num_urbs = tpriv->num_urbs; + int urb_idx = 0; + int i; + enum libusb_transfer_status status = LIBUSB_TRANSFER_COMPLETED; + + usbi_mutex_lock(&itransfer->lock); + for (i = 0; i < num_urbs; i++) { + if (urb == tpriv->iso_urbs[i]) { + urb_idx = i + 1; + break; + } + } + if (urb_idx == 0) { + usbi_err(TRANSFER_CTX(transfer), "could not locate urb!"); + usbi_mutex_unlock(&itransfer->lock); + return LIBUSB_ERROR_NOT_FOUND; + } + + usbi_dbg(TRANSFER_CTX(transfer), "handling completion status %d of iso urb %d/%d", urb->status, + urb_idx, num_urbs); + + /* copy isochronous results back in */ + + for (i = 0; i < urb->number_of_packets; i++) { + struct usbfs_iso_packet_desc *urb_desc = &urb->iso_frame_desc[i]; + struct libusb_iso_packet_descriptor *lib_desc = + &transfer->iso_packet_desc[tpriv->iso_packet_offset++]; + + lib_desc->status = LIBUSB_TRANSFER_COMPLETED; + switch (urb_desc->status) { + case 0: + break; + case -ENOENT: /* cancelled */ + case -ECONNRESET: + break; + case -ENODEV: + case -ESHUTDOWN: + usbi_dbg(TRANSFER_CTX(transfer), "packet %d - device removed", i); + lib_desc->status = LIBUSB_TRANSFER_NO_DEVICE; + break; + case -EPIPE: + usbi_dbg(TRANSFER_CTX(transfer), "packet %d - detected endpoint stall", i); + lib_desc->status = LIBUSB_TRANSFER_STALL; + break; + case -EOVERFLOW: + usbi_dbg(TRANSFER_CTX(transfer), "packet %d - overflow error", i); + lib_desc->status = LIBUSB_TRANSFER_OVERFLOW; + break; + case -ETIME: + case -EPROTO: + case -EILSEQ: + case -ECOMM: + case -ENOSR: + case -EXDEV: + usbi_dbg(TRANSFER_CTX(transfer), "packet %d - low-level USB error %d", i, urb_desc->status); + lib_desc->status = LIBUSB_TRANSFER_ERROR; + break; + default: + usbi_warn(TRANSFER_CTX(transfer), "packet %d - unrecognised urb status %d", + i, urb_desc->status); + lib_desc->status = LIBUSB_TRANSFER_ERROR; + break; + } + lib_desc->actual_length = urb_desc->actual_length; + } + + tpriv->num_retired++; + + if (tpriv->reap_action != NORMAL) { /* cancelled or submit_fail */ + usbi_dbg(TRANSFER_CTX(transfer), "CANCEL: urb status %d", urb->status); + + if (tpriv->num_retired == num_urbs) { + usbi_dbg(TRANSFER_CTX(transfer), "CANCEL: last URB handled, reporting"); + free_iso_urbs(tpriv); + if (tpriv->reap_action == CANCELLED) { + usbi_mutex_unlock(&itransfer->lock); + return usbi_handle_transfer_cancellation(itransfer); + } else { + usbi_mutex_unlock(&itransfer->lock); + return usbi_handle_transfer_completion(itransfer, LIBUSB_TRANSFER_ERROR); + } + } + goto out; + } + + switch (urb->status) { + case 0: + break; + case -ENOENT: /* cancelled */ + case -ECONNRESET: + break; + case -ESHUTDOWN: + usbi_dbg(TRANSFER_CTX(transfer), "device removed"); + status = LIBUSB_TRANSFER_NO_DEVICE; + break; + default: + usbi_warn(TRANSFER_CTX(transfer), "unrecognised urb status %d", urb->status); + status = LIBUSB_TRANSFER_ERROR; + break; + } + + /* if we've reaped all urbs then we're done */ + if (tpriv->num_retired == num_urbs) { + usbi_dbg(TRANSFER_CTX(transfer), "all URBs in transfer reaped --> complete!"); + free_iso_urbs(tpriv); + usbi_mutex_unlock(&itransfer->lock); + return usbi_handle_transfer_completion(itransfer, status); + } + +out: + usbi_mutex_unlock(&itransfer->lock); + return 0; +} + +static int handle_control_completion(struct usbi_transfer *itransfer, + struct usbfs_urb *urb) +{ + struct linux_transfer_priv *tpriv = usbi_get_transfer_priv(itransfer); + int status; + + usbi_mutex_lock(&itransfer->lock); + usbi_dbg(ITRANSFER_CTX(itransfer), "handling completion status %d", urb->status); + + itransfer->transferred += urb->actual_length; + + if (tpriv->reap_action == CANCELLED) { + if (urb->status && urb->status != -ENOENT) + usbi_warn(ITRANSFER_CTX(itransfer), "cancel: unrecognised urb status %d", + urb->status); + free(tpriv->urbs); + tpriv->urbs = NULL; + usbi_mutex_unlock(&itransfer->lock); + return usbi_handle_transfer_cancellation(itransfer); + } + + switch (urb->status) { + case 0: + status = LIBUSB_TRANSFER_COMPLETED; + break; + case -ENOENT: /* cancelled */ + status = LIBUSB_TRANSFER_CANCELLED; + break; + case -ENODEV: + case -ESHUTDOWN: + usbi_dbg(ITRANSFER_CTX(itransfer), "device removed"); + status = LIBUSB_TRANSFER_NO_DEVICE; + break; + case -EPIPE: + usbi_dbg(ITRANSFER_CTX(itransfer), "unsupported control request"); + status = LIBUSB_TRANSFER_STALL; + break; + case -EOVERFLOW: + usbi_dbg(ITRANSFER_CTX(itransfer), "overflow, actual_length=%d", urb->actual_length); + status = LIBUSB_TRANSFER_OVERFLOW; + break; + case -ETIME: + case -EPROTO: + case -EILSEQ: + case -ECOMM: + case -ENOSR: + usbi_dbg(ITRANSFER_CTX(itransfer), "low-level bus error %d", urb->status); + status = LIBUSB_TRANSFER_ERROR; + break; + default: + usbi_warn(ITRANSFER_CTX(itransfer), "unrecognised urb status %d", urb->status); + status = LIBUSB_TRANSFER_ERROR; + break; + } + + free(tpriv->urbs); + tpriv->urbs = NULL; + usbi_mutex_unlock(&itransfer->lock); + return usbi_handle_transfer_completion(itransfer, status); +} + +static int reap_for_handle(struct libusb_device_handle *handle) +{ + struct linux_device_handle_priv *hpriv = usbi_get_device_handle_priv(handle); + int r; + struct usbfs_urb *urb = NULL; + struct usbi_transfer *itransfer; + struct libusb_transfer *transfer; + + r = ioctl(hpriv->fd, IOCTL_USBFS_REAPURBNDELAY, &urb); + if (r < 0) { + if (errno == EAGAIN) + return 1; + if (errno == ENODEV) + return LIBUSB_ERROR_NO_DEVICE; + + usbi_err(HANDLE_CTX(handle), "reap failed, errno=%d", errno); + return LIBUSB_ERROR_IO; + } + + itransfer = urb->usercontext; + transfer = USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer); + + usbi_dbg(HANDLE_CTX(handle), "urb type=%u status=%d transferred=%d", urb->type, urb->status, urb->actual_length); + + switch (transfer->type) { + case LIBUSB_TRANSFER_TYPE_ISOCHRONOUS: + return handle_iso_completion(itransfer, urb); + case LIBUSB_TRANSFER_TYPE_BULK: + case LIBUSB_TRANSFER_TYPE_BULK_STREAM: + case LIBUSB_TRANSFER_TYPE_INTERRUPT: + return handle_bulk_completion(itransfer, urb); + case LIBUSB_TRANSFER_TYPE_CONTROL: + return handle_control_completion(itransfer, urb); + default: + usbi_err(HANDLE_CTX(handle), "unrecognised transfer type %u", transfer->type); + return LIBUSB_ERROR_OTHER; + } +} + +static int op_handle_events(struct libusb_context *ctx, + void *event_data, unsigned int count, unsigned int num_ready) +{ + struct pollfd *fds = event_data; + unsigned int n; + int r; + + usbi_mutex_lock(&ctx->open_devs_lock); + for (n = 0; n < count && num_ready > 0; n++) { + struct pollfd *pollfd = &fds[n]; + struct libusb_device_handle *handle; + struct linux_device_handle_priv *hpriv = NULL; + int reap_count; + + if (!pollfd->revents) + continue; + + num_ready--; + for_each_open_device(ctx, handle) { + hpriv = usbi_get_device_handle_priv(handle); + if (hpriv->fd == pollfd->fd) + break; + } + + if (!hpriv || hpriv->fd != pollfd->fd) { + usbi_err(ctx, "cannot find handle for fd %d", + pollfd->fd); + continue; + } + + if (pollfd->revents & POLLERR) { + /* remove the fd from the pollfd set so that it doesn't continuously + * trigger an event, and flag that it has been removed so op_close() + * doesn't try to remove it a second time */ + usbi_remove_event_source(HANDLE_CTX(handle), hpriv->fd); + hpriv->fd_removed = 1; + + /* device will still be marked as attached if hotplug monitor thread + * hasn't processed remove event yet */ + usbi_mutex_static_lock(&linux_hotplug_lock); + if (usbi_atomic_load(&handle->dev->attached)) + linux_device_disconnected(handle->dev->bus_number, + handle->dev->device_address); + usbi_mutex_static_unlock(&linux_hotplug_lock); + + if (hpriv->caps & USBFS_CAP_REAP_AFTER_DISCONNECT) { + do { + r = reap_for_handle(handle); + } while (r == 0); + } + + usbi_handle_disconnect(handle); + continue; + } + + reap_count = 0; + do { + r = reap_for_handle(handle); + } while (r == 0 && ++reap_count <= 25); + + if (r == 1 || r == LIBUSB_ERROR_NO_DEVICE) + continue; + else if (r < 0) + goto out; + } + + r = 0; +out: + usbi_mutex_unlock(&ctx->open_devs_lock); + return r; +} + +const struct usbi_os_backend usbi_backend = { + .name = "Linux usbfs", + .caps = USBI_CAP_HAS_HID_ACCESS|USBI_CAP_SUPPORTS_DETACH_KERNEL_DRIVER, + .init = op_init, + .exit = op_exit, + .set_option = op_set_option, + .hotplug_poll = op_hotplug_poll, + .get_active_config_descriptor = op_get_active_config_descriptor, + .get_config_descriptor = op_get_config_descriptor, + .get_config_descriptor_by_value = op_get_config_descriptor_by_value, + + .wrap_sys_device = op_wrap_sys_device, + .open = op_open, + .close = op_close, + .get_configuration = op_get_configuration, + .set_configuration = op_set_configuration, + .claim_interface = op_claim_interface, + .release_interface = op_release_interface, + + .set_interface_altsetting = op_set_interface, + .clear_halt = op_clear_halt, + .reset_device = op_reset_device, + + .alloc_streams = op_alloc_streams, + .free_streams = op_free_streams, + + .dev_mem_alloc = op_dev_mem_alloc, + .dev_mem_free = op_dev_mem_free, + + .kernel_driver_active = op_kernel_driver_active, + .detach_kernel_driver = op_detach_kernel_driver, + .attach_kernel_driver = op_attach_kernel_driver, + + .destroy_device = op_destroy_device, + + .submit_transfer = op_submit_transfer, + .cancel_transfer = op_cancel_transfer, + .clear_transfer_priv = op_clear_transfer_priv, + + .handle_events = op_handle_events, + + .device_priv_size = sizeof(struct linux_device_priv), + .device_handle_priv_size = sizeof(struct linux_device_handle_priv), + .transfer_priv_size = sizeof(struct linux_transfer_priv), +}; diff --git a/Radio/HW/RtlSdr/libusb/os/linux_usbfs.h b/Radio/HW/RtlSdr/libusb/os/linux_usbfs.h new file mode 100644 index 0000000..1238ffa --- /dev/null +++ b/Radio/HW/RtlSdr/libusb/os/linux_usbfs.h @@ -0,0 +1,211 @@ +/* + * usbfs header structures + * Copyright © 2007 Daniel Drake <dsd@gentoo.org> + * Copyright © 2001 Johannes Erdfelt <johannes@erdfelt.com> + * + * 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 + */ + +#ifndef LIBUSB_USBFS_H +#define LIBUSB_USBFS_H + +#include <linux/magic.h> +#include <linux/types.h> + +#define SYSFS_MOUNT_PATH "/sys" +#define SYSFS_DEVICE_PATH SYSFS_MOUNT_PATH "/bus/usb/devices" + +struct usbfs_ctrltransfer { + /* keep in sync with usbdevice_fs.h:usbdevfs_ctrltransfer */ + __u8 bmRequestType; + __u8 bRequest; + __u16 wValue; + __u16 wIndex; + __u16 wLength; + __u32 timeout; /* in milliseconds */ + + /* pointer to data */ + void *data; +}; + +struct usbfs_setinterface { + /* keep in sync with usbdevice_fs.h:usbdevfs_setinterface */ + unsigned int interface; + unsigned int altsetting; +}; + +#define USBFS_MAXDRIVERNAME 255 + +struct usbfs_getdriver { + unsigned int interface; + char driver[USBFS_MAXDRIVERNAME + 1]; +}; + +#define USBFS_URB_SHORT_NOT_OK 0x01 +#define USBFS_URB_ISO_ASAP 0x02 +#define USBFS_URB_BULK_CONTINUATION 0x04 +#define USBFS_URB_QUEUE_BULK 0x10 +#define USBFS_URB_ZERO_PACKET 0x40 + +#define USBFS_URB_TYPE_ISO 0 +#define USBFS_URB_TYPE_INTERRUPT 1 +#define USBFS_URB_TYPE_CONTROL 2 +#define USBFS_URB_TYPE_BULK 3 + +struct usbfs_iso_packet_desc { + unsigned int length; + unsigned int actual_length; + unsigned int status; +}; + +#define MAX_BULK_BUFFER_LENGTH 16384 +#define MAX_CTRL_BUFFER_LENGTH 4096 + +#define MAX_ISO_PACKETS_PER_URB 128 + +struct usbfs_urb { + unsigned char type; + unsigned char endpoint; + int status; + unsigned int flags; + void *buffer; + int buffer_length; + int actual_length; + int start_frame; + union { + int number_of_packets; /* Only used for isoc urbs */ + unsigned int stream_id; /* Only used with bulk streams */ + }; + int error_count; + unsigned int signr; + void *usercontext; + struct usbfs_iso_packet_desc iso_frame_desc[0]; +}; + +struct usbfs_connectinfo { + unsigned int devnum; + unsigned char slow; +}; + +struct usbfs_ioctl { + int ifno; /* interface 0..N ; negative numbers reserved */ + int ioctl_code; /* MUST encode size + direction of data so the + * macros in <asm/ioctl.h> give correct values */ + void *data; /* param buffer (in, or out) */ +}; + +#define USBFS_CAP_ZERO_PACKET 0x01 +#define USBFS_CAP_BULK_CONTINUATION 0x02 +#define USBFS_CAP_NO_PACKET_SIZE_LIM 0x04 +#define USBFS_CAP_BULK_SCATTER_GATHER 0x08 +#define USBFS_CAP_REAP_AFTER_DISCONNECT 0x10 + +#define USBFS_DISCONNECT_CLAIM_IF_DRIVER 0x01 +#define USBFS_DISCONNECT_CLAIM_EXCEPT_DRIVER 0x02 + +struct usbfs_disconnect_claim { + unsigned int interface; + unsigned int flags; + char driver[USBFS_MAXDRIVERNAME + 1]; +}; + +struct usbfs_streams { + unsigned int num_streams; /* Not used by USBDEVFS_FREE_STREAMS */ + unsigned int num_eps; + unsigned char eps[0]; +}; + +#define USBFS_SPEED_UNKNOWN 0 +#define USBFS_SPEED_LOW 1 +#define USBFS_SPEED_FULL 2 +#define USBFS_SPEED_HIGH 3 +#define USBFS_SPEED_WIRELESS 4 +#define USBFS_SPEED_SUPER 5 +#define USBFS_SPEED_SUPER_PLUS 6 + +#define IOCTL_USBFS_CONTROL _IOWR('U', 0, struct usbfs_ctrltransfer) +#define IOCTL_USBFS_SETINTERFACE _IOR('U', 4, struct usbfs_setinterface) +#define IOCTL_USBFS_SETCONFIGURATION _IOR('U', 5, unsigned int) +#define IOCTL_USBFS_GETDRIVER _IOW('U', 8, struct usbfs_getdriver) +#define IOCTL_USBFS_SUBMITURB _IOR('U', 10, struct usbfs_urb) +#define IOCTL_USBFS_DISCARDURB _IO('U', 11) +#define IOCTL_USBFS_REAPURBNDELAY _IOW('U', 13, void *) +#define IOCTL_USBFS_CLAIMINTERFACE _IOR('U', 15, unsigned int) +#define IOCTL_USBFS_RELEASEINTERFACE _IOR('U', 16, unsigned int) +#define IOCTL_USBFS_CONNECTINFO _IOW('U', 17, struct usbfs_connectinfo) +#define IOCTL_USBFS_IOCTL _IOWR('U', 18, struct usbfs_ioctl) +#define IOCTL_USBFS_RESET _IO('U', 20) +#define IOCTL_USBFS_CLEAR_HALT _IOR('U', 21, unsigned int) +#define IOCTL_USBFS_DISCONNECT _IO('U', 22) +#define IOCTL_USBFS_CONNECT _IO('U', 23) +#define IOCTL_USBFS_GET_CAPABILITIES _IOR('U', 26, __u32) +#define IOCTL_USBFS_DISCONNECT_CLAIM _IOR('U', 27, struct usbfs_disconnect_claim) +#define IOCTL_USBFS_ALLOC_STREAMS _IOR('U', 28, struct usbfs_streams) +#define IOCTL_USBFS_FREE_STREAMS _IOR('U', 29, struct usbfs_streams) +#define IOCTL_USBFS_DROP_PRIVILEGES _IOW('U', 30, __u32) +#define IOCTL_USBFS_GET_SPEED _IO('U', 31) + +extern usbi_mutex_static_t linux_hotplug_lock; + +#ifdef HAVE_LIBUDEV +int linux_udev_start_event_monitor(void); +int linux_udev_stop_event_monitor(void); +int linux_udev_scan_devices(struct libusb_context *ctx); +void linux_udev_hotplug_poll(void); +#else +int linux_netlink_start_event_monitor(void); +int linux_netlink_stop_event_monitor(void); +void linux_netlink_hotplug_poll(void); +#endif + +static inline int linux_start_event_monitor(void) +{ +#if defined(HAVE_LIBUDEV) + return linux_udev_start_event_monitor(); +#elif !defined(__ANDROID__) + return linux_netlink_start_event_monitor(); +#else + return LIBUSB_SUCCESS; +#endif +} + +static inline void linux_stop_event_monitor(void) +{ +#if defined(HAVE_LIBUDEV) + linux_udev_stop_event_monitor(); +#elif !defined(__ANDROID__) + linux_netlink_stop_event_monitor(); +#endif +} + +static inline void linux_hotplug_poll(void) +{ +#if defined(HAVE_LIBUDEV) + linux_udev_hotplug_poll(); +#elif !defined(__ANDROID__) + linux_netlink_hotplug_poll(); +#endif +} + +void linux_hotplug_enumerate(uint8_t busnum, uint8_t devaddr, const char *sys_name); +void linux_device_disconnected(uint8_t busnum, uint8_t devaddr); + +int linux_get_device_address(struct libusb_context *ctx, int detached, + uint8_t *busnum, uint8_t *devaddr, const char *dev_node, + const char *sys_name, int fd); +int linux_enumerate_device(struct libusb_context *ctx, + uint8_t busnum, uint8_t devaddr, const char *sysfs_dir); + +#endif diff --git a/Radio/HW/RtlSdr/libusb/os/threads_posix.c b/Radio/HW/RtlSdr/libusb/os/threads_posix.c new file mode 100644 index 0000000..0e0e221 --- /dev/null +++ b/Radio/HW/RtlSdr/libusb/os/threads_posix.c @@ -0,0 +1,129 @@ +/* + * libusb synchronization using POSIX Threads + * + * Copyright © 2011 Vitali Lovich <vlovich@aliph.com> + * Copyright © 2011 Peter Stuge <peter@stuge.se> + * + * 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 "libusbi.h" + +#include <errno.h> +#if defined(__ANDROID__) +# include <unistd.h> +#elif defined(__HAIKU__) +# include <os/kernel/OS.h> +#elif defined(__linux__) +# include <sys/syscall.h> +# include <unistd.h> +#elif defined(__NetBSD__) +# include <lwp.h> +#elif defined(__OpenBSD__) +# define _BSD_SOURCE +# include <sys/syscall.h> +# include <unistd.h> +#elif defined(__sun__) +# include <sys/lwp.h> +#endif + +void usbi_cond_init(pthread_cond_t *cond) +{ +#ifdef HAVE_PTHREAD_CONDATTR_SETCLOCK + pthread_condattr_t condattr; + + PTHREAD_CHECK(pthread_condattr_init(&condattr)); + PTHREAD_CHECK(pthread_condattr_setclock(&condattr, CLOCK_MONOTONIC)); + PTHREAD_CHECK(pthread_cond_init(cond, &condattr)); + PTHREAD_CHECK(pthread_condattr_destroy(&condattr)); +#else + PTHREAD_CHECK(pthread_cond_init(cond, NULL)); +#endif +} + +int usbi_cond_timedwait(pthread_cond_t *cond, + pthread_mutex_t *mutex, const struct timeval *tv) +{ + struct timespec timeout; + int r; + +#ifdef HAVE_PTHREAD_CONDATTR_SETCLOCK + usbi_get_monotonic_time(&timeout); +#else + usbi_get_real_time(&timeout); +#endif + + timeout.tv_sec += tv->tv_sec; + timeout.tv_nsec += tv->tv_usec * 1000L; + if (timeout.tv_nsec >= NSEC_PER_SEC) { + timeout.tv_nsec -= NSEC_PER_SEC; + timeout.tv_sec++; + } + + r = pthread_cond_timedwait(cond, mutex, &timeout); + if (r == 0) + return 0; + else if (r == ETIMEDOUT) + return LIBUSB_ERROR_TIMEOUT; + else + return LIBUSB_ERROR_OTHER; +} + +unsigned int usbi_get_tid(void) +{ + static _Thread_local unsigned int tl_tid; + int tid; + + if (tl_tid) + return tl_tid; + +#if defined(__ANDROID__) + tid = gettid(); +#elif defined(__APPLE__) +#ifdef HAVE_PTHREAD_THREADID_NP + uint64_t thread_id; + + if (pthread_threadid_np(NULL, &thread_id) == 0) + tid = (int)thread_id; + else + tid = -1; +#else + tid = (int)pthread_mach_thread_np(pthread_self()); +#endif +#elif defined(__HAIKU__) + tid = get_pthread_thread_id(pthread_self()); +#elif defined(__linux__) + tid = (int)syscall(SYS_gettid); +#elif defined(__NetBSD__) + tid = _lwp_self(); +#elif defined(__OpenBSD__) + /* The following only works with OpenBSD > 5.1 as it requires + * real thread support. For 5.1 and earlier, -1 is returned. */ + tid = syscall(SYS_getthrid); +#elif defined(__sun__) + tid = _lwp_self(); +#else + tid = -1; +#endif + + if (tid == -1) { + /* If we don't have a thread ID, at least return a unique + * value that can be used to distinguish individual + * threads. */ + tid = (int)(intptr_t)pthread_self(); + } + + return tl_tid = (unsigned int)tid; +} diff --git a/Radio/HW/RtlSdr/libusb/os/threads_posix.h b/Radio/HW/RtlSdr/libusb/os/threads_posix.h new file mode 100644 index 0000000..9322834 --- /dev/null +++ b/Radio/HW/RtlSdr/libusb/os/threads_posix.h @@ -0,0 +1,98 @@ +/* + * libusb synchronization using POSIX Threads + * + * Copyright © 2010 Peter Stuge <peter@stuge.se> + * + * 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 + */ + +#ifndef LIBUSB_THREADS_POSIX_H +#define LIBUSB_THREADS_POSIX_H + +#include <pthread.h> + +#define PTHREAD_CHECK(expression) ASSERT_EQ(expression, 0) + +#define USBI_MUTEX_INITIALIZER PTHREAD_MUTEX_INITIALIZER +typedef pthread_mutex_t usbi_mutex_static_t; +static inline void usbi_mutex_static_lock(usbi_mutex_static_t *mutex) +{ + PTHREAD_CHECK(pthread_mutex_lock(mutex)); +} +static inline void usbi_mutex_static_unlock(usbi_mutex_static_t *mutex) +{ + PTHREAD_CHECK(pthread_mutex_unlock(mutex)); +} + +typedef pthread_mutex_t usbi_mutex_t; +static inline void usbi_mutex_init(usbi_mutex_t *mutex) +{ + PTHREAD_CHECK(pthread_mutex_init(mutex, NULL)); +} +static inline void usbi_mutex_lock(usbi_mutex_t *mutex) +{ + PTHREAD_CHECK(pthread_mutex_lock(mutex)); +} +static inline void usbi_mutex_unlock(usbi_mutex_t *mutex) +{ + PTHREAD_CHECK(pthread_mutex_unlock(mutex)); +} +static inline int usbi_mutex_trylock(usbi_mutex_t *mutex) +{ + return pthread_mutex_trylock(mutex) == 0; +} +static inline void usbi_mutex_destroy(usbi_mutex_t *mutex) +{ + PTHREAD_CHECK(pthread_mutex_destroy(mutex)); +} + +typedef pthread_cond_t usbi_cond_t; +void usbi_cond_init(pthread_cond_t *cond); +static inline void usbi_cond_wait(usbi_cond_t *cond, usbi_mutex_t *mutex) +{ + PTHREAD_CHECK(pthread_cond_wait(cond, mutex)); +} +int usbi_cond_timedwait(usbi_cond_t *cond, + usbi_mutex_t *mutex, const struct timeval *tv); +static inline void usbi_cond_broadcast(usbi_cond_t *cond) +{ + PTHREAD_CHECK(pthread_cond_broadcast(cond)); +} +static inline void usbi_cond_destroy(usbi_cond_t *cond) +{ + PTHREAD_CHECK(pthread_cond_destroy(cond)); +} + +typedef pthread_key_t usbi_tls_key_t; +static inline void usbi_tls_key_create(usbi_tls_key_t *key) +{ + PTHREAD_CHECK(pthread_key_create(key, NULL)); +} +static inline void *usbi_tls_key_get(usbi_tls_key_t key) +{ + return pthread_getspecific(key); +} +static inline void usbi_tls_key_set(usbi_tls_key_t key, void *ptr) +{ + PTHREAD_CHECK(pthread_setspecific(key, ptr)); +} +static inline void usbi_tls_key_delete(usbi_tls_key_t key) +{ + PTHREAD_CHECK(pthread_key_delete(key)); +} + +unsigned int usbi_get_tid(void); + +#endif /* LIBUSB_THREADS_POSIX_H */ diff --git a/Radio/HW/RtlSdr/libusb/strerror.c b/Radio/HW/RtlSdr/libusb/strerror.c new file mode 100644 index 0000000..9445fa9 --- /dev/null +++ b/Radio/HW/RtlSdr/libusb/strerror.c @@ -0,0 +1,223 @@ +/* + * libusb strerror code + * Copyright © 2013 Hans de Goede <hdegoede@redhat.com> + * + * 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 "libusbi.h" + +#include <ctype.h> +#include <string.h> + +/** \ingroup libusb_misc + * How to add a new \ref libusb_strerror() translation: + * <ol> + * <li> Download the latest \c strerror.c from:<br> + * https://raw.github.com/libusb/libusb/master/libusb/strerror.c </li> + * <li> Open the file in an UTF-8 capable editor </li> + * <li> Add the 2 letter <a href="http://en.wikipedia.org/wiki/List_of_ISO_639-1_codes">ISO 639-1</a> + * code for your locale at the end of \c usbi_locale_supported[]<br> + * Eg. for Chinese, you would add "zh" so that: + * \code... usbi_locale_supported[] = { "en", "nl", "fr" };\endcode + * becomes: + * \code... usbi_locale_supported[] = { "en", "nl", "fr", "zh" };\endcode </li> + * <li> Copy the <tt>{ / * English (en) * / ... }</tt> section and add it at the end of \c usbi_localized_errors<br> + * Eg. for Chinese, the last section of \c usbi_localized_errors could look like: + * \code + * }, { / * Chinese (zh) * / + * "Success", + * ... + * "Other error", + * }, + * };\endcode </li> + * <li> Translate each of the English messages from the section you copied into your language </li> + * <li> Save the file (in UTF-8 format) and send it to \c libusb-devel\@lists.sourceforge.net </li> + * </ol> + */ + +static const char * const usbi_locale_supported[] = { "en", "nl", "fr", "ru", "de", "hu" }; +static const char * const usbi_localized_errors[ARRAYSIZE(usbi_locale_supported)][LIBUSB_ERROR_COUNT] = { + { /* English (en) */ + "Success", + "Input/Output Error", + "Invalid parameter", + "Access denied (insufficient permissions)", + "No such device (it may have been disconnected)", + "Entity not found", + "Resource busy", + "Operation timed out", + "Overflow", + "Pipe error", + "System call interrupted (perhaps due to signal)", + "Insufficient memory", + "Operation not supported or unimplemented on this platform", + "Other error", + }, { /* Dutch (nl) */ + "Gelukt", + "Invoer-/uitvoerfout", + "Ongeldig argument", + "Toegang geweigerd (onvoldoende toegangsrechten)", + "Apparaat bestaat niet (verbinding met apparaat verbroken?)", + "Niet gevonden", + "Apparaat of hulpbron is bezig", + "Bewerking verlopen", + "Waarde is te groot", + "Gebroken pijp", + "Onderbroken systeemaanroep", + "Onvoldoende geheugen beschikbaar", + "Bewerking wordt niet ondersteund", + "Andere fout", + }, { /* French (fr) */ + "Succès", + "Erreur d'entrée/sortie", + "Paramètre invalide", + "Accès refusé (permissions insuffisantes)", + "Périphérique introuvable (peut-être déconnecté)", + "Elément introuvable", + "Resource déjà occupée", + "Operation expirée", + "Débordement", + "Erreur de pipe", + "Appel système abandonné (peut-être à cause d’un signal)", + "Mémoire insuffisante", + "Opération non supportée or non implémentée sur cette plateforme", + "Autre erreur", + }, { /* Russian (ru) */ + "Успех", + "Ошибка ввода/вывода", + "Неверный параметр", + "Доступ запрещён (не хватает прав)", + "Устройство отсутствует (возможно, оно было отсоединено)", + "Элемент не найден", + "Ресурс занят", + "Истекло время ожидания операции", + "Переполнение", + "Ошибка канала", + "Системный вызов прерван (возможно, сигналом)", + "Память исчерпана", + "Операция не поддерживается данной платформой", + "Неизвестная ошибка" + }, { /* German (de) */ + "Erfolgreich", + "Eingabe-/Ausgabefehler", + "Ungültiger Parameter", + "Keine Berechtigung (Zugriffsrechte fehlen)", + "Kein passendes Gerät gefunden (es könnte entfernt worden sein)", + "Entität nicht gefunden", + "Die Ressource ist belegt", + "Die Wartezeit für die Operation ist abgelaufen", + "Mehr Daten empfangen als erwartet", + "Datenübergabe unterbrochen (broken pipe)", + "Unterbrechung während des Betriebssystemaufrufs", + "Nicht genügend Hauptspeicher verfügbar", + "Die Operation wird nicht unterstützt oder ist auf dieser Platform nicht implementiert", + "Allgemeiner Fehler", + }, { /* Hungarian (hu) */ + "Sikeres", + "Be-/kimeneti hiba", + "Érvénytelen paraméter", + "Hozzáférés megtagadva", + "Az eszköz nem található (eltávolították?)", + "Nem található", + "Az erőforrás foglalt", + "Időtúllépés", + "Túlcsordulás", + "Törött adatcsatorna", + "Rendszerhívás megszakítva", + "Nincs elég memória", + "A művelet nem támogatott ezen a rendszeren", + "Általános hiba", + }, +}; + +static const char * const (*usbi_error_strings)[LIBUSB_ERROR_COUNT] = &usbi_localized_errors[0]; + +/** \ingroup libusb_misc + * Set the language, and only the language, not the encoding! used for + * translatable libusb messages. + * + * This takes a locale string in the default setlocale format: lang[-region] + * or lang[_country_region][.codeset]. Only the lang part of the string is + * used, and only 2 letter ISO 639-1 codes are accepted for it, such as "de". + * The optional region, country_region or codeset parts are ignored. This + * means that functions which return translatable strings will NOT honor the + * specified encoding. + * All strings returned are encoded as UTF-8 strings. + * + * If libusb_setlocale() is not called, all messages will be in English. + * + * The following functions return translatable strings: libusb_strerror(). + * Note that the libusb log messages controlled through libusb_set_debug() + * are not translated, they are always in English. + * + * For POSIX UTF-8 environments if you want libusb to follow the standard + * locale settings, call libusb_setlocale(setlocale(LC_MESSAGES, NULL)), + * after your app has done its locale setup. + * + * \param locale locale-string in the form of lang[_country_region][.codeset] + * or lang[-region], where lang is a 2 letter ISO 639-1 code + * \returns LIBUSB_SUCCESS on success + * \returns LIBUSB_ERROR_INVALID_PARAM if the locale doesn't meet the requirements + * \returns LIBUSB_ERROR_NOT_FOUND if the requested language is not supported + * \returns a LIBUSB_ERROR code on other errors + */ + +int API_EXPORTED libusb_setlocale(const char *locale) +{ + size_t i; + + if (!locale || strlen(locale) < 2 + || (locale[2] != '\0' && locale[2] != '-' && locale[2] != '_' && locale[2] != '.')) + return LIBUSB_ERROR_INVALID_PARAM; + + for (i = 0; i < ARRAYSIZE(usbi_locale_supported); i++) { + if (usbi_locale_supported[i][0] == tolower((unsigned char)locale[0]) + && usbi_locale_supported[i][1] == tolower((unsigned char)locale[1])) + break; + } + + if (i == ARRAYSIZE(usbi_locale_supported)) + return LIBUSB_ERROR_NOT_FOUND; + + usbi_error_strings = &usbi_localized_errors[i]; + + return LIBUSB_SUCCESS; +} + +/** \ingroup libusb_misc + * Returns a constant string with a short description of the given error code, + * this description is intended for displaying to the end user and will be in + * the language set by libusb_setlocale(). + * + * The returned string is encoded in UTF-8. + * + * The messages always start with a capital letter and end without any dot. + * The caller must not free() the returned string. + * + * \param errcode the error code whose description is desired + * \returns a short description of the error code in UTF-8 encoding + */ +DEFAULT_VISIBILITY const char * LIBUSB_CALL libusb_strerror(int errcode) +{ + int errcode_index = -errcode; + + if (errcode_index < 0 || errcode_index >= LIBUSB_ERROR_COUNT) { + /* "Other Error", which should always be our last message, is returned */ + errcode_index = LIBUSB_ERROR_COUNT - 1; + } + + return (*usbi_error_strings)[errcode_index]; +} diff --git a/Radio/HW/RtlSdr/libusb/sync.c b/Radio/HW/RtlSdr/libusb/sync.c new file mode 100644 index 0000000..1fa1f0b --- /dev/null +++ b/Radio/HW/RtlSdr/libusb/sync.c @@ -0,0 +1,334 @@ +/* -*- Mode: C; indent-tabs-mode:t ; c-basic-offset:8 -*- */ +/* + * Synchronous I/O functions for libusb + * Copyright © 2007-2008 Daniel Drake <dsd@gentoo.org> + * Copyright © 2019 Nathan Hjelm <hjelmn@cs.unm.edu> + * Copyright © 2019 Google LLC. All rights reserved. + * + * 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 "libusbi.h" + +#include <string.h> + +/** + * @defgroup libusb_syncio Synchronous device I/O + * + * This page documents libusb's synchronous (blocking) API for USB device I/O. + * This interface is easy to use but has some limitations. More advanced users + * may wish to consider using the \ref libusb_asyncio "asynchronous I/O API" instead. + */ + +static void LIBUSB_CALL sync_transfer_cb(struct libusb_transfer *transfer) +{ + int *completed = transfer->user_data; + *completed = 1; + usbi_dbg(TRANSFER_CTX(transfer), "actual_length=%d", transfer->actual_length); + /* caller interprets result and frees transfer */ +} + +static void sync_transfer_wait_for_completion(struct libusb_transfer *transfer) +{ + int r, *completed = transfer->user_data; + struct libusb_context *ctx = HANDLE_CTX(transfer->dev_handle); + + while (!*completed) { + r = libusb_handle_events_completed(ctx, completed); + if (r < 0) { + if (r == LIBUSB_ERROR_INTERRUPTED) + continue; + usbi_err(ctx, "libusb_handle_events failed: %s, cancelling transfer and retrying", + libusb_error_name(r)); + libusb_cancel_transfer(transfer); + continue; + } + if (NULL == transfer->dev_handle) { + /* transfer completion after libusb_close() */ + transfer->status = LIBUSB_TRANSFER_NO_DEVICE; + *completed = 1; + } + } +} + +/** \ingroup libusb_syncio + * Perform a USB control transfer. + * + * The direction of the transfer is inferred from the bmRequestType field of + * the setup packet. + * + * The wValue, wIndex and wLength fields values should be given in host-endian + * byte order. + * + * \param dev_handle a handle for the device to communicate with + * \param bmRequestType the request type field for the setup packet + * \param bRequest the request field for the setup packet + * \param wValue the value field for the setup packet + * \param wIndex the index field for the setup packet + * \param data a suitably-sized data buffer for either input or output + * (depending on direction bits within bmRequestType) + * \param wLength the length field for the setup packet. The data buffer should + * be at least this size. + * \param timeout timeout (in milliseconds) that this function should wait + * before giving up due to no response being received. For an unlimited + * timeout, use value 0. + * \returns on success, the number of bytes actually transferred + * \returns LIBUSB_ERROR_TIMEOUT if the transfer timed out + * \returns LIBUSB_ERROR_PIPE if the control request was not supported by the + * device + * \returns LIBUSB_ERROR_NO_DEVICE if the device has been disconnected + * \returns LIBUSB_ERROR_BUSY if called from event handling context + * \returns LIBUSB_ERROR_INVALID_PARAM if the transfer size is larger than + * the operating system and/or hardware can support (see \ref asynclimits) + * \returns another LIBUSB_ERROR code on other failures + */ +int API_EXPORTED libusb_control_transfer(libusb_device_handle *dev_handle, + uint8_t bmRequestType, uint8_t bRequest, uint16_t wValue, uint16_t wIndex, + unsigned char *data, uint16_t wLength, unsigned int timeout) +{ + struct libusb_transfer *transfer; + unsigned char *buffer; + int completed = 0; + int r; + + if (usbi_handling_events(HANDLE_CTX(dev_handle))) + return LIBUSB_ERROR_BUSY; + + transfer = libusb_alloc_transfer(0); + if (!transfer) + return LIBUSB_ERROR_NO_MEM; + + buffer = malloc(LIBUSB_CONTROL_SETUP_SIZE + wLength); + if (!buffer) { + libusb_free_transfer(transfer); + return LIBUSB_ERROR_NO_MEM; + } + + libusb_fill_control_setup(buffer, bmRequestType, bRequest, wValue, wIndex, + wLength); + if ((bmRequestType & LIBUSB_ENDPOINT_DIR_MASK) == LIBUSB_ENDPOINT_OUT) + memcpy(buffer + LIBUSB_CONTROL_SETUP_SIZE, data, wLength); + + libusb_fill_control_transfer(transfer, dev_handle, buffer, + sync_transfer_cb, &completed, timeout); + transfer->flags = LIBUSB_TRANSFER_FREE_BUFFER; + r = libusb_submit_transfer(transfer); + if (r < 0) { + libusb_free_transfer(transfer); + return r; + } + + sync_transfer_wait_for_completion(transfer); + + if ((bmRequestType & LIBUSB_ENDPOINT_DIR_MASK) == LIBUSB_ENDPOINT_IN) + memcpy(data, libusb_control_transfer_get_data(transfer), + transfer->actual_length); + + switch (transfer->status) { + case LIBUSB_TRANSFER_COMPLETED: + r = transfer->actual_length; + break; + case LIBUSB_TRANSFER_TIMED_OUT: + r = LIBUSB_ERROR_TIMEOUT; + break; + case LIBUSB_TRANSFER_STALL: + r = LIBUSB_ERROR_PIPE; + break; + case LIBUSB_TRANSFER_NO_DEVICE: + r = LIBUSB_ERROR_NO_DEVICE; + break; + case LIBUSB_TRANSFER_OVERFLOW: + r = LIBUSB_ERROR_OVERFLOW; + break; + case LIBUSB_TRANSFER_ERROR: + case LIBUSB_TRANSFER_CANCELLED: + r = LIBUSB_ERROR_IO; + break; + default: + usbi_warn(HANDLE_CTX(dev_handle), + "unrecognised status code %d", transfer->status); + r = LIBUSB_ERROR_OTHER; + } + + libusb_free_transfer(transfer); + return r; +} + +static int do_sync_bulk_transfer(struct libusb_device_handle *dev_handle, + unsigned char endpoint, unsigned char *buffer, int length, + int *transferred, unsigned int timeout, unsigned char type) +{ + struct libusb_transfer *transfer; + int completed = 0; + int r; + + if (usbi_handling_events(HANDLE_CTX(dev_handle))) + return LIBUSB_ERROR_BUSY; + + transfer = libusb_alloc_transfer(0); + if (!transfer) + return LIBUSB_ERROR_NO_MEM; + + libusb_fill_bulk_transfer(transfer, dev_handle, endpoint, buffer, length, + sync_transfer_cb, &completed, timeout); + transfer->type = type; + + r = libusb_submit_transfer(transfer); + if (r < 0) { + libusb_free_transfer(transfer); + return r; + } + + sync_transfer_wait_for_completion(transfer); + + if (transferred) + *transferred = transfer->actual_length; + + switch (transfer->status) { + case LIBUSB_TRANSFER_COMPLETED: + r = 0; + break; + case LIBUSB_TRANSFER_TIMED_OUT: + r = LIBUSB_ERROR_TIMEOUT; + break; + case LIBUSB_TRANSFER_STALL: + r = LIBUSB_ERROR_PIPE; + break; + case LIBUSB_TRANSFER_OVERFLOW: + r = LIBUSB_ERROR_OVERFLOW; + break; + case LIBUSB_TRANSFER_NO_DEVICE: + r = LIBUSB_ERROR_NO_DEVICE; + break; + case LIBUSB_TRANSFER_ERROR: + case LIBUSB_TRANSFER_CANCELLED: + r = LIBUSB_ERROR_IO; + break; + default: + usbi_warn(HANDLE_CTX(dev_handle), + "unrecognised status code %d", transfer->status); + r = LIBUSB_ERROR_OTHER; + } + + libusb_free_transfer(transfer); + return r; +} + +/** \ingroup libusb_syncio + * Perform a USB bulk transfer. The direction of the transfer is inferred from + * the direction bits of the endpoint address. + * + * For bulk reads, the <tt>length</tt> field indicates the maximum length of + * data you are expecting to receive. If less data arrives than expected, + * this function will return that data, so be sure to check the + * <tt>transferred</tt> output parameter. + * + * You should also check the <tt>transferred</tt> parameter for bulk writes. + * Not all of the data may have been written. + * + * Also check <tt>transferred</tt> when dealing with a timeout error code. + * libusb may have to split your transfer into a number of chunks to satisfy + * underlying O/S requirements, meaning that the timeout may expire after + * the first few chunks have completed. libusb is careful not to lose any data + * that may have been transferred; do not assume that timeout conditions + * indicate a complete lack of I/O. See \ref asynctimeout for more details. + * + * \param dev_handle a handle for the device to communicate with + * \param endpoint the address of a valid endpoint to communicate with + * \param data a suitably-sized data buffer for either input or output + * (depending on endpoint) + * \param length for bulk writes, the number of bytes from data to be sent. for + * bulk reads, the maximum number of bytes to receive into the data buffer. + * \param transferred output location for the number of bytes actually + * transferred. Since version 1.0.21 (\ref LIBUSB_API_VERSION >= 0x01000105), + * it is legal to pass a NULL pointer if you do not wish to receive this + * information. + * \param timeout timeout (in milliseconds) that this function should wait + * before giving up due to no response being received. For an unlimited + * timeout, use value 0. + * + * \returns 0 on success (and populates <tt>transferred</tt>) + * \returns LIBUSB_ERROR_TIMEOUT if the transfer timed out (and populates + * <tt>transferred</tt>) + * \returns LIBUSB_ERROR_PIPE if the endpoint halted + * \returns LIBUSB_ERROR_OVERFLOW if the device offered more data, see + * \ref libusb_packetoverflow + * \returns LIBUSB_ERROR_NO_DEVICE if the device has been disconnected + * \returns LIBUSB_ERROR_BUSY if called from event handling context + * \returns LIBUSB_ERROR_INVALID_PARAM if the transfer size is larger than + * the operating system and/or hardware can support (see \ref asynclimits) + * \returns another LIBUSB_ERROR code on other failures + */ +int API_EXPORTED libusb_bulk_transfer(libusb_device_handle *dev_handle, + unsigned char endpoint, unsigned char *data, int length, + int *transferred, unsigned int timeout) +{ + return do_sync_bulk_transfer(dev_handle, endpoint, data, length, + transferred, timeout, LIBUSB_TRANSFER_TYPE_BULK); +} + +/** \ingroup libusb_syncio + * Perform a USB interrupt transfer. The direction of the transfer is inferred + * from the direction bits of the endpoint address. + * + * For interrupt reads, the <tt>length</tt> field indicates the maximum length + * of data you are expecting to receive. If less data arrives than expected, + * this function will return that data, so be sure to check the + * <tt>transferred</tt> output parameter. + * + * You should also check the <tt>transferred</tt> parameter for interrupt + * writes. Not all of the data may have been written. + * + * Also check <tt>transferred</tt> when dealing with a timeout error code. + * libusb may have to split your transfer into a number of chunks to satisfy + * underlying O/S requirements, meaning that the timeout may expire after + * the first few chunks have completed. libusb is careful not to lose any data + * that may have been transferred; do not assume that timeout conditions + * indicate a complete lack of I/O. See \ref asynctimeout for more details. + * + * The default endpoint bInterval value is used as the polling interval. + * + * \param dev_handle a handle for the device to communicate with + * \param endpoint the address of a valid endpoint to communicate with + * \param data a suitably-sized data buffer for either input or output + * (depending on endpoint) + * \param length for bulk writes, the number of bytes from data to be sent. for + * bulk reads, the maximum number of bytes to receive into the data buffer. + * \param transferred output location for the number of bytes actually + * transferred. Since version 1.0.21 (\ref LIBUSB_API_VERSION >= 0x01000105), + * it is legal to pass a NULL pointer if you do not wish to receive this + * information. + * \param timeout timeout (in milliseconds) that this function should wait + * before giving up due to no response being received. For an unlimited + * timeout, use value 0. + * + * \returns 0 on success (and populates <tt>transferred</tt>) + * \returns LIBUSB_ERROR_TIMEOUT if the transfer timed out + * \returns LIBUSB_ERROR_PIPE if the endpoint halted + * \returns LIBUSB_ERROR_OVERFLOW if the device offered more data, see + * \ref libusb_packetoverflow + * \returns LIBUSB_ERROR_NO_DEVICE if the device has been disconnected + * \returns LIBUSB_ERROR_BUSY if called from event handling context + * \returns LIBUSB_ERROR_INVALID_PARAM if the transfer size is larger than + * the operating system and/or hardware can support (see \ref asynclimits) + * \returns another LIBUSB_ERROR code on other error + */ +int API_EXPORTED libusb_interrupt_transfer(libusb_device_handle *dev_handle, + unsigned char endpoint, unsigned char *data, int length, + int *transferred, unsigned int timeout) +{ + return do_sync_bulk_transfer(dev_handle, endpoint, data, length, + transferred, timeout, LIBUSB_TRANSFER_TYPE_INTERRUPT); +} diff --git a/Radio/HW/RtlSdr/libusb/version.h b/Radio/HW/RtlSdr/libusb/version.h new file mode 100644 index 0000000..1fe9946 --- /dev/null +++ b/Radio/HW/RtlSdr/libusb/version.h @@ -0,0 +1,18 @@ +/* This file is parsed by m4 and windres and RC.EXE so please keep it simple. */ +#include "version_nano.h" +#ifndef LIBUSB_MAJOR +#define LIBUSB_MAJOR 1 +#endif +#ifndef LIBUSB_MINOR +#define LIBUSB_MINOR 0 +#endif +#ifndef LIBUSB_MICRO +#define LIBUSB_MICRO 25 +#endif +#ifndef LIBUSB_NANO +#define LIBUSB_NANO 0 +#endif +/* LIBUSB_RC is the release candidate suffix. Should normally be empty. */ +#ifndef LIBUSB_RC +#define LIBUSB_RC "" +#endif diff --git a/Radio/HW/RtlSdr/libusb/version_nano.h b/Radio/HW/RtlSdr/libusb/version_nano.h new file mode 100644 index 0000000..03df9b4 --- /dev/null +++ b/Radio/HW/RtlSdr/libusb/version_nano.h @@ -0,0 +1 @@ +#define LIBUSB_NANO 11692 diff --git a/Radio/HW/RtlSdr/r820/include/reg_field.h b/Radio/HW/RtlSdr/r820/include/reg_field.h new file mode 100644 index 0000000..18a6922 --- /dev/null +++ b/Radio/HW/RtlSdr/r820/include/reg_field.h @@ -0,0 +1,60 @@ +#ifndef _REG_FIELD_H +#define _REG_FIELD_H + +#include <stdint.h> +#include <stdarg.h> + +enum cmd_op { + CMD_OP_GET = (1 << 0), + CMD_OP_SET = (1 << 1), + CMD_OP_EXEC = (1 << 2), +}; + +enum pstate { + ST_IN_CMD, + ST_IN_ARG, +}; + +struct strbuf { + uint8_t idx; + char buf[32]; +}; + +struct cmd_state { + struct strbuf cmd; + struct strbuf arg; + enum pstate state; + void (*out)(const char *format, va_list ap); +}; + +struct cmd { + const char *cmd; + uint32_t ops; + int (*cb)(struct cmd_state *cs, enum cmd_op op, const char *cmd, + int argc, char **argv); + const char *help; +}; + +/* structure describing a field in a register */ +struct reg_field { + uint8_t reg; + uint8_t shift; + uint8_t width; +}; + +struct reg_field_ops { + const struct reg_field *fields; + const char **field_names; + uint32_t num_fields; + void *data; + int (*write_cb)(void *data, uint32_t reg, uint32_t val); + uint32_t (*read_cb)(void *data, uint32_t reg); +}; + +uint32_t reg_field_read(struct reg_field_ops *ops, struct reg_field *field); +int reg_field_write(struct reg_field_ops *ops, struct reg_field *field, uint32_t val); +int reg_field_cmd(struct cmd_state *cs, enum cmd_op op, + const char *cmd, int argc, char **argv, + struct reg_field_ops *ops); + +#endif diff --git a/Radio/HW/RtlSdr/r820/include/rtl-sdr.h b/Radio/HW/RtlSdr/r820/include/rtl-sdr.h new file mode 100644 index 0000000..44f2d59 --- /dev/null +++ b/Radio/HW/RtlSdr/r820/include/rtl-sdr.h @@ -0,0 +1,408 @@ +/* + * rtl-sdr, turns your Realtek RTL2832 based DVB dongle into a SDR receiver + * Copyright (C) 2012-2013 by Steve Markgraf <steve@steve-m.de> + * Copyright (C) 2012 by Dimitri Stolnikov <horiz0n@gmx.net> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef __RTL_SDR_H +#define __RTL_SDR_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include <stdint.h> +#include <rtl-sdr_export.h> + +typedef struct rtlsdr_dev rtlsdr_dev_t; + +RTLSDR_API uint32_t rtlsdr_get_device_count(void); +//uint32_t rtlsdr_get_device_count(void); + +RTLSDR_API const char* rtlsdr_get_device_name(uint32_t index); + +/*! + * Get USB device strings. + * + * NOTE: The string arguments must provide space for up to 256 bytes. + * + * \param index the device index + * \param manufact manufacturer name, may be NULL + * \param product product name, may be NULL + * \param serial serial number, may be NULL + * \return 0 on success + */ +RTLSDR_API int rtlsdr_get_device_usb_strings(uint32_t index, + char *manufact, + char *product, + char *serial); + +/*! + * Get device index by USB serial string descriptor. + * + * \param serial serial string of the device + * \return device index of first device where the name matched + * \return -1 if name is NULL + * \return -2 if no devices were found at all + * \return -3 if devices were found, but none with matching name + */ +RTLSDR_API int rtlsdr_get_index_by_serial(const char *serial); + +RTLSDR_API int rtlsdr_open(rtlsdr_dev_t **dev, uint32_t index); + +RTLSDR_API int rtlsdr_close(rtlsdr_dev_t *dev); + +/* configuration functions */ + +/*! + * Set crystal oscillator frequencies used for the RTL2832 and the tuner IC. + * + * Usually both ICs use the same clock. Changing the clock may make sense if + * you are applying an external clock to the tuner or to compensate the + * frequency (and samplerate) error caused by the original (cheap) crystal. + * + * NOTE: Call this function only if you fully understand the implications. + * + * \param dev the device handle given by rtlsdr_open() + * \param rtl_freq frequency value used to clock the RTL2832 in Hz + * \param tuner_freq frequency value used to clock the tuner IC in Hz + * \return 0 on success + */ +RTLSDR_API int rtlsdr_set_xtal_freq(rtlsdr_dev_t *dev, uint32_t rtl_freq, + uint32_t tuner_freq); + +/*! + * Get crystal oscillator frequencies used for the RTL2832 and the tuner IC. + * + * Usually both ICs use the same clock. + * + * \param dev the device handle given by rtlsdr_open() + * \param rtl_freq frequency value used to clock the RTL2832 in Hz + * \param tuner_freq frequency value used to clock the tuner IC in Hz + * \return 0 on success + */ +RTLSDR_API int rtlsdr_get_xtal_freq(rtlsdr_dev_t *dev, uint32_t *rtl_freq, + uint32_t *tuner_freq); + +/*! + * Get USB device strings. + * + * NOTE: The string arguments must provide space for up to 256 bytes. + * + * \param dev the device handle given by rtlsdr_open() + * \param manufact manufacturer name, may be NULL + * \param product product name, may be NULL + * \param serial serial number, may be NULL + * \return 0 on success + */ +RTLSDR_API int rtlsdr_get_usb_strings(rtlsdr_dev_t *dev, char *manufact, + char *product, char *serial); + +/*! + * Write the device EEPROM + * + * \param dev the device handle given by rtlsdr_open() + * \param data buffer of data to be written + * \param offset address where the data should be written + * \param len length of the data + * \return 0 on success + * \return -1 if device handle is invalid + * \return -2 if EEPROM size is exceeded + * \return -3 if no EEPROM was found + */ + +RTLSDR_API int rtlsdr_write_eeprom(rtlsdr_dev_t *dev, uint8_t *data, + uint8_t offset, uint16_t len); + +/*! + * Read the device EEPROM + * + * \param dev the device handle given by rtlsdr_open() + * \param data buffer where the data should be written + * \param offset address where the data should be read from + * \param len length of the data + * \return 0 on success + * \return -1 if device handle is invalid + * \return -2 if EEPROM size is exceeded + * \return -3 if no EEPROM was found + */ + +RTLSDR_API int rtlsdr_read_eeprom(rtlsdr_dev_t *dev, uint8_t *data, + uint8_t offset, uint16_t len); + +RTLSDR_API int rtlsdr_set_center_freq(rtlsdr_dev_t *dev, uint32_t freq); + +/*! + * Get actual frequency the device is tuned to. + * + * \param dev the device handle given by rtlsdr_open() + * \return 0 on error, frequency in Hz otherwise + */ +RTLSDR_API uint32_t rtlsdr_get_center_freq(rtlsdr_dev_t *dev); + +/*! + * Set the frequency correction value for the device. + * + * \param dev the device handle given by rtlsdr_open() + * \param ppm correction value in parts per million (ppm) + * \return 0 on success + */ +RTLSDR_API int rtlsdr_set_freq_correction(rtlsdr_dev_t *dev, int ppm); + +/*! + * Get actual frequency correction value of the device. + * + * \param dev the device handle given by rtlsdr_open() + * \return correction value in parts per million (ppm) + */ +RTLSDR_API int rtlsdr_get_freq_correction(rtlsdr_dev_t *dev); + +enum rtlsdr_tuner { + RTLSDR_TUNER_UNKNOWN = 0, + RTLSDR_TUNER_E4000, + RTLSDR_TUNER_FC0012, + RTLSDR_TUNER_FC0013, + RTLSDR_TUNER_FC2580, + RTLSDR_TUNER_R820T, + RTLSDR_TUNER_R828D +}; + +/*! + * Get the tuner type. + * + * \param dev the device handle given by rtlsdr_open() + * \return RTLSDR_TUNER_UNKNOWN on error, tuner type otherwise + */ +RTLSDR_API enum rtlsdr_tuner rtlsdr_get_tuner_type(rtlsdr_dev_t *dev); + +/*! + * Get a list of gains supported by the tuner. + * + * NOTE: The gains argument must be preallocated by the caller. If NULL is + * being given instead, the number of available gain values will be returned. + * + * \param dev the device handle given by rtlsdr_open() + * \param gains array of gain values. In tenths of a dB, 115 means 11.5 dB. + * \return <= 0 on error, number of available (returned) gain values otherwise + */ +RTLSDR_API int rtlsdr_get_tuner_gains(rtlsdr_dev_t *dev, int *gains); + +/*! + * Set the gain for the device. + * Manual gain mode must be enabled for this to work. + * + * Valid gain values (in tenths of a dB) for the E4000 tuner: + * -10, 15, 40, 65, 90, 115, 140, 165, 190, + * 215, 240, 290, 340, 420, 430, 450, 470, 490 + * + * Valid gain values may be queried with \ref rtlsdr_get_tuner_gains function. + * + * \param dev the device handle given by rtlsdr_open() + * \param gain in tenths of a dB, 115 means 11.5 dB. + * \return 0 on success + */ +RTLSDR_API int rtlsdr_set_tuner_gain(rtlsdr_dev_t *dev, int gain); + +/*! + * Set the bandwidth for the device. + * + * \param dev the device handle given by rtlsdr_open() + * \param bw bandwidth in Hz. Zero means automatic BW selection. + * \return 0 on success + */ +RTLSDR_API int rtlsdr_set_tuner_bandwidth(rtlsdr_dev_t *dev, uint32_t bw); + +/*! + * Get actual gain the device is configured to. + * + * \param dev the device handle given by rtlsdr_open() + * \return 0 on error, gain in tenths of a dB, 115 means 11.5 dB. + */ +RTLSDR_API int rtlsdr_get_tuner_gain(rtlsdr_dev_t *dev); + +/*! + * Set the intermediate frequency gain for the device. + * + * \param dev the device handle given by rtlsdr_open() + * \param stage intermediate frequency gain stage number (1 to 6 for E4000) + * \param gain in tenths of a dB, -30 means -3.0 dB. + * \return 0 on success + */ +RTLSDR_API int rtlsdr_set_tuner_if_gain(rtlsdr_dev_t *dev, int stage, int gain); + +/*! + * Set the gain mode (automatic/manual) for the device. + * Manual gain mode must be enabled for the gain setter function to work. + * + * \param dev the device handle given by rtlsdr_open() + * \param manual gain mode, 1 means manual gain mode shall be enabled. + * \return 0 on success + */ +RTLSDR_API int rtlsdr_set_tuner_gain_mode(rtlsdr_dev_t *dev, int manual); + +/*! + * Set the sample rate for the device, also selects the baseband filters + * according to the requested sample rate for tuners where this is possible. + * + * \param dev the device handle given by rtlsdr_open() + * \param rate the sample rate to be set, possible values are: + * 225001 - 300000 Hz + * 900001 - 3200000 Hz + * sample loss is to be expected for rates > 2400000 + * \return 0 on success, -EINVAL on invalid rate + */ +RTLSDR_API int rtlsdr_set_sample_rate(rtlsdr_dev_t *dev, uint32_t rate); + +/*! + * Get actual sample rate the device is configured to. + * + * \param dev the device handle given by rtlsdr_open() + * \return 0 on error, sample rate in Hz otherwise + */ +RTLSDR_API uint32_t rtlsdr_get_sample_rate(rtlsdr_dev_t *dev); + +/*! + * Enable test mode that returns an 8 bit counter instead of the samples. + * The counter is generated inside the RTL2832. + * + * \param dev the device handle given by rtlsdr_open() + * \param on mode, 1 means enabled, 0 disabled + * \return 0 on success + */ +RTLSDR_API int rtlsdr_set_testmode(rtlsdr_dev_t *dev, int on); + +/*! + * Enable or disable the internal digital AGC of the RTL2832. + * + * \param dev the device handle given by rtlsdr_open() + * \param on AGC mode, 1 means enabled, 0 disabled + * \return 0 on success + */ +RTLSDR_API int rtlsdr_set_agc_mode(rtlsdr_dev_t *dev, int on); + +/*! + * Enable or disable the direct sampling mode. When enabled, the IF mode + * of the RTL2832 is activated, and rtlsdr_set_center_freq() will control + * the IF-frequency of the DDC, which can be used to tune from 0 to 28.8 MHz + * (xtal frequency of the RTL2832). + * + * \param dev the device handle given by rtlsdr_open() + * \param on 0 means disabled, 1 I-ADC input enabled, 2 Q-ADC input enabled + * \return 0 on success + */ +RTLSDR_API int rtlsdr_set_direct_sampling(rtlsdr_dev_t *dev, int on); + +/*! + * Get state of the direct sampling mode + * + * \param dev the device handle given by rtlsdr_open() + * \return -1 on error, 0 means disabled, 1 I-ADC input enabled + * 2 Q-ADC input enabled + */ +RTLSDR_API int rtlsdr_get_direct_sampling(rtlsdr_dev_t *dev); + +/*! + * Enable or disable offset tuning for zero-IF tuners, which allows to avoid + * problems caused by the DC offset of the ADCs and 1/f noise. + * + * \param dev the device handle given by rtlsdr_open() + * \param on 0 means disabled, 1 enabled + * \return 0 on success + */ +RTLSDR_API int rtlsdr_set_offset_tuning(rtlsdr_dev_t *dev, int on); + +/*! + * Get state of the offset tuning mode + * + * \param dev the device handle given by rtlsdr_open() + * \return -1 on error, 0 means disabled, 1 enabled + */ +RTLSDR_API int rtlsdr_get_offset_tuning(rtlsdr_dev_t *dev); + +/* streaming functions */ + +RTLSDR_API int rtlsdr_reset_buffer(rtlsdr_dev_t *dev); + +RTLSDR_API int rtlsdr_read_sync(rtlsdr_dev_t *dev, void *buf, int len, int *n_read); + +typedef void(*rtlsdr_read_async_cb_t)(unsigned char *buf, uint32_t len, void *ctx); + +/*! + * Read samples from the device asynchronously. This function will block until + * it is being canceled using rtlsdr_cancel_async() + * + * NOTE: This function is deprecated and is subject for removal. + * + * \param dev the device handle given by rtlsdr_open() + * \param cb callback function to return received samples + * \param ctx user specific context to pass via the callback function + * \return 0 on success + */ +RTLSDR_API int rtlsdr_wait_async(rtlsdr_dev_t *dev, rtlsdr_read_async_cb_t cb, void *ctx); + +/*! + * Read samples from the device asynchronously. This function will block until + * it is being canceled using rtlsdr_cancel_async() + * + * \param dev the device handle given by rtlsdr_open() + * \param cb callback function to return received samples + * \param ctx user specific context to pass via the callback function + * \param buf_num optional buffer count, buf_num * buf_len = overall buffer size + * set to 0 for default buffer count (15) + * \param buf_len optional buffer length, must be multiple of 512, + * should be a multiple of 16384 (URB size), set to 0 + * for default buffer length (16 * 32 * 512) + * \return 0 on success + */ +RTLSDR_API int rtlsdr_read_async(rtlsdr_dev_t *dev, + rtlsdr_read_async_cb_t cb, + void *ctx, + uint32_t buf_num, + uint32_t buf_len); + +/*! + * Cancel all pending asynchronous operations on the device. + * + * \param dev the device handle given by rtlsdr_open() + * \return 0 on success + */ +RTLSDR_API int rtlsdr_cancel_async(rtlsdr_dev_t *dev); + +/*! + * Enable or disable the bias tee on GPIO PIN 0. + * + * \param dev the device handle given by rtlsdr_open() + * \param on 1 for Bias T on. 0 for Bias T off. + * \return -1 if device is not initialized. 0 otherwise. + */ +RTLSDR_API int rtlsdr_set_bias_tee(rtlsdr_dev_t *dev, int on); + +/*! + * Enable or disable the bias tee on the given GPIO pin. + * + * \param dev the device handle given by rtlsdr_open() + * \param gpio the gpio pin to configure as a Bias T control. + * \param on 1 for Bias T on. 0 for Bias T off. + * \return -1 if device is not initialized. 0 otherwise. + */ +RTLSDR_API int rtlsdr_set_bias_tee_gpio(rtlsdr_dev_t *dev, int gpio, int on); + + +#ifdef __cplusplus +} +#endif + +#endif /* __RTL_SDR_H */ diff --git a/Radio/HW/RtlSdr/r820/include/rtl-sdr_export.h b/Radio/HW/RtlSdr/r820/include/rtl-sdr_export.h new file mode 100644 index 0000000..69e178d --- /dev/null +++ b/Radio/HW/RtlSdr/r820/include/rtl-sdr_export.h @@ -0,0 +1,47 @@ +/* + * rtl-sdr, turns your Realtek RTL2832 based DVB dongle into a SDR receiver + * Copyright (C) 2012 by Hoernchen <la@tfc-server.de> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef RTLSDR_EXPORT_H +#define RTLSDR_EXPORT_H + +#if defined __GNUC__ +# if __GNUC__ >= 4 +# define __SDR_EXPORT __attribute__((visibility("default"))) +# define __SDR_IMPORT __attribute__((visibility("default"))) +# else +# define __SDR_EXPORT +# define __SDR_IMPORT +# endif +#elif _MSC_VER +# define __SDR_EXPORT __declspec(dllexport) +# define __SDR_IMPORT __declspec(dllimport) +#else +# define __SDR_EXPORT +# define __SDR_IMPORT +#endif + +#ifndef rtlsdr_STATIC +# ifdef rtlsdr_EXPORTS +# define RTLSDR_API __SDR_EXPORT +# else +# define RTLSDR_API __SDR_IMPORT +# endif +#else +#define RTLSDR_API +#endif +#endif /* RTLSDR_EXPORT_H */ diff --git a/Radio/HW/RtlSdr/r820/include/rtlsdr_i2c.h b/Radio/HW/RtlSdr/r820/include/rtlsdr_i2c.h new file mode 100644 index 0000000..7676689 --- /dev/null +++ b/Radio/HW/RtlSdr/r820/include/rtlsdr_i2c.h @@ -0,0 +1,8 @@ +#ifndef __I2C_H +#define __I2C_H + +uint32_t rtlsdr_get_tuner_clock(void *dev); +int rtlsdr_i2c_write_fn(void *dev, uint8_t addr, uint8_t *buf, int len); +int rtlsdr_i2c_read_fn(void *dev, uint8_t addr, uint8_t *buf, int len); + +#endif diff --git a/Radio/HW/RtlSdr/r820/include/tuner_e4k.h b/Radio/HW/RtlSdr/r820/include/tuner_e4k.h new file mode 100644 index 0000000..79591ce --- /dev/null +++ b/Radio/HW/RtlSdr/r820/include/tuner_e4k.h @@ -0,0 +1,222 @@ +#ifndef _E4K_TUNER_H +#define _E4K_TUNER_H + +/* + * Elonics E4000 tuner driver + * + * (C) 2011-2012 by Harald Welte <laforge@gnumonks.org> + * (C) 2012 by Sylvain Munaut <tnt@246tNt.com> + * (C) 2012 by Hoernchen <la@tfc-server.de> + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#define E4K_I2C_ADDR 0xc8 +#define E4K_CHECK_ADDR 0x02 +#define E4K_CHECK_VAL 0x40 + +enum e4k_reg { + E4K_REG_MASTER1 = 0x00, + E4K_REG_MASTER2 = 0x01, + E4K_REG_MASTER3 = 0x02, + E4K_REG_MASTER4 = 0x03, + E4K_REG_MASTER5 = 0x04, + E4K_REG_CLK_INP = 0x05, + E4K_REG_REF_CLK = 0x06, + E4K_REG_SYNTH1 = 0x07, + E4K_REG_SYNTH2 = 0x08, + E4K_REG_SYNTH3 = 0x09, + E4K_REG_SYNTH4 = 0x0a, + E4K_REG_SYNTH5 = 0x0b, + E4K_REG_SYNTH6 = 0x0c, + E4K_REG_SYNTH7 = 0x0d, + E4K_REG_SYNTH8 = 0x0e, + E4K_REG_SYNTH9 = 0x0f, + E4K_REG_FILT1 = 0x10, + E4K_REG_FILT2 = 0x11, + E4K_REG_FILT3 = 0x12, + // gap + E4K_REG_GAIN1 = 0x14, + E4K_REG_GAIN2 = 0x15, + E4K_REG_GAIN3 = 0x16, + E4K_REG_GAIN4 = 0x17, + // gap + E4K_REG_AGC1 = 0x1a, + E4K_REG_AGC2 = 0x1b, + E4K_REG_AGC3 = 0x1c, + E4K_REG_AGC4 = 0x1d, + E4K_REG_AGC5 = 0x1e, + E4K_REG_AGC6 = 0x1f, + E4K_REG_AGC7 = 0x20, + E4K_REG_AGC8 = 0x21, + // gap + E4K_REG_AGC11 = 0x24, + E4K_REG_AGC12 = 0x25, + // gap + E4K_REG_DC1 = 0x29, + E4K_REG_DC2 = 0x2a, + E4K_REG_DC3 = 0x2b, + E4K_REG_DC4 = 0x2c, + E4K_REG_DC5 = 0x2d, + E4K_REG_DC6 = 0x2e, + E4K_REG_DC7 = 0x2f, + E4K_REG_DC8 = 0x30, + // gap + E4K_REG_QLUT0 = 0x50, + E4K_REG_QLUT1 = 0x51, + E4K_REG_QLUT2 = 0x52, + E4K_REG_QLUT3 = 0x53, + // gap + E4K_REG_ILUT0 = 0x60, + E4K_REG_ILUT1 = 0x61, + E4K_REG_ILUT2 = 0x62, + E4K_REG_ILUT3 = 0x63, + // gap + E4K_REG_DCTIME1 = 0x70, + E4K_REG_DCTIME2 = 0x71, + E4K_REG_DCTIME3 = 0x72, + E4K_REG_DCTIME4 = 0x73, + E4K_REG_PWM1 = 0x74, + E4K_REG_PWM2 = 0x75, + E4K_REG_PWM3 = 0x76, + E4K_REG_PWM4 = 0x77, + E4K_REG_BIAS = 0x78, + E4K_REG_CLKOUT_PWDN = 0x7a, + E4K_REG_CHFILT_CALIB = 0x7b, + E4K_REG_I2C_REG_ADDR = 0x7d, + // FIXME +}; + +#define E4K_MASTER1_RESET (1 << 0) +#define E4K_MASTER1_NORM_STBY (1 << 1) +#define E4K_MASTER1_POR_DET (1 << 2) + +#define E4K_SYNTH1_PLL_LOCK (1 << 0) +#define E4K_SYNTH1_BAND_SHIF 1 + +#define E4K_SYNTH7_3PHASE_EN (1 << 3) + +#define E4K_SYNTH8_VCOCAL_UPD (1 << 2) + +#define E4K_FILT3_DISABLE (1 << 5) + +#define E4K_AGC1_LIN_MODE (1 << 4) +#define E4K_AGC1_LNA_UPDATE (1 << 5) +#define E4K_AGC1_LNA_G_LOW (1 << 6) +#define E4K_AGC1_LNA_G_HIGH (1 << 7) + +#define E4K_AGC6_LNA_CAL_REQ (1 << 4) + +#define E4K_AGC7_MIX_GAIN_AUTO (1 << 0) +#define E4K_AGC7_GAIN_STEP_5dB (1 << 5) + +#define E4K_AGC8_SENS_LIN_AUTO (1 << 0) + +#define E4K_AGC11_LNA_GAIN_ENH (1 << 0) + +#define E4K_DC1_CAL_REQ (1 << 0) + +#define E4K_DC5_I_LUT_EN (1 << 0) +#define E4K_DC5_Q_LUT_EN (1 << 1) +#define E4K_DC5_RANGE_DET_EN (1 << 2) +#define E4K_DC5_RANGE_EN (1 << 3) +#define E4K_DC5_TIMEVAR_EN (1 << 4) + +#define E4K_CLKOUT_DISABLE 0x96 + +#define E4K_CHFCALIB_CMD (1 << 0) + +#define E4K_AGC1_MOD_MASK 0xF + +enum e4k_agc_mode { + E4K_AGC_MOD_SERIAL = 0x0, + E4K_AGC_MOD_IF_PWM_LNA_SERIAL = 0x1, + E4K_AGC_MOD_IF_PWM_LNA_AUTONL = 0x2, + E4K_AGC_MOD_IF_PWM_LNA_SUPERV = 0x3, + E4K_AGC_MOD_IF_SERIAL_LNA_PWM = 0x4, + E4K_AGC_MOD_IF_PWM_LNA_PWM = 0x5, + E4K_AGC_MOD_IF_DIG_LNA_SERIAL = 0x6, + E4K_AGC_MOD_IF_DIG_LNA_AUTON = 0x7, + E4K_AGC_MOD_IF_DIG_LNA_SUPERV = 0x8, + E4K_AGC_MOD_IF_SERIAL_LNA_AUTON = 0x9, + E4K_AGC_MOD_IF_SERIAL_LNA_SUPERV = 0xa, +}; + +enum e4k_band { + E4K_BAND_VHF2 = 0, + E4K_BAND_VHF3 = 1, + E4K_BAND_UHF = 2, + E4K_BAND_L = 3, +}; + +enum e4k_mixer_filter_bw { + E4K_F_MIX_BW_27M = 0, + E4K_F_MIX_BW_4M6 = 8, + E4K_F_MIX_BW_4M2 = 9, + E4K_F_MIX_BW_3M8 = 10, + E4K_F_MIX_BW_3M4 = 11, + E4K_F_MIX_BW_3M = 12, + E4K_F_MIX_BW_2M7 = 13, + E4K_F_MIX_BW_2M3 = 14, + E4K_F_MIX_BW_1M9 = 15, +}; + +enum e4k_if_filter { + E4K_IF_FILTER_MIX, + E4K_IF_FILTER_CHAN, + E4K_IF_FILTER_RC +}; +struct e4k_pll_params { + uint32_t fosc; + uint32_t intended_flo; + uint32_t flo; + uint16_t x; + uint8_t z; + uint8_t r; + uint8_t r_idx; + uint8_t threephase; +}; + +struct e4k_state { + void *i2c_dev; + uint8_t i2c_addr; + enum e4k_band band; + struct e4k_pll_params vco; + void *rtl_dev; +}; + +int e4k_init(struct e4k_state *e4k); +int e4k_standby(struct e4k_state *e4k, int enable); +int e4k_if_gain_set(struct e4k_state *e4k, uint8_t stage, int8_t value); +int e4k_mixer_gain_set(struct e4k_state *e4k, int8_t value); +int e4k_commonmode_set(struct e4k_state *e4k, int8_t value); +int e4k_tune_freq(struct e4k_state *e4k, uint32_t freq); +int e4k_tune_params(struct e4k_state *e4k, struct e4k_pll_params *p); +uint32_t e4k_compute_pll_params(struct e4k_pll_params *oscp, uint32_t fosc, uint32_t intended_flo); +int e4k_if_filter_bw_get(struct e4k_state *e4k, enum e4k_if_filter filter); +int e4k_if_filter_bw_set(struct e4k_state *e4k, enum e4k_if_filter filter, + uint32_t bandwidth); +int e4k_if_filter_chan_enable(struct e4k_state *e4k, int on); +int e4k_rf_filter_set(struct e4k_state *e4k); + +int e4k_manual_dc_offset(struct e4k_state *e4k, int8_t iofs, int8_t irange, int8_t qofs, int8_t qrange); +int e4k_dc_offset_calibrate(struct e4k_state *e4k); +int e4k_dc_offset_gen_table(struct e4k_state *e4k); + +int e4k_set_lna_gain(struct e4k_state *e4k, int32_t gain); +int e4k_enable_manual_gain(struct e4k_state *e4k, uint8_t manual); +int e4k_set_enh_gain(struct e4k_state *e4k, int32_t gain); +#endif /* _E4K_TUNER_H */ diff --git a/Radio/HW/RtlSdr/r820/include/tuner_fc0012.h b/Radio/HW/RtlSdr/r820/include/tuner_fc0012.h new file mode 100644 index 0000000..9dd5356 --- /dev/null +++ b/Radio/HW/RtlSdr/r820/include/tuner_fc0012.h @@ -0,0 +1,36 @@ +/* + * Fitipower FC0012 tuner driver + * + * Copyright (C) 2012 Hans-Frieder Vogt <hfvogt@gmx.net> + * + * modified for use in librtlsdr + * Copyright (C) 2012 Steve Markgraf <steve@steve-m.de> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#ifndef _FC0012_H_ +#define _FC0012_H_ + +#define FC0012_I2C_ADDR 0xc6 +#define FC0012_CHECK_ADDR 0x00 +#define FC0012_CHECK_VAL 0xa1 + +int fc0012_init(void *dev); +int fc0012_set_params(void *dev, uint32_t freq, uint32_t bandwidth); +int fc0012_set_gain(void *dev, int gain); + +#endif diff --git a/Radio/HW/RtlSdr/r820/include/tuner_fc0013.h b/Radio/HW/RtlSdr/r820/include/tuner_fc0013.h new file mode 100644 index 0000000..68a26ee --- /dev/null +++ b/Radio/HW/RtlSdr/r820/include/tuner_fc0013.h @@ -0,0 +1,37 @@ +/* + * Fitipower FC0013 tuner driver + * + * Copyright (C) 2012 Hans-Frieder Vogt <hfvogt@gmx.net> + * + * modified for use in librtlsdr + * Copyright (C) 2012 Steve Markgraf <steve@steve-m.de> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#ifndef _FC0013_H_ +#define _FC0013_H_ + +#define FC0013_I2C_ADDR 0xc6 +#define FC0013_CHECK_ADDR 0x00 +#define FC0013_CHECK_VAL 0xa3 + +int fc0013_init(void *dev); +int fc0013_set_params(void *dev, uint32_t freq, uint32_t bandwidth); +int fc0013_set_gain_mode(void *dev, int manual); +int fc0013_set_lna_gain(void *dev, int gain); + +#endif diff --git a/Radio/HW/RtlSdr/r820/include/tuner_fc2580.h b/Radio/HW/RtlSdr/r820/include/tuner_fc2580.h new file mode 100644 index 0000000..9ebd935 --- /dev/null +++ b/Radio/HW/RtlSdr/r820/include/tuner_fc2580.h @@ -0,0 +1,127 @@ +#ifndef __TUNER_FC2580_H +#define __TUNER_FC2580_H + +#define BORDER_FREQ 2600000 //2.6GHz : The border frequency which determines whether Low VCO or High VCO is used +#define USE_EXT_CLK 0 //0 : Use internal XTAL Oscillator / 1 : Use External Clock input +#define OFS_RSSI 57 + +#define FC2580_I2C_ADDR 0xac +#define FC2580_CHECK_ADDR 0x01 +#define FC2580_CHECK_VAL 0x56 + +typedef enum { + FC2580_UHF_BAND, + FC2580_L_BAND, + FC2580_VHF_BAND, + FC2580_NO_BAND +} fc2580_band_type; + +typedef enum { + FC2580_FCI_FAIL, + FC2580_FCI_SUCCESS +} fc2580_fci_result_type; + +enum FUNCTION_STATUS +{ + FUNCTION_SUCCESS, + FUNCTION_ERROR, +}; + +extern void fc2580_wait_msec(void *pTuner, int a); + +fc2580_fci_result_type fc2580_i2c_write(void *pTuner, unsigned char reg, unsigned char val); +fc2580_fci_result_type fc2580_i2c_read(void *pTuner, unsigned char reg, unsigned char *read_data); + +/*============================================================================== + fc2580 initial setting + + This function is a generic function which gets called to initialize + + fc2580 in DVB-H mode or L-Band TDMB mode + + <input parameter> + + ifagc_mode + type : integer + 1 : Internal AGC + 2 : Voltage Control Mode + +==============================================================================*/ +fc2580_fci_result_type fc2580_set_init(void *pTuner, int ifagc_mode, unsigned int freq_xtal ); + +/*============================================================================== + fc2580 frequency setting + + This function is a generic function which gets called to change LO Frequency + + of fc2580 in DVB-H mode or L-Band TDMB mode + + <input parameter> + + f_lo + Value of target LO Frequency in 'kHz' unit + ex) 2.6GHz = 2600000 + +==============================================================================*/ +fc2580_fci_result_type fc2580_set_freq(void *pTuner, unsigned int f_lo, unsigned int freq_xtal ); + + +/*============================================================================== + fc2580 filter BW setting + + This function is a generic function which gets called to change Bandwidth + + frequency of fc2580's channel selection filter + + <input parameter> + + filter_bw + 1 : 1.53MHz(TDMB) + 6 : 6MHz + 7 : 7MHz + 8 : 7.8MHz + + +==============================================================================*/ +fc2580_fci_result_type fc2580_set_filter( void *pTuner, unsigned char filter_bw, unsigned int freq_xtal ); + +// The following context is FC2580 tuner API source code +// Definitions + +// AGC mode +enum FC2580_AGC_MODE +{ + FC2580_AGC_INTERNAL = 1, + FC2580_AGC_EXTERNAL = 2, +}; + + +// Bandwidth mode +enum FC2580_BANDWIDTH_MODE +{ + FC2580_BANDWIDTH_1530000HZ = 1, + FC2580_BANDWIDTH_6000000HZ = 6, + FC2580_BANDWIDTH_7000000HZ = 7, + FC2580_BANDWIDTH_8000000HZ = 8, +}; + +// Manipulaing functions +int +fc2580_Initialize( + void *pTuner + ); + +int +fc2580_SetRfFreqHz( + void *pTuner, + unsigned long RfFreqHz + ); + +// Extra manipulaing functions +int +fc2580_SetBandwidthMode( + void *pTuner, + int BandwidthMode + ); + +#endif diff --git a/Radio/HW/RtlSdr/r820/include/tuner_r82xx.h b/Radio/HW/RtlSdr/r820/include/tuner_r82xx.h new file mode 100644 index 0000000..f6c206a --- /dev/null +++ b/Radio/HW/RtlSdr/r820/include/tuner_r82xx.h @@ -0,0 +1,120 @@ +/* + * Rafael Micro R820T/R828D driver + * + * Copyright (C) 2013 Mauro Carvalho Chehab <mchehab@redhat.com> + * Copyright (C) 2013 Steve Markgraf <steve@steve-m.de> + * + * This driver is a heavily modified version of the driver found in the + * Linux kernel: + * http://git.linuxtv.org/linux-2.6.git/history/HEAD:/drivers/media/tuners/r820t.c + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef R82XX_H +#define R82XX_H + +#define R820T_I2C_ADDR 0x34 +#define R828D_I2C_ADDR 0x74 +#define R828D_XTAL_FREQ 16000000 + +#define R82XX_CHECK_ADDR 0x00 +#define R82XX_CHECK_VAL 0x69 + +#define R82XX_IF_FREQ 3570000 + +#define REG_SHADOW_START 5 +#define NUM_REGS 30 +#define NUM_IMR 5 +#define IMR_TRIAL 9 + +#define VER_NUM 49 + +enum r82xx_chip { + CHIP_R820T, + CHIP_R620D, + CHIP_R828D, + CHIP_R828, + CHIP_R828S, + CHIP_R820C, +}; + +enum r82xx_tuner_type { + TUNER_RADIO = 1, + TUNER_ANALOG_TV, + TUNER_DIGITAL_TV +}; + +enum r82xx_xtal_cap_value { + XTAL_LOW_CAP_30P = 0, + XTAL_LOW_CAP_20P, + XTAL_LOW_CAP_10P, + XTAL_LOW_CAP_0P, + XTAL_HIGH_CAP_0P +}; + +struct r82xx_config { + uint8_t i2c_addr; + uint32_t xtal; + enum r82xx_chip rafael_chip; + unsigned int max_i2c_msg_len; + int use_predetect; +}; + +struct r82xx_priv { + struct r82xx_config *cfg; + + uint8_t regs[NUM_REGS]; + uint8_t buf[NUM_REGS + 1]; + enum r82xx_xtal_cap_value xtal_cap_sel; + uint16_t pll; /* kHz */ + uint32_t int_freq; + uint8_t fil_cal_code; + uint8_t input; + int has_lock; + int init_done; + + /* Store current mode */ + uint32_t delsys; + enum r82xx_tuner_type type; + + uint32_t bw; /* in MHz */ + + void *rtl_dev; +}; + +struct r82xx_freq_range { + uint32_t freq; + uint8_t open_d; + uint8_t rf_mux_ploy; + uint8_t tf_c; + uint8_t xtal_cap20p; + uint8_t xtal_cap10p; + uint8_t xtal_cap0p; +}; + +enum r82xx_delivery_system { + SYS_UNDEFINED, + SYS_DVBT, + SYS_DVBT2, + SYS_ISDBT, +}; + +int r82xx_standby(struct r82xx_priv *priv); +int r82xx_init(struct r82xx_priv *priv); +int r82xx_set_freq(struct r82xx_priv *priv, uint32_t freq); +int r82xx_set_gain(struct r82xx_priv *priv, int set_manual_gain, int gain); +int r82xx_set_bandwidth(struct r82xx_priv *priv, int bandwidth, uint32_t rate); + +#endif diff --git a/Radio/HW/RtlSdr/r820/r820.swift b/Radio/HW/RtlSdr/r820/r820.swift new file mode 100644 index 0000000..f5e1177 --- /dev/null +++ b/Radio/HW/RtlSdr/r820/r820.swift @@ -0,0 +1,198 @@ +// +// r820.swift +// r820sdr-init +// +// Created by Jacky Jack on 15/03/2022. +// + +//https://www.kodeco.com/7181017-unsafe-swift-using-pointers-and-interacting-with-c + +import Foundation +//import TestRtlSdr +import libr820 + +public class R820API { + + + func R820() { + print("Start device API") + } + + func getDeviceCount() -> UInt32 { + return rtlsdr_get_device_count() + } + + + +} + +public class R820Tuner { + var deviceInitalized:Bool = false + var dev: OpaquePointer? = nil + + /** + Function to initialise tunner + - Parameters: + - + */ + func R820Tuner() { + print("Initialise tunner") + } + + func open(index devId: UInt32) { + let err = rtlsdr_open(&dev,devId) + if (err != 0) { + print("Error couldnt open device") + return + } + deviceInitalized = true + } + + func close() { + if self.deviceInitalized { + rtlsdr_close(dev); + dev = nil + } + } + + func getDeviceCount()->UInt32 { + return rtlsdr_get_device_count(); + } + + /* + func getDeviceName(index: UInt32) -> String { + return rtlsdr_get_device_name(index) + } + */ + + /* + func getDeviceUsbString() { + return rtlsdr_get_device_usb_strings(); + }*/ + func getIndexBySerial(serial: String) -> Int32 + { + return rtlsdr_get_index_by_serial(serial);//is this ok? + } + + func setXtalFreq(rtlFreq: UInt32, tunnerFreq: UInt32) -> Int32 { + return rtlsdr_set_xtal_freq(dev, rtlFreq, tunnerFreq); + } + + func getXtalFreq(rtlFreq: UnsafeMutablePointer<UInt32>?, tunnerFreq: UnsafeMutablePointer<UInt32>?)->Int32{ + return rtlsdr_get_xtal_freq(dev, rtlFreq, tunnerFreq) + } + + func getUsbStrings(manufact: UnsafeMutablePointer<CChar>?, product: UnsafeMutablePointer<CChar>?, serial: UnsafeMutablePointer<CChar>?) -> Int32{ + return rtlsdr_get_usb_strings(dev, manufact, product, serial) + } + + func writeEeprom(data: UnsafeMutablePointer<UInt8>?,offset: UInt8, len: UInt16) -> Int32 { + return rtlsdr_write_eeprom(dev, data, offset, len) + } + + func readEeprom(data: UnsafeMutablePointer<UInt8>?,offset: UInt8, len: UInt16) -> Int32 { + return rtlsdr_read_eeprom(dev, data, offset, len) + } + + func setCenterFreq(freq: UInt32) -> Int32{ + return rtlsdr_set_center_freq(dev, freq); + } + + func getCenterFreq()->UInt32 { + return rtlsdr_get_center_freq(dev) + } + + func setFreqCorrection(ppm: Int32) -> Int32 { + return rtlsdr_set_freq_correction(dev, ppm) + } + + func getFreqCorrection() -> Int32 { + return rtlsdr_get_freq_correction(dev) + } + + + func getTunerType() -> rtlsdr_tuner { + return rtlsdr_get_tuner_type(dev) + } + + func getTunerGains(gains: UnsafeMutablePointer<Int32>?) -> Int32 { + return rtlsdr_get_tuner_gains(dev, gains) + } + + func setTunerGain(gain: Int32) -> Int32 { + return rtlsdr_set_tuner_gain(dev,gain); + } + + func setTunnerBandwidth(bw: UInt32) -> Int32 { + return rtlsdr_set_tuner_bandwidth(dev, bw) + } + + func setTunerIfGain(stage: Int32, gain: Int32) -> Int32 { + return rtlsdr_set_tuner_if_gain(dev,stage,gain) + } + + func setTunerGainMode(manual: Int32) -> Int32 { + return rtlsdr_set_tuner_gain_mode(dev, manual) + } + + func setSampleRate(samplerate: UInt32) -> Int32 { + return rtlsdr_set_sample_rate(dev, samplerate) + } + + func getSampleRate() -> UInt32 { + return rtlsdr_get_sample_rate(dev) + } + + func setTestmode(on: Int32) -> Int32 { + return rtlsdr_set_testmode(dev, on); + } + + func setAgcMode(on: Int32) -> Int32 { + return rtlsdr_set_agc_mode(dev, on); + } + + func setDirectSampling(on: Int32) -> Int32 { + return rtlsdr_set_direct_sampling(dev, on) + } + + func getDirectSampling() -> Int32 { + return rtlsdr_get_direct_sampling(dev) + } + + func setOffsetTuning(on: Int32) -> Int32 { + return rtlsdr_set_offset_tuning(dev, on) + } + + func getOffsetTuning() -> Int32 { + return rtlsdr_get_offset_tuning(dev) + } + + func resetBuffer() -> Int32 { + return rtlsdr_reset_buffer(dev) + } + + func readSync(buf:UnsafeMutableRawPointer?,len: Int32,n_read:UnsafeMutablePointer<Int32>?) -> Int32 { + return rtlsdr_read_sync(dev, buf, len, n_read); + } + + func waitAsync(cb: rtlsdr_read_async_cb_t?, ctx:UnsafeMutableRawPointer?) -> Int32{ + return rtlsdr_wait_async(dev,cb, ctx); + } + + func readAsync(cb: rtlsdr_read_async_cb_t,ctx:UnsafeMutableRawPointer?, buf_num: UInt32, buf_len: UInt32) -> Int32 { + return rtlsdr_read_async(dev, cb, ctx, buf_num, buf_len) + } + + func cancelAsync() -> Int32 { + return rtlsdr_cancel_async(dev); + } + + func setBiasTee(on: Int32) -> Int32 { + return rtlsdr_set_bias_tee(dev, on) + } + + func setBiasTeeGpio(gpio: Int32, on: Int32) -> Int32 { + return rtlsdr_set_bias_tee_gpio(dev,gpio, on) + } + +} diff --git a/Radio/HW/RtlSdr/r820/src/libr820-Bridging-Header.h b/Radio/HW/RtlSdr/r820/src/libr820-Bridging-Header.h new file mode 100644 index 0000000..fd7a194 --- /dev/null +++ b/Radio/HW/RtlSdr/r820/src/libr820-Bridging-Header.h @@ -0,0 +1,5 @@ +// +// Use this file to import your target's public headers that you would like to expose to Swift. +// + +#include "../include/rtl-sdr.h" diff --git a/Radio/HW/RtlSdr/r820/src/librtlsdr.c b/Radio/HW/RtlSdr/r820/src/librtlsdr.c new file mode 100644 index 0000000..2deb38c --- /dev/null +++ b/Radio/HW/RtlSdr/r820/src/librtlsdr.c @@ -0,0 +1,2023 @@ +/* + * rtl-sdr, turns your Realtek RTL2832 based DVB dongle into a SDR receiver + * Copyright (C) 2012-2014 by Steve Markgraf <steve@steve-m.de> + * Copyright (C) 2012 by Dimitri Stolnikov <horiz0n@gmx.net> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <errno.h> +#include <signal.h> +#include <string.h> +#include <stdio.h> +#include <stdlib.h> +#ifndef _WIN32 +#include <unistd.h> +#define min(a, b) (((a) < (b)) ? (a) : (b)) +#endif + +#include "libusb.h" + +/* + * All libusb callback functions should be marked with the LIBUSB_CALL macro + * to ensure that they are compiled with the same calling convention as libusb. + * + * If the macro isn't available in older libusb versions, we simply define it. + */ +#ifndef LIBUSB_CALL +#define LIBUSB_CALL +#endif + +/* two raised to the power of n */ +#define TWO_POW(n) ((double)(1ULL<<(n))) + +#include "rtl-sdr.h" +#include "tuner_e4k.h" +#include "tuner_fc0012.h" +#include "tuner_fc0013.h" +#include "tuner_fc2580.h" +#include "tuner_r82xx.h" + +typedef struct rtlsdr_tuner_iface { + /* tuner interface */ + int (*init)(void *); + int (*exit)(void *); + int (*set_freq)(void *, uint32_t freq /* Hz */); + int (*set_bw)(void *, int bw /* Hz */); + int (*set_gain)(void *, int gain /* tenth dB */); + int (*set_if_gain)(void *, int stage, int gain /* tenth dB */); + int (*set_gain_mode)(void *, int manual); +} rtlsdr_tuner_iface_t; + +enum rtlsdr_async_status { + RTLSDR_INACTIVE = 0, + RTLSDR_CANCELING, + RTLSDR_RUNNING +}; + +#define FIR_LEN 16 + +/* + * FIR coefficients. + * + * The filter is running at XTal frequency. It is symmetric filter with 32 + * coefficients. Only first 16 coefficients are specified, the other 16 + * use the same values but in reversed order. The first coefficient in + * the array is the outer one, the last, the last is the inner one. + * First 8 coefficients are 8 bit signed integers, the next 8 coefficients + * are 12 bit signed integers. All coefficients have the same weight. + * + * Default FIR coefficients used for DAB/FM by the Windows driver, + * the DVB driver uses different ones + */ +static const int fir_default[FIR_LEN] = { + -54, -36, -41, -40, -32, -14, 14, 53, /* 8 bit signed */ + 101, 156, 215, 273, 327, 372, 404, 421 /* 12 bit signed */ +}; + +struct rtlsdr_dev { + libusb_context *ctx; + struct libusb_device_handle *devh; + uint32_t xfer_buf_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 */ + int fir[FIR_LEN]; + int direct_sampling; + /* tuner context */ + enum rtlsdr_tuner tuner_type; + rtlsdr_tuner_iface_t *tuner; + uint32_t tun_xtal; /* Hz */ + uint32_t freq; /* Hz */ + uint32_t bw; + uint32_t offs_freq; /* Hz */ + int corr; /* ppm */ + int gain; /* tenth dB */ + struct e4k_state e4k_s; + struct r82xx_config r82xx_c; + struct r82xx_priv r82xx_p; + /* status */ + int dev_lost; + int driver_active; + unsigned int xfer_errors; +}; + +void rtlsdr_set_gpio_bit(rtlsdr_dev_t *dev, uint8_t gpio, int val); +static int rtlsdr_set_if_freq(rtlsdr_dev_t *dev, uint32_t freq); + +/* generic tuner interface functions, shall be moved to the tuner implementations */ +int e4000_init(void *dev) { + rtlsdr_dev_t* devt = (rtlsdr_dev_t*)dev; + devt->e4k_s.i2c_addr = E4K_I2C_ADDR; + rtlsdr_get_xtal_freq(devt, NULL, &devt->e4k_s.vco.fosc); + devt->e4k_s.rtl_dev = dev; + return e4k_init(&devt->e4k_s); +} +int e4000_exit(void *dev) { + rtlsdr_dev_t* devt = (rtlsdr_dev_t*)dev; + return e4k_standby(&devt->e4k_s, 1); +} +int e4000_set_freq(void *dev, uint32_t freq) { + rtlsdr_dev_t* devt = (rtlsdr_dev_t*)dev; + return e4k_tune_freq(&devt->e4k_s, freq); +} + +int e4000_set_bw(void *dev, int bw) { + int r = 0; + rtlsdr_dev_t* devt = (rtlsdr_dev_t*)dev; + + r |= e4k_if_filter_bw_set(&devt->e4k_s, E4K_IF_FILTER_MIX, bw); + r |= e4k_if_filter_bw_set(&devt->e4k_s, E4K_IF_FILTER_RC, bw); + r |= e4k_if_filter_bw_set(&devt->e4k_s, E4K_IF_FILTER_CHAN, bw); + + return r; +} + +int e4000_set_gain(void *dev, int gain) { + rtlsdr_dev_t* devt = (rtlsdr_dev_t*)dev; + int mixgain = (gain > 340) ? 12 : 4; +#if 0 + int enhgain = (gain - 420); +#endif + if(e4k_set_lna_gain(&devt->e4k_s, min(300, gain - mixgain * 10)) == -EINVAL) + return -1; + if(e4k_mixer_gain_set(&devt->e4k_s, mixgain) == -EINVAL) + return -1; +#if 0 /* enhanced mixer gain seems to have no effect */ + if(enhgain >= 0) + if(e4k_set_enh_gain(&devt->e4k_s, enhgain) == -EINVAL) + return -1; +#endif + return 0; +} +int e4000_set_if_gain(void *dev, int stage, int gain) { + rtlsdr_dev_t* devt = (rtlsdr_dev_t*)dev; + return e4k_if_gain_set(&devt->e4k_s, (uint8_t)stage, (int8_t)(gain / 10)); +} +int e4000_set_gain_mode(void *dev, int manual) { + rtlsdr_dev_t* devt = (rtlsdr_dev_t*)dev; + return e4k_enable_manual_gain(&devt->e4k_s, manual); +} + +int _fc0012_init(void *dev) { return fc0012_init(dev); } +int fc0012_exit(void *dev) { return 0; } +int fc0012_set_freq(void *dev, uint32_t freq) { + /* select V-band/U-band filter */ + rtlsdr_set_gpio_bit(dev, 6, (freq > 300000000) ? 1 : 0); + return fc0012_set_params(dev, freq, 6000000); +} +int fc0012_set_bw(void *dev, int bw) { return 0; } +int _fc0012_set_gain(void *dev, int gain) { return fc0012_set_gain(dev, gain); } +int fc0012_set_gain_mode(void *dev, int manual) { return 0; } + +int _fc0013_init(void *dev) { return fc0013_init(dev); } +int fc0013_exit(void *dev) { return 0; } +int fc0013_set_freq(void *dev, uint32_t freq) { + return fc0013_set_params(dev, freq, 6000000); +} +int fc0013_set_bw(void *dev, int bw) { return 0; } +int _fc0013_set_gain(void *dev, int gain) { return fc0013_set_lna_gain(dev, gain); } + +int fc2580_init(void *dev) { return fc2580_Initialize(dev); } +int fc2580_exit(void *dev) { return 0; } +int _fc2580_set_freq(void *dev, uint32_t freq) { + return fc2580_SetRfFreqHz(dev, freq); +} +int fc2580_set_bw(void *dev, int bw) { return fc2580_SetBandwidthMode(dev, 1); } +int fc2580_set_gain(void *dev, int gain) { return 0; } +int fc2580_set_gain_mode(void *dev, int manual) { return 0; } + +int r820t_init(void *dev) { + rtlsdr_dev_t* devt = (rtlsdr_dev_t*)dev; + devt->r82xx_p.rtl_dev = dev; + + if (devt->tuner_type == RTLSDR_TUNER_R828D) { + devt->r82xx_c.i2c_addr = R828D_I2C_ADDR; + devt->r82xx_c.rafael_chip = CHIP_R828D; + } else { + devt->r82xx_c.i2c_addr = R820T_I2C_ADDR; + devt->r82xx_c.rafael_chip = CHIP_R820T; + } + + rtlsdr_get_xtal_freq(devt, NULL, &devt->r82xx_c.xtal); + + devt->r82xx_c.max_i2c_msg_len = 8; + devt->r82xx_c.use_predetect = 0; + devt->r82xx_p.cfg = &devt->r82xx_c; + + return r82xx_init(&devt->r82xx_p); +} +int r820t_exit(void *dev) { + rtlsdr_dev_t* devt = (rtlsdr_dev_t*)dev; + return r82xx_standby(&devt->r82xx_p); +} + +int r820t_set_freq(void *dev, uint32_t freq) { + rtlsdr_dev_t* devt = (rtlsdr_dev_t*)dev; + return r82xx_set_freq(&devt->r82xx_p, freq); +} + +int r820t_set_bw(void *dev, int bw) { + int r; + rtlsdr_dev_t* devt = (rtlsdr_dev_t*)dev; + + r = r82xx_set_bandwidth(&devt->r82xx_p, bw, devt->rate); + if(r < 0) + return r; + r = rtlsdr_set_if_freq(devt, r); + if (r) + return r; + return rtlsdr_set_center_freq(devt, devt->freq); +} + +int r820t_set_gain(void *dev, int gain) { + rtlsdr_dev_t* devt = (rtlsdr_dev_t*)dev; + return r82xx_set_gain(&devt->r82xx_p, 1, gain); +} +int r820t_set_gain_mode(void *dev, int manual) { + rtlsdr_dev_t* devt = (rtlsdr_dev_t*)dev; + return r82xx_set_gain(&devt->r82xx_p, manual, 0); +} + +/* definition order must match enum rtlsdr_tuner */ +static rtlsdr_tuner_iface_t tuners[] = { + { + NULL, NULL, NULL, NULL, NULL, NULL, NULL /* dummy for unknown tuners */ + }, + { + e4000_init, e4000_exit, + e4000_set_freq, e4000_set_bw, e4000_set_gain, e4000_set_if_gain, + e4000_set_gain_mode + }, + { + _fc0012_init, fc0012_exit, + fc0012_set_freq, fc0012_set_bw, _fc0012_set_gain, NULL, + fc0012_set_gain_mode + }, + { + _fc0013_init, fc0013_exit, + fc0013_set_freq, fc0013_set_bw, _fc0013_set_gain, NULL, + fc0013_set_gain_mode + }, + { + fc2580_init, fc2580_exit, + _fc2580_set_freq, fc2580_set_bw, fc2580_set_gain, NULL, + fc2580_set_gain_mode + }, + { + r820t_init, r820t_exit, + r820t_set_freq, r820t_set_bw, r820t_set_gain, NULL, + r820t_set_gain_mode + }, + { + r820t_init, r820t_exit, + r820t_set_freq, r820t_set_bw, r820t_set_gain, NULL, + r820t_set_gain_mode + }, +}; + +typedef struct rtlsdr_dongle { + uint16_t vid; + uint16_t pid; + const char *name; +} rtlsdr_dongle_t; + +/* + * Please add your device here and send a patch to osmocom-sdr@lists.osmocom.org + */ +static rtlsdr_dongle_t known_devices[] = { + { 0x0bda, 0x2832, "Generic RTL2832U" }, + { 0x0bda, 0x2838, "Generic RTL2832U OEM" }, + { 0x0413, 0x6680, "DigitalNow Quad DVB-T PCI-E card" }, + { 0x0413, 0x6f0f, "Leadtek WinFast DTV Dongle mini D" }, + { 0x0458, 0x707f, "Genius TVGo DVB-T03 USB dongle (Ver. B)" }, + { 0x0ccd, 0x00a9, "Terratec Cinergy T Stick Black (rev 1)" }, + { 0x0ccd, 0x00b3, "Terratec NOXON DAB/DAB+ USB dongle (rev 1)" }, + { 0x0ccd, 0x00b4, "Terratec Deutschlandradio DAB Stick" }, + { 0x0ccd, 0x00b5, "Terratec NOXON DAB Stick - Radio Energy" }, + { 0x0ccd, 0x00b7, "Terratec Media Broadcast DAB Stick" }, + { 0x0ccd, 0x00b8, "Terratec BR DAB Stick" }, + { 0x0ccd, 0x00b9, "Terratec WDR DAB Stick" }, + { 0x0ccd, 0x00c0, "Terratec MuellerVerlag DAB Stick" }, + { 0x0ccd, 0x00c6, "Terratec Fraunhofer DAB Stick" }, + { 0x0ccd, 0x00d3, "Terratec Cinergy T Stick RC (Rev.3)" }, + { 0x0ccd, 0x00d7, "Terratec T Stick PLUS" }, + { 0x0ccd, 0x00e0, "Terratec NOXON DAB/DAB+ USB dongle (rev 2)" }, + { 0x1554, 0x5020, "PixelView PV-DT235U(RN)" }, + { 0x15f4, 0x0131, "Astrometa DVB-T/DVB-T2" }, + { 0x15f4, 0x0133, "HanfTek DAB+FM+DVB-T" }, + { 0x185b, 0x0620, "Compro Videomate U620F"}, + { 0x185b, 0x0650, "Compro Videomate U650F"}, + { 0x185b, 0x0680, "Compro Videomate U680F"}, + { 0x1b80, 0xd393, "GIGABYTE GT-U7300" }, + { 0x1b80, 0xd394, "DIKOM USB-DVBT HD" }, + { 0x1b80, 0xd395, "Peak 102569AGPK" }, + { 0x1b80, 0xd397, "KWorld KW-UB450-T USB DVB-T Pico TV" }, + { 0x1b80, 0xd398, "Zaapa ZT-MINDVBZP" }, + { 0x1b80, 0xd39d, "SVEON STV20 DVB-T USB & FM" }, + { 0x1b80, 0xd3a4, "Twintech UT-40" }, + { 0x1b80, 0xd3a8, "ASUS U3100MINI_PLUS_V2" }, + { 0x1b80, 0xd3af, "SVEON STV27 DVB-T USB & FM" }, + { 0x1b80, 0xd3b0, "SVEON STV21 DVB-T USB & FM" }, + { 0x1d19, 0x1101, "Dexatek DK DVB-T Dongle (Logilink VG0002A)" }, + { 0x1d19, 0x1102, "Dexatek DK DVB-T Dongle (MSI DigiVox mini II V3.0)" }, + { 0x1d19, 0x1103, "Dexatek Technology Ltd. DK 5217 DVB-T Dongle" }, + { 0x1d19, 0x1104, "MSI DigiVox Micro HD" }, + { 0x1f4d, 0xa803, "Sweex DVB-T USB" }, + { 0x1f4d, 0xb803, "GTek T803" }, + { 0x1f4d, 0xc803, "Lifeview LV5TDeluxe" }, + { 0x1f4d, 0xd286, "MyGica TD312" }, + { 0x1f4d, 0xd803, "PROlectrix DV107669" }, +}; + +#define DEFAULT_BUF_NUMBER 15 +#define DEFAULT_BUF_LENGTH (16 * 32 * 512) + +#define DEF_RTL_XTAL_FREQ 28800000 +#define MIN_RTL_XTAL_FREQ (DEF_RTL_XTAL_FREQ - 1000) +#define MAX_RTL_XTAL_FREQ (DEF_RTL_XTAL_FREQ + 1000) + +#define CTRL_IN (LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_ENDPOINT_IN) +#define CTRL_OUT (LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_ENDPOINT_OUT) +#define CTRL_TIMEOUT 300 +#define BULK_TIMEOUT 0 + +#define EEPROM_ADDR 0xa0 + +enum usb_reg { + USB_SYSCTL = 0x2000, + USB_CTRL = 0x2010, + USB_STAT = 0x2014, + USB_EPA_CFG = 0x2144, + USB_EPA_CTL = 0x2148, + USB_EPA_MAXPKT = 0x2158, + USB_EPA_MAXPKT_2 = 0x215a, + USB_EPA_FIFO_CFG = 0x2160, +}; + +enum sys_reg { + DEMOD_CTL = 0x3000, + GPO = 0x3001, + GPI = 0x3002, + GPOE = 0x3003, + GPD = 0x3004, + SYSINTE = 0x3005, + SYSINTS = 0x3006, + GP_CFG0 = 0x3007, + GP_CFG1 = 0x3008, + SYSINTE_1 = 0x3009, + SYSINTS_1 = 0x300a, + DEMOD_CTL_1 = 0x300b, + IR_SUSPEND = 0x300c, +}; + +enum blocks { + DEMODB = 0, + USBB = 1, + SYSB = 2, + TUNB = 3, + ROMB = 4, + IRB = 5, + IICB = 6, +}; + +int rtlsdr_read_array(rtlsdr_dev_t *dev, uint8_t block, uint16_t addr, uint8_t *array, uint8_t len) +{ + int r; + uint16_t index = (block << 8); + + r = libusb_control_transfer(dev->devh, CTRL_IN, 0, addr, index, array, len, CTRL_TIMEOUT); +#if 0 + if (r < 0) + fprintf(stderr, "%s failed with %d\n", __FUNCTION__, r); +#endif + return r; +} + +int rtlsdr_write_array(rtlsdr_dev_t *dev, uint8_t block, uint16_t addr, uint8_t *array, uint8_t len) +{ + int r; + uint16_t index = (block << 8) | 0x10; + + r = libusb_control_transfer(dev->devh, CTRL_OUT, 0, addr, index, array, len, CTRL_TIMEOUT); +#if 0 + if (r < 0) + fprintf(stderr, "%s failed with %d\n", __FUNCTION__, r); +#endif + return r; +} + +int rtlsdr_i2c_write_reg(rtlsdr_dev_t *dev, uint8_t i2c_addr, uint8_t reg, uint8_t val) +{ + uint16_t addr = i2c_addr; + uint8_t data[2]; + + data[0] = reg; + data[1] = val; + return rtlsdr_write_array(dev, IICB, addr, (uint8_t *)&data, 2); +} + +uint8_t rtlsdr_i2c_read_reg(rtlsdr_dev_t *dev, uint8_t i2c_addr, uint8_t reg) +{ + uint16_t addr = i2c_addr; + uint8_t data = 0; + + rtlsdr_write_array(dev, IICB, addr, ®, 1); + rtlsdr_read_array(dev, IICB, addr, &data, 1); + + return data; +} + +int rtlsdr_i2c_write(rtlsdr_dev_t *dev, uint8_t i2c_addr, uint8_t *buffer, int len) +{ + uint16_t addr = i2c_addr; + + if (!dev) + return -1; + + return rtlsdr_write_array(dev, IICB, addr, buffer, len); +} + +int rtlsdr_i2c_read(rtlsdr_dev_t *dev, uint8_t i2c_addr, uint8_t *buffer, int len) +{ + uint16_t addr = i2c_addr; + + if (!dev) + return -1; + + return rtlsdr_read_array(dev, IICB, addr, buffer, len); +} + +uint16_t rtlsdr_read_reg(rtlsdr_dev_t *dev, uint8_t block, uint16_t addr, uint8_t len) +{ + int r; + unsigned char data[2]; + uint16_t index = (block << 8); + uint16_t reg; + + r = libusb_control_transfer(dev->devh, CTRL_IN, 0, addr, index, data, len, CTRL_TIMEOUT); + + if (r < 0) + fprintf(stderr, "%s failed with %d\n", __FUNCTION__, r); + + reg = (data[1] << 8) | data[0]; + + return reg; +} + +int rtlsdr_write_reg(rtlsdr_dev_t *dev, uint8_t block, uint16_t addr, uint16_t val, uint8_t len) +{ + int r; + unsigned char data[2]; + + uint16_t index = (block << 8) | 0x10; + + if (len == 1) + data[0] = val & 0xff; + else + data[0] = val >> 8; + + data[1] = val & 0xff; + + r = libusb_control_transfer(dev->devh, CTRL_OUT, 0, addr, index, data, len, CTRL_TIMEOUT); + + if (r < 0) + fprintf(stderr, "%s failed with %d\n", __FUNCTION__, r); + + return r; +} + +uint16_t rtlsdr_demod_read_reg(rtlsdr_dev_t *dev, uint8_t page, uint16_t addr, uint8_t len) +{ + int r; + unsigned char data[2]; + + uint16_t index = page; + uint16_t reg; + addr = (addr << 8) | 0x20; + + r = libusb_control_transfer(dev->devh, CTRL_IN, 0, addr, index, data, len, CTRL_TIMEOUT); + + if (r < 0) + fprintf(stderr, "%s failed with %d\n", __FUNCTION__, r); + + reg = (data[1] << 8) | data[0]; + + return reg; +} + +int rtlsdr_demod_write_reg(rtlsdr_dev_t *dev, uint8_t page, uint16_t addr, uint16_t val, uint8_t len) +{ + int r; + unsigned char data[2]; + uint16_t index = 0x10 | page; + addr = (addr << 8) | 0x20; + + if (len == 1) + data[0] = val & 0xff; + else + data[0] = val >> 8; + + data[1] = val & 0xff; + + r = libusb_control_transfer(dev->devh, CTRL_OUT, 0, addr, index, data, len, CTRL_TIMEOUT); + + if (r < 0) + fprintf(stderr, "%s failed with %d\n", __FUNCTION__, r); + + rtlsdr_demod_read_reg(dev, 0x0a, 0x01, 1); + + return (r == len) ? 0 : -1; +} + +void rtlsdr_set_gpio_bit(rtlsdr_dev_t *dev, uint8_t gpio, int val) +{ + uint16_t r; + + gpio = 1 << gpio; + r = rtlsdr_read_reg(dev, SYSB, GPO, 1); + r = val ? (r | gpio) : (r & ~gpio); + rtlsdr_write_reg(dev, SYSB, GPO, r, 1); +} + +void rtlsdr_set_gpio_output(rtlsdr_dev_t *dev, uint8_t gpio) +{ + int r; + gpio = 1 << gpio; + + r = rtlsdr_read_reg(dev, SYSB, GPD, 1); + rtlsdr_write_reg(dev, SYSB, GPD, r & ~gpio, 1); + r = rtlsdr_read_reg(dev, SYSB, GPOE, 1); + rtlsdr_write_reg(dev, SYSB, GPOE, r | gpio, 1); +} + +void rtlsdr_set_i2c_repeater(rtlsdr_dev_t *dev, int on) +{ + rtlsdr_demod_write_reg(dev, 1, 0x01, on ? 0x18 : 0x10, 1); +} + +int rtlsdr_set_fir(rtlsdr_dev_t *dev) +{ + uint8_t fir[20]; + + int i; + /* format: int8_t[8] */ + for (i = 0; i < 8; ++i) { + const int val = dev->fir[i]; + if (val < -128 || val > 127) { + return -1; + } + fir[i] = val; + } + /* format: int12_t[8] */ + for (i = 0; i < 8; i += 2) { + const int val0 = dev->fir[8+i]; + const int val1 = dev->fir[8+i+1]; + if (val0 < -2048 || val0 > 2047 || val1 < -2048 || val1 > 2047) { + return -1; + } + fir[8+i*3/2] = val0 >> 4; + fir[8+i*3/2+1] = (val0 << 4) | ((val1 >> 8) & 0x0f); + fir[8+i*3/2+2] = val1; + } + + for (i = 0; i < (int)sizeof(fir); i++) { + if (rtlsdr_demod_write_reg(dev, 1, 0x1c + i, fir[i], 1)) + return -1; + } + + return 0; +} + +void rtlsdr_init_baseband(rtlsdr_dev_t *dev) +{ + unsigned int i; + + /* initialize USB */ + rtlsdr_write_reg(dev, USBB, USB_SYSCTL, 0x09, 1); + rtlsdr_write_reg(dev, USBB, USB_EPA_MAXPKT, 0x0002, 2); + rtlsdr_write_reg(dev, USBB, USB_EPA_CTL, 0x1002, 2); + + /* poweron demod */ + rtlsdr_write_reg(dev, SYSB, DEMOD_CTL_1, 0x22, 1); + rtlsdr_write_reg(dev, SYSB, DEMOD_CTL, 0xe8, 1); + + /* reset demod (bit 3, soft_rst) */ + rtlsdr_demod_write_reg(dev, 1, 0x01, 0x14, 1); + rtlsdr_demod_write_reg(dev, 1, 0x01, 0x10, 1); + + /* disable spectrum inversion and adjacent channel rejection */ + rtlsdr_demod_write_reg(dev, 1, 0x15, 0x00, 1); + rtlsdr_demod_write_reg(dev, 1, 0x16, 0x0000, 2); + + /* clear both DDC shift and IF frequency registers */ + for (i = 0; i < 6; i++) + rtlsdr_demod_write_reg(dev, 1, 0x16 + i, 0x00, 1); + + rtlsdr_set_fir(dev); + + /* enable SDR mode, disable DAGC (bit 5) */ + rtlsdr_demod_write_reg(dev, 0, 0x19, 0x05, 1); + + /* init FSM state-holding register */ + rtlsdr_demod_write_reg(dev, 1, 0x93, 0xf0, 1); + rtlsdr_demod_write_reg(dev, 1, 0x94, 0x0f, 1); + + /* disable AGC (en_dagc, bit 0) (this seems to have no effect) */ + rtlsdr_demod_write_reg(dev, 1, 0x11, 0x00, 1); + + /* disable RF and IF AGC loop */ + rtlsdr_demod_write_reg(dev, 1, 0x04, 0x00, 1); + + /* disable PID filter (enable_PID = 0) */ + rtlsdr_demod_write_reg(dev, 0, 0x61, 0x60, 1); + + /* opt_adc_iq = 0, default ADC_I/ADC_Q datapath */ + rtlsdr_demod_write_reg(dev, 0, 0x06, 0x80, 1); + + /* Enable Zero-IF mode (en_bbin bit), DC cancellation (en_dc_est), + * IQ estimation/compensation (en_iq_comp, en_iq_est) */ + rtlsdr_demod_write_reg(dev, 1, 0xb1, 0x1b, 1); + + /* disable 4.096 MHz clock output on pin TP_CK0 */ + rtlsdr_demod_write_reg(dev, 0, 0x0d, 0x83, 1); +} + +int rtlsdr_deinit_baseband(rtlsdr_dev_t *dev) +{ + int r = 0; + + if (!dev) + return -1; + + if (dev->tuner && dev->tuner->exit) { + rtlsdr_set_i2c_repeater(dev, 1); + r = dev->tuner->exit(dev); /* deinitialize tuner */ + rtlsdr_set_i2c_repeater(dev, 0); + } + + /* poweroff demodulator and ADCs */ + rtlsdr_write_reg(dev, SYSB, DEMOD_CTL, 0x20, 1); + + return r; +} + +static int rtlsdr_set_if_freq(rtlsdr_dev_t *dev, uint32_t freq) +{ + uint32_t rtl_xtal; + int32_t if_freq; + uint8_t tmp; + int r; + + if (!dev) + return -1; + + /* read corrected clock value */ + if (rtlsdr_get_xtal_freq(dev, &rtl_xtal, NULL)) + return -2; + + if_freq = ((freq * TWO_POW(22)) / rtl_xtal) * (-1); + + tmp = (if_freq >> 16) & 0x3f; + r = rtlsdr_demod_write_reg(dev, 1, 0x19, tmp, 1); + tmp = (if_freq >> 8) & 0xff; + r |= rtlsdr_demod_write_reg(dev, 1, 0x1a, tmp, 1); + tmp = if_freq & 0xff; + r |= rtlsdr_demod_write_reg(dev, 1, 0x1b, tmp, 1); + + return r; +} + +int rtlsdr_set_sample_freq_correction(rtlsdr_dev_t *dev, int ppm) +{ + int r = 0; + uint8_t tmp; + int16_t offs = ppm * (-1) * TWO_POW(24) / 1000000; + + tmp = offs & 0xff; + r |= rtlsdr_demod_write_reg(dev, 1, 0x3f, tmp, 1); + tmp = (offs >> 8) & 0x3f; + r |= rtlsdr_demod_write_reg(dev, 1, 0x3e, tmp, 1); + + return r; +} + +int rtlsdr_set_xtal_freq(rtlsdr_dev_t *dev, uint32_t rtl_freq, uint32_t tuner_freq) +{ + int r = 0; + + if (!dev) + return -1; + + if (rtl_freq > 0 && + (rtl_freq < MIN_RTL_XTAL_FREQ || rtl_freq > MAX_RTL_XTAL_FREQ)) + return -2; + + if (rtl_freq > 0 && dev->rtl_xtal != rtl_freq) { + dev->rtl_xtal = rtl_freq; + + /* update xtal-dependent settings */ + if (dev->rate) + r = rtlsdr_set_sample_rate(dev, dev->rate); + } + + if (dev->tun_xtal != tuner_freq) { + if (0 == tuner_freq) + dev->tun_xtal = dev->rtl_xtal; + else + dev->tun_xtal = tuner_freq; + + /* read corrected clock value into e4k and r82xx structure */ + if (rtlsdr_get_xtal_freq(dev, NULL, &dev->e4k_s.vco.fosc) || + rtlsdr_get_xtal_freq(dev, NULL, &dev->r82xx_c.xtal)) + return -3; + + /* update xtal-dependent settings */ + if (dev->freq) + r = rtlsdr_set_center_freq(dev, dev->freq); + } + + return r; +} + +int rtlsdr_get_xtal_freq(rtlsdr_dev_t *dev, uint32_t *rtl_freq, uint32_t *tuner_freq) +{ + if (!dev) + return -1; + + #define APPLY_PPM_CORR(val,ppm) (((val) * (1.0 + (ppm) / 1e6))) + + if (rtl_freq) + *rtl_freq = (uint32_t) APPLY_PPM_CORR(dev->rtl_xtal, dev->corr); + + if (tuner_freq) + *tuner_freq = (uint32_t) APPLY_PPM_CORR(dev->tun_xtal, dev->corr); + + return 0; +} + +int rtlsdr_get_usb_strings(rtlsdr_dev_t *dev, char *manufact, char *product, + char *serial) +{ + struct libusb_device_descriptor dd; + libusb_device *device = NULL; + const int buf_max = 256; + int r = 0; + + if (!dev || !dev->devh) + return -1; + + device = libusb_get_device(dev->devh); + + r = libusb_get_device_descriptor(device, &dd); + if (r < 0) + return -1; + + if (manufact) { + memset(manufact, 0, buf_max); + libusb_get_string_descriptor_ascii(dev->devh, dd.iManufacturer, + (unsigned char *)manufact, + buf_max); + } + + if (product) { + memset(product, 0, buf_max); + libusb_get_string_descriptor_ascii(dev->devh, dd.iProduct, + (unsigned char *)product, + buf_max); + } + + if (serial) { + memset(serial, 0, buf_max); + libusb_get_string_descriptor_ascii(dev->devh, dd.iSerialNumber, + (unsigned char *)serial, + buf_max); + } + + return 0; +} + +int rtlsdr_write_eeprom(rtlsdr_dev_t *dev, uint8_t *data, uint8_t offset, uint16_t len) +{ + int r = 0; + int i; + uint8_t cmd[2]; + + if (!dev) + return -1; + + if ((len + offset) > 256) + return -2; + + for (i = 0; i < len; i++) { + cmd[0] = i + offset; + r = rtlsdr_write_array(dev, IICB, EEPROM_ADDR, cmd, 1); + r = rtlsdr_read_array(dev, IICB, EEPROM_ADDR, &cmd[1], 1); + + /* only write the byte if it differs */ + if (cmd[1] == data[i]) + continue; + + cmd[1] = data[i]; + r = rtlsdr_write_array(dev, IICB, EEPROM_ADDR, cmd, 2); + if (r != sizeof(cmd)) + return -3; + + /* for some EEPROMs (e.g. ATC 240LC02) we need a delay + * between write operations, otherwise they will fail */ +#ifdef _WIN32 + Sleep(5); +#else + usleep(5000); +#endif + } + + return 0; +} + +int rtlsdr_read_eeprom(rtlsdr_dev_t *dev, uint8_t *data, uint8_t offset, uint16_t len) +{ + int r = 0; + int i; + + if (!dev) + return -1; + + if ((len + offset) > 256) + return -2; + + r = rtlsdr_write_array(dev, IICB, EEPROM_ADDR, &offset, 1); + if (r < 0) + return -3; + + for (i = 0; i < len; i++) { + r = rtlsdr_read_array(dev, IICB, EEPROM_ADDR, data + i, 1); + + if (r < 0) + return -3; + } + + return r; +} + +int rtlsdr_set_center_freq(rtlsdr_dev_t *dev, uint32_t freq) +{ + int r = -1; + + if (!dev || !dev->tuner) + return -1; + + if (dev->direct_sampling) { + r = rtlsdr_set_if_freq(dev, freq); + } else if (dev->tuner && dev->tuner->set_freq) { + rtlsdr_set_i2c_repeater(dev, 1); + r = dev->tuner->set_freq(dev, freq - dev->offs_freq); + rtlsdr_set_i2c_repeater(dev, 0); + } + + if (!r) + dev->freq = freq; + else + dev->freq = 0; + + return r; +} + +uint32_t rtlsdr_get_center_freq(rtlsdr_dev_t *dev) +{ + if (!dev) + return 0; + + return dev->freq; +} + +int rtlsdr_set_freq_correction(rtlsdr_dev_t *dev, int ppm) +{ + int r = 0; + + if (!dev) + return -1; + + if (dev->corr == ppm) + return -2; + + dev->corr = ppm; + + r |= rtlsdr_set_sample_freq_correction(dev, ppm); + + /* read corrected clock value into e4k and r82xx structure */ + if (rtlsdr_get_xtal_freq(dev, NULL, &dev->e4k_s.vco.fosc) || + rtlsdr_get_xtal_freq(dev, NULL, &dev->r82xx_c.xtal)) + return -3; + + if (dev->freq) /* retune to apply new correction value */ + r |= rtlsdr_set_center_freq(dev, dev->freq); + + return r; +} + +int rtlsdr_get_freq_correction(rtlsdr_dev_t *dev) +{ + if (!dev) + return 0; + + return dev->corr; +} + +enum rtlsdr_tuner rtlsdr_get_tuner_type(rtlsdr_dev_t *dev) +{ + if (!dev) + return RTLSDR_TUNER_UNKNOWN; + + return dev->tuner_type; +} + +int rtlsdr_get_tuner_gains(rtlsdr_dev_t *dev, int *gains) +{ + /* all gain values are expressed in tenths of a dB */ + const int e4k_gains[] = { -10, 15, 40, 65, 90, 115, 140, 165, 190, 215, + 240, 290, 340, 420 }; + const int fc0012_gains[] = { -99, -40, 71, 179, 192 }; + const int fc0013_gains[] = { -99, -73, -65, -63, -60, -58, -54, 58, 61, + 63, 65, 67, 68, 70, 71, 179, 181, 182, + 184, 186, 188, 191, 197 }; + const int fc2580_gains[] = { 0 /* no gain values */ }; + const int r82xx_gains[] = { 0, 9, 14, 27, 37, 77, 87, 125, 144, 157, + 166, 197, 207, 229, 254, 280, 297, 328, + 338, 364, 372, 386, 402, 421, 434, 439, + 445, 480, 496 }; + const int unknown_gains[] = { 0 /* no gain values */ }; + + const int *ptr = NULL; + int len = 0; + + if (!dev) + return -1; + + switch (dev->tuner_type) { + case RTLSDR_TUNER_E4000: + ptr = e4k_gains; len = sizeof(e4k_gains); + break; + case RTLSDR_TUNER_FC0012: + ptr = fc0012_gains; len = sizeof(fc0012_gains); + break; + case RTLSDR_TUNER_FC0013: + ptr = fc0013_gains; len = sizeof(fc0013_gains); + break; + case RTLSDR_TUNER_FC2580: + ptr = fc2580_gains; len = sizeof(fc2580_gains); + break; + case RTLSDR_TUNER_R820T: + case RTLSDR_TUNER_R828D: + ptr = r82xx_gains; len = sizeof(r82xx_gains); + break; + default: + ptr = unknown_gains; len = sizeof(unknown_gains); + break; + } + + if (!gains) { /* no buffer provided, just return the count */ + return len / sizeof(int); + } else { + if (len) + memcpy(gains, ptr, len); + + return len / sizeof(int); + } +} + +int rtlsdr_set_tuner_bandwidth(rtlsdr_dev_t *dev, uint32_t bw) +{ + int r = 0; + + if (!dev || !dev->tuner) + return -1; + + if (dev->tuner->set_bw) { + rtlsdr_set_i2c_repeater(dev, 1); + r = dev->tuner->set_bw(dev, bw > 0 ? bw : dev->rate); + rtlsdr_set_i2c_repeater(dev, 0); + if (r) + return r; + dev->bw = bw; + } + return r; +} + +int rtlsdr_set_tuner_gain(rtlsdr_dev_t *dev, int gain) +{ + int r = 0; + + if (!dev || !dev->tuner) + return -1; + + if (dev->tuner->set_gain) { + rtlsdr_set_i2c_repeater(dev, 1); + r = dev->tuner->set_gain((void *)dev, gain); + rtlsdr_set_i2c_repeater(dev, 0); + } + + if (!r) + dev->gain = gain; + else + dev->gain = 0; + + return r; +} + +int rtlsdr_get_tuner_gain(rtlsdr_dev_t *dev) +{ + if (!dev) + return 0; + + return dev->gain; +} + +int rtlsdr_set_tuner_if_gain(rtlsdr_dev_t *dev, int stage, int gain) +{ + int r = 0; + + if (!dev || !dev->tuner) + return -1; + + if (dev->tuner->set_if_gain) { + rtlsdr_set_i2c_repeater(dev, 1); + r = dev->tuner->set_if_gain(dev, stage, gain); + rtlsdr_set_i2c_repeater(dev, 0); + } + + return r; +} + +int rtlsdr_set_tuner_gain_mode(rtlsdr_dev_t *dev, int mode) +{ + int r = 0; + + if (!dev || !dev->tuner) + return -1; + + if (dev->tuner->set_gain_mode) { + rtlsdr_set_i2c_repeater(dev, 1); + r = dev->tuner->set_gain_mode((void *)dev, mode); + rtlsdr_set_i2c_repeater(dev, 0); + } + + return r; +} + +int rtlsdr_set_sample_rate(rtlsdr_dev_t *dev, uint32_t samp_rate) +{ + int r = 0; + uint16_t tmp; + uint32_t rsamp_ratio, real_rsamp_ratio; + double real_rate; + + if (!dev) + return -1; + + /* check if the rate is supported by the resampler */ + if ((samp_rate <= 225000) || (samp_rate > 3200000) || + ((samp_rate > 300000) && (samp_rate <= 900000))) { + fprintf(stderr, "Invalid sample rate: %u Hz\n", samp_rate); + return -EINVAL; + } + + rsamp_ratio = (dev->rtl_xtal * TWO_POW(22)) / samp_rate; + rsamp_ratio &= 0x0ffffffc; + + real_rsamp_ratio = rsamp_ratio | ((rsamp_ratio & 0x08000000) << 1); + real_rate = (dev->rtl_xtal * TWO_POW(22)) / real_rsamp_ratio; + + if ( ((double)samp_rate) != real_rate ) + fprintf(stderr, "Exact sample rate is: %f Hz\n", real_rate); + + dev->rate = (uint32_t)real_rate; + + if (dev->tuner && dev->tuner->set_bw) { + rtlsdr_set_i2c_repeater(dev, 1); + dev->tuner->set_bw(dev, dev->bw > 0 ? dev->bw : dev->rate); + rtlsdr_set_i2c_repeater(dev, 0); + } + + tmp = (rsamp_ratio >> 16); + r |= rtlsdr_demod_write_reg(dev, 1, 0x9f, tmp, 2); + tmp = rsamp_ratio & 0xffff; + r |= rtlsdr_demod_write_reg(dev, 1, 0xa1, tmp, 2); + + r |= rtlsdr_set_sample_freq_correction(dev, dev->corr); + + /* reset demod (bit 3, soft_rst) */ + r |= rtlsdr_demod_write_reg(dev, 1, 0x01, 0x14, 1); + r |= rtlsdr_demod_write_reg(dev, 1, 0x01, 0x10, 1); + + /* recalculate offset frequency if offset tuning is enabled */ + if (dev->offs_freq) + rtlsdr_set_offset_tuning(dev, 1); + + return r; +} + +uint32_t rtlsdr_get_sample_rate(rtlsdr_dev_t *dev) +{ + if (!dev) + return 0; + + return dev->rate; +} + +int rtlsdr_set_testmode(rtlsdr_dev_t *dev, int on) +{ + if (!dev) + return -1; + + return rtlsdr_demod_write_reg(dev, 0, 0x19, on ? 0x03 : 0x05, 1); +} + +int rtlsdr_set_agc_mode(rtlsdr_dev_t *dev, int on) +{ + if (!dev) + return -1; + + return rtlsdr_demod_write_reg(dev, 0, 0x19, on ? 0x25 : 0x05, 1); +} + +int rtlsdr_set_direct_sampling(rtlsdr_dev_t *dev, int on) +{ + int r = 0; + + if (!dev) + return -1; + + if (on) { + if (dev->tuner && dev->tuner->exit) { + rtlsdr_set_i2c_repeater(dev, 1); + r = dev->tuner->exit(dev); + rtlsdr_set_i2c_repeater(dev, 0); + } + + /* disable Zero-IF mode */ + r |= rtlsdr_demod_write_reg(dev, 1, 0xb1, 0x1a, 1); + + /* disable spectrum inversion */ + r |= rtlsdr_demod_write_reg(dev, 1, 0x15, 0x00, 1); + + /* only enable In-phase ADC input */ + r |= rtlsdr_demod_write_reg(dev, 0, 0x08, 0x4d, 1); + + /* swap I and Q ADC, this allows to select between two inputs */ + r |= rtlsdr_demod_write_reg(dev, 0, 0x06, (on > 1) ? 0x90 : 0x80, 1); + + fprintf(stderr, "Enabled direct sampling mode, input %i\n", on); + dev->direct_sampling = on; + } else { + if (dev->tuner && dev->tuner->init) { + rtlsdr_set_i2c_repeater(dev, 1); + r |= dev->tuner->init(dev); + rtlsdr_set_i2c_repeater(dev, 0); + } + + if ((dev->tuner_type == RTLSDR_TUNER_R820T) || + (dev->tuner_type == RTLSDR_TUNER_R828D)) { + r |= rtlsdr_set_if_freq(dev, R82XX_IF_FREQ); + + /* enable spectrum inversion */ + r |= rtlsdr_demod_write_reg(dev, 1, 0x15, 0x01, 1); + } else { + r |= rtlsdr_set_if_freq(dev, 0); + + /* enable In-phase + Quadrature ADC input */ + r |= rtlsdr_demod_write_reg(dev, 0, 0x08, 0xcd, 1); + + /* Enable Zero-IF mode */ + r |= rtlsdr_demod_write_reg(dev, 1, 0xb1, 0x1b, 1); + } + + /* opt_adc_iq = 0, default ADC_I/ADC_Q datapath */ + r |= rtlsdr_demod_write_reg(dev, 0, 0x06, 0x80, 1); + + fprintf(stderr, "Disabled direct sampling mode\n"); + dev->direct_sampling = 0; + } + + r |= rtlsdr_set_center_freq(dev, dev->freq); + + return r; +} + +int rtlsdr_get_direct_sampling(rtlsdr_dev_t *dev) +{ + if (!dev) + return -1; + + return dev->direct_sampling; +} + +int rtlsdr_set_offset_tuning(rtlsdr_dev_t *dev, int on) +{ + int r = 0; + int bw; + + if (!dev) + return -1; + + if ((dev->tuner_type == RTLSDR_TUNER_R820T) || + (dev->tuner_type == RTLSDR_TUNER_R828D)) + return -2; + + if (dev->direct_sampling) + return -3; + + /* based on keenerds 1/f noise measurements */ + dev->offs_freq = on ? ((dev->rate / 2) * 170 / 100) : 0; + r |= rtlsdr_set_if_freq(dev, dev->offs_freq); + + if (dev->tuner && dev->tuner->set_bw) { + rtlsdr_set_i2c_repeater(dev, 1); + if (on) { + bw = 2 * dev->offs_freq; + } else if (dev->bw > 0) { + bw = dev->bw; + } else { + bw = dev->rate; + } + dev->tuner->set_bw(dev, bw); + rtlsdr_set_i2c_repeater(dev, 0); + } + + if (dev->freq > dev->offs_freq) + r |= rtlsdr_set_center_freq(dev, dev->freq); + + return r; +} + +int rtlsdr_get_offset_tuning(rtlsdr_dev_t *dev) +{ + if (!dev) + return -1; + + return (dev->offs_freq) ? 1 : 0; +} + +static rtlsdr_dongle_t *find_known_device(uint16_t vid, uint16_t pid) +{ + unsigned int i; + rtlsdr_dongle_t *device = NULL; + + for (i = 0; i < sizeof(known_devices)/sizeof(rtlsdr_dongle_t); i++ ) { + if (known_devices[i].vid == vid && known_devices[i].pid == pid) { + device = &known_devices[i]; + break; + } + } + + return device; +} + +uint32_t rtlsdr_get_device_count(void) +{ + int i,r; + libusb_context *ctx; + libusb_device **list; + uint32_t device_count = 0; + struct libusb_device_descriptor dd; + ssize_t cnt; + + r = libusb_init(&ctx); + if(r < 0) + return 0; + + cnt = libusb_get_device_list(ctx, &list); + + for (i = 0; i < cnt; i++) { + libusb_get_device_descriptor(list[i], &dd); + + if (find_known_device(dd.idVendor, dd.idProduct)) + device_count++; + } + + libusb_free_device_list(list, 1); + + libusb_exit(ctx); + + return device_count; +} + +const char *rtlsdr_get_device_name(uint32_t index) +{ + int i,r; + libusb_context *ctx; + libusb_device **list; + struct libusb_device_descriptor dd; + rtlsdr_dongle_t *device = NULL; + uint32_t device_count = 0; + ssize_t cnt; + + r = libusb_init(&ctx); + if(r < 0) + return ""; + + cnt = libusb_get_device_list(ctx, &list); + + for (i = 0; i < cnt; i++) { + libusb_get_device_descriptor(list[i], &dd); + + device = find_known_device(dd.idVendor, dd.idProduct); + + if (device) { + device_count++; + + if (index == device_count - 1) + break; + } + } + + libusb_free_device_list(list, 1); + + libusb_exit(ctx); + + if (device) + return device->name; + else + return ""; +} + +int rtlsdr_get_device_usb_strings(uint32_t index, char *manufact, + char *product, char *serial) +{ + int r = -2; + int i; + libusb_context *ctx; + libusb_device **list; + struct libusb_device_descriptor dd; + rtlsdr_dongle_t *device = NULL; + rtlsdr_dev_t devt; + uint32_t device_count = 0; + ssize_t cnt; + + r = libusb_init(&ctx); + if(r < 0) + return r; + + cnt = libusb_get_device_list(ctx, &list); + + for (i = 0; i < cnt; i++) { + libusb_get_device_descriptor(list[i], &dd); + + device = find_known_device(dd.idVendor, dd.idProduct); + + if (device) { + device_count++; + + if (index == device_count - 1) { + r = libusb_open(list[i], &devt.devh); + if (!r) { + r = rtlsdr_get_usb_strings(&devt, + manufact, + product, + serial); + libusb_close(devt.devh); + } + break; + } + } + } + + libusb_free_device_list(list, 1); + + libusb_exit(ctx); + + return r; +} + +int rtlsdr_get_index_by_serial(const char *serial) +{ + int i, cnt, r; + char str[256]; + + if (!serial) + return -1; + + cnt = rtlsdr_get_device_count(); + + if (!cnt) + return -2; + + for (i = 0; i < cnt; i++) { + r = rtlsdr_get_device_usb_strings(i, NULL, NULL, str); + if (!r && !strcmp(serial, str)) + return i; + } + + return -3; +} + +int rtlsdr_open(rtlsdr_dev_t **out_dev, uint32_t index) +{ + int r; + int i; + libusb_device **list; + rtlsdr_dev_t *dev = NULL; + libusb_device *device = NULL; + uint32_t device_count = 0; + struct libusb_device_descriptor dd; + uint8_t reg; + ssize_t cnt; + + dev = malloc(sizeof(rtlsdr_dev_t)); + if (NULL == dev) + return -ENOMEM; + + memset(dev, 0, sizeof(rtlsdr_dev_t)); + memcpy(dev->fir, fir_default, sizeof(fir_default)); + + r = libusb_init(&dev->ctx); + if(r < 0){ + free(dev); + return -1; + } + + dev->dev_lost = 1; + + cnt = libusb_get_device_list(dev->ctx, &list); + + for (i = 0; i < cnt; i++) { + device = list[i]; + + libusb_get_device_descriptor(list[i], &dd); + + if (find_known_device(dd.idVendor, dd.idProduct)) { + device_count++; + } + + if (index == device_count - 1) + break; + + device = NULL; + } + + if (!device) { + r = -1; + goto err; + } + + r = libusb_open(device, &dev->devh); + if (r < 0) { + libusb_free_device_list(list, 1); + fprintf(stderr, "usb_open error %d\n", r); + if(r == LIBUSB_ERROR_ACCESS) + fprintf(stderr, "Please fix the device permissions, e.g. " + "by installing the udev rules file rtl-sdr.rules\n"); + goto err; + } + + libusb_free_device_list(list, 1); + + if (libusb_kernel_driver_active(dev->devh, 0) == 1) { + dev->driver_active = 1; + +#ifdef DETACH_KERNEL_DRIVER + if (!libusb_detach_kernel_driver(dev->devh, 0)) { + fprintf(stderr, "Detached kernel driver\n"); + } else { + fprintf(stderr, "Detaching kernel driver failed!"); + goto err; + } +#else + fprintf(stderr, "\nKernel driver is active, or device is " + "claimed by second instance of librtlsdr." + "\nIn the first case, please either detach" + " or blacklist the kernel module\n" + "(dvb_usb_rtl28xxu), or enable automatic" + " detaching at compile time.\n\n"); +#endif + } + + r = libusb_claim_interface(dev->devh, 0); + if (r < 0) { + fprintf(stderr, "usb_claim_interface error %d\n", r); + goto err; + } + + dev->rtl_xtal = DEF_RTL_XTAL_FREQ; + + /* perform a dummy write, if it fails, reset the device */ + if (rtlsdr_write_reg(dev, USBB, USB_SYSCTL, 0x09, 1) < 0) { + fprintf(stderr, "Resetting device...\n"); + libusb_reset_device(dev->devh); + } + + rtlsdr_init_baseband(dev); + dev->dev_lost = 0; + + /* Probe tuners */ + rtlsdr_set_i2c_repeater(dev, 1); + + reg = rtlsdr_i2c_read_reg(dev, E4K_I2C_ADDR, E4K_CHECK_ADDR); + if (reg == E4K_CHECK_VAL) { + fprintf(stderr, "Found Elonics E4000 tuner\n"); + dev->tuner_type = RTLSDR_TUNER_E4000; + goto found; + } + + reg = rtlsdr_i2c_read_reg(dev, FC0013_I2C_ADDR, FC0013_CHECK_ADDR); + if (reg == FC0013_CHECK_VAL) { + fprintf(stderr, "Found Fitipower FC0013 tuner\n"); + dev->tuner_type = RTLSDR_TUNER_FC0013; + goto found; + } + + reg = rtlsdr_i2c_read_reg(dev, R820T_I2C_ADDR, R82XX_CHECK_ADDR); + if (reg == R82XX_CHECK_VAL) { + fprintf(stderr, "Found Rafael Micro R820T tuner\n"); + dev->tuner_type = RTLSDR_TUNER_R820T; + goto found; + } + + reg = rtlsdr_i2c_read_reg(dev, R828D_I2C_ADDR, R82XX_CHECK_ADDR); + if (reg == R82XX_CHECK_VAL) { + fprintf(stderr, "Found Rafael Micro R828D tuner\n"); + dev->tuner_type = RTLSDR_TUNER_R828D; + goto found; + } + + /* initialise GPIOs */ + rtlsdr_set_gpio_output(dev, 4); + + /* reset tuner before probing */ + rtlsdr_set_gpio_bit(dev, 4, 1); + rtlsdr_set_gpio_bit(dev, 4, 0); + + reg = rtlsdr_i2c_read_reg(dev, FC2580_I2C_ADDR, FC2580_CHECK_ADDR); + if ((reg & 0x7f) == FC2580_CHECK_VAL) { + fprintf(stderr, "Found FCI 2580 tuner\n"); + dev->tuner_type = RTLSDR_TUNER_FC2580; + goto found; + } + + reg = rtlsdr_i2c_read_reg(dev, FC0012_I2C_ADDR, FC0012_CHECK_ADDR); + if (reg == FC0012_CHECK_VAL) { + fprintf(stderr, "Found Fitipower FC0012 tuner\n"); + rtlsdr_set_gpio_output(dev, 6); + dev->tuner_type = RTLSDR_TUNER_FC0012; + goto found; + } + +found: + /* use the rtl clock value by default */ + dev->tun_xtal = dev->rtl_xtal; + dev->tuner = &tuners[dev->tuner_type]; + + switch (dev->tuner_type) { + case RTLSDR_TUNER_R828D: + dev->tun_xtal = R828D_XTAL_FREQ; + /* fall-through */ + case RTLSDR_TUNER_R820T: + /* disable Zero-IF mode */ + rtlsdr_demod_write_reg(dev, 1, 0xb1, 0x1a, 1); + + /* only enable In-phase ADC input */ + rtlsdr_demod_write_reg(dev, 0, 0x08, 0x4d, 1); + + /* the R82XX use 3.57 MHz IF for the DVB-T 6 MHz mode, and + * 4.57 MHz for the 8 MHz mode */ + rtlsdr_set_if_freq(dev, R82XX_IF_FREQ); + + /* enable spectrum inversion */ + rtlsdr_demod_write_reg(dev, 1, 0x15, 0x01, 1); + break; + case RTLSDR_TUNER_UNKNOWN: + fprintf(stderr, "No supported tuner found\n"); + rtlsdr_set_direct_sampling(dev, 1); + break; + default: + break; + } + + if (dev->tuner->init) + r = dev->tuner->init(dev); + + rtlsdr_set_i2c_repeater(dev, 0); + + *out_dev = dev; + + return 0; +err: + if (dev) { + if (dev->devh) + libusb_close(dev->devh); + + if (dev->ctx) + libusb_exit(dev->ctx); + + free(dev); + } + + return r; +} + +int rtlsdr_close(rtlsdr_dev_t *dev) +{ + if (!dev) + return -1; + + if(!dev->dev_lost) { + /* block until all async operations have been completed (if any) */ + while (RTLSDR_INACTIVE != dev->async_status) { +#ifdef _WIN32 + Sleep(1); +#else + usleep(1000); +#endif + } + + rtlsdr_deinit_baseband(dev); + } + + libusb_release_interface(dev->devh, 0); + +#ifdef DETACH_KERNEL_DRIVER + if (dev->driver_active) { + if (!libusb_attach_kernel_driver(dev->devh, 0)) + fprintf(stderr, "Reattached kernel driver\n"); + else + fprintf(stderr, "Reattaching kernel driver failed!\n"); + } +#endif + + libusb_close(dev->devh); + + libusb_exit(dev->ctx); + + free(dev); + + return 0; +} + +int rtlsdr_reset_buffer(rtlsdr_dev_t *dev) +{ + if (!dev) + return -1; + + rtlsdr_write_reg(dev, USBB, USB_EPA_CTL, 0x1002, 2); + rtlsdr_write_reg(dev, USBB, USB_EPA_CTL, 0x0000, 2); + + return 0; +} + +int rtlsdr_read_sync(rtlsdr_dev_t *dev, void *buf, int len, int *n_read) +{ + if (!dev) + return -1; + + 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) +{ + unsigned int i; + + if (!dev) + return -1; + + if (!dev->xfer) { + dev->xfer = malloc(dev->xfer_buf_num * + sizeof(struct libusb_transfer *)); + + for(i = 0; i < dev->xfer_buf_num; ++i) + dev->xfer[i] = libusb_alloc_transfer(0); + } + + if (dev->xfer_buf) + return -2; + + dev->xfer_buf = malloc(dev->xfer_buf_num * sizeof(unsigned char *)); + memset(dev->xfer_buf, 0, dev->xfer_buf_num * sizeof(unsigned char *)); + +#if defined(ENABLE_ZEROCOPY) && defined (__linux__) && LIBUSB_API_VERSION >= 0x01000105 + fprintf(stderr, "Allocating %d zero-copy buffers\n", dev->xfer_buf_num); + + 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; + } + } 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; + } + } + + /* 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); + } + } +#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); + + if (!dev->xfer_buf[i]) + return -ENOMEM; + } + } + + return 0; +} + +static int _rtlsdr_free_async_buffers(rtlsdr_dev_t *dev) +{ + unsigned int i; + + if (!dev) + return -1; + + if (dev->xfer) { + for(i = 0; i < dev->xfer_buf_num; ++i) { + if (dev->xfer[i]) { + libusb_free_transfer(dev->xfer[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; + } + + return 0; +} + +int rtlsdr_read_async(rtlsdr_dev_t *dev, rtlsdr_read_async_cb_t cb, void *ctx, + uint32_t buf_num, uint32_t buf_len) +{ + unsigned int i; + int r = 0; + struct timeval tv = { 1, 0 }; + struct timeval zerotv = { 0, 0 }; + enum rtlsdr_async_status next_status = RTLSDR_INACTIVE; + + if (!dev) + return -1; + + if (RTLSDR_INACTIVE != dev->async_status) + return -2; + + dev->async_status = RTLSDR_RUNNING; + dev->async_cancel = 0; + + dev->cb = cb; + dev->cb_ctx = ctx; + + if (buf_num > 0) + dev->xfer_buf_num = buf_num; + else + dev->xfer_buf_num = DEFAULT_BUF_NUMBER; + + if (buf_len > 0 && buf_len % 512 == 0) /* len must be multiple of 512 */ + dev->xfer_buf_len = buf_len; + else + dev->xfer_buf_len = DEFAULT_BUF_LENGTH; + + _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); + + r = libusb_submit_transfer(dev->xfer[i]); + if (r < 0) { + fprintf(stderr, "Failed to submit transfer %i\n" + "Please increase your allowed " + "usbfs buffer size with the " + "following command:\n" + "echo 0 > /sys/module/usbcore" + "/parameters/usbfs_memory_mb\n", i); + dev->async_status = RTLSDR_CANCELING; + break; + } + } + + while (RTLSDR_INACTIVE != dev->async_status) { + r = libusb_handle_events_timeout_completed(dev->ctx, &tv, + &dev->async_cancel); + if (r < 0) { + /*fprintf(stderr, "handle_events returned: %d\n", r);*/ + if (r == LIBUSB_ERROR_INTERRUPTED) /* stray signal */ + continue; + break; + } + + if (RTLSDR_CANCELING == dev->async_status) { + next_status = RTLSDR_INACTIVE; + + if (!dev->xfer) + break; + + for(i = 0; i < dev->xfer_buf_num; ++i) { + if (!dev->xfer[i]) + 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 */ +#ifdef _WIN32 + Sleep(1); +#endif + libusb_handle_events_timeout_completed(dev->ctx, + &zerotv, NULL); + if (r < 0) + continue; + + next_status = RTLSDR_CANCELING; + } + } + + if (dev->dev_lost || RTLSDR_INACTIVE == next_status) { + /* handle any events that still need to + * be handled before exiting after we + * just cancelled all transfers */ + libusb_handle_events_timeout_completed(dev->ctx, + &zerotv, NULL); + break; + } + } + } + + _rtlsdr_free_async_buffers(dev); + + dev->async_status = next_status; + + return r; +} + +int rtlsdr_cancel_async(rtlsdr_dev_t *dev) +{ + if (!dev) + return -1; + + /* if streaming, try to cancel gracefully */ + if (RTLSDR_RUNNING == dev->async_status) { + dev->async_status = RTLSDR_CANCELING; + dev->async_cancel = 1; + return 0; + } + + /* if called while in pending state, change the state forcefully */ +#if 0 + if (RTLSDR_INACTIVE != dev->async_status) { + dev->async_status = RTLSDR_INACTIVE; + return 0; + } +#endif + return -2; +} + +uint32_t rtlsdr_get_tuner_clock(void *dev) +{ + uint32_t tuner_freq; + + if (!dev) + return 0; + + /* read corrected clock value */ + if (rtlsdr_get_xtal_freq((rtlsdr_dev_t *)dev, NULL, &tuner_freq)) + return 0; + + return tuner_freq; +} + +int rtlsdr_i2c_write_fn(void *dev, uint8_t addr, uint8_t *buf, int len) +{ + if (dev) + return rtlsdr_i2c_write(((rtlsdr_dev_t *)dev), addr, buf, len); + + return -1; +} + +int rtlsdr_i2c_read_fn(void *dev, uint8_t addr, uint8_t *buf, int len) +{ + if (dev) + return rtlsdr_i2c_read(((rtlsdr_dev_t *)dev), addr, buf, len); + + return -1; +} + +int rtlsdr_set_bias_tee_gpio(rtlsdr_dev_t *dev, int gpio, int on) +{ + if (!dev) + return -1; + + rtlsdr_set_gpio_output(dev, gpio); + rtlsdr_set_gpio_bit(dev, gpio, on); + + return 0; +} + +int rtlsdr_set_bias_tee(rtlsdr_dev_t *dev, int on) +{ + return rtlsdr_set_bias_tee_gpio(dev, 0, on); +} diff --git a/Radio/HW/RtlSdr/r820/src/r820sdr-Bridging-Header.h b/Radio/HW/RtlSdr/r820/src/r820sdr-Bridging-Header.h new file mode 100644 index 0000000..fd7a194 --- /dev/null +++ b/Radio/HW/RtlSdr/r820/src/r820sdr-Bridging-Header.h @@ -0,0 +1,5 @@ +// +// Use this file to import your target's public headers that you would like to expose to Swift. +// + +#include "../include/rtl-sdr.h" diff --git a/Radio/HW/RtlSdr/r820/src/test.swift b/Radio/HW/RtlSdr/r820/src/test.swift new file mode 100644 index 0000000..f35c146 --- /dev/null +++ b/Radio/HW/RtlSdr/r820/src/test.swift @@ -0,0 +1,17 @@ +// +// test.swift +// PrySDR +// +// Created by Jacky Jack on 01/11/2024. +// + +import Foundation + + +final class r820sdr { + let deviceCounr = rtlsdr_get_device_count() + //r820sdr.rtlsdr_get_device_count() + //libusb_open(1, 1); + //r82xx +} + diff --git a/Radio/HW/RtlSdr/r820/src/tuner_e4k.c b/Radio/HW/RtlSdr/r820/src/tuner_e4k.c new file mode 100644 index 0000000..e4fb11e --- /dev/null +++ b/Radio/HW/RtlSdr/r820/src/tuner_e4k.c @@ -0,0 +1,1000 @@ +/* + * Elonics E4000 tuner driver + * + * (C) 2011-2012 by Harald Welte <laforge@gnumonks.org> + * (C) 2012 by Sylvain Munaut <tnt@246tNt.com> + * (C) 2012 by Hoernchen <la@tfc-server.de> + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <limits.h> +#include <stdint.h> +#include <errno.h> +#include <string.h> +#include <stdio.h> + +#include <reg_field.h> +#include <tuner_e4k.h> +#include <rtlsdr_i2c.h> + +#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0])) + +/* If this is defined, the limits are somewhat relaxed compared to what the + * vendor claims is possible */ +#define OUT_OF_SPEC + +#define MHZ(x) ((x)*1000*1000) +#define KHZ(x) ((x)*1000) + +uint32_t unsigned_delta(uint32_t a, uint32_t b) +{ + if (a > b) + return a - b; + else + return b - a; +} + +/* look-up table bit-width -> mask */ +static const uint8_t width2mask[] = { + 0, 1, 3, 7, 0xf, 0x1f, 0x3f, 0x7f, 0xff +}; + +/*********************************************************************** + * Register Access */ + +/*! \brief Write a register of the tuner chip + * \param[in] e4k reference to the tuner + * \param[in] reg number of the register + * \param[in] val value to be written + * \returns 0 on success, negative in case of error + */ +static int e4k_reg_write(struct e4k_state *e4k, uint8_t reg, uint8_t val) +{ + int r; + uint8_t data[2]; + data[0] = reg; + data[1] = val; + + r = rtlsdr_i2c_write_fn(e4k->rtl_dev, e4k->i2c_addr, data, 2); + return r == 2 ? 0 : -1; +} + +/*! \brief Read a register of the tuner chip + * \param[in] e4k reference to the tuner + * \param[in] reg number of the register + * \returns positive 8bit register contents on success, negative in case of error + */ +static int e4k_reg_read(struct e4k_state *e4k, uint8_t reg) +{ + uint8_t data = reg; + + if (rtlsdr_i2c_write_fn(e4k->rtl_dev, e4k->i2c_addr, &data, 1) < 1) + return -1; + + if (rtlsdr_i2c_read_fn(e4k->rtl_dev, e4k->i2c_addr, &data, 1) < 1) + return -1; + + return data; +} + +/*! \brief Set or clear some (masked) bits inside a register + * \param[in] e4k reference to the tuner + * \param[in] reg number of the register + * \param[in] mask bit-mask of the value + * \param[in] val data value to be written to register + * \returns 0 on success, negative in case of error + */ +static int e4k_reg_set_mask(struct e4k_state *e4k, uint8_t reg, + uint8_t mask, uint8_t val) +{ + uint8_t tmp = e4k_reg_read(e4k, reg); + + if ((tmp & mask) == val) + return 0; + + return e4k_reg_write(e4k, reg, (tmp & ~mask) | (val & mask)); +} + +/*! \brief Write a given field inside a register + * \param[in] e4k reference to the tuner + * \param[in] field structure describing the field + * \param[in] val value to be written + * \returns 0 on success, negative in case of error + */ +static int e4k_field_write(struct e4k_state *e4k, const struct reg_field *field, uint8_t val) +{ + int rc; + uint8_t mask; + + rc = e4k_reg_read(e4k, field->reg); + if (rc < 0) + return rc; + + mask = width2mask[field->width] << field->shift; + + return e4k_reg_set_mask(e4k, field->reg, mask, val << field->shift); +} + +/*! \brief Read a given field inside a register + * \param[in] e4k reference to the tuner + * \param[in] field structure describing the field + * \returns positive value of the field, negative in case of error + */ +static int e4k_field_read(struct e4k_state *e4k, const struct reg_field *field) +{ + int rc; + + rc = e4k_reg_read(e4k, field->reg); + if (rc < 0) + return rc; + + rc = (rc >> field->shift) & width2mask[field->width]; + + return rc; +} + +/*********************************************************************** + * Filter Control */ + +static const uint32_t rf_filt_center_uhf[] = { + MHZ(360), MHZ(380), MHZ(405), MHZ(425), + MHZ(450), MHZ(475), MHZ(505), MHZ(540), + MHZ(575), MHZ(615), MHZ(670), MHZ(720), + MHZ(760), MHZ(840), MHZ(890), MHZ(970) +}; + +static const uint32_t rf_filt_center_l[] = { + MHZ(1300), MHZ(1320), MHZ(1360), MHZ(1410), + MHZ(1445), MHZ(1460), MHZ(1490), MHZ(1530), + MHZ(1560), MHZ(1590), MHZ(1640), MHZ(1660), + MHZ(1680), MHZ(1700), MHZ(1720), MHZ(1750) +}; + +static int closest_arr_idx(const uint32_t *arr, unsigned int arr_size, uint32_t freq) +{ + unsigned int i, bi = 0; + uint32_t best_delta = 0xffffffff; + + /* iterate over the array containing a list of the center + * frequencies, selecting the closest one */ + for (i = 0; i < arr_size; i++) { + uint32_t delta = unsigned_delta(freq, arr[i]); + if (delta < best_delta) { + best_delta = delta; + bi = i; + } + } + + return bi; +} + +/* return 4-bit index as to which RF filter to select */ +static int choose_rf_filter(enum e4k_band band, uint32_t freq) +{ + int rc; + + switch (band) { + case E4K_BAND_VHF2: + case E4K_BAND_VHF3: + rc = 0; + break; + case E4K_BAND_UHF: + rc = closest_arr_idx(rf_filt_center_uhf, + ARRAY_SIZE(rf_filt_center_uhf), + freq); + break; + case E4K_BAND_L: + rc = closest_arr_idx(rf_filt_center_l, + ARRAY_SIZE(rf_filt_center_l), + freq); + break; + default: + rc = -EINVAL; + break; + } + + return rc; +} + +/* \brief Automatically select apropriate RF filter based on e4k state */ +int e4k_rf_filter_set(struct e4k_state *e4k) +{ + int rc; + + rc = choose_rf_filter(e4k->band, e4k->vco.flo); + if (rc < 0) + return rc; + + return e4k_reg_set_mask(e4k, E4K_REG_FILT1, 0xF, rc); +} + +/* Mixer Filter */ +static const uint32_t mix_filter_bw[] = { + KHZ(27000), KHZ(27000), KHZ(27000), KHZ(27000), + KHZ(27000), KHZ(27000), KHZ(27000), KHZ(27000), + KHZ(4600), KHZ(4200), KHZ(3800), KHZ(3400), + KHZ(3300), KHZ(2700), KHZ(2300), KHZ(1900) +}; + +/* IF RC Filter */ +static const uint32_t ifrc_filter_bw[] = { + KHZ(21400), KHZ(21000), KHZ(17600), KHZ(14700), + KHZ(12400), KHZ(10600), KHZ(9000), KHZ(7700), + KHZ(6400), KHZ(5300), KHZ(4400), KHZ(3400), + KHZ(2600), KHZ(1800), KHZ(1200), KHZ(1000) +}; + +/* IF Channel Filter */ +static const uint32_t ifch_filter_bw[] = { + KHZ(5500), KHZ(5300), KHZ(5000), KHZ(4800), + KHZ(4600), KHZ(4400), KHZ(4300), KHZ(4100), + KHZ(3900), KHZ(3800), KHZ(3700), KHZ(3600), + KHZ(3400), KHZ(3300), KHZ(3200), KHZ(3100), + KHZ(3000), KHZ(2950), KHZ(2900), KHZ(2800), + KHZ(2750), KHZ(2700), KHZ(2600), KHZ(2550), + KHZ(2500), KHZ(2450), KHZ(2400), KHZ(2300), + KHZ(2280), KHZ(2240), KHZ(2200), KHZ(2150) +}; + +static const uint32_t *if_filter_bw[] = { + mix_filter_bw, + ifch_filter_bw, + ifrc_filter_bw, +}; + +static const uint32_t if_filter_bw_len[] = { + ARRAY_SIZE(mix_filter_bw), + ARRAY_SIZE(ifch_filter_bw), + ARRAY_SIZE(ifrc_filter_bw), +}; + +static const struct reg_field if_filter_fields[] = { + { + E4K_REG_FILT2, 4, 4, + }, + { + E4K_REG_FILT3, 0, 5, + }, + { + E4K_REG_FILT2, 0, 4, + } +}; + +static int find_if_bw(enum e4k_if_filter filter, uint32_t bw) +{ + if (filter >= ARRAY_SIZE(if_filter_bw)) + return -EINVAL; + + return closest_arr_idx(if_filter_bw[filter], + if_filter_bw_len[filter], bw); +} + +/*! \brief Set the filter band-width of any of the IF filters + * \param[in] e4k reference to the tuner chip + * \param[in] filter filter to be configured + * \param[in] bandwidth bandwidth to be configured + * \returns positive actual filter band-width, negative in case of error + */ +int e4k_if_filter_bw_set(struct e4k_state *e4k, enum e4k_if_filter filter, + uint32_t bandwidth) +{ + int bw_idx; + const struct reg_field *field; + + if (filter >= ARRAY_SIZE(if_filter_bw)) + return -EINVAL; + + bw_idx = find_if_bw(filter, bandwidth); + + field = &if_filter_fields[filter]; + + return e4k_field_write(e4k, field, bw_idx); +} + +/*! \brief Enables / Disables the channel filter + * \param[in] e4k reference to the tuner chip + * \param[in] on 1=filter enabled, 0=filter disabled + * \returns 0 success, negative errors + */ +int e4k_if_filter_chan_enable(struct e4k_state *e4k, int on) +{ + return e4k_reg_set_mask(e4k, E4K_REG_FILT3, E4K_FILT3_DISABLE, + on ? 0 : E4K_FILT3_DISABLE); +} + +int e4k_if_filter_bw_get(struct e4k_state *e4k, enum e4k_if_filter filter) +{ + const uint32_t *arr; + int rc; + const struct reg_field *field; + + if (filter >= ARRAY_SIZE(if_filter_bw)) + return -EINVAL; + + field = &if_filter_fields[filter]; + + rc = e4k_field_read(e4k, field); + if (rc < 0) + return rc; + + arr = if_filter_bw[filter]; + + return arr[rc]; +} + + +/*********************************************************************** + * Frequency Control */ + +#define E4K_FVCO_MIN_KHZ 2600000 /* 2.6 GHz */ +#define E4K_FVCO_MAX_KHZ 3900000 /* 3.9 GHz */ +#define E4K_PLL_Y 65536 + +#ifdef OUT_OF_SPEC +#define E4K_FLO_MIN_MHZ 50 +#define E4K_FLO_MAX_MHZ 2200UL +#else +#define E4K_FLO_MIN_MHZ 64 +#define E4K_FLO_MAX_MHZ 1700 +#endif + +struct pll_settings { + uint32_t freq; + uint8_t reg_synth7; + uint8_t mult; +}; + +static const struct pll_settings pll_vars[] = { + {KHZ(72400), (1 << 3) | 7, 48}, + {KHZ(81200), (1 << 3) | 6, 40}, + {KHZ(108300), (1 << 3) | 5, 32}, + {KHZ(162500), (1 << 3) | 4, 24}, + {KHZ(216600), (1 << 3) | 3, 16}, + {KHZ(325000), (1 << 3) | 2, 12}, + {KHZ(350000), (1 << 3) | 1, 8}, + {KHZ(432000), (0 << 3) | 3, 8}, + {KHZ(667000), (0 << 3) | 2, 6}, + {KHZ(1200000), (0 << 3) | 1, 4} +}; + +static int is_fvco_valid(uint32_t fvco_z) +{ + /* check if the resulting fosc is valid */ + if (fvco_z/1000 < E4K_FVCO_MIN_KHZ || + fvco_z/1000 > E4K_FVCO_MAX_KHZ) { + fprintf(stderr, "[E4K] Fvco %u invalid\n", fvco_z); + return 0; + } + + return 1; +} + +static int is_fosc_valid(uint32_t fosc) +{ + if (fosc < MHZ(16) || fosc > MHZ(30)) { + fprintf(stderr, "[E4K] Fosc %u invalid\n", fosc); + return 0; + } + + return 1; +} + +static int is_z_valid(uint32_t z) +{ + if (z > 255) { + fprintf(stderr, "[E4K] Z %u invalid\n", z); + return 0; + } + + return 1; +} + +/*! \brief Determine if 3-phase mixing shall be used or not */ +static int use_3ph_mixing(uint32_t flo) +{ + /* this is a magic number somewhre between VHF and UHF */ + if (flo < MHZ(350)) + return 1; + + return 0; +} + +/* \brief compute Fvco based on Fosc, Z and X + * \returns positive value (Fvco in Hz), 0 in case of error */ +static uint64_t compute_fvco(uint32_t f_osc, uint8_t z, uint16_t x) +{ + uint64_t fvco_z, fvco_x, fvco; + + /* We use the following transformation in order to + * handle the fractional part with integer arithmetic: + * Fvco = Fosc * (Z + X/Y) <=> Fvco = Fosc * Z + (Fosc * X)/Y + * This avoids X/Y = 0. However, then we would overflow a 32bit + * integer, as we cannot hold e.g. 26 MHz * 65536 either. + */ + fvco_z = (uint64_t)f_osc * z; + +#if 0 + if (!is_fvco_valid(fvco_z)) + return 0; +#endif + + fvco_x = ((uint64_t)f_osc * x) / E4K_PLL_Y; + + fvco = fvco_z + fvco_x; + + return fvco; +} + +static uint32_t compute_flo(uint32_t f_osc, uint8_t z, uint16_t x, uint8_t r) +{ + uint64_t fvco = compute_fvco(f_osc, z, x); + if (fvco == 0) + return -EINVAL; + + return fvco / r; +} + +static int e4k_band_set(struct e4k_state *e4k, enum e4k_band band) +{ + int rc; + + switch (band) { + case E4K_BAND_VHF2: + case E4K_BAND_VHF3: + case E4K_BAND_UHF: + e4k_reg_write(e4k, E4K_REG_BIAS, 3); + break; + case E4K_BAND_L: + e4k_reg_write(e4k, E4K_REG_BIAS, 0); + break; + } + + /* workaround: if we don't reset this register before writing to it, + * we get a gap between 325-350 MHz */ + rc = e4k_reg_set_mask(e4k, E4K_REG_SYNTH1, 0x06, 0); + rc = e4k_reg_set_mask(e4k, E4K_REG_SYNTH1, 0x06, band << 1); + if (rc >= 0) + e4k->band = band; + + return rc; +} + +/*! \brief Compute PLL parameters for givent target frequency + * \param[out] oscp Oscillator parameters, if computation successful + * \param[in] fosc Clock input frequency applied to the chip (Hz) + * \param[in] intended_flo target tuning frequency (Hz) + * \returns actual PLL frequency, as close as possible to intended_flo, + * 0 in case of error + */ +uint32_t e4k_compute_pll_params(struct e4k_pll_params *oscp, uint32_t fosc, uint32_t intended_flo) +{ + uint32_t i; + uint8_t r = 2; + uint64_t intended_fvco, remainder; + uint64_t z = 0; + uint32_t x; + int flo; + int three_phase_mixing = 0; + oscp->r_idx = 0; + + if (!is_fosc_valid(fosc)) + return 0; + + for(i = 0; i < ARRAY_SIZE(pll_vars); ++i) { + if(intended_flo < pll_vars[i].freq) { + three_phase_mixing = (pll_vars[i].reg_synth7 & 0x08) ? 1 : 0; + oscp->r_idx = pll_vars[i].reg_synth7; + r = pll_vars[i].mult; + break; + } + } + + //fprintf(stderr, "[E4K] Fint=%u, R=%u\n", intended_flo, r); + + /* flo(max) = 1700MHz, R(max) = 48, we need 64bit! */ + intended_fvco = (uint64_t)intended_flo * r; + + /* compute integral component of multiplier */ + z = intended_fvco / fosc; + + /* compute fractional part. this will not overflow, + * as fosc(max) = 30MHz and z(max) = 255 */ + remainder = intended_fvco - (fosc * z); + /* remainder(max) = 30MHz, E4K_PLL_Y = 65536 -> 64bit! */ + x = (remainder * E4K_PLL_Y) / fosc; + /* x(max) as result of this computation is 65536 */ + + flo = compute_flo(fosc, z, x, r); + + oscp->fosc = fosc; + oscp->flo = flo; + oscp->intended_flo = intended_flo; + oscp->r = r; +// oscp->r_idx = pll_vars[i].reg_synth7 & 0x0; + oscp->threephase = three_phase_mixing; + oscp->x = x; + oscp->z = z; + + return flo; +} + +int e4k_tune_params(struct e4k_state *e4k, struct e4k_pll_params *p) +{ + /* program R + 3phase/2phase */ + e4k_reg_write(e4k, E4K_REG_SYNTH7, p->r_idx); + /* program Z */ + e4k_reg_write(e4k, E4K_REG_SYNTH3, p->z); + /* program X */ + e4k_reg_write(e4k, E4K_REG_SYNTH4, p->x & 0xff); + e4k_reg_write(e4k, E4K_REG_SYNTH5, p->x >> 8); + + /* we're in auto calibration mode, so there's no need to trigger it */ + + memcpy(&e4k->vco, p, sizeof(e4k->vco)); + + /* set the band */ + if (e4k->vco.flo < MHZ(140)) + e4k_band_set(e4k, E4K_BAND_VHF2); + else if (e4k->vco.flo < MHZ(350)) + e4k_band_set(e4k, E4K_BAND_VHF3); + else if (e4k->vco.flo < MHZ(1135)) + e4k_band_set(e4k, E4K_BAND_UHF); + else + e4k_band_set(e4k, E4K_BAND_L); + + /* select and set proper RF filter */ + e4k_rf_filter_set(e4k); + + return e4k->vco.flo; +} + +/*! \brief High-level tuning API, just specify frquency + * + * This function will compute matching PLL parameters, program them into the + * hardware and set the band as well as RF filter. + * + * \param[in] e4k reference to tuner + * \param[in] freq frequency in Hz + * \returns actual tuned frequency, negative in case of error + */ +int e4k_tune_freq(struct e4k_state *e4k, uint32_t freq) +{ + uint32_t rc; + struct e4k_pll_params p; + + /* determine PLL parameters */ + rc = e4k_compute_pll_params(&p, e4k->vco.fosc, freq); + if (!rc) + return -EINVAL; + + /* actually tune to those parameters */ + rc = e4k_tune_params(e4k, &p); + + /* check PLL lock */ + rc = e4k_reg_read(e4k, E4K_REG_SYNTH1); + if (!(rc & 0x01)) { + fprintf(stderr, "[E4K] PLL not locked for %u Hz!\n", freq); + return -1; + } + + return 0; +} + +/*********************************************************************** + * Gain Control */ + +static const int8_t if_stage1_gain[] = { + -3, 6 +}; + +static const int8_t if_stage23_gain[] = { + 0, 3, 6, 9 +}; + +static const int8_t if_stage4_gain[] = { + 0, 1, 2, 2 +}; + +static const int8_t if_stage56_gain[] = { + 3, 6, 9, 12, 15, 15, 15, 15 +}; + +static const int8_t *if_stage_gain[] = { + 0, + if_stage1_gain, + if_stage23_gain, + if_stage23_gain, + if_stage4_gain, + if_stage56_gain, + if_stage56_gain +}; + +static const uint8_t if_stage_gain_len[] = { + 0, + ARRAY_SIZE(if_stage1_gain), + ARRAY_SIZE(if_stage23_gain), + ARRAY_SIZE(if_stage23_gain), + ARRAY_SIZE(if_stage4_gain), + ARRAY_SIZE(if_stage56_gain), + ARRAY_SIZE(if_stage56_gain) +}; + +static const struct reg_field if_stage_gain_regs[] = { + { 0, 0, 0 }, + { E4K_REG_GAIN3, 0, 1 }, + { E4K_REG_GAIN3, 1, 2 }, + { E4K_REG_GAIN3, 3, 2 }, + { E4K_REG_GAIN3, 5, 2 }, + { E4K_REG_GAIN4, 0, 3 }, + { E4K_REG_GAIN4, 3, 3 } +}; + +static const int32_t lnagain[] = { + -50, 0, + -25, 1, + 0, 4, + 25, 5, + 50, 6, + 75, 7, + 100, 8, + 125, 9, + 150, 10, + 175, 11, + 200, 12, + 250, 13, + 300, 14, +}; + +static const int32_t enhgain[] = { + 10, 30, 50, 70 +}; + +int e4k_set_lna_gain(struct e4k_state *e4k, int32_t gain) +{ + uint32_t i; + for(i = 0; i < ARRAY_SIZE(lnagain)/2; ++i) { + if(lnagain[i*2] == gain) { + e4k_reg_set_mask(e4k, E4K_REG_GAIN1, 0xf, lnagain[i*2+1]); + return gain; + } + } + return -EINVAL; +} + +int e4k_set_enh_gain(struct e4k_state *e4k, int32_t gain) +{ + uint32_t i; + for(i = 0; i < ARRAY_SIZE(enhgain); ++i) { + if(enhgain[i] == gain) { + e4k_reg_set_mask(e4k, E4K_REG_AGC11, 0x7, E4K_AGC11_LNA_GAIN_ENH | (i << 1)); + return gain; + } + } + e4k_reg_set_mask(e4k, E4K_REG_AGC11, 0x7, 0); + + /* special case: 0 = off*/ + if(0 == gain) + return 0; + else + return -EINVAL; +} + +int e4k_enable_manual_gain(struct e4k_state *e4k, uint8_t manual) +{ + if (manual) { + /* Set LNA mode to manual */ + e4k_reg_set_mask(e4k, E4K_REG_AGC1, E4K_AGC1_MOD_MASK, E4K_AGC_MOD_SERIAL); + + /* Set Mixer Gain Control to manual */ + e4k_reg_set_mask(e4k, E4K_REG_AGC7, E4K_AGC7_MIX_GAIN_AUTO, 0); + } else { + /* Set LNA mode to auto */ + e4k_reg_set_mask(e4k, E4K_REG_AGC1, E4K_AGC1_MOD_MASK, E4K_AGC_MOD_IF_SERIAL_LNA_AUTON); + /* Set Mixer Gain Control to auto */ + e4k_reg_set_mask(e4k, E4K_REG_AGC7, E4K_AGC7_MIX_GAIN_AUTO, 1); + + e4k_reg_set_mask(e4k, E4K_REG_AGC11, 0x7, 0); + } + + return 0; +} + +static int find_stage_gain(uint8_t stage, int8_t val) +{ + const int8_t *arr; + int i; + + if (stage >= ARRAY_SIZE(if_stage_gain)) + return -EINVAL; + + arr = if_stage_gain[stage]; + + for (i = 0; i < if_stage_gain_len[stage]; i++) { + if (arr[i] == val) + return i; + } + return -EINVAL; +} + +/*! \brief Set the gain of one of the IF gain stages + * \param e4k handle to the tuner chip + * \param stage number of the stage (1..6) + * \param value gain value in dB + * \returns 0 on success, negative in case of error + */ +int e4k_if_gain_set(struct e4k_state *e4k, uint8_t stage, int8_t value) +{ + int rc; + uint8_t mask; + const struct reg_field *field; + + rc = find_stage_gain(stage, value); + if (rc < 0) + return rc; + + /* compute the bit-mask for the given gain field */ + field = &if_stage_gain_regs[stage]; + mask = width2mask[field->width] << field->shift; + + return e4k_reg_set_mask(e4k, field->reg, mask, rc << field->shift); +} + +int e4k_mixer_gain_set(struct e4k_state *e4k, int8_t value) +{ + uint8_t bit; + + switch (value) { + case 4: + bit = 0; + break; + case 12: + bit = 1; + break; + default: + return -EINVAL; + } + + return e4k_reg_set_mask(e4k, E4K_REG_GAIN2, 1, bit); +} + +int e4k_commonmode_set(struct e4k_state *e4k, int8_t value) +{ + if(value < 0) + return -EINVAL; + else if(value > 7) + return -EINVAL; + + return e4k_reg_set_mask(e4k, E4K_REG_DC7, 7, value); +} + +/*********************************************************************** + * DC Offset */ + +int e4k_manual_dc_offset(struct e4k_state *e4k, int8_t iofs, int8_t irange, int8_t qofs, int8_t qrange) +{ + int res; + + if((iofs < 0x00) || (iofs > 0x3f)) + return -EINVAL; + if((irange < 0x00) || (irange > 0x03)) + return -EINVAL; + if((qofs < 0x00) || (qofs > 0x3f)) + return -EINVAL; + if((qrange < 0x00) || (qrange > 0x03)) + return -EINVAL; + + res = e4k_reg_set_mask(e4k, E4K_REG_DC2, 0x3f, iofs); + if(res < 0) + return res; + + res = e4k_reg_set_mask(e4k, E4K_REG_DC3, 0x3f, qofs); + if(res < 0) + return res; + + res = e4k_reg_set_mask(e4k, E4K_REG_DC4, 0x33, (qrange << 4) | irange); + return res; +} + +/*! \brief Perform a DC offset calibration right now + * \param [e4k] handle to the tuner chip + */ +int e4k_dc_offset_calibrate(struct e4k_state *e4k) +{ + /* make sure the DC range detector is enabled */ + e4k_reg_set_mask(e4k, E4K_REG_DC5, E4K_DC5_RANGE_DET_EN, E4K_DC5_RANGE_DET_EN); + + return e4k_reg_write(e4k, E4K_REG_DC1, 0x01); +} + + +static const int8_t if_gains_max[] = { + 0, 6, 9, 9, 2, 15, 15 +}; + +struct gain_comb { + int8_t mixer_gain; + int8_t if1_gain; + uint8_t reg; +}; + +static const struct gain_comb dc_gain_comb[] = { + { 4, -3, 0x50 }, + { 4, 6, 0x51 }, + { 12, -3, 0x52 }, + { 12, 6, 0x53 }, +}; + +#define TO_LUT(offset, range) (offset | (range << 6)) + +int e4k_dc_offset_gen_table(struct e4k_state *e4k) +{ + uint32_t i; + + /* FIXME: read ont current gain values and write them back + * before returning to the caller */ + + /* disable auto mixer gain */ + e4k_reg_set_mask(e4k, E4K_REG_AGC7, E4K_AGC7_MIX_GAIN_AUTO, 0); + + /* set LNA/IF gain to full manual */ + e4k_reg_set_mask(e4k, E4K_REG_AGC1, E4K_AGC1_MOD_MASK, + E4K_AGC_MOD_SERIAL); + + /* set all 'other' gains to maximum */ + for (i = 2; i <= 6; i++) + e4k_if_gain_set(e4k, i, if_gains_max[i]); + + /* iterate over all mixer + if_stage_1 gain combinations */ + for (i = 0; i < ARRAY_SIZE(dc_gain_comb); i++) { + uint8_t offs_i, offs_q, range, range_i, range_q; + + /* set the combination of mixer / if1 gain */ + e4k_mixer_gain_set(e4k, dc_gain_comb[i].mixer_gain); + e4k_if_gain_set(e4k, 1, dc_gain_comb[i].if1_gain); + + /* perform actual calibration */ + e4k_dc_offset_calibrate(e4k); + + /* extract I/Q offset and range values */ + offs_i = e4k_reg_read(e4k, E4K_REG_DC2) & 0x3f; + offs_q = e4k_reg_read(e4k, E4K_REG_DC3) & 0x3f; + range = e4k_reg_read(e4k, E4K_REG_DC4); + range_i = range & 0x3; + range_q = (range >> 4) & 0x3; + + fprintf(stderr, "[E4K] Table %u I=%u/%u, Q=%u/%u\n", + i, range_i, offs_i, range_q, offs_q); + + /* write into the table */ + e4k_reg_write(e4k, dc_gain_comb[i].reg, + TO_LUT(offs_q, range_q)); + e4k_reg_write(e4k, dc_gain_comb[i].reg + 0x10, + TO_LUT(offs_i, range_i)); + } + + return 0; +} + +/*********************************************************************** + * Standby */ + +/*! \brief Enable/disable standby mode + */ +int e4k_standby(struct e4k_state *e4k, int enable) +{ + e4k_reg_set_mask(e4k, E4K_REG_MASTER1, E4K_MASTER1_NORM_STBY, + enable ? 0 : E4K_MASTER1_NORM_STBY); + + return 0; +} + +/*********************************************************************** + * Initialization */ + +static int magic_init(struct e4k_state *e4k) +{ + e4k_reg_write(e4k, 0x7e, 0x01); + e4k_reg_write(e4k, 0x7f, 0xfe); + e4k_reg_write(e4k, 0x82, 0x00); + e4k_reg_write(e4k, 0x86, 0x50); /* polarity A */ + e4k_reg_write(e4k, 0x87, 0x20); + e4k_reg_write(e4k, 0x88, 0x01); + e4k_reg_write(e4k, 0x9f, 0x7f); + e4k_reg_write(e4k, 0xa0, 0x07); + + return 0; +} + +/*! \brief Initialize the E4K tuner + */ +int e4k_init(struct e4k_state *e4k) +{ + /* make a dummy i2c read or write command, will not be ACKed! */ + e4k_reg_read(e4k, 0); + + /* Make sure we reset everything and clear POR indicator */ + e4k_reg_write(e4k, E4K_REG_MASTER1, + E4K_MASTER1_RESET | + E4K_MASTER1_NORM_STBY | + E4K_MASTER1_POR_DET + ); + + /* Configure clock input */ + e4k_reg_write(e4k, E4K_REG_CLK_INP, 0x00); + + /* Disable clock output */ + e4k_reg_write(e4k, E4K_REG_REF_CLK, 0x00); + e4k_reg_write(e4k, E4K_REG_CLKOUT_PWDN, 0x96); + + /* Write some magic values into registers */ + magic_init(e4k); +#if 0 + /* Set common mode voltage a bit higher for more margin 850 mv */ + e4k_commonmode_set(e4k, 4); + + /* Initialize DC offset lookup tables */ + e4k_dc_offset_gen_table(e4k); + + /* Enable time variant DC correction */ + e4k_reg_write(e4k, E4K_REG_DCTIME1, 0x01); + e4k_reg_write(e4k, E4K_REG_DCTIME2, 0x01); +#endif + + /* Set LNA mode to manual */ + e4k_reg_write(e4k, E4K_REG_AGC4, 0x10); /* High threshold */ + e4k_reg_write(e4k, E4K_REG_AGC5, 0x04); /* Low threshold */ + e4k_reg_write(e4k, E4K_REG_AGC6, 0x1a); /* LNA calib + loop rate */ + + e4k_reg_set_mask(e4k, E4K_REG_AGC1, E4K_AGC1_MOD_MASK, + E4K_AGC_MOD_SERIAL); + + /* Set Mixer Gain Control to manual */ + e4k_reg_set_mask(e4k, E4K_REG_AGC7, E4K_AGC7_MIX_GAIN_AUTO, 0); + +#if 0 + /* Enable LNA Gain enhancement */ + e4k_reg_set_mask(e4k, E4K_REG_AGC11, 0x7, + E4K_AGC11_LNA_GAIN_ENH | (2 << 1)); + + /* Enable automatic IF gain mode switching */ + e4k_reg_set_mask(e4k, E4K_REG_AGC8, 0x1, E4K_AGC8_SENS_LIN_AUTO); +#endif + + /* Use auto-gain as default */ + e4k_enable_manual_gain(e4k, 0); + + /* Select moderate gain levels */ + e4k_if_gain_set(e4k, 1, 6); + e4k_if_gain_set(e4k, 2, 0); + e4k_if_gain_set(e4k, 3, 0); + e4k_if_gain_set(e4k, 4, 0); + e4k_if_gain_set(e4k, 5, 9); + e4k_if_gain_set(e4k, 6, 9); + + /* Set the most narrow filter we can possibly use */ + e4k_if_filter_bw_set(e4k, E4K_IF_FILTER_MIX, KHZ(1900)); + e4k_if_filter_bw_set(e4k, E4K_IF_FILTER_RC, KHZ(1000)); + e4k_if_filter_bw_set(e4k, E4K_IF_FILTER_CHAN, KHZ(2150)); + e4k_if_filter_chan_enable(e4k, 1); + + /* Disable time variant DC correction and LUT */ + e4k_reg_set_mask(e4k, E4K_REG_DC5, 0x03, 0); + e4k_reg_set_mask(e4k, E4K_REG_DCTIME1, 0x03, 0); + e4k_reg_set_mask(e4k, E4K_REG_DCTIME2, 0x03, 0); + + return 0; +} diff --git a/Radio/HW/RtlSdr/r820/src/tuner_fc0012.c b/Radio/HW/RtlSdr/r820/src/tuner_fc0012.c new file mode 100644 index 0000000..768cf1c --- /dev/null +++ b/Radio/HW/RtlSdr/r820/src/tuner_fc0012.c @@ -0,0 +1,345 @@ +/* + * Fitipower FC0012 tuner driver + * + * Copyright (C) 2012 Hans-Frieder Vogt <hfvogt@gmx.net> + * + * modified for use in librtlsdr + * Copyright (C) 2012 Steve Markgraf <steve@steve-m.de> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <stdint.h> +#include <stdio.h> + +#include "rtlsdr_i2c.h" +#include "tuner_fc0012.h" + +static int fc0012_writereg(void *dev, uint8_t reg, uint8_t val) +{ + uint8_t data[2]; + data[0] = reg; + data[1] = val; + + if (rtlsdr_i2c_write_fn(dev, FC0012_I2C_ADDR, data, 2) < 0) + return -1; + + return 0; +} + +static int fc0012_readreg(void *dev, uint8_t reg, uint8_t *val) +{ + uint8_t data = reg; + + if (rtlsdr_i2c_write_fn(dev, FC0012_I2C_ADDR, &data, 1) < 0) + return -1; + + if (rtlsdr_i2c_read_fn(dev, FC0012_I2C_ADDR, &data, 1) < 0) + return -1; + + *val = data; + + return 0; +} + +/* Incomplete list of register settings: + * + * Name Reg Bits Desc + * CHIP_ID 0x00 0-7 Chip ID (constant 0xA1) + * RF_A 0x01 0-3 Number of count-to-9 cycles in RF + * divider (suggested: 2..9) + * RF_M 0x02 0-7 Total number of cycles (to-8 and to-9) + * in RF divider + * RF_K_HIGH 0x03 0-6 Bits 8..14 of fractional divider + * RF_K_LOW 0x04 0-7 Bits 0..7 of fractional RF divider + * RF_OUTDIV_A 0x05 3-7 Power of two required? + * LNA_POWER_DOWN 0x06 0 Set to 1 to switch off low noise amp + * RF_OUTDIV_B 0x06 1 Set to select 3 instead of 2 for the + * RF output divider + * VCO_SPEED 0x06 3 Select tuning range of VCO: + * 0 = Low range, (ca. 1.1 - 1.5GHz) + * 1 = High range (ca. 1.4 - 1.8GHz) + * BANDWIDTH 0x06 6-7 Set bandwidth. 6MHz = 0x80, 7MHz=0x40 + * 8MHz=0x00 + * XTAL_SPEED 0x07 5 Set to 1 for 28.8MHz Crystal input + * or 0 for 36MHz + * <agc params> 0x08 0-7 + * EN_CAL_RSSI 0x09 4 Enable calibrate RSSI + * (Receive Signal Strength Indicator) + * LNA_FORCE 0x0d 0 + * AGC_FORCE 0x0d ? + * LNA_GAIN 0x13 3-4 Low noise amp gain + * LNA_COMPS 0x15 3 ? + * VCO_CALIB 0x0e 7 Set high then low to calibrate VCO + * (fast lock?) + * VCO_VOLTAGE 0x0e 0-6 Read Control voltage of VCO + * (big value -> low freq) + */ + +int fc0012_init(void *dev) +{ + int ret = 0; + unsigned int i; + uint8_t reg[] = { + 0x00, /* dummy reg. 0 */ + 0x05, /* reg. 0x01 */ + 0x10, /* reg. 0x02 */ + 0x00, /* reg. 0x03 */ + 0x00, /* reg. 0x04 */ + 0x0f, /* reg. 0x05: may also be 0x0a */ + 0x00, /* reg. 0x06: divider 2, VCO slow */ + 0x00, /* reg. 0x07: may also be 0x0f */ + 0xff, /* reg. 0x08: AGC Clock divide by 256, AGC gain 1/256, + Loop Bw 1/8 */ + 0x6e, /* reg. 0x09: Disable LoopThrough, Enable LoopThrough: 0x6f */ + 0xb8, /* reg. 0x0a: Disable LO Test Buffer */ + 0x82, /* reg. 0x0b: Output Clock is same as clock frequency, + may also be 0x83 */ + 0xfc, /* reg. 0x0c: depending on AGC Up-Down mode, may need 0xf8 */ + 0x02, /* reg. 0x0d: AGC Not Forcing & LNA Forcing, 0x02 for DVB-T */ + 0x00, /* reg. 0x0e */ + 0x00, /* reg. 0x0f */ + 0x00, /* reg. 0x10: may also be 0x0d */ + 0x00, /* reg. 0x11 */ + 0x1f, /* reg. 0x12: Set to maximum gain */ + 0x08, /* reg. 0x13: Set to Middle Gain: 0x08, + Low Gain: 0x00, High Gain: 0x10, enable IX2: 0x80 */ + 0x00, /* reg. 0x14 */ + 0x04, /* reg. 0x15: Enable LNA COMPS */ + }; + +#if 0 + switch (rtlsdr_get_tuner_clock(dev)) { + case FC_XTAL_27_MHZ: + case FC_XTAL_28_8_MHZ: + reg[0x07] |= 0x20; + break; + case FC_XTAL_36_MHZ: + default: + break; + } +#endif + reg[0x07] |= 0x20; + +// if (priv->dual_master) + reg[0x0c] |= 0x02; + + for (i = 1; i < sizeof(reg); i++) { + ret = fc0012_writereg(dev, i, reg[i]); + if (ret) + break; + } + + return ret; +} + +int fc0012_set_params(void *dev, uint32_t freq, uint32_t bandwidth) +{ + int i, ret = 0; + uint8_t reg[7], am, pm, multi, tmp; + uint64_t f_vco; + uint32_t xtal_freq_div_2; + uint16_t xin, xdiv; + int vco_select = 0; + + xtal_freq_div_2 = rtlsdr_get_tuner_clock(dev) / 2; + + /* select frequency divider and the frequency of VCO */ + if (freq < 37084000) { /* freq * 96 < 3560000000 */ + multi = 96; + reg[5] = 0x82; + reg[6] = 0x00; + } else if (freq < 55625000) { /* freq * 64 < 3560000000 */ + multi = 64; + reg[5] = 0x82; + reg[6] = 0x02; + } else if (freq < 74167000) { /* freq * 48 < 3560000000 */ + multi = 48; + reg[5] = 0x42; + reg[6] = 0x00; + } else if (freq < 111250000) { /* freq * 32 < 3560000000 */ + multi = 32; + reg[5] = 0x42; + reg[6] = 0x02; + } else if (freq < 148334000) { /* freq * 24 < 3560000000 */ + multi = 24; + reg[5] = 0x22; + reg[6] = 0x00; + } else if (freq < 222500000) { /* freq * 16 < 3560000000 */ + multi = 16; + reg[5] = 0x22; + reg[6] = 0x02; + } else if (freq < 296667000) { /* freq * 12 < 3560000000 */ + multi = 12; + reg[5] = 0x12; + reg[6] = 0x00; + } else if (freq < 445000000) { /* freq * 8 < 3560000000 */ + multi = 8; + reg[5] = 0x12; + reg[6] = 0x02; + } else if (freq < 593334000) { /* freq * 6 < 3560000000 */ + multi = 6; + reg[5] = 0x0a; + reg[6] = 0x00; + } else { + multi = 4; + reg[5] = 0x0a; + reg[6] = 0x02; + } + + f_vco = freq * multi; + + if (f_vco >= 3060000000U) { + reg[6] |= 0x08; + vco_select = 1; + } + + /* From divided value (XDIV) determined the FA and FP value */ + xdiv = (uint16_t)(f_vco / xtal_freq_div_2); + if ((f_vco - xdiv * xtal_freq_div_2) >= (xtal_freq_div_2 / 2)) + xdiv++; + + pm = (uint8_t)(xdiv / 8); + am = (uint8_t)(xdiv - (8 * pm)); + + if (am < 2) { + am += 8; + pm--; + } + + if (pm > 31) { + reg[1] = am + (8 * (pm - 31)); + reg[2] = 31; + } else { + reg[1] = am; + reg[2] = pm; + } + + if ((reg[1] > 15) || (reg[2] < 0x0b)) { + fprintf(stderr, "[FC0012] no valid PLL combination " + "found for %u Hz!\n", freq); + return -1; + } + + /* fix clock out */ + reg[6] |= 0x20; + + /* From VCO frequency determines the XIN ( fractional part of Delta + Sigma PLL) and divided value (XDIV) */ + xin = (uint16_t)((f_vco - (f_vco / xtal_freq_div_2) * xtal_freq_div_2) / 1000); + xin = (xin << 15) / (xtal_freq_div_2 / 1000); + if (xin >= 16384) + xin += 32768; + + reg[3] = xin >> 8; /* xin with 9 bit resolution */ + reg[4] = xin & 0xff; + + reg[6] &= 0x3f; /* bits 6 and 7 describe the bandwidth */ + switch (bandwidth) { + case 6000000: + reg[6] |= 0x80; + break; + case 7000000: + reg[6] |= 0x40; + break; + case 8000000: + default: + break; + } + + /* modified for Realtek demod */ + reg[5] |= 0x07; + + for (i = 1; i <= 6; i++) { + ret = fc0012_writereg(dev, i, reg[i]); + if (ret) + goto exit; + } + + /* VCO Calibration */ + ret = fc0012_writereg(dev, 0x0e, 0x80); + if (!ret) + ret = fc0012_writereg(dev, 0x0e, 0x00); + + /* VCO Re-Calibration if needed */ + if (!ret) + ret = fc0012_writereg(dev, 0x0e, 0x00); + + if (!ret) { +// msleep(10); + ret = fc0012_readreg(dev, 0x0e, &tmp); + } + if (ret) + goto exit; + + /* vco selection */ + tmp &= 0x3f; + + if (vco_select) { + if (tmp > 0x3c) { + reg[6] &= ~0x08; + ret = fc0012_writereg(dev, 0x06, reg[6]); + if (!ret) + ret = fc0012_writereg(dev, 0x0e, 0x80); + if (!ret) + ret = fc0012_writereg(dev, 0x0e, 0x00); + } + } else { + if (tmp < 0x02) { + reg[6] |= 0x08; + ret = fc0012_writereg(dev, 0x06, reg[6]); + if (!ret) + ret = fc0012_writereg(dev, 0x0e, 0x80); + if (!ret) + ret = fc0012_writereg(dev, 0x0e, 0x00); + } + } + +exit: + return ret; +} + +int fc0012_set_gain(void *dev, int gain) +{ + int ret; + uint8_t tmp = 0; + + ret = fc0012_readreg(dev, 0x13, &tmp); + + /* mask bits off */ + tmp &= 0xe0; + + switch (gain) { + case -99: /* -9.9 dB */ + tmp |= 0x02; + break; + case -40: /* -4 dB */ + break; + case 71: + tmp |= 0x08; /* 7.1 dB */ + break; + case 179: + tmp |= 0x17; /* 17.9 dB */ + break; + case 192: + default: + tmp |= 0x10; /* 19.2 dB */ + break; + } + + ret = fc0012_writereg(dev, 0x13, tmp); + + return ret; +} diff --git a/Radio/HW/RtlSdr/r820/src/tuner_fc0013.c b/Radio/HW/RtlSdr/r820/src/tuner_fc0013.c new file mode 100644 index 0000000..5984dfb --- /dev/null +++ b/Radio/HW/RtlSdr/r820/src/tuner_fc0013.c @@ -0,0 +1,500 @@ +/* + * Fitipower FC0013 tuner driver + * + * Copyright (C) 2012 Hans-Frieder Vogt <hfvogt@gmx.net> + * partially based on driver code from Fitipower + * Copyright (C) 2010 Fitipower Integrated Technology Inc + * + * modified for use in librtlsdr + * Copyright (C) 2012 Steve Markgraf <steve@steve-m.de> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include <stdint.h> +#include <stdio.h> + +#include "rtlsdr_i2c.h" +#include "tuner_fc0013.h" + +static int fc0013_writereg(void *dev, uint8_t reg, uint8_t val) +{ + uint8_t data[2]; + data[0] = reg; + data[1] = val; + + if (rtlsdr_i2c_write_fn(dev, FC0013_I2C_ADDR, data, 2) < 0) + return -1; + + return 0; +} + +static int fc0013_readreg(void *dev, uint8_t reg, uint8_t *val) +{ + uint8_t data = reg; + + if (rtlsdr_i2c_write_fn(dev, FC0013_I2C_ADDR, &data, 1) < 0) + return -1; + + if (rtlsdr_i2c_read_fn(dev, FC0013_I2C_ADDR, &data, 1) < 0) + return -1; + + *val = data; + + return 0; +} + +int fc0013_init(void *dev) +{ + int ret = 0; + unsigned int i; + uint8_t reg[] = { + 0x00, /* reg. 0x00: dummy */ + 0x09, /* reg. 0x01 */ + 0x16, /* reg. 0x02 */ + 0x00, /* reg. 0x03 */ + 0x00, /* reg. 0x04 */ + 0x17, /* reg. 0x05 */ + 0x02, /* reg. 0x06: LPF bandwidth */ + 0x0a, /* reg. 0x07: CHECK */ + 0xff, /* reg. 0x08: AGC Clock divide by 256, AGC gain 1/256, + Loop Bw 1/8 */ + 0x6e, /* reg. 0x09: Disable LoopThrough, Enable LoopThrough: 0x6f */ + 0xb8, /* reg. 0x0a: Disable LO Test Buffer */ + 0x82, /* reg. 0x0b: CHECK */ + 0xfc, /* reg. 0x0c: depending on AGC Up-Down mode, may need 0xf8 */ + 0x01, /* reg. 0x0d: AGC Not Forcing & LNA Forcing, may need 0x02 */ + 0x00, /* reg. 0x0e */ + 0x00, /* reg. 0x0f */ + 0x00, /* reg. 0x10 */ + 0x00, /* reg. 0x11 */ + 0x00, /* reg. 0x12 */ + 0x00, /* reg. 0x13 */ + 0x50, /* reg. 0x14: DVB-t High Gain, UHF. + Middle Gain: 0x48, Low Gain: 0x40 */ + 0x01, /* reg. 0x15 */ + }; +#if 0 + switch (rtlsdr_get_tuner_clock(dev)) { + case FC_XTAL_27_MHZ: + case FC_XTAL_28_8_MHZ: + reg[0x07] |= 0x20; + break; + case FC_XTAL_36_MHZ: + default: + break; + } +#endif + reg[0x07] |= 0x20; + +// if (dev->dual_master) + reg[0x0c] |= 0x02; + + for (i = 1; i < sizeof(reg); i++) { + ret = fc0013_writereg(dev, i, reg[i]); + if (ret < 0) + break; + } + + return ret; +} + +int fc0013_rc_cal_add(void *dev, int rc_val) +{ + int ret; + uint8_t rc_cal; + int val; + + /* push rc_cal value, get rc_cal value */ + ret = fc0013_writereg(dev, 0x10, 0x00); + if (ret) + goto error_out; + + /* get rc_cal value */ + ret = fc0013_readreg(dev, 0x10, &rc_cal); + if (ret) + goto error_out; + + rc_cal &= 0x0f; + + val = (int)rc_cal + rc_val; + + /* forcing rc_cal */ + ret = fc0013_writereg(dev, 0x0d, 0x11); + if (ret) + goto error_out; + + /* modify rc_cal value */ + if (val > 15) + ret = fc0013_writereg(dev, 0x10, 0x0f); + else if (val < 0) + ret = fc0013_writereg(dev, 0x10, 0x00); + else + ret = fc0013_writereg(dev, 0x10, (uint8_t)val); + +error_out: + return ret; +} + +int fc0013_rc_cal_reset(void *dev) +{ + int ret; + + ret = fc0013_writereg(dev, 0x0d, 0x01); + if (!ret) + ret = fc0013_writereg(dev, 0x10, 0x00); + + return ret; +} + +static int fc0013_set_vhf_track(void *dev, uint32_t freq) +{ + int ret; + uint8_t tmp; + + ret = fc0013_readreg(dev, 0x1d, &tmp); + if (ret) + goto error_out; + tmp &= 0xe3; + if (freq <= 177500000) { /* VHF Track: 7 */ + ret = fc0013_writereg(dev, 0x1d, tmp | 0x1c); + } else if (freq <= 184500000) { /* VHF Track: 6 */ + ret = fc0013_writereg(dev, 0x1d, tmp | 0x18); + } else if (freq <= 191500000) { /* VHF Track: 5 */ + ret = fc0013_writereg(dev, 0x1d, tmp | 0x14); + } else if (freq <= 198500000) { /* VHF Track: 4 */ + ret = fc0013_writereg(dev, 0x1d, tmp | 0x10); + } else if (freq <= 205500000) { /* VHF Track: 3 */ + ret = fc0013_writereg(dev, 0x1d, tmp | 0x0c); + } else if (freq <= 219500000) { /* VHF Track: 2 */ + ret = fc0013_writereg(dev, 0x1d, tmp | 0x08); + } else if (freq < 300000000) { /* VHF Track: 1 */ + ret = fc0013_writereg(dev, 0x1d, tmp | 0x04); + } else { /* UHF and GPS */ + ret = fc0013_writereg(dev, 0x1d, tmp | 0x1c); + } + +error_out: + return ret; +} + +int fc0013_set_params(void *dev, uint32_t freq, uint32_t bandwidth) +{ + int i, ret = 0; + uint8_t reg[7], am, pm, multi, tmp; + uint64_t f_vco; + uint32_t xtal_freq_div_2; + uint16_t xin, xdiv; + int vco_select = 0; + + xtal_freq_div_2 = rtlsdr_get_tuner_clock(dev) / 2; + + /* set VHF track */ + ret = fc0013_set_vhf_track(dev, freq); + if (ret) + goto exit; + + if (freq < 300000000) { + /* enable VHF filter */ + ret = fc0013_readreg(dev, 0x07, &tmp); + if (ret) + goto exit; + ret = fc0013_writereg(dev, 0x07, tmp | 0x10); + if (ret) + goto exit; + + /* disable UHF & disable GPS */ + ret = fc0013_readreg(dev, 0x14, &tmp); + if (ret) + goto exit; + ret = fc0013_writereg(dev, 0x14, tmp & 0x1f); + if (ret) + goto exit; + } else if (freq <= 862000000) { + /* disable VHF filter */ + ret = fc0013_readreg(dev, 0x07, &tmp); + if (ret) + goto exit; + ret = fc0013_writereg(dev, 0x07, tmp & 0xef); + if (ret) + goto exit; + + /* enable UHF & disable GPS */ + ret = fc0013_readreg(dev, 0x14, &tmp); + if (ret) + goto exit; + ret = fc0013_writereg(dev, 0x14, (tmp & 0x1f) | 0x40); + if (ret) + goto exit; + } else { + /* disable VHF filter */ + ret = fc0013_readreg(dev, 0x07, &tmp); + if (ret) + goto exit; + ret = fc0013_writereg(dev, 0x07, tmp & 0xef); + if (ret) + goto exit; + + /* enable UHF & disable GPS */ + ret = fc0013_readreg(dev, 0x14, &tmp); + if (ret) + goto exit; + ret = fc0013_writereg(dev, 0x14, (tmp & 0x1f) | 0x40); + if (ret) + goto exit; + } + + /* select frequency divider and the frequency of VCO */ + if (freq < 37084000) { /* freq * 96 < 3560000000 */ + multi = 96; + reg[5] = 0x82; + reg[6] = 0x00; + } else if (freq < 55625000) { /* freq * 64 < 3560000000 */ + multi = 64; + reg[5] = 0x02; + reg[6] = 0x02; + } else if (freq < 74167000) { /* freq * 48 < 3560000000 */ + multi = 48; + reg[5] = 0x42; + reg[6] = 0x00; + } else if (freq < 111250000) { /* freq * 32 < 3560000000 */ + multi = 32; + reg[5] = 0x82; + reg[6] = 0x02; + } else if (freq < 148334000) { /* freq * 24 < 3560000000 */ + multi = 24; + reg[5] = 0x22; + reg[6] = 0x00; + } else if (freq < 222500000) { /* freq * 16 < 3560000000 */ + multi = 16; + reg[5] = 0x42; + reg[6] = 0x02; + } else if (freq < 296667000) { /* freq * 12 < 3560000000 */ + multi = 12; + reg[5] = 0x12; + reg[6] = 0x00; + } else if (freq < 445000000) { /* freq * 8 < 3560000000 */ + multi = 8; + reg[5] = 0x22; + reg[6] = 0x02; + } else if (freq < 593334000) { /* freq * 6 < 3560000000 */ + multi = 6; + reg[5] = 0x0a; + reg[6] = 0x00; + } else if (freq < 950000000) { /* freq * 4 < 3800000000 */ + multi = 4; + reg[5] = 0x12; + reg[6] = 0x02; + } else { + multi = 2; + reg[5] = 0x0a; + reg[6] = 0x02; + } + + f_vco = freq * multi; + + if (f_vco >= 3060000000U) { + reg[6] |= 0x08; + vco_select = 1; + } + + /* From divided value (XDIV) determined the FA and FP value */ + xdiv = (uint16_t)(f_vco / xtal_freq_div_2); + if ((f_vco - xdiv * xtal_freq_div_2) >= (xtal_freq_div_2 / 2)) + xdiv++; + + pm = (uint8_t)(xdiv / 8); + am = (uint8_t)(xdiv - (8 * pm)); + + if (am < 2) { + am += 8; + pm--; + } + + if (pm > 31) { + reg[1] = am + (8 * (pm - 31)); + reg[2] = 31; + } else { + reg[1] = am; + reg[2] = pm; + } + + if ((reg[1] > 15) || (reg[2] < 0x0b)) { + fprintf(stderr, "[FC0013] no valid PLL combination " + "found for %u Hz!\n", freq); + return -1; + } + + /* fix clock out */ + reg[6] |= 0x20; + + /* From VCO frequency determines the XIN ( fractional part of Delta + Sigma PLL) and divided value (XDIV) */ + xin = (uint16_t)((f_vco - (f_vco / xtal_freq_div_2) * xtal_freq_div_2) / 1000); + xin = (xin << 15) / (xtal_freq_div_2 / 1000); + if (xin >= 16384) + xin += 32768; + + reg[3] = xin >> 8; + reg[4] = xin & 0xff; + + reg[6] &= 0x3f; /* bits 6 and 7 describe the bandwidth */ + switch (bandwidth) { + case 6000000: + reg[6] |= 0x80; + break; + case 7000000: + reg[6] |= 0x40; + break; + case 8000000: + default: + break; + } + + /* modified for Realtek demod */ + reg[5] |= 0x07; + + for (i = 1; i <= 6; i++) { + ret = fc0013_writereg(dev, i, reg[i]); + if (ret) + goto exit; + } + + ret = fc0013_readreg(dev, 0x11, &tmp); + if (ret) + goto exit; + if (multi == 64) + ret = fc0013_writereg(dev, 0x11, tmp | 0x04); + else + ret = fc0013_writereg(dev, 0x11, tmp & 0xfb); + if (ret) + goto exit; + + /* VCO Calibration */ + ret = fc0013_writereg(dev, 0x0e, 0x80); + if (!ret) + ret = fc0013_writereg(dev, 0x0e, 0x00); + + /* VCO Re-Calibration if needed */ + if (!ret) + ret = fc0013_writereg(dev, 0x0e, 0x00); + + if (!ret) { +// msleep(10); + ret = fc0013_readreg(dev, 0x0e, &tmp); + } + if (ret) + goto exit; + + /* vco selection */ + tmp &= 0x3f; + + if (vco_select) { + if (tmp > 0x3c) { + reg[6] &= ~0x08; + ret = fc0013_writereg(dev, 0x06, reg[6]); + if (!ret) + ret = fc0013_writereg(dev, 0x0e, 0x80); + if (!ret) + ret = fc0013_writereg(dev, 0x0e, 0x00); + } + } else { + if (tmp < 0x02) { + reg[6] |= 0x08; + ret = fc0013_writereg(dev, 0x06, reg[6]); + if (!ret) + ret = fc0013_writereg(dev, 0x0e, 0x80); + if (!ret) + ret = fc0013_writereg(dev, 0x0e, 0x00); + } + } + +exit: + return ret; +} + +int fc0013_set_gain_mode(void *dev, int manual) +{ + int ret = 0; + uint8_t tmp = 0; + + ret |= fc0013_readreg(dev, 0x0d, &tmp); + + if (manual) + tmp |= (1 << 3); + else + tmp &= ~(1 << 3); + + ret |= fc0013_writereg(dev, 0x0d, tmp); + + /* set a fixed IF-gain for now */ + ret |= fc0013_writereg(dev, 0x13, 0x0a); + + return ret; +} + +int fc0013_lna_gains[] ={ + -99, 0x02, + -73, 0x03, + -65, 0x05, + -63, 0x04, + -63, 0x00, + -60, 0x07, + -58, 0x01, + -54, 0x06, + 58, 0x0f, + 61, 0x0e, + 63, 0x0d, + 65, 0x0c, + 67, 0x0b, + 68, 0x0a, + 70, 0x09, + 71, 0x08, + 179, 0x17, + 181, 0x16, + 182, 0x15, + 184, 0x14, + 186, 0x13, + 188, 0x12, + 191, 0x11, + 197, 0x10 +}; + +#define GAIN_CNT (sizeof(fc0013_lna_gains) / sizeof(int) / 2) + +int fc0013_set_lna_gain(void *dev, int gain) +{ + int ret = 0; + unsigned int i; + uint8_t tmp = 0; + + ret |= fc0013_readreg(dev, 0x14, &tmp); + + /* mask bits off */ + tmp &= 0xe0; + + for (i = 0; i < GAIN_CNT; i++) { + if ((fc0013_lna_gains[i*2] >= gain) || (i+1 == GAIN_CNT)) { + tmp |= fc0013_lna_gains[i*2 + 1]; + break; + } + } + + /* set gain */ + ret |= fc0013_writereg(dev, 0x14, tmp); + + return ret; +} diff --git a/Radio/HW/RtlSdr/r820/src/tuner_fc2580.c b/Radio/HW/RtlSdr/r820/src/tuner_fc2580.c new file mode 100644 index 0000000..d2eeba5 --- /dev/null +++ b/Radio/HW/RtlSdr/r820/src/tuner_fc2580.c @@ -0,0 +1,494 @@ +/* + * FCI FC2580 tuner driver, taken from the kernel driver that can be found + * on http://linux.terratec.de/tv_en.html + * + * This driver is a mess, and should be cleaned up/rewritten. + * + */ + +#include <stdint.h> + +#include "rtlsdr_i2c.h" +#include "tuner_fc2580.h" + +/* 16.384 MHz (at least on the Logilink VG0002A) */ +#define CRYSTAL_FREQ 16384000 + +/* glue functions to rtl-sdr code */ + +fc2580_fci_result_type fc2580_i2c_write(void *pTuner, unsigned char reg, unsigned char val) +{ + uint8_t data[2]; + + data[0] = reg; + data[1] = val; + + if (rtlsdr_i2c_write_fn(pTuner, FC2580_I2C_ADDR, data, 2) < 0) + return FC2580_FCI_FAIL; + + return FC2580_FCI_SUCCESS; +} + +fc2580_fci_result_type fc2580_i2c_read(void *pTuner, unsigned char reg, unsigned char *read_data) +{ + uint8_t data = reg; + + if (rtlsdr_i2c_write_fn(pTuner, FC2580_I2C_ADDR, &data, 1) < 0) + return FC2580_FCI_FAIL; + + if (rtlsdr_i2c_read_fn(pTuner, FC2580_I2C_ADDR, &data, 1) < 0) + return FC2580_FCI_FAIL; + + *read_data = data; + + return FC2580_FCI_SUCCESS; +} + +int +fc2580_Initialize( + void *pTuner + ) +{ + int AgcMode; + unsigned int CrystalFreqKhz; + + //TODO set AGC mode + AgcMode = FC2580_AGC_EXTERNAL; + + // Initialize tuner with AGC mode. + // Note: CrystalFreqKhz = round(CrystalFreqHz / 1000) + CrystalFreqKhz = (unsigned int)((CRYSTAL_FREQ + 500) / 1000); + + if(fc2580_set_init(pTuner, AgcMode, CrystalFreqKhz) != FC2580_FCI_SUCCESS) + goto error_status_initialize_tuner; + + + return FUNCTION_SUCCESS; + + +error_status_initialize_tuner: + return FUNCTION_ERROR; +} + +int +fc2580_SetRfFreqHz( + void *pTuner, + unsigned long RfFreqHz + ) +{ + unsigned int RfFreqKhz; + unsigned int CrystalFreqKhz; + + // Set tuner RF frequency in KHz. + // Note: RfFreqKhz = round(RfFreqHz / 1000) + // CrystalFreqKhz = round(CrystalFreqHz / 1000) + RfFreqKhz = (unsigned int)((RfFreqHz + 500) / 1000); + CrystalFreqKhz = (unsigned int)((CRYSTAL_FREQ + 500) / 1000); + + if(fc2580_set_freq(pTuner, RfFreqKhz, CrystalFreqKhz) != FC2580_FCI_SUCCESS) + goto error_status_set_tuner_rf_frequency; + + return FUNCTION_SUCCESS; + +error_status_set_tuner_rf_frequency: + return FUNCTION_ERROR; +} + +/** + +@brief Set FC2580 tuner bandwidth mode. + +*/ +int +fc2580_SetBandwidthMode( + void *pTuner, + int BandwidthMode + ) +{ + unsigned int CrystalFreqKhz; + + // Set tuner bandwidth mode. + // Note: CrystalFreqKhz = round(CrystalFreqHz / 1000) + CrystalFreqKhz = (unsigned int)((CRYSTAL_FREQ + 500) / 1000); + + if(fc2580_set_filter(pTuner, (unsigned char)BandwidthMode, CrystalFreqKhz) != FC2580_FCI_SUCCESS) + goto error_status_set_tuner_bandwidth_mode; + + return FUNCTION_SUCCESS; + + +error_status_set_tuner_bandwidth_mode: + return FUNCTION_ERROR; +} + +void fc2580_wait_msec(void *pTuner, int a) +{ + /* USB latency is enough for now ;) */ +// usleep(a * 1000); + return; +} + +/*============================================================================== + fc2580 initial setting + + This function is a generic function which gets called to initialize + + fc2580 in DVB-H mode or L-Band TDMB mode + + <input parameter> + + ifagc_mode + type : integer + 1 : Internal AGC + 2 : Voltage Control Mode + +==============================================================================*/ +fc2580_fci_result_type fc2580_set_init( void *pTuner, int ifagc_mode, unsigned int freq_xtal ) +{ + fc2580_fci_result_type result = FC2580_FCI_SUCCESS; + + result &= fc2580_i2c_write(pTuner, 0x00, 0x00); /*** Confidential ***/ + result &= fc2580_i2c_write(pTuner, 0x12, 0x86); + result &= fc2580_i2c_write(pTuner, 0x14, 0x5C); + result &= fc2580_i2c_write(pTuner, 0x16, 0x3C); + result &= fc2580_i2c_write(pTuner, 0x1F, 0xD2); + result &= fc2580_i2c_write(pTuner, 0x09, 0xD7); + result &= fc2580_i2c_write(pTuner, 0x0B, 0xD5); + result &= fc2580_i2c_write(pTuner, 0x0C, 0x32); + result &= fc2580_i2c_write(pTuner, 0x0E, 0x43); + result &= fc2580_i2c_write(pTuner, 0x21, 0x0A); + result &= fc2580_i2c_write(pTuner, 0x22, 0x82); + if( ifagc_mode == 1 ) + { + result &= fc2580_i2c_write(pTuner, 0x45, 0x10); //internal AGC + result &= fc2580_i2c_write(pTuner, 0x4C, 0x00); //HOLD_AGC polarity + } + else if( ifagc_mode == 2 ) + { + result &= fc2580_i2c_write(pTuner, 0x45, 0x20); //Voltage Control Mode + result &= fc2580_i2c_write(pTuner, 0x4C, 0x02); //HOLD_AGC polarity + } + result &= fc2580_i2c_write(pTuner, 0x3F, 0x88); + result &= fc2580_i2c_write(pTuner, 0x02, 0x0E); + result &= fc2580_i2c_write(pTuner, 0x58, 0x14); + result &= fc2580_set_filter(pTuner, 8, freq_xtal); //BW = 7.8MHz + + return result; +} + + +/*============================================================================== + fc2580 frequency setting + + This function is a generic function which gets called to change LO Frequency + + of fc2580 in DVB-H mode or L-Band TDMB mode + + <input parameter> + freq_xtal: kHz + + f_lo + Value of target LO Frequency in 'kHz' unit + ex) 2.6GHz = 2600000 + +==============================================================================*/ +fc2580_fci_result_type fc2580_set_freq( void *pTuner, unsigned int f_lo, unsigned int freq_xtal ) +{ + unsigned int f_diff, f_diff_shifted, n_val, k_val; + unsigned int f_vco, r_val, f_comp; + unsigned char pre_shift_bits = 4;// number of preshift to prevent overflow in shifting f_diff to f_diff_shifted + unsigned char data_0x18; + unsigned char data_0x02 = (USE_EXT_CLK<<5)|0x0E; + + fc2580_band_type band = ( f_lo > 1000000 )? FC2580_L_BAND : ( f_lo > 400000 )? FC2580_UHF_BAND : FC2580_VHF_BAND; + + fc2580_fci_result_type result = FC2580_FCI_SUCCESS; + + f_vco = ( band == FC2580_UHF_BAND )? f_lo * 4 : (( band == FC2580_L_BAND )? f_lo * 2 : f_lo * 12); + r_val = ( f_vco >= 2*76*freq_xtal )? 1 : ( f_vco >= 76*freq_xtal )? 2 : 4; + f_comp = freq_xtal/r_val; + n_val = ( f_vco / 2 ) / f_comp; + + f_diff = f_vco - 2* f_comp * n_val; + f_diff_shifted = f_diff << ( 20 - pre_shift_bits ); + k_val = f_diff_shifted / ( ( 2* f_comp ) >> pre_shift_bits ); + + if( f_diff_shifted - k_val * ( ( 2* f_comp ) >> pre_shift_bits ) >= ( f_comp >> pre_shift_bits ) ) + k_val = k_val + 1; + + if( f_vco >= BORDER_FREQ ) //Select VCO Band + data_0x02 = data_0x02 | 0x08; //0x02[3] = 1; + else + data_0x02 = data_0x02 & 0xF7; //0x02[3] = 0; + +// if( band != curr_band ) { + switch(band) + { + case FC2580_UHF_BAND: + data_0x02 = (data_0x02 & 0x3F); + + result &= fc2580_i2c_write(pTuner, 0x25, 0xF0); + result &= fc2580_i2c_write(pTuner, 0x27, 0x77); + result &= fc2580_i2c_write(pTuner, 0x28, 0x53); + result &= fc2580_i2c_write(pTuner, 0x29, 0x60); + result &= fc2580_i2c_write(pTuner, 0x30, 0x09); + result &= fc2580_i2c_write(pTuner, 0x50, 0x8C); + result &= fc2580_i2c_write(pTuner, 0x53, 0x50); + + if( f_lo < 538000 ) + result &= fc2580_i2c_write(pTuner, 0x5F, 0x13); + else + result &= fc2580_i2c_write(pTuner, 0x5F, 0x15); + + if( f_lo < 538000 ) + { + result &= fc2580_i2c_write(pTuner, 0x61, 0x07); + result &= fc2580_i2c_write(pTuner, 0x62, 0x06); + result &= fc2580_i2c_write(pTuner, 0x67, 0x06); + result &= fc2580_i2c_write(pTuner, 0x68, 0x08); + result &= fc2580_i2c_write(pTuner, 0x69, 0x10); + result &= fc2580_i2c_write(pTuner, 0x6A, 0x12); + } + else if( f_lo < 794000 ) + { + result &= fc2580_i2c_write(pTuner, 0x61, 0x03); + result &= fc2580_i2c_write(pTuner, 0x62, 0x03); + result &= fc2580_i2c_write(pTuner, 0x67, 0x03); //ACI improve + result &= fc2580_i2c_write(pTuner, 0x68, 0x05); //ACI improve + result &= fc2580_i2c_write(pTuner, 0x69, 0x0C); + result &= fc2580_i2c_write(pTuner, 0x6A, 0x0E); + } + else + { + result &= fc2580_i2c_write(pTuner, 0x61, 0x07); + result &= fc2580_i2c_write(pTuner, 0x62, 0x06); + result &= fc2580_i2c_write(pTuner, 0x67, 0x07); + result &= fc2580_i2c_write(pTuner, 0x68, 0x09); + result &= fc2580_i2c_write(pTuner, 0x69, 0x10); + result &= fc2580_i2c_write(pTuner, 0x6A, 0x12); + } + + result &= fc2580_i2c_write(pTuner, 0x63, 0x15); + + result &= fc2580_i2c_write(pTuner, 0x6B, 0x0B); + result &= fc2580_i2c_write(pTuner, 0x6C, 0x0C); + result &= fc2580_i2c_write(pTuner, 0x6D, 0x78); + result &= fc2580_i2c_write(pTuner, 0x6E, 0x32); + result &= fc2580_i2c_write(pTuner, 0x6F, 0x14); + result &= fc2580_set_filter(pTuner, 8, freq_xtal); //BW = 7.8MHz + break; + case FC2580_VHF_BAND: + data_0x02 = (data_0x02 & 0x3F) | 0x80; + result &= fc2580_i2c_write(pTuner, 0x27, 0x77); + result &= fc2580_i2c_write(pTuner, 0x28, 0x33); + result &= fc2580_i2c_write(pTuner, 0x29, 0x40); + result &= fc2580_i2c_write(pTuner, 0x30, 0x09); + result &= fc2580_i2c_write(pTuner, 0x50, 0x8C); + result &= fc2580_i2c_write(pTuner, 0x53, 0x50); + result &= fc2580_i2c_write(pTuner, 0x5F, 0x0F); + result &= fc2580_i2c_write(pTuner, 0x61, 0x07); + result &= fc2580_i2c_write(pTuner, 0x62, 0x00); + result &= fc2580_i2c_write(pTuner, 0x63, 0x15); + result &= fc2580_i2c_write(pTuner, 0x67, 0x03); + result &= fc2580_i2c_write(pTuner, 0x68, 0x05); + result &= fc2580_i2c_write(pTuner, 0x69, 0x10); + result &= fc2580_i2c_write(pTuner, 0x6A, 0x12); + result &= fc2580_i2c_write(pTuner, 0x6B, 0x08); + result &= fc2580_i2c_write(pTuner, 0x6C, 0x0A); + result &= fc2580_i2c_write(pTuner, 0x6D, 0x78); + result &= fc2580_i2c_write(pTuner, 0x6E, 0x32); + result &= fc2580_i2c_write(pTuner, 0x6F, 0x54); + result &= fc2580_set_filter(pTuner, 7, freq_xtal); //BW = 6.8MHz + break; + case FC2580_L_BAND: + data_0x02 = (data_0x02 & 0x3F) | 0x40; + result &= fc2580_i2c_write(pTuner, 0x2B, 0x70); + result &= fc2580_i2c_write(pTuner, 0x2C, 0x37); + result &= fc2580_i2c_write(pTuner, 0x2D, 0xE7); + result &= fc2580_i2c_write(pTuner, 0x30, 0x09); + result &= fc2580_i2c_write(pTuner, 0x44, 0x20); + result &= fc2580_i2c_write(pTuner, 0x50, 0x8C); + result &= fc2580_i2c_write(pTuner, 0x53, 0x50); + result &= fc2580_i2c_write(pTuner, 0x5F, 0x0F); + result &= fc2580_i2c_write(pTuner, 0x61, 0x0F); + result &= fc2580_i2c_write(pTuner, 0x62, 0x00); + result &= fc2580_i2c_write(pTuner, 0x63, 0x13); + result &= fc2580_i2c_write(pTuner, 0x67, 0x00); + result &= fc2580_i2c_write(pTuner, 0x68, 0x02); + result &= fc2580_i2c_write(pTuner, 0x69, 0x0C); + result &= fc2580_i2c_write(pTuner, 0x6A, 0x0E); + result &= fc2580_i2c_write(pTuner, 0x6B, 0x08); + result &= fc2580_i2c_write(pTuner, 0x6C, 0x0A); + result &= fc2580_i2c_write(pTuner, 0x6D, 0xA0); + result &= fc2580_i2c_write(pTuner, 0x6E, 0x50); + result &= fc2580_i2c_write(pTuner, 0x6F, 0x14); + result &= fc2580_set_filter(pTuner, 1, freq_xtal); //BW = 1.53MHz + break; + default: + break; + } +// curr_band = band; +// } + + //A command about AGC clock's pre-divide ratio + if( freq_xtal >= 28000 ) + result &= fc2580_i2c_write(pTuner, 0x4B, 0x22 ); + + //Commands about VCO Band and PLL setting. + result &= fc2580_i2c_write(pTuner, 0x02, data_0x02); + data_0x18 = ( ( r_val == 1 )? 0x00 : ( ( r_val == 2 )? 0x10 : 0x20 ) ) + (unsigned char)(k_val >> 16); + result &= fc2580_i2c_write(pTuner, 0x18, data_0x18); //Load 'R' value and high part of 'K' values + result &= fc2580_i2c_write(pTuner, 0x1A, (unsigned char)( k_val >> 8 ) ); //Load middle part of 'K' value + result &= fc2580_i2c_write(pTuner, 0x1B, (unsigned char)( k_val ) ); //Load lower part of 'K' value + result &= fc2580_i2c_write(pTuner, 0x1C, (unsigned char)( n_val ) ); //Load 'N' value + + //A command about UHF LNA Load Cap + if( band == FC2580_UHF_BAND ) + result &= fc2580_i2c_write(pTuner, 0x2D, ( f_lo <= (unsigned int)794000 )? 0x9F : 0x8F ); //LNA_OUT_CAP + + + return result; +} + + +/*============================================================================== + fc2580 filter BW setting + + This function is a generic function which gets called to change Bandwidth + + frequency of fc2580's channel selection filter + + <input parameter> + freq_xtal: kHz + + filter_bw + 1 : 1.53MHz(TDMB) + 6 : 6MHz (Bandwidth 6MHz) + 7 : 6.8MHz (Bandwidth 7MHz) + 8 : 7.8MHz (Bandwidth 8MHz) + + +==============================================================================*/ +fc2580_fci_result_type fc2580_set_filter( void *pTuner, unsigned char filter_bw, unsigned int freq_xtal ) +{ + unsigned char cal_mon = 0, i; + fc2580_fci_result_type result = FC2580_FCI_SUCCESS; + + if(filter_bw == 1) + { + result &= fc2580_i2c_write(pTuner, 0x36, 0x1C); + result &= fc2580_i2c_write(pTuner, 0x37, (unsigned char)(4151*freq_xtal/1000000) ); + result &= fc2580_i2c_write(pTuner, 0x39, 0x00); + result &= fc2580_i2c_write(pTuner, 0x2E, 0x09); + } + if(filter_bw == 6) + { + result &= fc2580_i2c_write(pTuner, 0x36, 0x18); + result &= fc2580_i2c_write(pTuner, 0x37, (unsigned char)(4400*freq_xtal/1000000) ); + result &= fc2580_i2c_write(pTuner, 0x39, 0x00); + result &= fc2580_i2c_write(pTuner, 0x2E, 0x09); + } + else if(filter_bw == 7) + { + result &= fc2580_i2c_write(pTuner, 0x36, 0x18); + result &= fc2580_i2c_write(pTuner, 0x37, (unsigned char)(3910*freq_xtal/1000000) ); + result &= fc2580_i2c_write(pTuner, 0x39, 0x80); + result &= fc2580_i2c_write(pTuner, 0x2E, 0x09); + } + else if(filter_bw == 8) + { + result &= fc2580_i2c_write(pTuner, 0x36, 0x18); + result &= fc2580_i2c_write(pTuner, 0x37, (unsigned char)(3300*freq_xtal/1000000) ); + result &= fc2580_i2c_write(pTuner, 0x39, 0x80); + result &= fc2580_i2c_write(pTuner, 0x2E, 0x09); + } + + + for(i=0; i<5; i++) + { + fc2580_wait_msec(pTuner, 5);//wait 5ms + result &= fc2580_i2c_read(pTuner, 0x2F, &cal_mon); + if( (cal_mon & 0xC0) != 0xC0) + { + result &= fc2580_i2c_write(pTuner, 0x2E, 0x01); + result &= fc2580_i2c_write(pTuner, 0x2E, 0x09); + } + else + break; + } + + result &= fc2580_i2c_write(pTuner, 0x2E, 0x01); + + return result; +} + +/*============================================================================== + fc2580 RSSI function + + This function is a generic function which returns fc2580's + + current RSSI value. + + <input parameter> + none + + <return value> + int + rssi : estimated input power. + +==============================================================================*/ +//int fc2580_get_rssi(void) { +// +// unsigned char s_lna, s_rfvga, s_cfs, s_ifvga; +// int ofs_lna, ofs_rfvga, ofs_csf, ofs_ifvga, rssi; +// +// fc2580_i2c_read(0x71, &s_lna ); +// fc2580_i2c_read(0x72, &s_rfvga ); +// fc2580_i2c_read(0x73, &s_cfs ); +// fc2580_i2c_read(0x74, &s_ifvga ); +// +// +// ofs_lna = +// (curr_band==FC2580_UHF_BAND)? +// (s_lna==0)? 0 : +// (s_lna==1)? -6 : +// (s_lna==2)? -17 : +// (s_lna==3)? -22 : -30 : +// (curr_band==FC2580_VHF_BAND)? +// (s_lna==0)? 0 : +// (s_lna==1)? -6 : +// (s_lna==2)? -19 : +// (s_lna==3)? -24 : -32 : +// (curr_band==FC2580_L_BAND)? +// (s_lna==0)? 0 : +// (s_lna==1)? -6 : +// (s_lna==2)? -11 : +// (s_lna==3)? -16 : -34 : +// 0;//FC2580_NO_BAND +// ofs_rfvga = -s_rfvga+((s_rfvga>=11)? 1 : 0) + ((s_rfvga>=18)? 1 : 0); +// ofs_csf = -6*s_cfs; +// ofs_ifvga = s_ifvga/4; +// +// return rssi = ofs_lna+ofs_rfvga+ofs_csf+ofs_ifvga+OFS_RSSI; +// +//} + +/*============================================================================== + fc2580 Xtal frequency Setting + + This function is a generic function which sets + + the frequency of xtal. + + <input parameter> + + frequency + frequency value of internal(external) Xtal(clock) in kHz unit. + +==============================================================================*/ +//void fc2580_set_freq_xtal(unsigned int frequency) { +// +// freq_xtal = frequency; +// +//} + diff --git a/Radio/HW/RtlSdr/r820/src/tuner_r82xx.c b/Radio/HW/RtlSdr/r820/src/tuner_r82xx.c new file mode 100644 index 0000000..997abd7 --- /dev/null +++ b/Radio/HW/RtlSdr/r820/src/tuner_r82xx.c @@ -0,0 +1,1274 @@ +/* + * Rafael Micro R820T/R828D driver + * + * Copyright (C) 2013 Mauro Carvalho Chehab <mchehab@redhat.com> + * Copyright (C) 2013 Steve Markgraf <steve@steve-m.de> + * + * This driver is a heavily modified version of the driver found in the + * Linux kernel: + * http://git.linuxtv.org/linux-2.6.git/history/HEAD:/drivers/media/tuners/r820t.c + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <stdio.h> +#include <stdint.h> +#include <string.h> + +#include "rtlsdr_i2c.h" +#include "tuner_r82xx.h" + +#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0])) +#define MHZ(x) ((x)*1000*1000) +#define KHZ(x) ((x)*1000) + +/* + * Static constants + */ + +/* Those initial values start from REG_SHADOW_START */ +static const uint8_t r82xx_init_array[NUM_REGS] = { + 0x83, 0x32, 0x75, /* 05 to 07 */ + 0xc0, 0x40, 0xd6, 0x6c, /* 08 to 0b */ + 0xf5, 0x63, 0x75, 0x68, /* 0c to 0f */ + 0x6c, 0x83, 0x80, 0x00, /* 10 to 13 */ + 0x0f, 0x00, 0xc0, 0x30, /* 14 to 17 */ + 0x48, 0xcc, 0x60, 0x00, /* 18 to 1b */ + 0x54, 0xae, 0x4a, 0xc0 /* 1c to 1f */ +}; + +/* Tuner frequency ranges */ +static const struct r82xx_freq_range freq_ranges[] = { + { + /* .freq = */ 0, /* Start freq, in MHz */ + /* .open_d = */ 0x08, /* low */ + /* .rf_mux_ploy = */ 0x02, /* R26[7:6]=0 (LPF) R26[1:0]=2 (low) */ + /* .tf_c = */ 0xdf, /* R27[7:0] band2,band0 */ + /* .xtal_cap20p = */ 0x02, /* R16[1:0] 20pF (10) */ + /* .xtal_cap10p = */ 0x01, + /* .xtal_cap0p = */ 0x00, + }, { + /* .freq = */ 50, /* Start freq, in MHz */ + /* .open_d = */ 0x08, /* low */ + /* .rf_mux_ploy = */ 0x02, /* R26[7:6]=0 (LPF) R26[1:0]=2 (low) */ + /* .tf_c = */ 0xbe, /* R27[7:0] band4,band1 */ + /* .xtal_cap20p = */ 0x02, /* R16[1:0] 20pF (10) */ + /* .xtal_cap10p = */ 0x01, + /* .xtal_cap0p = */ 0x00, + }, { + /* .freq = */ 55, /* Start freq, in MHz */ + /* .open_d = */ 0x08, /* low */ + /* .rf_mux_ploy = */ 0x02, /* R26[7:6]=0 (LPF) R26[1:0]=2 (low) */ + /* .tf_c = */ 0x8b, /* R27[7:0] band7,band4 */ + /* .xtal_cap20p = */ 0x02, /* R16[1:0] 20pF (10) */ + /* .xtal_cap10p = */ 0x01, + /* .xtal_cap0p = */ 0x00, + }, { + /* .freq = */ 60, /* Start freq, in MHz */ + /* .open_d = */ 0x08, /* low */ + /* .rf_mux_ploy = */ 0x02, /* R26[7:6]=0 (LPF) R26[1:0]=2 (low) */ + /* .tf_c = */ 0x7b, /* R27[7:0] band8,band4 */ + /* .xtal_cap20p = */ 0x02, /* R16[1:0] 20pF (10) */ + /* .xtal_cap10p = */ 0x01, + /* .xtal_cap0p = */ 0x00, + }, { + /* .freq = */ 65, /* Start freq, in MHz */ + /* .open_d = */ 0x08, /* low */ + /* .rf_mux_ploy = */ 0x02, /* R26[7:6]=0 (LPF) R26[1:0]=2 (low) */ + /* .tf_c = */ 0x69, /* R27[7:0] band9,band6 */ + /* .xtal_cap20p = */ 0x02, /* R16[1:0] 20pF (10) */ + /* .xtal_cap10p = */ 0x01, + /* .xtal_cap0p = */ 0x00, + }, { + /* .freq = */ 70, /* Start freq, in MHz */ + /* .open_d = */ 0x08, /* low */ + /* .rf_mux_ploy = */ 0x02, /* R26[7:6]=0 (LPF) R26[1:0]=2 (low) */ + /* .tf_c = */ 0x58, /* R27[7:0] band10,band7 */ + /* .xtal_cap20p = */ 0x02, /* R16[1:0] 20pF (10) */ + /* .xtal_cap10p = */ 0x01, + /* .xtal_cap0p = */ 0x00, + }, { + /* .freq = */ 75, /* Start freq, in MHz */ + /* .open_d = */ 0x00, /* high */ + /* .rf_mux_ploy = */ 0x02, /* R26[7:6]=0 (LPF) R26[1:0]=2 (low) */ + /* .tf_c = */ 0x44, /* R27[7:0] band11,band11 */ + /* .xtal_cap20p = */ 0x02, /* R16[1:0] 20pF (10) */ + /* .xtal_cap10p = */ 0x01, + /* .xtal_cap0p = */ 0x00, + }, { + /* .freq = */ 80, /* Start freq, in MHz */ + /* .open_d = */ 0x00, /* high */ + /* .rf_mux_ploy = */ 0x02, /* R26[7:6]=0 (LPF) R26[1:0]=2 (low) */ + /* .tf_c = */ 0x44, /* R27[7:0] band11,band11 */ + /* .xtal_cap20p = */ 0x02, /* R16[1:0] 20pF (10) */ + /* .xtal_cap10p = */ 0x01, + /* .xtal_cap0p = */ 0x00, + }, { + /* .freq = */ 90, /* Start freq, in MHz */ + /* .open_d = */ 0x00, /* high */ + /* .rf_mux_ploy = */ 0x02, /* R26[7:6]=0 (LPF) R26[1:0]=2 (low) */ + /* .tf_c = */ 0x34, /* R27[7:0] band12,band11 */ + /* .xtal_cap20p = */ 0x01, /* R16[1:0] 10pF (01) */ + /* .xtal_cap10p = */ 0x01, + /* .xtal_cap0p = */ 0x00, + }, { + /* .freq = */ 100, /* Start freq, in MHz */ + /* .open_d = */ 0x00, /* high */ + /* .rf_mux_ploy = */ 0x02, /* R26[7:6]=0 (LPF) R26[1:0]=2 (low) */ + /* .tf_c = */ 0x34, /* R27[7:0] band12,band11 */ + /* .xtal_cap20p = */ 0x01, /* R16[1:0] 10pF (01) */ + /* .xtal_cap10p = */ 0x01, + /* .xtal_cap0p = */ 0x00, + }, { + /* .freq = */ 110, /* Start freq, in MHz */ + /* .open_d = */ 0x00, /* high */ + /* .rf_mux_ploy = */ 0x02, /* R26[7:6]=0 (LPF) R26[1:0]=2 (low) */ + /* .tf_c = */ 0x24, /* R27[7:0] band13,band11 */ + /* .xtal_cap20p = */ 0x01, /* R16[1:0] 10pF (01) */ + /* .xtal_cap10p = */ 0x01, + /* .xtal_cap0p = */ 0x00, + }, { + /* .freq = */ 120, /* Start freq, in MHz */ + /* .open_d = */ 0x00, /* high */ + /* .rf_mux_ploy = */ 0x02, /* R26[7:6]=0 (LPF) R26[1:0]=2 (low) */ + /* .tf_c = */ 0x24, /* R27[7:0] band13,band11 */ + /* .xtal_cap20p = */ 0x01, /* R16[1:0] 10pF (01) */ + /* .xtal_cap10p = */ 0x01, + /* .xtal_cap0p = */ 0x00, + }, { + /* .freq = */ 140, /* Start freq, in MHz */ + /* .open_d = */ 0x00, /* high */ + /* .rf_mux_ploy = */ 0x02, /* R26[7:6]=0 (LPF) R26[1:0]=2 (low) */ + /* .tf_c = */ 0x14, /* R27[7:0] band14,band11 */ + /* .xtal_cap20p = */ 0x01, /* R16[1:0] 10pF (01) */ + /* .xtal_cap10p = */ 0x01, + /* .xtal_cap0p = */ 0x00, + }, { + /* .freq = */ 180, /* Start freq, in MHz */ + /* .open_d = */ 0x00, /* high */ + /* .rf_mux_ploy = */ 0x02, /* R26[7:6]=0 (LPF) R26[1:0]=2 (low) */ + /* .tf_c = */ 0x13, /* R27[7:0] band14,band12 */ + /* .xtal_cap20p = */ 0x00, /* R16[1:0] 0pF (00) */ + /* .xtal_cap10p = */ 0x00, + /* .xtal_cap0p = */ 0x00, + }, { + /* .freq = */ 220, /* Start freq, in MHz */ + /* .open_d = */ 0x00, /* high */ + /* .rf_mux_ploy = */ 0x02, /* R26[7:6]=0 (LPF) R26[1:0]=2 (low) */ + /* .tf_c = */ 0x13, /* R27[7:0] band14,band12 */ + /* .xtal_cap20p = */ 0x00, /* R16[1:0] 0pF (00) */ + /* .xtal_cap10p = */ 0x00, + /* .xtal_cap0p = */ 0x00, + }, { + /* .freq = */ 250, /* Start freq, in MHz */ + /* .open_d = */ 0x00, /* high */ + /* .rf_mux_ploy = */ 0x02, /* R26[7:6]=0 (LPF) R26[1:0]=2 (low) */ + /* .tf_c = */ 0x11, /* R27[7:0] highest,highest */ + /* .xtal_cap20p = */ 0x00, /* R16[1:0] 0pF (00) */ + /* .xtal_cap10p = */ 0x00, + /* .xtal_cap0p = */ 0x00, + }, { + /* .freq = */ 280, /* Start freq, in MHz */ + /* .open_d = */ 0x00, /* high */ + /* .rf_mux_ploy = */ 0x02, /* R26[7:6]=0 (LPF) R26[1:0]=2 (low) */ + /* .tf_c = */ 0x00, /* R27[7:0] highest,highest */ + /* .xtal_cap20p = */ 0x00, /* R16[1:0] 0pF (00) */ + /* .xtal_cap10p = */ 0x00, + /* .xtal_cap0p = */ 0x00, + }, { + /* .freq = */ 310, /* Start freq, in MHz */ + /* .open_d = */ 0x00, /* high */ + /* .rf_mux_ploy = */ 0x41, /* R26[7:6]=1 (bypass) R26[1:0]=1 (middle) */ + /* .tf_c = */ 0x00, /* R27[7:0] highest,highest */ + /* .xtal_cap20p = */ 0x00, /* R16[1:0] 0pF (00) */ + /* .xtal_cap10p = */ 0x00, + /* .xtal_cap0p = */ 0x00, + }, { + /* .freq = */ 450, /* Start freq, in MHz */ + /* .open_d = */ 0x00, /* high */ + /* .rf_mux_ploy = */ 0x41, /* R26[7:6]=1 (bypass) R26[1:0]=1 (middle) */ + /* .tf_c = */ 0x00, /* R27[7:0] highest,highest */ + /* .xtal_cap20p = */ 0x00, /* R16[1:0] 0pF (00) */ + /* .xtal_cap10p = */ 0x00, + /* .xtal_cap0p = */ 0x00, + }, { + /* .freq = */ 588, /* Start freq, in MHz */ + /* .open_d = */ 0x00, /* high */ + /* .rf_mux_ploy = */ 0x40, /* R26[7:6]=1 (bypass) R26[1:0]=0 (highest) */ + /* .tf_c = */ 0x00, /* R27[7:0] highest,highest */ + /* .xtal_cap20p = */ 0x00, /* R16[1:0] 0pF (00) */ + /* .xtal_cap10p = */ 0x00, + /* .xtal_cap0p = */ 0x00, + }, { + /* .freq = */ 650, /* Start freq, in MHz */ + /* .open_d = */ 0x00, /* high */ + /* .rf_mux_ploy = */ 0x40, /* R26[7:6]=1 (bypass) R26[1:0]=0 (highest) */ + /* .tf_c = */ 0x00, /* R27[7:0] highest,highest */ + /* .xtal_cap20p = */ 0x00, /* R16[1:0] 0pF (00) */ + /* .xtal_cap10p = */ 0x00, + /* .xtal_cap0p = */ 0x00, + } +}; + +static int r82xx_xtal_capacitor[][2] = { + { 0x0b, XTAL_LOW_CAP_30P }, + { 0x02, XTAL_LOW_CAP_20P }, + { 0x01, XTAL_LOW_CAP_10P }, + { 0x00, XTAL_LOW_CAP_0P }, + { 0x10, XTAL_HIGH_CAP_0P }, +}; + +/* + * I2C read/write code and shadow registers logic + */ +static void shadow_store(struct r82xx_priv *priv, uint8_t reg, const uint8_t *val, + int len) +{ + int r = reg - REG_SHADOW_START; + + if (r < 0) { + len += r; + r = 0; + } + if (len <= 0) + return; + if (len > NUM_REGS - r) + len = NUM_REGS - r; + + memcpy(&priv->regs[r], val, len); +} + +static int r82xx_write(struct r82xx_priv *priv, uint8_t reg, const uint8_t *val, + unsigned int len) +{ + int rc, size, pos = 0; + + /* Store the shadow registers */ + shadow_store(priv, reg, val, len); + + do { + if (len > priv->cfg->max_i2c_msg_len - 1) + size = priv->cfg->max_i2c_msg_len - 1; + else + size = len; + + /* Fill I2C buffer */ + priv->buf[0] = reg; + memcpy(&priv->buf[1], &val[pos], size); + + rc = rtlsdr_i2c_write_fn(priv->rtl_dev, priv->cfg->i2c_addr, + priv->buf, size + 1); + + if (rc != size + 1) { + fprintf(stderr, "%s: i2c wr failed=%d reg=%02x len=%d\n", + __FUNCTION__, rc, reg, size); + if (rc < 0) + return rc; + return -1; + } + + reg += size; + len -= size; + pos += size; + } while (len > 0); + + return 0; +} + +static int r82xx_write_reg(struct r82xx_priv *priv, uint8_t reg, uint8_t val) +{ + return r82xx_write(priv, reg, &val, 1); +} + +static int r82xx_read_cache_reg(struct r82xx_priv *priv, int reg) +{ + reg -= REG_SHADOW_START; + + if (reg >= 0 && reg < NUM_REGS) + return priv->regs[reg]; + else + return -1; +} + +static int r82xx_write_reg_mask(struct r82xx_priv *priv, uint8_t reg, uint8_t val, + uint8_t bit_mask) +{ + int rc = r82xx_read_cache_reg(priv, reg); + + if (rc < 0) + return rc; + + val = (rc & ~bit_mask) | (val & bit_mask); + + return r82xx_write(priv, reg, &val, 1); +} + +static uint8_t r82xx_bitrev(uint8_t byte) +{ + const uint8_t lut[16] = { 0x0, 0x8, 0x4, 0xc, 0x2, 0xa, 0x6, 0xe, + 0x1, 0x9, 0x5, 0xd, 0x3, 0xb, 0x7, 0xf }; + + return (lut[byte & 0xf] << 4) | lut[byte >> 4]; +} + +static int r82xx_read(struct r82xx_priv *priv, uint8_t reg, uint8_t *val, int len) +{ + int rc, i; + uint8_t *p = &priv->buf[1]; + + priv->buf[0] = reg; + + rc = rtlsdr_i2c_write_fn(priv->rtl_dev, priv->cfg->i2c_addr, priv->buf, 1); + + if (rc != 1) { + fprintf(stderr, "%s: i2c wr failed=%d reg=%02x len=%d\n", + __FUNCTION__, rc, reg, 1); + if (rc < 0) + return rc; + return -1; + } + + rc = rtlsdr_i2c_read_fn(priv->rtl_dev, priv->cfg->i2c_addr, p, len); + + if (rc != len) { + fprintf(stderr, "%s: i2c rd failed=%d reg=%02x len=%d\n", + __FUNCTION__, rc, reg, len); + if (rc < 0) + return rc; + return -1; + } + + /* Copy data to the output buffer */ + for (i = 0; i < len; i++) + val[i] = r82xx_bitrev(p[i]); + + return 0; +} + +/* + * r82xx tuning logic + */ + +static int r82xx_set_mux(struct r82xx_priv *priv, uint32_t freq) +{ + const struct r82xx_freq_range *range; + int rc; + unsigned int i; + uint8_t val; + + /* Get the proper frequency range */ + freq = freq / 1000000; + for (i = 0; i < ARRAY_SIZE(freq_ranges) - 1; i++) { + if (freq < freq_ranges[i + 1].freq) + break; + } + range = &freq_ranges[i]; + + /* Open Drain */ + rc = r82xx_write_reg_mask(priv, 0x17, range->open_d, 0x08); + if (rc < 0) + return rc; + + /* RF_MUX,Polymux */ + rc = r82xx_write_reg_mask(priv, 0x1a, range->rf_mux_ploy, 0xc3); + if (rc < 0) + return rc; + + /* TF BAND */ + rc = r82xx_write_reg(priv, 0x1b, range->tf_c); + if (rc < 0) + return rc; + + /* XTAL CAP & Drive */ + switch (priv->xtal_cap_sel) { + case XTAL_LOW_CAP_30P: + case XTAL_LOW_CAP_20P: + val = range->xtal_cap20p | 0x08; + break; + case XTAL_LOW_CAP_10P: + val = range->xtal_cap10p | 0x08; + break; + case XTAL_HIGH_CAP_0P: + val = range->xtal_cap0p | 0x00; + break; + default: + case XTAL_LOW_CAP_0P: + val = range->xtal_cap0p | 0x08; + break; + } + rc = r82xx_write_reg_mask(priv, 0x10, val, 0x0b); + if (rc < 0) + return rc; + + rc = r82xx_write_reg_mask(priv, 0x08, 0x00, 0x3f); + if (rc < 0) + return rc; + + rc = r82xx_write_reg_mask(priv, 0x09, 0x00, 0x3f); + + return rc; +} + +static int r82xx_set_pll(struct r82xx_priv *priv, uint32_t freq) +{ + int rc, i; + unsigned sleep_time = 10000; + uint64_t vco_freq; + uint32_t vco_fra; /* VCO contribution by SDM (kHz) */ + uint32_t vco_min = 1770000; + uint32_t vco_max = vco_min * 2; + uint32_t freq_khz, pll_ref, pll_ref_khz; + uint16_t n_sdm = 2; + uint16_t sdm = 0; + uint8_t mix_div = 2; + uint8_t div_buf = 0; + uint8_t div_num = 0; + uint8_t vco_power_ref = 2; + uint8_t refdiv2 = 0; + uint8_t ni, si, nint, vco_fine_tune, val; + uint8_t data[5]; + + /* Frequency in kHz */ + freq_khz = (freq + 500) / 1000; + pll_ref = priv->cfg->xtal; + pll_ref_khz = (priv->cfg->xtal + 500) / 1000; + + rc = r82xx_write_reg_mask(priv, 0x10, refdiv2, 0x10); + if (rc < 0) + return rc; + + /* set pll autotune = 128kHz */ + rc = r82xx_write_reg_mask(priv, 0x1a, 0x00, 0x0c); + if (rc < 0) + return rc; + + /* set VCO current = 100 */ + rc = r82xx_write_reg_mask(priv, 0x12, 0x80, 0xe0); + if (rc < 0) + return rc; + + /* Calculate divider */ + while (mix_div <= 64) { + if (((freq_khz * mix_div) >= vco_min) && + ((freq_khz * mix_div) < vco_max)) { + div_buf = mix_div; + while (div_buf > 2) { + div_buf = div_buf >> 1; + div_num++; + } + break; + } + mix_div = mix_div << 1; + } + + rc = r82xx_read(priv, 0x00, data, sizeof(data)); + if (rc < 0) + return rc; + + if (priv->cfg->rafael_chip == CHIP_R828D) + vco_power_ref = 1; + + vco_fine_tune = (data[4] & 0x30) >> 4; + + if (vco_fine_tune > vco_power_ref) + div_num = div_num - 1; + else if (vco_fine_tune < vco_power_ref) + div_num = div_num + 1; + + rc = r82xx_write_reg_mask(priv, 0x10, div_num << 5, 0xe0); + if (rc < 0) + return rc; + + vco_freq = (uint64_t)freq * (uint64_t)mix_div; + nint = vco_freq / (2 * pll_ref); + vco_fra = (vco_freq - 2 * pll_ref * nint) / 1000; + + if (nint > ((128 / vco_power_ref) - 1)) { + fprintf(stderr, "[R82XX] No valid PLL values for %u Hz!\n", freq); + return -1; + } + + ni = (nint - 13) / 4; + si = nint - 4 * ni - 13; + + rc = r82xx_write_reg(priv, 0x14, ni + (si << 6)); + if (rc < 0) + return rc; + + /* pw_sdm */ + if (!vco_fra) + val = 0x08; + else + val = 0x00; + + rc = r82xx_write_reg_mask(priv, 0x12, val, 0x08); + if (rc < 0) + return rc; + + /* sdm calculator */ + while (vco_fra > 1) { + if (vco_fra > (2 * pll_ref_khz / n_sdm)) { + sdm = sdm + 32768 / (n_sdm / 2); + vco_fra = vco_fra - 2 * pll_ref_khz / n_sdm; + if (n_sdm >= 0x8000) + break; + } + n_sdm <<= 1; + } + + rc = r82xx_write_reg(priv, 0x16, sdm >> 8); + if (rc < 0) + return rc; + rc = r82xx_write_reg(priv, 0x15, sdm & 0xff); + if (rc < 0) + return rc; + + for (i = 0; i < 2; i++) { +// usleep_range(sleep_time, sleep_time + 1000); + + /* Check if PLL has locked */ + rc = r82xx_read(priv, 0x00, data, 3); + if (rc < 0) + return rc; + if (data[2] & 0x40) + break; + + if (!i) { + /* Didn't lock. Increase VCO current */ + rc = r82xx_write_reg_mask(priv, 0x12, 0x60, 0xe0); + if (rc < 0) + return rc; + } + } + + if (!(data[2] & 0x40)) { + fprintf(stderr, "[R82XX] PLL not locked!\n"); + priv->has_lock = 0; + return 0; + } + + priv->has_lock = 1; + + /* set pll autotune = 8kHz */ + rc = r82xx_write_reg_mask(priv, 0x1a, 0x08, 0x08); + + return rc; +} + +static int r82xx_sysfreq_sel(struct r82xx_priv *priv, uint32_t freq, + enum r82xx_tuner_type type, + uint32_t delsys) +{ + int rc; + uint8_t mixer_top, lna_top, cp_cur, div_buf_cur, lna_vth_l, mixer_vth_l; + uint8_t air_cable1_in, cable2_in, pre_dect, lna_discharge, filter_cur; + + switch (delsys) { + case SYS_DVBT: + if ((freq == 506000000) || (freq == 666000000) || + (freq == 818000000)) { + mixer_top = 0x14; /* mixer top:14 , top-1, low-discharge */ + lna_top = 0xe5; /* detect bw 3, lna top:4, predet top:2 */ + cp_cur = 0x28; /* 101, 0.2 */ + div_buf_cur = 0x20; /* 10, 200u */ + } else { + mixer_top = 0x24; /* mixer top:13 , top-1, low-discharge */ + lna_top = 0xe5; /* detect bw 3, lna top:4, predet top:2 */ + cp_cur = 0x38; /* 111, auto */ + div_buf_cur = 0x30; /* 11, 150u */ + } + lna_vth_l = 0x53; /* lna vth 0.84 , vtl 0.64 */ + mixer_vth_l = 0x75; /* mixer vth 1.04, vtl 0.84 */ + air_cable1_in = 0x00; + cable2_in = 0x00; + pre_dect = 0x40; + lna_discharge = 14; + filter_cur = 0x40; /* 10, low */ + break; + case SYS_DVBT2: + mixer_top = 0x24; /* mixer top:13 , top-1, low-discharge */ + lna_top = 0xe5; /* detect bw 3, lna top:4, predet top:2 */ + lna_vth_l = 0x53; /* lna vth 0.84 , vtl 0.64 */ + mixer_vth_l = 0x75; /* mixer vth 1.04, vtl 0.84 */ + air_cable1_in = 0x00; + cable2_in = 0x00; + pre_dect = 0x40; + lna_discharge = 14; + cp_cur = 0x38; /* 111, auto */ + div_buf_cur = 0x30; /* 11, 150u */ + filter_cur = 0x40; /* 10, low */ + break; + case SYS_ISDBT: + mixer_top = 0x24; /* mixer top:13 , top-1, low-discharge */ + lna_top = 0xe5; /* detect bw 3, lna top:4, predet top:2 */ + lna_vth_l = 0x75; /* lna vth 1.04 , vtl 0.84 */ + mixer_vth_l = 0x75; /* mixer vth 1.04, vtl 0.84 */ + air_cable1_in = 0x00; + cable2_in = 0x00; + pre_dect = 0x40; + lna_discharge = 14; + cp_cur = 0x38; /* 111, auto */ + div_buf_cur = 0x30; /* 11, 150u */ + filter_cur = 0x40; /* 10, low */ + break; + default: /* DVB-T 8M */ + mixer_top = 0x24; /* mixer top:13 , top-1, low-discharge */ + lna_top = 0xe5; /* detect bw 3, lna top:4, predet top:2 */ + lna_vth_l = 0x53; /* lna vth 0.84 , vtl 0.64 */ + mixer_vth_l = 0x75; /* mixer vth 1.04, vtl 0.84 */ + air_cable1_in = 0x00; + cable2_in = 0x00; + pre_dect = 0x40; + lna_discharge = 14; + cp_cur = 0x38; /* 111, auto */ + div_buf_cur = 0x30; /* 11, 150u */ + filter_cur = 0x40; /* 10, low */ + break; + } + + if (priv->cfg->use_predetect) { + rc = r82xx_write_reg_mask(priv, 0x06, pre_dect, 0x40); + if (rc < 0) + return rc; + } + + rc = r82xx_write_reg_mask(priv, 0x1d, lna_top, 0xc7); + if (rc < 0) + return rc; + rc = r82xx_write_reg_mask(priv, 0x1c, mixer_top, 0xf8); + if (rc < 0) + return rc; + rc = r82xx_write_reg(priv, 0x0d, lna_vth_l); + if (rc < 0) + return rc; + rc = r82xx_write_reg(priv, 0x0e, mixer_vth_l); + if (rc < 0) + return rc; + + priv->input = air_cable1_in; + + /* Air-IN only for Astrometa */ + rc = r82xx_write_reg_mask(priv, 0x05, air_cable1_in, 0x60); + if (rc < 0) + return rc; + rc = r82xx_write_reg_mask(priv, 0x06, cable2_in, 0x08); + if (rc < 0) + return rc; + + rc = r82xx_write_reg_mask(priv, 0x11, cp_cur, 0x38); + if (rc < 0) + return rc; + rc = r82xx_write_reg_mask(priv, 0x17, div_buf_cur, 0x30); + if (rc < 0) + return rc; + rc = r82xx_write_reg_mask(priv, 0x0a, filter_cur, 0x60); + if (rc < 0) + return rc; + + /* + * Set LNA + */ + + if (type != TUNER_ANALOG_TV) { + /* LNA TOP: lowest */ + rc = r82xx_write_reg_mask(priv, 0x1d, 0, 0x38); + if (rc < 0) + return rc; + + /* 0: normal mode */ + rc = r82xx_write_reg_mask(priv, 0x1c, 0, 0x04); + if (rc < 0) + return rc; + + /* 0: PRE_DECT off */ + rc = r82xx_write_reg_mask(priv, 0x06, 0, 0x40); + if (rc < 0) + return rc; + + /* agc clk 250hz */ + rc = r82xx_write_reg_mask(priv, 0x1a, 0x30, 0x30); + if (rc < 0) + return rc; + +// msleep(250); + + /* write LNA TOP = 3 */ + rc = r82xx_write_reg_mask(priv, 0x1d, 0x18, 0x38); + if (rc < 0) + return rc; + + /* + * write discharge mode + * FIXME: IMHO, the mask here is wrong, but it matches + * what's there at the original driver + */ + rc = r82xx_write_reg_mask(priv, 0x1c, mixer_top, 0x04); + if (rc < 0) + return rc; + + /* LNA discharge current */ + rc = r82xx_write_reg_mask(priv, 0x1e, lna_discharge, 0x1f); + if (rc < 0) + return rc; + + /* agc clk 60hz */ + rc = r82xx_write_reg_mask(priv, 0x1a, 0x20, 0x30); + if (rc < 0) + return rc; + } else { + /* PRE_DECT off */ + rc = r82xx_write_reg_mask(priv, 0x06, 0, 0x40); + if (rc < 0) + return rc; + + /* write LNA TOP */ + rc = r82xx_write_reg_mask(priv, 0x1d, lna_top, 0x38); + if (rc < 0) + return rc; + + /* + * write discharge mode + * FIXME: IMHO, the mask here is wrong, but it matches + * what's there at the original driver + */ + rc = r82xx_write_reg_mask(priv, 0x1c, mixer_top, 0x04); + if (rc < 0) + return rc; + + /* LNA discharge current */ + rc = r82xx_write_reg_mask(priv, 0x1e, lna_discharge, 0x1f); + if (rc < 0) + return rc; + + /* agc clk 1Khz, external det1 cap 1u */ + rc = r82xx_write_reg_mask(priv, 0x1a, 0x00, 0x30); + if (rc < 0) + return rc; + + rc = r82xx_write_reg_mask(priv, 0x10, 0x00, 0x04); + if (rc < 0) + return rc; + } + return 0; +} + +static int r82xx_set_tv_standard(struct r82xx_priv *priv, + unsigned bw, + enum r82xx_tuner_type type, + uint32_t delsys) + +{ + int rc, i; + uint32_t if_khz, filt_cal_lo; + uint8_t data[5]; + uint8_t filt_gain, img_r, filt_q, hp_cor, ext_enable, loop_through; + uint8_t lt_att, flt_ext_widest, polyfil_cur; + int need_calibration; + + /* BW < 6 MHz */ + if_khz = 3570; + filt_cal_lo = 56000; /* 52000->56000 */ + filt_gain = 0x10; /* +3db, 6mhz on */ + img_r = 0x00; /* image negative */ + filt_q = 0x10; /* r10[4]:low q(1'b1) */ + hp_cor = 0x6b; /* 1.7m disable, +2cap, 1.0mhz */ + ext_enable = 0x60; /* r30[6]=1 ext enable; r30[5]:1 ext at lna max-1 */ + loop_through = 0x01; /* r5[7], lt off */ + lt_att = 0x00; /* r31[7], lt att enable */ + flt_ext_widest = 0x00; /* r15[7]: flt_ext_wide off */ + polyfil_cur = 0x60; /* r25[6:5]:min */ + + /* Initialize the shadow registers */ + memcpy(priv->regs, r82xx_init_array, sizeof(r82xx_init_array)); + + /* Init Flag & Xtal_check Result (inits VGA gain, needed?)*/ + rc = r82xx_write_reg_mask(priv, 0x0c, 0x00, 0x0f); + if (rc < 0) + return rc; + + /* version */ + rc = r82xx_write_reg_mask(priv, 0x13, VER_NUM, 0x3f); + if (rc < 0) + return rc; + + /* for LT Gain test */ + if (type != TUNER_ANALOG_TV) { + rc = r82xx_write_reg_mask(priv, 0x1d, 0x00, 0x38); + if (rc < 0) + return rc; +// usleep_range(1000, 2000); + } + priv->int_freq = if_khz * 1000; + + /* Check if standard changed. If so, filter calibration is needed */ + /* as we call this function only once in rtlsdr, force calibration */ + need_calibration = 1; + + if (need_calibration) { + for (i = 0; i < 2; i++) { + /* Set filt_cap */ + rc = r82xx_write_reg_mask(priv, 0x0b, hp_cor, 0x60); + if (rc < 0) + return rc; + + /* set cali clk =on */ + rc = r82xx_write_reg_mask(priv, 0x0f, 0x04, 0x04); + if (rc < 0) + return rc; + + /* X'tal cap 0pF for PLL */ + rc = r82xx_write_reg_mask(priv, 0x10, 0x00, 0x03); + if (rc < 0) + return rc; + + rc = r82xx_set_pll(priv, filt_cal_lo * 1000); + if (rc < 0 || !priv->has_lock) + return rc; + + /* Start Trigger */ + rc = r82xx_write_reg_mask(priv, 0x0b, 0x10, 0x10); + if (rc < 0) + return rc; + +// usleep_range(1000, 2000); + + /* Stop Trigger */ + rc = r82xx_write_reg_mask(priv, 0x0b, 0x00, 0x10); + if (rc < 0) + return rc; + + /* set cali clk =off */ + rc = r82xx_write_reg_mask(priv, 0x0f, 0x00, 0x04); + if (rc < 0) + return rc; + + /* Check if calibration worked */ + rc = r82xx_read(priv, 0x00, data, sizeof(data)); + if (rc < 0) + return rc; + + priv->fil_cal_code = data[4] & 0x0f; + if (priv->fil_cal_code && priv->fil_cal_code != 0x0f) + break; + } + /* narrowest */ + if (priv->fil_cal_code == 0x0f) + priv->fil_cal_code = 0; + } + + rc = r82xx_write_reg_mask(priv, 0x0a, + filt_q | priv->fil_cal_code, 0x1f); + if (rc < 0) + return rc; + + /* Set BW, Filter_gain, & HP corner */ + rc = r82xx_write_reg_mask(priv, 0x0b, hp_cor, 0xef); + if (rc < 0) + return rc; + + /* Set Img_R */ + rc = r82xx_write_reg_mask(priv, 0x07, img_r, 0x80); + if (rc < 0) + return rc; + + /* Set filt_3dB, V6MHz */ + rc = r82xx_write_reg_mask(priv, 0x06, filt_gain, 0x30); + if (rc < 0) + return rc; + + /* channel filter extension */ + rc = r82xx_write_reg_mask(priv, 0x1e, ext_enable, 0x60); + if (rc < 0) + return rc; + + /* Loop through */ + rc = r82xx_write_reg_mask(priv, 0x05, loop_through, 0x80); + if (rc < 0) + return rc; + + /* Loop through attenuation */ + rc = r82xx_write_reg_mask(priv, 0x1f, lt_att, 0x80); + if (rc < 0) + return rc; + + /* filter extension widest */ + rc = r82xx_write_reg_mask(priv, 0x0f, flt_ext_widest, 0x80); + if (rc < 0) + return rc; + + /* RF poly filter current */ + rc = r82xx_write_reg_mask(priv, 0x19, polyfil_cur, 0x60); + if (rc < 0) + return rc; + + /* Store current standard. If it changes, re-calibrate the tuner */ + priv->delsys = delsys; + priv->type = type; + priv->bw = bw; + + return 0; +} + +static int r82xx_read_gain(struct r82xx_priv *priv) +{ + uint8_t data[4]; + int rc; + + rc = r82xx_read(priv, 0x00, data, sizeof(data)); + if (rc < 0) + return rc; + + return ((data[3] & 0x0f) << 1) + ((data[3] & 0xf0) >> 4); +} + +/* measured with a Racal 6103E GSM test set at 928 MHz with -60 dBm + * input power, for raw results see: + * http://steve-m.de/projects/rtl-sdr/gain_measurement/r820t/ + */ + +#define VGA_BASE_GAIN -47 +static const int r82xx_vga_gain_steps[] = { + 0, 26, 26, 30, 42, 35, 24, 13, 14, 32, 36, 34, 35, 37, 35, 36 +}; + +static const int r82xx_lna_gain_steps[] = { + 0, 9, 13, 40, 38, 13, 31, 22, 26, 31, 26, 14, 19, 5, 35, 13 +}; + +static const int r82xx_mixer_gain_steps[] = { + 0, 5, 10, 10, 19, 9, 10, 25, 17, 10, 8, 16, 13, 6, 3, -8 +}; + +int r82xx_set_gain(struct r82xx_priv *priv, int set_manual_gain, int gain) +{ + int rc; + + if (set_manual_gain) { + int i, total_gain = 0; + uint8_t mix_index = 0, lna_index = 0; + uint8_t data[4]; + + /* LNA auto off */ + rc = r82xx_write_reg_mask(priv, 0x05, 0x10, 0x10); + if (rc < 0) + return rc; + + /* Mixer auto off */ + rc = r82xx_write_reg_mask(priv, 0x07, 0, 0x10); + if (rc < 0) + return rc; + + rc = r82xx_read(priv, 0x00, data, sizeof(data)); + if (rc < 0) + return rc; + + /* set fixed VGA gain for now (16.3 dB) */ + rc = r82xx_write_reg_mask(priv, 0x0c, 0x08, 0x9f); + if (rc < 0) + return rc; + + for (i = 0; i < 15; i++) { + if (total_gain >= gain) + break; + + total_gain += r82xx_lna_gain_steps[++lna_index]; + + if (total_gain >= gain) + break; + + total_gain += r82xx_mixer_gain_steps[++mix_index]; + } + + /* set LNA gain */ + rc = r82xx_write_reg_mask(priv, 0x05, lna_index, 0x0f); + if (rc < 0) + return rc; + + /* set Mixer gain */ + rc = r82xx_write_reg_mask(priv, 0x07, mix_index, 0x0f); + if (rc < 0) + return rc; + } else { + /* LNA */ + rc = r82xx_write_reg_mask(priv, 0x05, 0, 0x10); + if (rc < 0) + return rc; + + /* Mixer */ + rc = r82xx_write_reg_mask(priv, 0x07, 0x10, 0x10); + if (rc < 0) + return rc; + + /* set fixed VGA gain for now (26.5 dB) */ + rc = r82xx_write_reg_mask(priv, 0x0c, 0x0b, 0x9f); + if (rc < 0) + return rc; + } + + return 0; +} + +/* Bandwidth contribution by low-pass filter. */ +static const int r82xx_if_low_pass_bw_table[] = { + 1700000, 1600000, 1550000, 1450000, 1200000, 900000, 700000, 550000, 450000, 350000 +}; + +#define FILT_HP_BW1 350000 +#define FILT_HP_BW2 380000 +int r82xx_set_bandwidth(struct r82xx_priv *priv, int bw, uint32_t rate) +{ + int rc; + unsigned int i; + int real_bw = 0; + uint8_t reg_0a; + uint8_t reg_0b; + + if (bw > 7000000) { + // BW: 8 MHz + reg_0a = 0x10; + reg_0b = 0x0b; + priv->int_freq = 4570000; + } else if (bw > 6000000) { + // BW: 7 MHz + reg_0a = 0x10; + reg_0b = 0x2a; + priv->int_freq = 4570000; + } else if (bw > r82xx_if_low_pass_bw_table[0] + FILT_HP_BW1 + FILT_HP_BW2) { + // BW: 6 MHz + reg_0a = 0x10; + reg_0b = 0x6b; + priv->int_freq = 3570000; + } else { + reg_0a = 0x00; + reg_0b = 0x80; + priv->int_freq = 2300000; + + if (bw > r82xx_if_low_pass_bw_table[0] + FILT_HP_BW1) { + bw -= FILT_HP_BW2; + priv->int_freq += FILT_HP_BW2; + real_bw += FILT_HP_BW2; + } else { + reg_0b |= 0x20; + } + + if (bw > r82xx_if_low_pass_bw_table[0]) { + bw -= FILT_HP_BW1; + priv->int_freq += FILT_HP_BW1; + real_bw += FILT_HP_BW1; + } else { + reg_0b |= 0x40; + } + + // find low-pass filter + for(i = 0; i < ARRAY_SIZE(r82xx_if_low_pass_bw_table); ++i) { + if (bw > r82xx_if_low_pass_bw_table[i]) + break; + } + --i; + reg_0b |= 15 - i; + real_bw += r82xx_if_low_pass_bw_table[i]; + + priv->int_freq -= real_bw / 2; + } + + rc = r82xx_write_reg_mask(priv, 0x0a, reg_0a, 0x10); + if (rc < 0) + return rc; + + rc = r82xx_write_reg_mask(priv, 0x0b, reg_0b, 0xef); + if (rc < 0) + return rc; + + return priv->int_freq; +} +#undef FILT_HP_BW1 +#undef FILT_HP_BW2 + +int r82xx_set_freq(struct r82xx_priv *priv, uint32_t freq) +{ + int rc = -1; + uint32_t lo_freq = freq + priv->int_freq; + uint8_t air_cable1_in; + + rc = r82xx_set_mux(priv, lo_freq); + if (rc < 0) + goto err; + + rc = r82xx_set_pll(priv, lo_freq); + if (rc < 0 || !priv->has_lock) + goto err; + + /* switch between 'Cable1' and 'Air-In' inputs on sticks with + * R828D tuner. We switch at 345 MHz, because that's where the + * noise-floor has about the same level with identical LNA + * settings. The original driver used 320 MHz. */ + air_cable1_in = (freq > MHZ(345)) ? 0x00 : 0x60; + + if ((priv->cfg->rafael_chip == CHIP_R828D) && + (air_cable1_in != priv->input)) { + priv->input = air_cable1_in; + rc = r82xx_write_reg_mask(priv, 0x05, air_cable1_in, 0x60); + } + +err: + if (rc < 0) + fprintf(stderr, "%s: failed=%d\n", __FUNCTION__, rc); + return rc; +} + +/* + * r82xx standby logic + */ + +int r82xx_standby(struct r82xx_priv *priv) +{ + int rc; + + /* If device was not initialized yet, don't need to standby */ + if (!priv->init_done) + return 0; + + rc = r82xx_write_reg(priv, 0x06, 0xb1); + if (rc < 0) + return rc; + rc = r82xx_write_reg(priv, 0x05, 0xa0); + if (rc < 0) + return rc; + rc = r82xx_write_reg(priv, 0x07, 0x3a); + if (rc < 0) + return rc; + rc = r82xx_write_reg(priv, 0x08, 0x40); + if (rc < 0) + return rc; + rc = r82xx_write_reg(priv, 0x09, 0xc0); + if (rc < 0) + return rc; + rc = r82xx_write_reg(priv, 0x0a, 0x36); + if (rc < 0) + return rc; + rc = r82xx_write_reg(priv, 0x0c, 0x35); + if (rc < 0) + return rc; + rc = r82xx_write_reg(priv, 0x0f, 0x68); + if (rc < 0) + return rc; + rc = r82xx_write_reg(priv, 0x11, 0x03); + if (rc < 0) + return rc; + rc = r82xx_write_reg(priv, 0x17, 0xf4); + if (rc < 0) + return rc; + rc = r82xx_write_reg(priv, 0x19, 0x0c); + + /* Force initial calibration */ + priv->type = -1; + + return rc; +} + +/* + * r82xx device init logic + */ + +static int r82xx_xtal_check(struct r82xx_priv *priv) +{ + int rc; + unsigned int i; + uint8_t data[3], val; + + /* Initialize the shadow registers */ + memcpy(priv->regs, r82xx_init_array, sizeof(r82xx_init_array)); + + /* cap 30pF & Drive Low */ + rc = r82xx_write_reg_mask(priv, 0x10, 0x0b, 0x0b); + if (rc < 0) + return rc; + + /* set pll autotune = 128kHz */ + rc = r82xx_write_reg_mask(priv, 0x1a, 0x00, 0x0c); + if (rc < 0) + return rc; + + /* set manual initial reg = 111111; */ + rc = r82xx_write_reg_mask(priv, 0x13, 0x7f, 0x7f); + if (rc < 0) + return rc; + + /* set auto */ + rc = r82xx_write_reg_mask(priv, 0x13, 0x00, 0x40); + if (rc < 0) + return rc; + + /* Try several xtal capacitor alternatives */ + for (i = 0; i < ARRAY_SIZE(r82xx_xtal_capacitor); i++) { + rc = r82xx_write_reg_mask(priv, 0x10, + r82xx_xtal_capacitor[i][0], 0x1b); + if (rc < 0) + return rc; + +// usleep_range(5000, 6000); + + rc = r82xx_read(priv, 0x00, data, sizeof(data)); + if (rc < 0) + return rc; + if (!(data[2] & 0x40)) + continue; + + val = data[2] & 0x3f; + + if (priv->cfg->xtal == 16000000 && (val > 29 || val < 23)) + break; + + if (val != 0x3f) + break; + } + + if (i == ARRAY_SIZE(r82xx_xtal_capacitor)) + return -1; + + return r82xx_xtal_capacitor[i][1]; +} + +int r82xx_init(struct r82xx_priv *priv) +{ + int rc; + + /* TODO: R828D might need r82xx_xtal_check() */ + priv->xtal_cap_sel = XTAL_HIGH_CAP_0P; + + /* Initialize registers */ + rc = r82xx_write(priv, 0x05, + r82xx_init_array, sizeof(r82xx_init_array)); + + rc = r82xx_set_tv_standard(priv, 3, TUNER_DIGITAL_TV, 0); + if (rc < 0) + goto err; + + rc = r82xx_sysfreq_sel(priv, 0, TUNER_DIGITAL_TV, SYS_DVBT); + + priv->init_done = 1; + +err: + if (rc < 0) + fprintf(stderr, "%s: failed=%d\n", __FUNCTION__, rc); + return rc; +} + +#if 0 +/* Not used, for now */ +static int r82xx_gpio(struct r82xx_priv *priv, int enable) +{ + return r82xx_write_reg_mask(priv, 0x0f, enable ? 1 : 0, 0x01); +} +#endif diff --git a/Radio/Test/TestRtlSdr.swift b/Radio/Test/TestRtlSdr.swift deleted file mode 100644 index f58dd5d..0000000 --- a/Radio/Test/TestRtlSdr.swift +++ /dev/null @@ -1,7 +0,0 @@ -// -// TestRtlSdr.swift -// PrySDR -// -// Created by Jacky Jack on 25/10/2024. -// - diff --git a/Radio/Test/TestAirSpy.swift b/Radio/Utils/TestAirSpy.swift index 2a60862..2a60862 100644 --- a/Radio/Test/TestAirSpy.swift +++ b/Radio/Utils/TestAirSpy.swift diff --git a/Radio/Test/TestAirSpyHF.swift b/Radio/Utils/TestAirSpyHF.swift index 86d093c..86d093c 100644 --- a/Radio/Test/TestAirSpyHF.swift +++ b/Radio/Utils/TestAirSpyHF.swift diff --git a/Radio/Test/TestBladeRF.swift b/Radio/Utils/TestBladeRF.swift index b71b5b0..b71b5b0 100644 --- a/Radio/Test/TestBladeRF.swift +++ b/Radio/Utils/TestBladeRF.swift diff --git a/Radio/Utils/TestRtlSdr/main.swift b/Radio/Utils/TestRtlSdr/main.swift new file mode 100644 index 0000000..86a66ee --- /dev/null +++ b/Radio/Utils/TestRtlSdr/main.swift @@ -0,0 +1,16 @@ +// +// main.swift +// TestRtlSdr +// +// Created by Jacky Jack on 01/11/2024. +// + +import Foundation +import libr820 + + +let rtldev = R820Tuner() +let count = rtldev.getDeviceCount() +rtldev.open(index: 0) +rtldev.close() + |