Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitmodules
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
[submodule "external/btstack"]
path = external/btstack
url = https://github.com/bluekitchen/btstack.git
url = https://github.com/guimaraf/btstack.git
branch = fix-switch-pro
2 changes: 1 addition & 1 deletion external/btstack
101 changes: 100 additions & 1 deletion src/components/bluepad32/bt/uni_bt_bredr.c
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
#include "bt/uni_bt_allowlist.h"
#include "bt/uni_bt_defines.h"
#include "bt/uni_bt_sdp.h"
#include "parser/uni_hid_parser_switch.h"
#include "platform/uni_platform.h"
#include "uni_common.h"
#include "uni_config.h"
Expand All @@ -30,6 +31,97 @@ _Static_assert(INQUIRY_REMOTE_NAME_TIMEOUT_MS < HID_DEVICE_CONNECTION_TIMEOUT_MS

static bool bt_bredr_enabled = true;

typedef struct {
bool in_use;
bd_addr_t addr;
} switch_candidate_entry_t;

static switch_candidate_entry_t switch_candidates[CONFIG_BLUEPAD32_MAX_DEVICES];

static bool is_switch_candidate_device(uni_hid_device_t* d) {
if (d == NULL) {
return false;
}

switch (d->controller_type) {
case CONTROLLER_TYPE_SwitchProController:
case CONTROLLER_TYPE_SwitchJoyConLeft:
case CONTROLLER_TYPE_SwitchJoyConRight:
case CONTROLLER_TYPE_SwitchJoyConPair:
case CONTROLLER_TYPE_SwitchInputOnlyController:
case CONTROLLER_TYPE_XInputSwitchController:
return true;
default:
break;
}

if (uni_hid_device_has_name(d) && uni_hid_parser_switch_does_name_match(d, d->name)) {
if (!uni_hid_device_has_controller_type(d)) {
uni_hid_device_guess_controller_type_from_pid_vid(d);
}
return true;
}

return false;
}

static void remember_switch_candidate_addr(const bd_addr_t addr) {
int free_slot = -1;

for (int i = 0; i < ARRAY_SIZE(switch_candidates); i++) {
if (switch_candidates[i].in_use && bd_addr_cmp(switch_candidates[i].addr, addr) == 0) {
return;
}
if (!switch_candidates[i].in_use && free_slot < 0) {
free_slot = i;
}
}

if (free_slot < 0) {
free_slot = 0;
}

switch_candidates[free_slot].in_use = true;
bd_addr_copy(switch_candidates[free_slot].addr, addr);
}

static bool is_switch_candidate_addr(const bd_addr_t addr) {
for (int i = 0; i < ARRAY_SIZE(switch_candidates); i++) {
if (switch_candidates[i].in_use && bd_addr_cmp(switch_candidates[i].addr, addr) == 0) {
return true;
}
}
return false;
}

static void remember_switch_candidate_device(uni_hid_device_t* d) {
if (is_switch_candidate_device(d)) {
remember_switch_candidate_addr(d->conn.btaddr);
}
}

bool l2cap_classic_incoming_can_use_relaxed_security(const bd_addr_t address,
uint16_t psm,
gap_security_level_t required_level) {
ARG_UNUSED(required_level);

if (psm != PSM_HID_CONTROL && psm != PSM_HID_INTERRUPT) {
return false;
}

if (is_switch_candidate_addr(address)) {
return true;
}

uni_hid_device_t* d = uni_hid_device_get_instance_for_address(address);
if (!is_switch_candidate_device(d)) {
return false;
}

remember_switch_candidate_addr(address);
return true;
}

static void l2cap_create_control_connection(uni_hid_device_t* d) {
uint8_t status;
status = l2cap_create_channel(uni_bt_packet_handler, d->conn.btaddr, BLUETOOTH_PSM_HID_CONTROL,
Expand Down Expand Up @@ -426,7 +518,7 @@ void uni_bt_bredr_on_l2cap_channel_opened(uint16_t channel, const uint8_t* packe
// Practice showed that if the connection fails, just disconnect/remove
// so that the connection can start again.
if (status == L2CAP_CONNECTION_RESPONSE_RESULT_REFUSED_SECURITY) {
logi("Probably GAP-security-related issues. Set GAP security to 2\n");
logi("Probably incoming security mismatch. Dropping key and retrying cleanly\n");
}
logi("Removing key for device: %s.\n", bd_addr_to_str(address));
gap_drop_link_key_for_bd_addr(device->conn.btaddr);
Expand Down Expand Up @@ -573,6 +665,11 @@ void uni_bt_bredr_on_gap_inquiry_result(uint16_t channel, const uint8_t* packet,
return;
}
logi("Device already added, waiting (current state=0x%02x)...\n", d->conn.state);
if (name_len > 0 && !uni_hid_device_has_name(d)) {
uni_hid_device_set_name(d, name_buffer);
uni_bt_conn_set_state(&d->conn, UNI_BT_CONN_STATE_REMOTE_NAME_FETCHED);
remember_switch_candidate_device(d);
}
} else {
// Device not found, create one.
d = uni_hid_device_create(addr);
Expand All @@ -589,6 +686,7 @@ void uni_bt_bredr_on_gap_inquiry_result(uint16_t channel, const uint8_t* packet,
if (name_len > 0 && !uni_hid_device_has_name(d)) {
uni_hid_device_set_name(d, name_buffer);
uni_bt_conn_set_state(&d->conn, UNI_BT_CONN_STATE_REMOTE_NAME_FETCHED);
remember_switch_candidate_device(d);
}
}
uni_bt_bredr_process_fsm(d);
Expand Down Expand Up @@ -740,6 +838,7 @@ void uni_bt_bredr_on_hci_remote_name_request_complete(uint16_t channel, const ui
}
logi("Name: '%s'\n", name);
uni_hid_device_set_name(d, name);
remember_switch_candidate_device(d);
// It could happen that the device is already connected, but the NAME_REQUEST
// has just finished. So, do not update the state:
// See: https://gitlab.com/ricardoquesada/bluepad32/-/issues/21
Expand Down
7 changes: 7 additions & 0 deletions src/components/bluepad32/include/uni.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,13 @@ extern "C" {
#include "parser/uni_hid_parser_keyboard.h"
#include "parser/uni_hid_parser_mouse.h"
#include "parser/uni_hid_parser_xboxone.h"
#include "parser/uni_hid_parser_ds4.h"
#include "parser/uni_hid_parser_ds3.h"
#include "parser/uni_hid_parser_switch.h"
#include "parser/uni_hid_parser_stadia.h"
#include "parser/uni_hid_parser_psmove.h"
#include "parser/uni_hid_parser_xboxone.h"
#include "parser/uni_hid_parser_wii.h"
#include "platform/uni_platform.h"
#include "uni_circular_buffer.h"
#include "uni_console.h"
Expand Down
14 changes: 13 additions & 1 deletion src/components/bluepad32/parser/uni_hid_parser_switch.c
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,8 @@ typedef struct switch_instance_s {
// Debug only
int debug_fd; // File descriptor where dump is saved
uint32_t debug_addr; // Current dump address

uint8_t timeout_count; // Failsafe for clones
} switch_instance_t;
_Static_assert(sizeof(switch_instance_t) < HID_DEVICE_MAX_PARSER_DATA, "Switch instance too big");

Expand Down Expand Up @@ -1423,7 +1425,17 @@ static void on_switch_set_rumble_off(btstack_timer_source_t* ts) {
void switch_setup_timeout_callback(btstack_timer_source_t* ts) {
uni_hid_device_t* d = btstack_run_loop_get_timer_context(ts);
switch_instance_t* ins = get_switch_instance(d);
logi("Switch: setup timer timeout, failed state: 0x%02x\n", ins->state);

ins->timeout_count++;
if (ins->timeout_count >= 3) {
logi("Switch: setup timer timeout limit reached (clone?), forcing READY state\n");
ins->state = STATE_READY;
btstack_run_loop_remove_timer(&ins->setup_timer);
uni_hid_device_set_ready_complete(d);
return;
}

logi("Switch: setup timer timeout, failed state: 0x%02x (count: %d)\n", ins->state, ins->timeout_count);
process_fsm(d);
}

Expand Down
2 changes: 1 addition & 1 deletion src/components/bluepad32/uni_property.c
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ static const uni_property_t properties[] = {
},
{UNI_PROPERTY_IDX_GAP_INQ_LEN, UNI_PROPERTY_NAME_GAP_INQ_LEN, UNI_PROPERTY_TYPE_U8,
.default_value.u8 = UNI_BT_INQUIRY_LENGTH},
// It seems that with gap_security_level(0) all controllers work except Nintendo Switch Pro controller.
// Keep Level 2 as the default baseline. Switch-specific incoming exceptions are handled elsewhere.
{UNI_PROPERTY_IDX_GAP_LEVEL, UNI_PROPERTY_NAME_GAP_LEVEL, UNI_PROPERTY_TYPE_U8,
#ifdef CONFIG_BLUEPAD32_GAP_SECURITY
.default_value.u8 = 2
Expand Down