btstack/platform/windows/hci_transport_h2_winusb.c

1381 lines
50 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* Copyright (C) 2014 BlueKitchen GmbH
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. 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.
* 3. Neither the name of the copyright holders nor the names of
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
* 4. Any redistribution, use, or modification is done solely for
* personal benefit and not for any commercial purpose or for
* monetary gain.
*
* THIS SOFTWARE IS PROVIDED BY BLUEKITCHEN GMBH 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 MATTHIAS
* RINGWALD 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.
*
* Please inquire about commercial licensing options at
* contact@bluekitchen-gmbh.com
*
*/
#define __BTSTACK_FILE__ "hci_transport_h2_winusb.c"
/*
* hci_transport_usb.c
*
* HCI Transport API implementation for USB
*
* Created by Matthias Ringwald on 7/5/09.
*/
// Interface Number - Alternate Setting - suggested Endpoint Address - Endpoint Type - Suggested Max Packet Size
// HCI Commands 0 0 0x00 Control 8/16/32/64
// HCI Events 0 0 0x81 Interrupt (IN) 16
// ACL Data 0 0 0x82 Bulk (IN) 32/64
// ACL Data 0 0 0x02 Bulk (OUT) 32/64
// SCO Data 0 0 0x83 Isochronous (IN)
// SCO Data 0 0 0x03 Isochronous (Out)
#include <stdio.h>
#include <strings.h>
#include <string.h>
#include <unistd.h> /* UNIX standard function definitions */
#include <sys/types.h>
#include <inttypes.h> // to print long long int (aka 64 bit ints)
#include "btstack_config.h"
#include "btstack_debug.h"
#include "hci.h"
#include "hci_transport.h"
#include <Windows.h>
#include <SetupAPI.h>
#include <Winusb.h>
#ifdef ENABLE_SCO_OVER_HCI
// Isochronous Add-On
// Function signatures frome https://abi-laboratory.pro/compatibility/Windows_7.0_to_Windows_8.1/x86_64/info/winusb.dll/symbols.html
// MSDN documentation has multiple errors (Jan 2017), annotated below
typedef PVOID WINUSB_ISOCH_BUFFER_HANDLE, *PWINUSB_ISOCH_BUFFER_HANDLE;
typedef struct _WINUSB_PIPE_INFORMATION_EX {
USBD_PIPE_TYPE PipeType;
UCHAR PipeId;
USHORT MaximumPacketSize;
UCHAR Interval;
ULONG MaximumBytesPerInterval;
} WINUSB_PIPE_INFORMATION_EX, *PWINUSB_PIPE_INFORMATION_EX;
typedef WINBOOL (WINAPI * WinUsb_QueryPipeEx_t) (
WINUSB_INTERFACE_HANDLE InterfaceHandle,
UCHAR AlternateInterfaceNumber,
UCHAR PipeIndex,
PWINUSB_PIPE_INFORMATION_EX PipeInformationEx
);
typedef WINBOOL (WINAPI * WinUsb_RegisterIsochBuffer_t)(
WINUSB_INTERFACE_HANDLE InterfaceHandle,
UCHAR PipeID,
PVOID Buffer,
ULONG BufferLength,
PWINUSB_ISOCH_BUFFER_HANDLE BufferHandle
);
typedef WINBOOL (WINAPI * WinUsb_ReadIsochPipe_t)(
PWINUSB_ISOCH_BUFFER_HANDLE BufferHandle,
ULONG Offset,
ULONG Length,
PULONG FrameNumber,
ULONG NumberOfPackets, // MSDN lists PULONG
PUSBD_ISO_PACKET_DESCRIPTOR IsoPacketDescriptors, // MSDN lists PULONG
LPOVERLAPPED Overlapped
);
typedef WINBOOL (WINAPI * WinUsb_ReadIsochPipeAsap_t)(
PWINUSB_ISOCH_BUFFER_HANDLE BufferHandle,
ULONG Offset,
ULONG Length,
BOOL ContinueStream,
ULONG NumberOfPackets, // MSDN lists PULONG
PUSBD_ISO_PACKET_DESCRIPTOR IsoPacketDescriptors,
LPOVERLAPPED Overlapped
);
typedef WINBOOL (WINAPI * WinUsb_WriteIsochPipe_t)(
PWINUSB_ISOCH_BUFFER_HANDLE BufferHandle,
ULONG Offset,
ULONG Length,
PULONG FrameNumber,
LPOVERLAPPED Overlapped
);
typedef WINBOOL (WINAPI * WinUsb_WriteIsochPipeAsap_t)(
PWINUSB_ISOCH_BUFFER_HANDLE BufferHandle,
ULONG Offset,
ULONG Length,
BOOL ContinueStream,
LPOVERLAPPED Overlapped
);
typedef WINBOOL (WINAPI * WinUsb_UnregisterIsochBuffer_t)(
PWINUSB_ISOCH_BUFFER_HANDLE BufferHandle
);
typedef WINBOOL (WINAPI * WinUsb_GetCurrentFrameNumber_t)(
WINUSB_INTERFACE_HANDLE InterfaceHandle, // MSDN lists 'Device handle returned from CreateFile'
PULONG CurrentFrameNumber,
LARGE_INTEGER *TimeStamp
);
static WinUsb_QueryPipeEx_t WinUsb_QueryPipeEx;
static WinUsb_RegisterIsochBuffer_t WinUsb_RegisterIsochBuffer;
static WinUsb_ReadIsochPipe_t WinUsb_ReadIsochPipe;
static WinUsb_ReadIsochPipeAsap_t WinUsb_ReadIsochPipeAsap;
static WinUsb_WriteIsochPipe_t WinUsb_WriteIsochPipe;
static WinUsb_WriteIsochPipeAsap_t WinUsb_WriteIsochPipeAsap;
static WinUsb_UnregisterIsochBuffer_t WinUsb_UnregisterIsochBuffer;
static WinUsb_GetCurrentFrameNumber_t WinUsb_GetCurrentFrameNumber;
#endif
// Doesn't work as expected
// #define SCHEDULE_SCO_IN_TRANSFERS_MANUALLY
// Not tested yet
// #define SCHEDULE_SCO_OUT_TRANSFERS_MANUALLY
//
// Bluetooth USB Transport Alternate Settings:
//
// 0: No active voice channels (for USB compliance)
// 1: One 8 kHz voice channel with 8-bit encoding
// 2: Two 8 kHz voice channels with 8-bit encoding or one 8 kHz voice channel with 16-bit encoding
// 3: Three 8 kHz voice channels with 8-bit encoding
// 4: Two 8 kHz voice channels with 16-bit encoding or one 16 kHz voice channel with 16-bit encoding
// 5: Three 8 kHz voice channels with 16-bit encoding or one 8 kHz voice channel with 16-bit encoding and one 16 kHz voice channel with 16-bit encoding
// --> support only a single SCO connection
#define ALT_SETTING (1)
// alt setting for 1-3 connections and 8/16 bit
const int alt_setting_8_bit[] = {1,2,3};
const int alt_setting_16_bit[] = {2,4,5};
// for ALT_SETTING >= 1 and 8-bit channel, we need the following isochronous packets
// One complete SCO packet with 24 frames every 3 frames (== 3 ms)
#define NUM_ISO_PACKETS (3)
const uint16_t iso_packet_size_for_alt_setting[] = {
0,
9,
17,
25,
33,
49,
63,
};
// 49 bytes is the max usb packet size for alternate setting 5 (Three 8 kHz 16-bit channels or one 8 kHz 16-bit channel and one 16 kHz 16-bit channel)
// note: alt setting 6 has max packet size of 63 every 7.5 ms = 472.5 bytes / HCI packet, while max SCO packet has 255 byte payload
#define SCO_PACKET_SIZE (49 * NUM_ISO_PACKETS)
#define ISOC_BUFFERS 8
// Outgoing SCO packet queue
// simplified ring buffer implementation
#define SCO_RING_BUFFER_COUNT (20)
#define SCO_RING_BUFFER_SIZE (SCO_RING_BUFFER_COUNT * SCO_PACKET_SIZE)
/** Request type bits of the "bmRequestType" field in control transfers. */
enum usb_request_type {
USB_REQUEST_TYPE_STANDARD = (0x00 << 5),
USB_REQUEST_TYPE_CLASS = (0x01 << 5),
USB_REQUEST_TYPE_VENDOR = (0x02 << 5),
};
/** Recipient bits of the "bmRequestType" field in control transfers. Values 4 through 31 are reserved. */
enum usb_request_recipient {
USB_RECIPIENT_DEVICE = 0x00,
USB_RECIPIENT_INTERFACE = 0x01,
USB_RECIPIENT_ENDPOINT = 0x02,
USB_RECIPIENT_OTHER = 0x03,
};
// This is the GUID for the USB device class
static GUID GUID_DEVINTERFACE_USB_DEVICE =
{ 0xA5DCBF10L, 0x6530, 0x11D2, { 0x90, 0x1F, 0x00, 0xC0, 0x4F, 0xB9, 0x51, 0xED } };
static void usb_dummy_handler(uint8_t packet_type, uint8_t *packet, uint16_t size);
static void (*packet_handler)(uint8_t packet_type, uint8_t *packet, uint16_t size) = &usb_dummy_handler;
// endpoint addresses
static int event_in_addr;
static int acl_in_addr;
static int acl_out_addr;
static int sco_in_addr;
static int sco_out_addr;
//
static HANDLE usb_device_handle;
static WINUSB_INTERFACE_HANDLE usb_interface_0_handle;
static WINUSB_INTERFACE_HANDLE usb_interface_1_handle;
static OVERLAPPED usb_overlapped_event_in;
static OVERLAPPED usb_overlapped_command_out;
static OVERLAPPED usb_overlapped_acl_in;
static OVERLAPPED usb_overlapped_acl_out;
static btstack_data_source_t usb_data_source_event_in;
static btstack_data_source_t usb_data_source_command_out;
static btstack_data_source_t usb_data_source_acl_in;
static btstack_data_source_t usb_data_source_acl_out;
//
static int usb_command_out_active;
static int usb_acl_out_active;
// buffer for HCI Events and ACL Packets
static uint8_t hci_event_in_buffer[2 + 255];
static uint8_t hci_acl_in_buffer[HCI_INCOMING_PRE_BUFFER_SIZE + HCI_ACL_BUFFER_SIZE];
#ifdef ENABLE_SCO_OVER_HCI
typedef enum {
H2_W4_SCO_HEADER = 1,
H2_W4_PAYLOAD,
} H2_SCO_STATE;
// SCO Incoming Windows
static uint8_t hci_sco_in_buffer[ISOC_BUFFERS * SCO_PACKET_SIZE];
static WINUSB_ISOCH_BUFFER_HANDLE hci_sco_in_buffer_handle;
static USBD_ISO_PACKET_DESCRIPTOR hci_sco_packet_descriptors[ISOC_BUFFERS * NUM_ISO_PACKETS];
static OVERLAPPED usb_overlapped_sco_in[ISOC_BUFFERS];
static int usb_sco_in_expected_transfer;
// SCO Incoming Run Loop
static btstack_data_source_t usb_data_source_sco_in[ISOC_BUFFERS];
// SCO Incoming HCI
static H2_SCO_STATE sco_state;
static uint8_t sco_buffer[SCO_PACKET_SIZE];
static uint16_t sco_read_pos;
static uint16_t sco_bytes_to_read;
// SCO Outgoing Windows
static WINUSB_ISOCH_BUFFER_HANDLE hci_sco_out_buffer_handle;
static OVERLAPPED usb_overlapped_sco_out[SCO_RING_BUFFER_COUNT];
static int sco_ring_transfers_active;
static int usb_sco_out_expected_transfer;
#ifdef SCHEDULE_SCO_IN_TRANSFERS_MANUALLY
// next tranfer
static ULONG sco_next_transfer_at_frame;
#endif
// SCO Outgoing Run Loop
static btstack_data_source_t usb_data_source_sco_out[SCO_RING_BUFFER_COUNT];
// SCO Outgoing HCI
static uint8_t sco_ring_buffer[SCO_RING_BUFFER_SIZE];
static int sco_ring_write; // packet idx
// SCO Reconfiguration - pause/resume
static uint16_t sco_voice_setting;
static int sco_num_connections;
static int sco_shutdown;
static uint16_t iso_packet_size;
#endif
#if 0
// list of known devices, using VendorID/ProductID tuples
static const uint16_t known_bluetooth_devices[] = {
// DeLOCK Bluetooth 4.0
0x0a5c, 0x21e8,
// Asus BT400
0x0b05, 0x17cb,
};
static int num_known_devices = sizeof(known_bluetooth_devices) / sizeof(uint16_t) / 2;
static int usb_is_known_bluetooth_device(uint16_t vendor_id, uint16_t product_id){
int i;
for (i=0; i<num_known_devices; i++){
if (known_bluetooth_devices[i*2] == vendor_id && known_bluetooth_devices[i*2+1] == product_id){
return 1;
}
}
return 0;
}
#endif
#ifdef ENABLE_SCO_OVER_HCI
static void sco_ring_init(void){
sco_ring_write = 0;
sco_ring_transfers_active = 0;
}
static int sco_ring_have_space(void){
return sco_ring_transfers_active < SCO_RING_BUFFER_COUNT;
}
static void usb_sco_register_buffers(void){
BOOL result;
result = WinUsb_RegisterIsochBuffer(usb_interface_1_handle, sco_in_addr, hci_sco_in_buffer, sizeof(hci_sco_in_buffer), &hci_sco_in_buffer_handle);
if (!result) {
log_error("usb_sco_register_buffers: register in buffer failed, error %lu", GetLastError());
}
log_info("hci_sco_in_buffer_handle %p", hci_sco_in_buffer_handle);
result = WinUsb_RegisterIsochBuffer(usb_interface_1_handle, sco_out_addr, sco_ring_buffer, sizeof(sco_ring_buffer), &hci_sco_out_buffer_handle);
if (!result) {
log_error("usb_sco_unregister_buffers: register out buffer failed, error %lu", GetLastError());
}
log_info("hci_sco_out_buffer_handle %p", hci_sco_out_buffer_handle);
}
static void usb_sco_unregister_buffers(void){
if (hci_sco_in_buffer_handle){
WinUsb_UnregisterIsochBuffer(hci_sco_in_buffer_handle);
hci_sco_in_buffer_handle = NULL;
}
if (hci_sco_out_buffer_handle){
WinUsb_UnregisterIsochBuffer(hci_sco_out_buffer_handle);
hci_sco_out_buffer_handle = NULL;
}
}
#endif
static void usb_register_packet_handler(void (*handler)(uint8_t packet_type, uint8_t *packet, uint16_t size)){
log_info("registering packet handler");
packet_handler = handler;
}
static void usb_dummy_handler(uint8_t packet_type, uint8_t *packet, uint16_t size){
}
static void usb_init(const void *transport_config){
}
static void usb_free_resources(void){
if (usb_interface_1_handle){
WinUsb_Free(usb_interface_1_handle);
usb_interface_1_handle = NULL;
}
if (usb_interface_0_handle){
WinUsb_Free(usb_interface_0_handle);
usb_interface_0_handle = NULL;
}
if (usb_device_handle) {
CloseHandle(usb_device_handle);
usb_device_handle = NULL;
}
#ifdef ENABLE_SCO_OVER_HCI
usb_sco_unregister_buffers();
#endif
}
static void usb_submit_event_in_transfer(void){
// submit transfer
BOOL result = WinUsb_ReadPipe(usb_interface_0_handle, event_in_addr, hci_event_in_buffer, sizeof(hci_event_in_buffer), NULL, &usb_overlapped_event_in);
if (!result) {
if (GetLastError() != ERROR_IO_PENDING) goto exit_on_error;
}
// IO_PENDING -> wait for completed
btstack_run_loop_enable_data_source_callbacks(&usb_data_source_event_in, DATA_SOURCE_CALLBACK_READ);
return;
exit_on_error:
log_error("usb_submit_event_in_transfer: winusb last error %lu", GetLastError());
}
static void usb_submit_acl_in_transfer(void){
// submit transfer
BOOL result = WinUsb_ReadPipe(usb_interface_0_handle, acl_in_addr, hci_acl_in_buffer, sizeof(hci_acl_in_buffer), NULL, &usb_overlapped_acl_in);
if (!result) {
if (GetLastError() != ERROR_IO_PENDING) goto exit_on_error;
}
// IO_PENDING -> wait for completed
btstack_run_loop_enable_data_source_callbacks(&usb_data_source_acl_in, DATA_SOURCE_CALLBACK_READ);
return;
exit_on_error:
log_error("usb_submit_acl_in_transfer: winusb last error %lu", GetLastError());
}
#ifdef ENABLE_SCO_OVER_HCI
#ifdef SCHEDULE_SCO_IN_TRANSFERS_MANUALLY
// frame number gets updated
static void usb_submit_sco_in_transfer_at_frame(int i, ULONG * frame_number){
if (sco_shutdown){
log_info("USB SCO Shutdown:: usb_submit_sco_in_transfer_at_frame called");
return;
}
LARGE_INTEGER timestamp;
ULONG current_frame_number;
WinUsb_GetCurrentFrameNumber(usb_interface_0_handle, &current_frame_number, &timestamp);
ULONG frame_before = *frame_number;
BOOL result = WinUsb_ReadIsochPipe(hci_sco_in_buffer_handle, i * SCO_PACKET_SIZE, iso_packet_size * NUM_ISO_PACKETS,
frame_number, NUM_ISO_PACKETS, &hci_sco_packet_descriptors[i * NUM_ISO_PACKETS], &usb_overlapped_sco_in[i]);
// log_info("WinUsb_ReadIsochPipe #%02u: current %lu, planned %lu - buffer %lu", i, current_frame_number, frame_before, frame_before - current_frame_number);
if (!result) {
if (GetLastError() == ERROR_IO_PENDING) {
} else {
goto exit_on_error;
}
}
return;
exit_on_error:
log_error("usb_submit_sco_in_transfer: winusb last error %lu", GetLastError());
}
#else
static void usb_submit_sco_in_transfer_asap(int i, int continue_stream){
if (sco_shutdown){
log_info("USB SCO Shutdown:: usb_submit_sco_in_transfer_at_frame called");
return;
}
LARGE_INTEGER timestamp;
ULONG current_frame_number;
WinUsb_GetCurrentFrameNumber(usb_interface_0_handle, &current_frame_number, &timestamp);
// log_info("usb_submit_sco_in_transfer[%02u]: current frame %lu", i, current_frame_number);
BOOL result = WinUsb_ReadIsochPipeAsap(hci_sco_in_buffer_handle, i * SCO_PACKET_SIZE, iso_packet_size * NUM_ISO_PACKETS,
continue_stream, NUM_ISO_PACKETS, &hci_sco_packet_descriptors[i * NUM_ISO_PACKETS], &usb_overlapped_sco_in[i]);
if (!result) {
if (GetLastError() != ERROR_IO_PENDING) goto exit_on_error;
}
return;
exit_on_error:
log_error("usb_submit_sco_in_transfer: winusb last error %lu", GetLastError());
}
#endif
#endif
static void usb_process_event_in(btstack_data_source_t *ds, btstack_data_source_callback_type_t callback_type) {
btstack_run_loop_disable_data_source_callbacks(ds, DATA_SOURCE_CALLBACK_READ);
DWORD bytes_read;
BOOL ok = WinUsb_GetOverlappedResult(usb_interface_0_handle, &usb_overlapped_event_in, &bytes_read, FALSE);
if(!ok){
DWORD err = GetLastError();
if (err == ERROR_IO_INCOMPLETE){
// IO_INCOMPLETE -> wait for completed
btstack_run_loop_enable_data_source_callbacks(ds, DATA_SOURCE_CALLBACK_READ);
} else {
log_error("usb_process_event_in: error reading");
}
return;
}
// notify uppper
packet_handler(HCI_EVENT_PACKET, hci_event_in_buffer, bytes_read);
// re-submit transfer
usb_submit_event_in_transfer();
}
static void usb_process_acl_in(btstack_data_source_t *ds, btstack_data_source_callback_type_t callback_type) {
btstack_run_loop_disable_data_source_callbacks(ds, DATA_SOURCE_CALLBACK_READ);
DWORD bytes_read;
BOOL ok = WinUsb_GetOverlappedResult(usb_interface_0_handle, &usb_overlapped_acl_in, &bytes_read, FALSE);
if(!ok){
DWORD err = GetLastError();
if (err == ERROR_IO_INCOMPLETE){
// IO_INCOMPLETE -> wait for completed
btstack_run_loop_enable_data_source_callbacks(ds, DATA_SOURCE_CALLBACK_READ);
} else {
log_error("usb_process_acl_in: error writing");
}
return;
}
// notify uppper
packet_handler(HCI_ACL_DATA_PACKET, hci_acl_in_buffer, bytes_read);
// re-submit transfer
usb_submit_acl_in_transfer();
}
#ifdef ENABLE_SCO_OVER_HCI
static void sco_state_machine_init(void){
sco_state = H2_W4_SCO_HEADER;
sco_read_pos = 0;
sco_bytes_to_read = 3;
}
static void sco_handle_data(uint8_t * buffer, uint16_t size){
// printf("sco_handle_data: state %u, pos %u, to read %u, size %u", sco_state, sco_read_pos, sco_bytes_to_read, size);
while (size){
if (size < sco_bytes_to_read){
// just store incomplete data
memcpy(&sco_buffer[sco_read_pos], buffer, size);
sco_read_pos += size;
sco_bytes_to_read -= size;
return;
}
// copy requested data
memcpy(&sco_buffer[sco_read_pos], buffer, sco_bytes_to_read);
sco_read_pos += sco_bytes_to_read;
buffer += sco_bytes_to_read;
size -= sco_bytes_to_read;
// chunk read successfully, next action
switch (sco_state){
case H2_W4_SCO_HEADER:
sco_state = H2_W4_PAYLOAD;
sco_bytes_to_read = sco_buffer[2];
if (sco_bytes_to_read > (sizeof(sco_buffer)-3)){
log_error("sco_handle_data: sco packet len > packet size");
sco_state_machine_init();
}
break;
case H2_W4_PAYLOAD:
// packet complete
packet_handler(HCI_SCO_DATA_PACKET, sco_buffer, sco_read_pos);
sco_state_machine_init();
break;
}
}
}
static void usb_process_sco_out(btstack_data_source_t *ds, btstack_data_source_callback_type_t callback_type){
btstack_run_loop_disable_data_source_callbacks(ds, DATA_SOURCE_CALLBACK_WRITE);
if (sco_shutdown){
log_info("USB SCO Shutdown:: usb_process_sco_out called");
return;
}
// get current frame number
ULONG current_frame_number;
LARGE_INTEGER timestamp;
WinUsb_GetCurrentFrameNumber(usb_interface_0_handle, &current_frame_number, &timestamp);
// find index
int transfer_index;
for (transfer_index=0;transfer_index<SCO_RING_BUFFER_COUNT;transfer_index++){
if (ds == &usb_data_source_sco_out[transfer_index]) break;
}
// log_info("usb_process_sco_out[%02u] -- current frame %lu", transfer_index, current_frame_number);
DWORD bytes_transferred;
BOOL ok = WinUsb_GetOverlappedResult(usb_interface_0_handle, &usb_overlapped_sco_out[transfer_index], &bytes_transferred, FALSE);
// log_info("usb_process_sco_out_done: #%u result %u, bytes %u, state %u", transfer_index, ok, (int) bytes_transferred, sco_state);
if(!ok){
DWORD err = GetLastError();
if (err == ERROR_IO_INCOMPLETE){
// IO_INCOMPLETE -> wait for completed
btstack_run_loop_enable_data_source_callbacks(&usb_data_source_sco_out[transfer_index], DATA_SOURCE_CALLBACK_WRITE);
return;
}
log_error("usb_process_sco_out_done[%02u]: error writing %u, Internal %x", transfer_index, (int) err, (int) usb_overlapped_sco_out[transfer_index].Internal);
}
// decrease tab
sco_ring_transfers_active--;
// enable next data source callback
if (sco_ring_transfers_active){
// update expected and wait for completion
usb_sco_out_expected_transfer = (transfer_index+ 1) % SCO_RING_BUFFER_COUNT;
// log_info("usb_process_sco_out_done[%02u]: wait for transfer %02u", transfer_index, usb_sco_out_expected_transfer);
btstack_run_loop_enable_data_source_callbacks(&usb_data_source_sco_out[usb_sco_out_expected_transfer], DATA_SOURCE_CALLBACK_WRITE);
}
// log_info("usb_process_sco_out_done: transfers active %u", sco_ring_transfers_active);
// mark free
if (sco_ring_have_space()) {
uint8_t event[] = { HCI_EVENT_SCO_CAN_SEND_NOW, 0};
packet_handler(HCI_EVENT_PACKET, &event[0], sizeof(event));
}
}
static void usb_process_sco_in(btstack_data_source_t *ds, btstack_data_source_callback_type_t callback_type){
btstack_run_loop_disable_data_source_callbacks(ds, DATA_SOURCE_CALLBACK_READ);
if (sco_shutdown){
log_info("USB SCO Shutdown: usb_process_sco_out called");
return;
}
// find index
int i;
for (i=0;i<ISOC_BUFFERS;i++){
if (ds == &usb_data_source_sco_in[i]) break;
}
int transfer_index = i;
// ULONG current_frame_number;
// LARGE_INTEGER timestamp;
// WinUsb_GetCurrentFrameNumber(usb_interface_0_handle, &current_frame_number, &timestamp);
// log_info("usb_process_sco_in[%02u] -- current frame %lu", transfer_index, current_frame_number);
DWORD bytes_transferred;
BOOL ok = WinUsb_GetOverlappedResult(usb_interface_0_handle, &usb_overlapped_sco_in[transfer_index], &bytes_transferred, FALSE);
if(!ok) {
DWORD err = GetLastError();
if (err == ERROR_IO_INCOMPLETE) {
// IO_INCOMPLETE -> wait for completed
btstack_run_loop_enable_data_source_callbacks(ds, DATA_SOURCE_CALLBACK_READ);
return;
}
log_error("usb_process_sco_in[%02u]: error reading %u, Internal %x", transfer_index, (int) err, (int) usb_overlapped_sco_out[i].Internal);
}
if (ok){
for (i=0;i<NUM_ISO_PACKETS;i++){
USBD_ISO_PACKET_DESCRIPTOR * packet_descriptor = &hci_sco_packet_descriptors[transfer_index * NUM_ISO_PACKETS + i];
if (packet_descriptor->Length){
uint8_t * iso_data = &hci_sco_in_buffer[transfer_index * SCO_PACKET_SIZE + packet_descriptor->Offset];
uint16_t iso_len = packet_descriptor->Length;
sco_handle_data(iso_data, iso_len);
}
}
}
#ifdef SCHEDULE_SCO_IN_TRANSFERS_MANUALLY
usb_submit_sco_in_transfer_at_frame(i, &sco_next_transfer_at_frame);
#else
usb_submit_sco_in_transfer_asap(transfer_index, 1);
#endif
// update expected and wait for completion
usb_sco_in_expected_transfer = (transfer_index+ 1) % ISOC_BUFFERS;
// log_info("usb_process_sco_in[%02u]: enable data source %02u", transfer_index, usb_sco_in_expected_transfer);
btstack_run_loop_enable_data_source_callbacks(&usb_data_source_sco_in[usb_sco_in_expected_transfer], DATA_SOURCE_CALLBACK_READ);
}
#endif
static void usb_process_command_out(btstack_data_source_t *ds, btstack_data_source_callback_type_t callback_type){
btstack_run_loop_disable_data_source_callbacks(ds, DATA_SOURCE_CALLBACK_WRITE);
// update stata before submitting transfer
usb_command_out_active = 0;
// notify upper stack that provided buffer can be used again
uint8_t event[] = { HCI_EVENT_TRANSPORT_PACKET_SENT, 0};
packet_handler(HCI_EVENT_PACKET, &event[0], sizeof(event));
}
static void usb_process_acl_out(btstack_data_source_t *ds, btstack_data_source_callback_type_t callback_type){
btstack_run_loop_disable_data_source_callbacks(ds, DATA_SOURCE_CALLBACK_WRITE);
// update stata before submitting transfer
usb_acl_out_active = 0;
// notify upper stack that provided buffer can be used again
uint8_t event[] = { HCI_EVENT_TRANSPORT_PACKET_SENT, 0};
packet_handler(HCI_EVENT_PACKET, &event[0], sizeof(event));
}
static BOOL usb_scan_for_bluetooth_endpoints(void) {
int i;
USB_INTERFACE_DESCRIPTOR usb_interface_descriptor;
// reset
event_in_addr = 0;
acl_in_addr = 0;
acl_out_addr = 0;
log_info("Scanning USB Entpoints:");
// look for Event and ACL pipes on Interface #0
BOOL result = WinUsb_QueryInterfaceSettings(usb_interface_0_handle, 0, &usb_interface_descriptor);
if (!result) goto exit_on_error;
for (i=0;i<usb_interface_descriptor.bNumEndpoints;i++){
WINUSB_PIPE_INFORMATION pipe;
result = WinUsb_QueryPipe(
usb_interface_0_handle,
0,
(UCHAR) i,
&pipe);
if (!result) goto exit_on_error;
log_info("Interface #0, Alt #0, Pipe idx #%u: type %u, id 0x%02x, max packet size %u,",
i, pipe.PipeType, pipe.PipeId, pipe.MaximumPacketSize);
switch (pipe.PipeType){
case USB_ENDPOINT_TYPE_INTERRUPT:
if (event_in_addr) continue;
event_in_addr = pipe.PipeId;
log_info("-> using 0x%2.2X for HCI Events", event_in_addr);
break;
case USB_ENDPOINT_TYPE_BULK:
if (pipe.PipeId & 0x80) {
if (acl_in_addr) continue;
acl_in_addr = pipe.PipeId;
log_info("-> using 0x%2.2X for ACL Data In", acl_in_addr);
} else {
if (acl_out_addr) continue;
acl_out_addr = pipe.PipeId;
log_info("-> using 0x%2.2X for ACL Data Out", acl_out_addr);
}
break;
default:
break;
}
}
#ifdef ENABLE_SCO_OVER_HCI
sco_out_addr = 0;
sco_in_addr = 0;
// look for SCO pipes on Interface #1, Alt Setting 1
int alt_setting = 1;
result = WinUsb_QueryInterfaceSettings(usb_interface_1_handle, alt_setting, &usb_interface_descriptor);
if (!result) goto exit_on_error;
for (i=0;i<usb_interface_descriptor.bNumEndpoints;i++){
WINUSB_PIPE_INFORMATION_EX pipe;
result = WinUsb_QueryPipeEx(
usb_interface_1_handle,
alt_setting,
(UCHAR) i,
&pipe);
if (!result) goto exit_on_error;
log_info("Interface #1, Alt #%u, Pipe idx #%u: type %u, id 0x%02x, max packet size %u, interval %u, max bytes per interval %u",
alt_setting, i, pipe.PipeType, pipe.PipeId, pipe.MaximumPacketSize, pipe.Interval, (int) pipe.MaximumBytesPerInterval);
switch (pipe.PipeType){
case USB_ENDPOINT_TYPE_ISOCHRONOUS:
if (pipe.PipeId & 0x80) {
if (sco_in_addr) continue;
sco_in_addr = pipe.PipeId;
log_info("-> using 0x%2.2X for SCO Data In", sco_in_addr);
} else {
if (sco_out_addr) continue;
sco_out_addr = pipe.PipeId;
log_info("-> using 0x%2.2X for SCO Data Out", sco_out_addr);
}
break;
default:
break;
}
}
if (!sco_in_addr){
log_error("Couldn't find pipe for SCO IN!");
return FALSE;
}
if (!sco_out_addr){
log_error("Couldn't find pipe for SCO IN!");
return FALSE;
}
#endif
// check if all found
if (!event_in_addr){
log_error("Couldn't find pipe for Event IN!");
return FALSE;
}
if (!acl_in_addr){
log_error("Couldn't find pipe for ACL IN!");
return FALSE;
}
if (!acl_out_addr){
log_error("Couldn't find pipe for ACL OUT!");
return FALSE;
}
// all clear
return TRUE;
exit_on_error:
log_error("usb_scan_for_bluetooth_endpoints: last error %lu", GetLastError());
return FALSE;
}
#ifdef ENABLE_SCO_OVER_HCI
static int usb_sco_start(void){
printf("usb_sco_start\n");
log_info("usb_sco_start");
sco_shutdown = 0;
sco_state_machine_init();
sco_ring_init();
// calc alt setting
int alt_setting;
if (sco_voice_setting & 0x0020){
// 16-bit PCM
alt_setting = alt_setting_16_bit[sco_num_connections-1];
} else {
// 8-bit PCM or mSBC
alt_setting = alt_setting_8_bit[sco_num_connections-1];
}
log_info("Switching to setting %u on interface 1..", alt_setting);
// WinUsb_SetCurrentAlternateSetting returns TRUE if the operation succeeds.
BOOL result = WinUsb_SetCurrentAlternateSetting(usb_interface_1_handle, alt_setting);
if (!result) goto exit_on_error;
// derive iso packet size from alt setting
iso_packet_size = iso_packet_size_for_alt_setting[alt_setting];
// register isochronous buffer after setting alternate setting
usb_sco_register_buffers();
#ifdef SCHEDULE_SCO_IN_TRANSFERS_MANUALLY
// get current frame number
ULONG current_frame_number;
LARGE_INTEGER timestamp;
WinUsb_GetCurrentFrameNumber(usb_interface_0_handle, &current_frame_number, &timestamp);
// plan for next tranfer
sco_next_transfer_at_frame = current_frame_number + ISOC_BUFFERS * NUM_ISO_PACKETS;
#endif
int i;
for (i=0;i<ISOC_BUFFERS;i++){
#ifdef SCHEDULE_SCO_IN_TRANSFERS_MANUALLY
usb_submit_sco_in_transfer_at_frame(i, &sco_next_transfer_at_frame);
#else
usb_submit_sco_in_transfer_asap(i, 0);
#endif
}
usb_sco_in_expected_transfer = 0;
// only await first transfer to return
btstack_run_loop_enable_data_source_callbacks(&usb_data_source_sco_in[usb_sco_in_expected_transfer], DATA_SOURCE_CALLBACK_READ);
return 1;
exit_on_error:
log_error("usb_sco_start: last error %lu", GetLastError());
usb_free_resources();
return 0;
}
static void usb_sco_stop(void){
printf("usb_sco_stop\n");
log_info("usb_sco_stop");
sco_shutdown = 1;
// abort SCO transfers
WinUsb_AbortPipe(usb_interface_0_handle, sco_in_addr);
WinUsb_AbortPipe(usb_interface_0_handle, sco_out_addr);
// unlock/free SCO buffers
usb_sco_unregister_buffers();
int alt_setting = 0;
log_info("Switching to setting %u on interface 1..", alt_setting);
// WinUsb_SetCurrentAlternateSetting returns TRUE if the operation succeeds.
WinUsb_SetCurrentAlternateSetting(usb_interface_1_handle, alt_setting);
}
#endif
// returns 0 if successful, -1 otherwise
static int usb_try_open_device(const char * device_path){
// open file
usb_device_handle = CreateFile(device_path,
GENERIC_WRITE | GENERIC_READ,
FILE_SHARE_WRITE | FILE_SHARE_READ,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED,
NULL);
log_info("Opening USB device: %p", usb_device_handle);
if (!usb_device_handle) goto exit_on_error;
// WinUsb_Initialize returns TRUE if the operation succeed
BOOL result = WinUsb_Initialize(usb_device_handle, &usb_interface_0_handle);
if (!result) goto exit_on_error;
// Detect USB Dongle based Class, Subclass, and Protocol
// The class code (bDeviceClass) is 0xE0 Wireless Controller.
// The SubClass code (bDeviceSubClass) is 0x01 RF Controller.
// The Protocol code (bDeviceProtocol) is 0x01 Bluetooth programming.
USB_INTERFACE_DESCRIPTOR usb_interface_descriptor;
result = WinUsb_QueryInterfaceSettings(usb_interface_0_handle, 0, &usb_interface_descriptor);
if (!result) goto exit_on_error;
//
if (usb_interface_descriptor.bInterfaceClass != 0xe0 ||
usb_interface_descriptor.bInterfaceSubClass != 0x01 ||
usb_interface_descriptor.bInterfaceProtocol != 0x01){
// TODO: fallback to whitelist
log_info("Class, Subclass, Protocol does not match Bluetooth device");
usb_free_resources();
return 0;
}
#ifdef ENABLE_SCO_OVER_HCI
log_info("Claiming interface 1...");
// WinUsb_GetAssociatedInterface returns TRUE if the operation succeeds.
// We use index 1 - assuming it refers to interface #1 with libusb
// A value of 0 indicates the first associated interface, a value of 1 indicates the second associated interface, and so on.
result = WinUsb_GetAssociatedInterface(usb_interface_0_handle, 0, &usb_interface_1_handle);
if (!result) goto exit_on_error;
log_info("Claiming interface 1: success");
#endif
result = usb_scan_for_bluetooth_endpoints();
if (!result) {
log_error("Could not find all Bluetooth Endpoints!");
usb_free_resources();
return 0;
}
#ifdef ENABLE_SCO_OVER_HCI
int i;
memset(hci_sco_packet_descriptors, 0, sizeof(hci_sco_packet_descriptors));
log_info("Size of packet descriptors for SCO IN%u", (int) sizeof(hci_sco_packet_descriptors));
// setup async io && btstack handler
memset(&usb_overlapped_sco_in, 0, sizeof(usb_overlapped_sco_in));
for (i=0;i<ISOC_BUFFERS;i++){
usb_overlapped_sco_in[i].hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
// log_info_hexdump(&usb_overlapped_sco_in[i], sizeof(OVERLAPPED));
// log_info("data source SCO in %u, handle %p", i, usb_overlapped_sco_in[i].hEvent);
usb_data_source_sco_in[i].handle = usb_overlapped_sco_in[i].hEvent;
btstack_run_loop_set_data_source_handler(&usb_data_source_sco_in[i], &usb_process_sco_in);
btstack_run_loop_add_data_source(&usb_data_source_sco_in[i]);
}
memset(&usb_overlapped_sco_out, 0, sizeof(usb_overlapped_sco_out));
for (i=0;i<SCO_RING_BUFFER_COUNT;i++){
usb_overlapped_sco_out[i].hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
// log_info("data source SCO out %u, handle %p", i, usb_overlapped_sco_out[i].hEvent);
usb_data_source_sco_out[i].handle = usb_overlapped_sco_out[i].hEvent;
btstack_run_loop_set_data_source_handler(&usb_data_source_sco_out[i], &usb_process_sco_out);
btstack_run_loop_add_data_source(&usb_data_source_sco_out[i]);
}
#endif
// setup async io
memset(&usb_overlapped_event_in, 0, sizeof(usb_overlapped_event_in));
memset(&usb_overlapped_command_out, 0, sizeof(usb_overlapped_command_out));
memset(&usb_overlapped_acl_out, 0, sizeof(usb_overlapped_acl_out));
memset(&usb_overlapped_acl_in, 0, sizeof(usb_overlapped_acl_in));
usb_overlapped_event_in.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
usb_overlapped_command_out.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
usb_overlapped_acl_in.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
usb_overlapped_acl_out.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
// setup btstack data soures
usb_data_source_event_in.handle = usb_overlapped_event_in.hEvent;
btstack_run_loop_set_data_source_handler(&usb_data_source_event_in, &usb_process_event_in);
btstack_run_loop_add_data_source(&usb_data_source_event_in);
usb_data_source_command_out.handle = usb_overlapped_command_out.hEvent;
btstack_run_loop_set_data_source_handler(&usb_data_source_command_out, &usb_process_command_out);
btstack_run_loop_add_data_source(&usb_data_source_command_out);
usb_data_source_acl_in.handle = usb_overlapped_acl_in.hEvent;
btstack_run_loop_set_data_source_handler(&usb_data_source_acl_in, &usb_process_acl_in);
btstack_run_loop_add_data_source(&usb_data_source_acl_in);
usb_data_source_acl_out.handle = usb_overlapped_acl_out.hEvent;
btstack_run_loop_set_data_source_handler(&usb_data_source_acl_out, &usb_process_acl_out);
btstack_run_loop_add_data_source(&usb_data_source_acl_out);
// submit all incoming transfers
usb_submit_event_in_transfer();
usb_submit_acl_in_transfer();
return 1;
exit_on_error:
log_error("usb_try_open_device: last error %lu", GetLastError());
usb_free_resources();
return 0;
}
#ifdef ENABLE_SCO_OVER_HCI
#define WinUSB_Lookup(fn) do { fn = (fn##_t) GetProcAddress(h, #fn); log_info("%-30s %p", #fn, fn); if (!fn) return FALSE; } while(0)
static BOOL usb_lookup_symbols(void){
// lookup runtime symbols missing in current mingw64 distribution
HMODULE h = GetModuleHandleA("WinUSB");
log_info("%-30s %p", "WinUSB", h);
WinUSB_Lookup(WinUsb_QueryPipeEx);
WinUSB_Lookup(WinUsb_RegisterIsochBuffer);
WinUSB_Lookup(WinUsb_ReadIsochPipe);
WinUSB_Lookup(WinUsb_ReadIsochPipeAsap);
WinUSB_Lookup(WinUsb_WriteIsochPipe);
WinUSB_Lookup(WinUsb_WriteIsochPipeAsap);
WinUSB_Lookup(WinUsb_UnregisterIsochBuffer);
WinUSB_Lookup(WinUsb_GetCurrentFrameNumber);
return TRUE;
}
#endif
// returns 0 on success, -1 otherwise
static int usb_open(void){
int r = -1;
#ifdef ENABLE_SCO_OVER_HCI
BOOL ok = usb_lookup_symbols();
if (!ok){
log_error("usb_open: Failed to lookup WinSUB ISOCHRONOUS functions. Please disable ENABLE_SCO_OVER_HCI or use Windows 8.1 or higher");
return r;
}
sco_state_machine_init();
sco_ring_init();
#endif
HDEVINFO hDevInfo;
SP_DEVICE_INTERFACE_DATA DevIntfData;
PSP_DEVICE_INTERFACE_DETAIL_DATA DevIntfDetailData;
SP_DEVINFO_DATA DevData;
DWORD dwSize;
DWORD dwMemberIdx;
// default endpoint addresses
event_in_addr = 0x81; // EP1, IN interrupt
acl_in_addr = 0x82; // EP2, IN bulk
acl_out_addr = 0x02; // EP2, OUT bulk
sco_in_addr = 0x83; // EP3, IN isochronous
sco_out_addr = 0x03; // EP3, OUT isochronous
// We will try to get device information set for all USB devices that have a
// device interface and are currently present on the system (plugged in).
hDevInfo = SetupDiGetClassDevs(&GUID_DEVINTERFACE_USB_DEVICE, NULL, 0, DIGCF_DEVICEINTERFACE | DIGCF_PRESENT);
log_info("usb_open: SetupDiGetClassDevs -> %p", hDevInfo);
if (hDevInfo == INVALID_HANDLE_VALUE) return -1;
// Prepare to enumerate all device interfaces for the device information
// set that we retrieved with SetupDiGetClassDevs(..)
DevIntfData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA);
dwMemberIdx = 0;
// Next, we will keep calling this SetupDiEnumDeviceInterfaces(..) until this
// function causes GetLastError() to return ERROR_NO_MORE_ITEMS. With each
// call the dwMemberIdx value needs to be incremented to retrieve the next
// device interface information.
SetupDiEnumDeviceInterfaces(hDevInfo, NULL, (LPGUID) &GUID_DEVINTERFACE_USB_DEVICE,
dwMemberIdx, &DevIntfData);
while(GetLastError() != ERROR_NO_MORE_ITEMS){
// As a last step we will need to get some more details for each
// of device interface information we are able to retrieve. This
// device interface detail gives us the information we need to identify
// the device (VID/PID), and decide if it's useful to us. It will also
// provide a DEVINFO_DATA structure which we can use to know the serial
// port name for a virtual com port.
DevData.cbSize = sizeof(DevData);
// Get the required buffer size. Call SetupDiGetDeviceInterfaceDetail with
// a NULL DevIntfDetailData pointer, a DevIntfDetailDataSize
// of zero, and a valid RequiredSize variable. In response to such a call,
// this function returns the required buffer size at dwSize.
SetupDiGetDeviceInterfaceDetail(
hDevInfo, &DevIntfData, NULL, 0, &dwSize, NULL);
// Allocate memory for the DeviceInterfaceDetail struct. Don't forget to
// deallocate it later!
DevIntfDetailData = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, dwSize);
DevIntfDetailData->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA);
if (SetupDiGetDeviceInterfaceDetail(hDevInfo, &DevIntfData,
DevIntfDetailData, dwSize, &dwSize, &DevData))
{
// Finally we can start checking if we've found a useable device,
// by inspecting the DevIntfDetailData->DevicePath variable.
// The DevicePath looks something like this:
//
// \\?\usb#vid_04d8&pid_0033#5&19f2438f&0&2#{a5dcbf10-6530-11d2-901f-00c04fb951ed}
//
log_info("usb_open: Device Path: %s", DevIntfDetailData->DevicePath);
#if 0
// check for hard-coded vendor/product ids
char vid_pid_match[30];
uint16_t vid = 0x0a12;
uint16_t pid = 0x0001;
sprintf(vid_pid_match, "\\\\?\\usb#vid_%04x&pid_%04x", vid, pid);
if (strncmp(DevIntfDetailData->DevicePath, &vid_pid_match[0], strlen(vid_pid_match)) == 0 ){
log_info("Matched search string %s", vid_pid_match);
BOOL result = usb_try_open_device(DevIntfDetailData->DevicePath);
if (result){
log_info("usb_open: Device opened, stop scanning");
r = 0;
} else {
log_error("usb_open: Device open failed");
}
}
#endif
// try all devices
BOOL result = usb_try_open_device(DevIntfDetailData->DevicePath);
if (result){
log_info("usb_open: Device opened, stop scanning");
r = 0;
} else {
log_error("usb_open: Device open failed");
}
}
HeapFree(GetProcessHeap(), 0, DevIntfDetailData);
if (r == 0) break;
// Continue looping
SetupDiEnumDeviceInterfaces(
hDevInfo, NULL, &GUID_DEVINTERFACE_USB_DEVICE, ++dwMemberIdx, &DevIntfData);
}
SetupDiDestroyDeviceInfoList(hDevInfo);
log_info("usb_open: done");
return r;
}
static int usb_close(void){
// remove data sources
btstack_run_loop_remove_data_source(&usb_data_source_command_out);
btstack_run_loop_remove_data_source(&usb_data_source_event_in);
btstack_run_loop_remove_data_source(&usb_data_source_acl_in);
btstack_run_loop_remove_data_source(&usb_data_source_acl_out);
#ifdef ENABLE_SCO_OVER_HCI
int i;
for (i=0;i<ISOC_BUFFERS;i++){
btstack_run_loop_remove_data_source(&usb_data_source_sco_in[i]);
}
for (i=0;i<SCO_RING_BUFFER_COUNT;i++){
btstack_run_loop_remove_data_source(&usb_data_source_sco_out[i]);
}
#endif
// stop transfers
WinUsb_AbortPipe(usb_interface_0_handle, event_in_addr);
WinUsb_AbortPipe(usb_interface_0_handle, acl_in_addr);
WinUsb_AbortPipe(usb_interface_0_handle, acl_out_addr);
#ifdef ENABLE_SCO_OVER_HCI
usb_sco_stop();
#endif
usb_acl_out_active = 0;
// control transfer cannot be stopped, just wait for completion
if (usb_command_out_active){
DWORD bytes_transferred;
WinUsb_GetOverlappedResult(usb_interface_0_handle, &usb_overlapped_command_out, &bytes_transferred, TRUE);
usb_command_out_active = 0;
}
// free everything
usb_free_resources();
return 0;
}
static int usb_can_send_packet_now(uint8_t packet_type){
// return 0;
switch (packet_type){
case HCI_COMMAND_DATA_PACKET:
return !usb_command_out_active;
case HCI_ACL_DATA_PACKET:
return !usb_acl_out_active;
#ifdef ENABLE_SCO_OVER_HCI
case HCI_SCO_DATA_PACKET:
// return 0;
return sco_ring_have_space();
#endif
default:
return 0;
}
}
static int usb_send_cmd_packet(uint8_t *packet, int size){
// update stata before submitting transfer
usb_command_out_active = 1;
// Start trasnsfer
WINUSB_SETUP_PACKET setup_packet;
memset(&setup_packet, 0, sizeof(setup_packet));
setup_packet.RequestType = USB_REQUEST_TYPE_CLASS | USB_RECIPIENT_INTERFACE;
setup_packet.Length = sizeof(size);
BOOL result = WinUsb_ControlTransfer(usb_interface_0_handle, setup_packet, packet, size, NULL, &usb_overlapped_command_out);
if (!result) {
if (GetLastError() != ERROR_IO_PENDING) goto exit_on_error;
}
// IO_PENDING -> wait for completed
btstack_run_loop_enable_data_source_callbacks(&usb_data_source_command_out, DATA_SOURCE_CALLBACK_WRITE);
return 0;
exit_on_error:
log_error("winusb: last error %lu", GetLastError());
return -1;
}
static int usb_send_acl_packet(uint8_t *packet, int size){
// update stata before submitting transfer
usb_acl_out_active = 1;
// Start trasnsfer
BOOL ok = WinUsb_WritePipe(usb_interface_0_handle, acl_out_addr, packet, size, NULL, &usb_overlapped_acl_out);
if (!ok) {
if (GetLastError() != ERROR_IO_PENDING) goto exit_on_error;
}
// IO_PENDING -> wait for completed
btstack_run_loop_enable_data_source_callbacks(&usb_data_source_acl_out, DATA_SOURCE_CALLBACK_WRITE);
return 0;
exit_on_error:
log_error("winusb: last error %lu", GetLastError());
return -1;
}
#ifdef ENABLE_SCO_OVER_HCI
static int usb_send_sco_packet(uint8_t *packet, int size){
if (size > SCO_PACKET_SIZE){
log_error("usb_send_sco_packet: size %u > SCO_PACKET_SIZE %u", size, SCO_PACKET_SIZE);
return -1;
}
// get current frame number
ULONG current_frame_number;
LARGE_INTEGER timestamp;
WinUsb_GetCurrentFrameNumber(usb_interface_0_handle, &current_frame_number, &timestamp);
// store packet in free slot
int transfer_index = sco_ring_write;
uint8_t * data = &sco_ring_buffer[transfer_index * SCO_PACKET_SIZE];
memcpy(data, packet, size);
// setup transfer
int continue_stream = sco_ring_transfers_active > 0;
BOOL ok = WinUsb_WriteIsochPipeAsap(hci_sco_out_buffer_handle, transfer_index * SCO_PACKET_SIZE, size, continue_stream, &usb_overlapped_sco_out[transfer_index]);
// log_info("usb_send_sco_packet: using slot #%02u, current frame %lu, continue stream %u, ok %u", transfer_index, current_frame_number, continue_stream, ok);
if (!ok) {
if (GetLastError() != ERROR_IO_PENDING) goto exit_on_error;
}
// successful started transfer, enable data source callback if first active transfer
if (sco_ring_transfers_active == 0){
usb_sco_out_expected_transfer = transfer_index;
btstack_run_loop_enable_data_source_callbacks(&usb_data_source_sco_out[transfer_index], DATA_SOURCE_CALLBACK_WRITE);
}
// mark slot as full
sco_ring_write = (sco_ring_write + 1) % SCO_RING_BUFFER_COUNT;
sco_ring_transfers_active++;
// notify upper stack that provided buffer can be used again
uint8_t event[] = { HCI_EVENT_TRANSPORT_PACKET_SENT, 0};
packet_handler(HCI_EVENT_PACKET, &event[0], sizeof(event));
// log_info("usb_send_sco_packet: transfers active %u", sco_ring_transfers_active);
// and if we have more space for SCO packets
if (sco_ring_have_space()) {
uint8_t event_sco[] = { HCI_EVENT_SCO_CAN_SEND_NOW, 0};
packet_handler(HCI_EVENT_PACKET, &event_sco[0], sizeof(event_sco));
}
return 0;
exit_on_error:
log_error("usb_send_sco_packet: last error %lu", GetLastError());
return -1;
}
#endif
static int usb_send_packet(uint8_t packet_type, uint8_t * packet, int size){
switch (packet_type){
case HCI_COMMAND_DATA_PACKET:
return usb_send_cmd_packet(packet, size);
case HCI_ACL_DATA_PACKET:
return usb_send_acl_packet(packet, size);
#ifdef ENABLE_SCO_OVER_HCI
case HCI_SCO_DATA_PACKET:
return usb_send_sco_packet(packet, size);
#endif
default:
return -1;
}
}
#ifdef ENABLE_SCO_OVER_HCI
static void usb_set_sco_config(uint16_t voice_setting, int num_connections){
log_info("usb_set_sco_config: voice settings 0x%04x, num connections %u", voice_setting, num_connections);
if (num_connections != sco_num_connections){
sco_voice_setting = voice_setting;
if (sco_num_connections){
usb_sco_stop();
}
sco_num_connections = num_connections;
if (num_connections){
usb_sco_start();
}
}
}
#endif
// get usb singleton
static const hci_transport_t hci_transport_usb = {
/* const char * name; */ "H2_WINUSB",
/* void (*init) (const void *transport_config); */ &usb_init,
/* int (*open)(void); */ &usb_open,
/* int (*close)(void); */ &usb_close,
/* void (*register_packet_handler)(void (*handler)(...); */ &usb_register_packet_handler,
/* int (*can_send_packet_now)(uint8_t packet_type); */ &usb_can_send_packet_now,
/* int (*send_packet)(...); */ &usb_send_packet,
/* int (*set_baudrate)(uint32_t baudrate); */ NULL,
/* void (*reset_link)(void); */ NULL,
/* void (*set_sco_config)(uint16_t voice_setting, int num_connections); */ usb_set_sco_config,
};
const hci_transport_t * hci_transport_usb_instance(void) {
return &hci_transport_usb;
}