Skip to content
Merged
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
15 changes: 15 additions & 0 deletions boards/ugl/uhk-80/shared.dtsi
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,13 @@
gpio-as-nreset;
};

/* Use the internal DC/DC converter for the main (REG1) regulator instead of the LDO. It
* roughly halves the SoC supply current, and requires the DC/DC inductor to be populated
* on the DCC pins, which it is on the UHK80 halves. */
&reg1 {
regulator-initial-mode = <NRF5X_REG_MODE_DCDC>;
};

&gpiote {
status = "okay";
};
Expand All @@ -11,6 +18,14 @@
sense-edge-mask = <0xffffffff>;
};

/* Same SENSE/DETECT-based edge detection as gpio0. Without it, edge interrupts on port 1
* each consume one of the 8 GPIOTE channels, which the bridge RX wake (RXD is P1.8 on the
* left half) exhausted after a few thousand re-arms and then silently stopped waking. */
&gpio1 {
status = "okay";
sense-edge-mask = <0xffffffff>;
};

&uart0 {
compatible = "nordic,nrf-uarte";
status = "okay";
Expand Down
4 changes: 4 additions & 0 deletions boards/ugl/uhk-80/uhk-80-left.dts
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,10 @@

zephyr,user {
io-channels = <&adc 1>;

/* Bridge UART RXD as a plain GPIO, used as a wake-on-edge source while the
* bridge is RX-disabled for low power (matches uart1_default RX = P1.8). */
bridge-rx-gpios = <&gpio1 8 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)>;
};

aliases {
Expand Down
4 changes: 4 additions & 0 deletions boards/ugl/uhk-80/uhk-80-right.dts
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,10 @@
uart-modules-sleep = <&uart2_sleep>;
i2c-modules-default = <&i2c2_default>;
i2c-modules-sleep = <&i2c2_sleep>;

/* Bridge UART RXD as a plain GPIO, used as a wake-on-edge source while the
* bridge is RX-disabled for low power (matches uart1_default RX = P0.11). */
bridge-rx-gpios = <&gpio0 11 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)>;
};

aliases {
Expand Down
3 changes: 3 additions & 0 deletions device/prj.conf.overlays/nrf_shared.conf
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ CONFIG_BT=y
# CONFIG_BT_DEBUG_LOG=y
CONFIG_BT_SMP=y

# Force L4-secure connections
CONFIG_BT_SMP_SC_ONLY=y

CONFIG_BT_FILTER_ACCEPT_LIST=y

# Shrink the SoftDevice controller's per-event reservation and
Expand Down
160 changes: 141 additions & 19 deletions device/src/bt_conn.c
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
#include "keyboard/oled/widgets/widget.h"
#include "event_scheduler.h"
#include "host_connection.h"
#include "power_mode.h"
#include "nus_client.h"
#include "nus_server.h"
#include "device.h"
Expand Down Expand Up @@ -90,6 +91,8 @@ uint32_t Bt_LastConnectedTime = 0;

static void disconnectAllHids();
static void auth_cancel(struct bt_conn *conn);
static void watchSecurity(struct bt_conn* conn);
static void securityEstablished(struct bt_conn* conn);

// BLE APIs that allocate HCI/ATT buffers (data length update, MTU exchange,
// GATT discovery, security elevation) must not run directly in connection
Expand Down Expand Up @@ -364,6 +367,51 @@ static void configureLatency(struct bt_conn *conn, latency_mode_t latencyMode) {
setLatency(conn, &conn_params);
}
break;
case LatencyMode_Idle: {
// i.e., at most 240ms windows
const struct bt_le_conn_param conn_params = BT_LE_CONN_PARAM_INIT(
6, 12,
16,
100
);
setLatency(conn, &conn_params);
}
break;
}
}

static latency_mode_t desiredLatencyMode(connection_id_t connectionId) {
connection_type_t connectionType = Connections_Type(connectionId);
if (connectionType != ConnectionType_BtHid && connectionType != ConnectionType_NusDongle) {
// don't sleep right-left link
return LatencyMode_NUS;
}
bool awake = CurrentPowerMode <= PowerMode_LastAwake;
if (awake && connectionId == CurrentHostConnectionId) {
return LatencyMode_BleHid;
}
return LatencyMode_Idle;
}

// Last mode actually applied per peer, so that the recompute is idempotent - every
// configureLatency triggers an on-air renegotiation. -1 = none yet.
static int8_t appliedLatencyMode[PeerCount];

void BtConn_UpdateConnectionLatencies(void) {
// Host switchover and power modes are a right-half concept.
if (!DEVICE_IS_UHK80_RIGHT) {
return;
}
for (uint8_t i = PeerIdFirst; i < PeerCount; i++) {
if (Peers[i].conn == NULL) {
appliedLatencyMode[i] = -1;
continue;
}
latency_mode_t mode = desiredLatencyMode(Peers[i].connectionId);
if ((int8_t)mode != appliedLatencyMode[i]) {
appliedLatencyMode[i] = (int8_t)mode;
configureLatency(Peers[i].conn, mode);
}
}
}

Expand Down Expand Up @@ -532,7 +580,7 @@ static void connectNus(struct bt_conn *conn, connection_id_t connectionId, conne
static void connectHid(struct bt_conn *conn, connection_id_t connectionId, connection_type_t connectionType) {
assignPeer(conn, connectionId, connectionType);

configureLatency(conn, LatencyMode_NUS);
configureLatency(conn, LatencyMode_BleHid);

// Assume that HOGP is ready
LOG_INF("Established HID connection with %s", GetPeerStringByConn(conn));
Expand Down Expand Up @@ -630,6 +678,8 @@ static void disconnected(struct bt_conn *conn, uint8_t reason) {

LOG_INF("Disconnected from %s, reason %u", GetPeerStringByConn(conn), reason);

securityEstablished(conn);

if (DEVICE_IS_UHK80_LEFT && peerId == PeerIdRight) {
NusServer_Disconnected();
}
Expand Down Expand Up @@ -732,6 +782,7 @@ static void connectedFlow(struct bt_conn *conn) {
} else {
if (isWanted(conn, connectionId, connectionType)) {
bt_conn_set_security(conn, BT_SECURITY_L4);
watchSecurity(conn);
// advertising/scanning needs to be started only after peers are assigned :-/
} else {
youAreNotWanted(conn);
Expand Down Expand Up @@ -786,24 +837,83 @@ static void scheduleBtFlow(struct bt_conn *conn, bt_flow_t action, connection_id
k_work_submit(&btDeferredWork);
}

static void securityChanged(struct bt_conn *conn, bt_security_t level, enum bt_security_err err) {
BT_TRACE_AND_ASSERT("bc3");
// In case of failure, disconnect
if (err || level < BT_SECURITY_L4) {
LOG_WRN("Bt security failed: %s, level %u, err %d, disconnecting", GetPeerStringByConn(conn), level, err);
// A peer can connect, ignore our security request and simply sit there. It can read
// nothing (every characteristic requires authentication) but it holds a peripheral slot
// and keeps the radio busy, and no callback ever tells us: no pairing starts, so
// pairing_failed does not fire, and no security change happens, so security_changed does
// not either. The stack will not time it out for us - bt_conn_set_security only emits an
// SMP Security Request, and smp_send_security_req deliberately does not arm the SMP timer
// ("SMP timer is not restarted for SecRequest"). So we have to watch it ourselves.
#define SECURITY_ESTABLISHMENT_TIMEOUT 30000

static struct bt_conn* pendingSecurityConns[PeerCount];
static uint32_t pendingSecurityDeadlines[PeerCount];

static void watchSecurity(struct bt_conn* conn) {
for (uint8_t i = 0; i < PeerCount; i++) {
if (pendingSecurityConns[i] == NULL || pendingSecurityConns[i] == conn) {
pendingSecurityConns[i] = conn;
pendingSecurityDeadlines[i] = Timer_GetCurrentTime() + SECURITY_ESTABLISHMENT_TIMEOUT;
EventScheduler_Reschedule(pendingSecurityDeadlines[i], EventSchedulerEvent_CheckConnectionSecurity, "security establishment timeout");
return;
}
}
}

static void securityEstablished(struct bt_conn* conn) {
for (uint8_t i = 0; i < PeerCount; i++) {
if (pendingSecurityConns[i] == conn) {
pendingSecurityConns[i] = NULL;
}
}
}

void BtConn_CheckConnectionSecurity(void) {
uint32_t now = Timer_GetCurrentTime();

for (uint8_t i = 0; i < PeerCount; i++) {
struct bt_conn* conn = pendingSecurityConns[i];
if (conn == NULL) {
continue;
}

struct bt_conn_info info;
int err = bt_conn_get_info(conn, &info);
if (err == 0 && info.state == BT_CONN_STATE_CONNECTED) {
bt_conn_auth_cancel(conn);
// bt_conn_disconnect(conn, BT_REASON_PERMANENT);
if (bt_conn_get_info(conn, &info) != 0 || info.state != BT_CONN_STATE_CONNECTED) {
pendingSecurityConns[i] = NULL;
continue;
}

if (info.security.level >= BT_SECURITY_L4) {
pendingSecurityConns[i] = NULL;
continue;
}

if (now >= pendingSecurityDeadlines[i]) {
LOG_WRN("No security established within timeout - disconnecting %s", GetPeerStringByConn(conn));
pendingSecurityConns[i] = NULL;
bt_conn_disconnect(conn, BT_REASON_TEMPORARY);
} else {
// Sometimes securityChanged gets called twice, resulting in a race and a crash, so check for it \efp.
LOG_WRN("The connection (%s) isn't even connected! Ignoring.", GetPeerStringByConn(conn));
EventScheduler_Reschedule(pendingSecurityDeadlines[i], EventSchedulerEvent_CheckConnectionSecurity, "security establishment timeout");
}
}
}

static void securityChanged(struct bt_conn *conn, bt_security_t level, enum bt_security_err err) {
BT_TRACE_AND_ASSERT("bc3");

// Report only - do NOT disconnect here. This fires on every security failure, including
// bonded reconnects where no pairing was attempted (smp.c:3112), and it runs just before
// smp_pairing_complete's automatic bt_smp_start_security retry. Disconnecting here
// suppresses that retry, so transient failures that used to self-heal become full
// reconnect cycles. Enforcement belongs to CONFIG_BT_SMP_SC_ONLY plus the attribute
// permissions; a genuinely failed pairing is dropped in pairing_failed instead.
if (err || level < BT_SECURITY_L4) {
LOG_WRN("Bt security not established: %s, level %u, err %d", GetPeerStringByConn(conn), level, err);
return;
}

securityEstablished(conn);


// Ignore connection that is being paired. At this point, the central is
// probably talking to us via an anonymous address, and it will yet change.
Expand All @@ -823,8 +933,8 @@ __attribute__((unused)) static void infoLatencyParamsUpdated(struct bt_conn* con
LOG_DBG("%s conn params: interval=%u ms, latency=%u, timeout=%u ms", GetPeerStringByConn(conn), interval * 5 / 4, latency, timeout * 10);

int8_t peerId = GetPeerIdByConn(conn);
connection_type_t connectionType = Connections_Type(Peers[peerId].connectionId);
bool isUhkPeer = isUhkDeviceConnection(connectionType);
connection_id_t connectionId = Peers[peerId].connectionId;
connection_type_t connectionType = Connections_Type(connectionId);

if (connectionType == ConnectionType_BtHid || connectionType == ConnectionType_NusDongle) {
uint32_t intervalMs = (interval * 5 + 3) / 4;
Expand All @@ -834,8 +944,12 @@ __attribute__((unused)) static void infoLatencyParamsUpdated(struct bt_conn* con
Peers[peerId].bleReportIntervalMs = intervalMs;
}

if (interval > 10) {
configureLatency(conn, isUhkPeer ? LatencyMode_NUS : LatencyMode_BleHid);
// Re-force our interval if the peer negotiated a long one - unless a long one is what
// we asked for, in which case accept whatever was granted rather than fight over it.
latency_mode_t mode = desiredLatencyMode(connectionId);
if (mode != LatencyMode_Idle && interval > 10) {
appliedLatencyMode[peerId] = (int8_t)mode;
configureLatency(conn, mode);
}
}

Expand Down Expand Up @@ -1040,10 +1154,18 @@ static void pairing_failed(struct bt_conn *conn, enum bt_security_err reason) {
PairingScreen_Feedback("Pairing failed!");
}

// TODO: should we here?
//safeDisconnect(conn, BT_REASON_PERMANENT);
LOG_WRN("Pairing failed: %s, reason %d - disconnecting", GetPeerStringByConn(conn), reason);

LOG_WRN("Pairing failed: %s, reason %d", GetPeerStringByConn(conn), reason);
// Unlike security_changed, this only fires when a pairing procedure was actually
// underway (guarded by SMP_FLAG_PAIRING), so it cannot catch a bonded reconnect that
// merely failed to encrypt. A pairing that failed will not succeed by itself - with
// SC_ONLY the peer has been told why - so drop the link rather than leave it parked
// at the aggressive interval. PIN_OR_KEY_MISSING means our bond is stale and every
// retry fails identically, so report that as permanent.
uint8_t disconnectReason = reason == BT_SECURITY_ERR_PIN_OR_KEY_MISSING
? BT_REASON_PERMANENT
: BT_REASON_TEMPORARY;
bt_conn_disconnect(conn, disconnectReason);
}


Expand Down
4 changes: 4 additions & 0 deletions device/src/bt_conn.h
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
typedef enum {
LatencyMode_NUS,
LatencyMode_BleHid,
LatencyMode_Idle,
} latency_mode_t;

// Variables:
Expand All @@ -73,6 +74,9 @@ typedef enum {
void BtConn_DisconnectAll();
void BtConn_DisconnectOne(connection_id_t connectionId);

void BtConn_UpdateConnectionLatencies(void);

void BtConn_CheckConnectionSecurity(void);
void BtConn_ReserveConnections();
void Bt_SetConnectionConfigured(struct bt_conn* conn);
uint8_t BtConn_UnusedPeripheralConnectionCount();
Expand Down
4 changes: 3 additions & 1 deletion device/src/bt_manager.c
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ void BtManager_StopBt() {
LOG_INF("OOB: Bluetooth stopped");
}

void BtManager_CheckLeftBleVsUart() {
void BtManager_CheckBleVsUart() {
if (DEVICE_IS_UHK80_LEFT) {
bool uartReady = Connections_IsReady(ConnectionId_UartRight);

Expand All @@ -113,6 +113,8 @@ void BtManager_CheckLeftBleVsUart() {
} else {
BtManager_StartScanningAndAdvertisingAsync(false, "Left UART not ready — resume BLE");
}
} else if (DEVICE_IS_UHK80_RIGHT) {
BtManager_StartScanningAndAdvertisingAsync(false, "BridgeVsUart check");
}
}

Expand Down
2 changes: 1 addition & 1 deletion device/src/bt_manager.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,6 @@
void BtManager_StartScanningAndAdvertising();
void BtManager_StartScanningAndAdvertisingAsync(bool wasAggresive, const char* eventLabel);

void BtManager_CheckLeftBleVsUart();
void BtManager_CheckBleVsUart();

#endif // __BT_MANAGER_H__
4 changes: 4 additions & 0 deletions device/src/connections.c
Original file line number Diff line number Diff line change
Expand Up @@ -526,6 +526,10 @@ void Connections_HandleSwitchover(connection_id_t connectionId, bool forceSwitch
}

DeviceState_Update(Connections_Target(connectionId));

// TODO: revise this after switchover merge
// The active host may have changed: give it the low-latency interval and relax the rest.
BtConn_UpdateConnectionLatencies();
}

bool Connections_IsCurrentHostAwake(void) {
Expand Down
3 changes: 0 additions & 3 deletions device/src/keyboard/charger.c
Original file line number Diff line number Diff line change
Expand Up @@ -165,9 +165,6 @@ static bool handleStateTransition(battery_manager_automaton_state_t newState) {
currentChargingAutomatonState = newState;
switch (newState) {
case BatteryManagerAutomatonState_TurnOff:
// LOG_INF("Going to shut down. Measured voltage %d, computed voltage %d, powered %d\n", rawVoltage, batteryState->batteryVoltage, batteryState->powered);
// PowerMode_ActivateMode(PowerMode_AutoShutDown, false, false);
// break;
case BatteryManagerAutomatonState_Powersaving:
stateChanged |= setPowersaving(true);
break;
Expand Down
Loading
Loading