pebble/src/fw/comm/internals/bt_conn_mgr.c

414 lines
14 KiB
C

/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#define FILE_LOG_COLOR LOG_COLOR_BLUE
#include <bluetooth/responsiveness.h>
#include "comm/ble/gap_le_connect_params.h"
#include "comm/ble/gap_le_connection.h"
#include "comm/bt_conn_mgr.h"
#include "comm/bt_lock.h"
#include "drivers/rtc.h"
#include "kernel/event_loop.h"
#include "kernel/pbl_malloc.h"
#include "services/common/new_timer/new_timer.h"
#include "services/common/regular_timer.h"
#include "services/common/system_task.h"
#include "system/logging.h"
#include "system/passert.h"
#include "util/list.h"
#include "util/math.h"
#include "util/rand.h"
#include <stdlib.h>
//! The Bluetooth Connection Manager is responsible for managing the power
//! state of the active bluetooth connections. Sub-modules using bluetooth are
//! expected to notify this module when they are active or expect inbound data
//! and want to minimize latency. Using this info, the module decides whether
//! the LE or classic connection needs to be bumped out of its lower power
//! state in order to respond more quickly.
//!
//! Note: This module currently only manages the LE connections. In the
//! future, we will add support for handling classic connections as well
typedef struct {
ListNode list_node;
uint32_t timeout; // time to stop this request (in rtc ticks)
ResponseTimeState req_state;
BtConsumer consumer;
ResponsivenessGrantedHandler granted_handler;
} ConnectionStateRequest;
typedef struct ConnectionMgrInfo {
// callback which returns us to a low power state if user of API does not exit
// a high power state
RegularTimerInfo watchdog_cb_info;
// current running state of the connection
ResponseTimeState curr_requested_state;
// A list of consumers who have requested changes to latency state != ResponseTimeMax
ConnectionStateRequest *requests;
} ConnectionMgrInfo;
ResponseTimeState gap_le_connect_params_get_actual_state(GAPLEConnection *connection);
//! Walks through and finds the lowest latency requested for the given type of
//! connection. Also detects the longest amount of time that interval has been
//! requested. Also gets the consumer that is responsible for the lowest latency + longest timeout
//! combo. These pieces of information are then returned to the caller.
static ResponseTimeState prv_determine_latency_for_connection(
ConnectionStateRequest *requests, uint16_t *secs_to_wait, BtConsumer *consumer_out) {
ResponseTimeState state = ResponseTimeMax;
uint32_t timeout = 0;
BtConsumer responsible_consumer = BtConsumerNone;
ConnectionStateRequest *curr_request = requests;
while (curr_request != NULL) {
if (curr_request->req_state > state) {
// reset our tracker, we have found a higher power mode requested
timeout = curr_request->timeout;
state = curr_request->req_state;
responsible_consumer = curr_request->consumer;
} else if (curr_request->req_state == state) {
if (curr_request->timeout > timeout) {
timeout = curr_request->timeout;
responsible_consumer = curr_request->consumer;
}
}
curr_request = (ConnectionStateRequest *)list_get_next(&curr_request->list_node);
}
if (consumer_out) {
*consumer_out = responsible_consumer;
}
if (secs_to_wait) {
uint32_t curr_ticks = rtc_get_ticks();
if (curr_ticks < timeout) {
uint16_t wait_time = (timeout - curr_ticks) / RTC_TICKS_HZ;
*secs_to_wait = MAX(1, wait_time);
} else {
*secs_to_wait = 0;
}
}
return state;
}
/*
* LE connection manager handling for a gateway connection
*/
static void prv_bt_le_gateway_response_latency_watchdog_cb(void *data);
static void prv_granted_kernel_main_cb(void *ctx) {
ResponsivenessGrantedHandler granted_handler = ctx;
granted_handler();
}
static void prv_schedule_granted_handler(ResponsivenessGrantedHandler granted_handler) {
PBL_ASSERTN(granted_handler);
launcher_task_add_callback(prv_granted_kernel_main_cb, granted_handler);
}
//! extern'd for gap_le_connect_params.c
void conn_mgr_handle_desired_state_granted(GAPLEConnection *hdl,
ResponseTimeState granted_state) {
bt_lock_assert_held(true);
ConnectionStateRequest *curr_request = hdl->conn_mgr_info->requests;
while (curr_request != NULL) {
if (curr_request->granted_handler &&
curr_request->req_state <= granted_state) {
prv_schedule_granted_handler(curr_request->granted_handler);
curr_request->granted_handler = NULL;
}
curr_request = (ConnectionStateRequest *)list_get_next(&curr_request->list_node);
}
}
static void prv_handle_response_latency_for_le_conn(GAPLEConnection *hdl) {
uint16_t secs_til_max_latency;
ResponseTimeState state;
BtConsumer responsible_consumer;
#ifdef RECOVERY_FW
// We don't care if we burn up some power from PRF and we want FW to update quickly
secs_til_max_latency = MAX_PERIOD_RUN_FOREVER;
state = ResponseTimeMin;
responsible_consumer = 0;
#else
state = prv_determine_latency_for_connection(hdl->conn_mgr_info->requests,
&secs_til_max_latency, &responsible_consumer);
#endif
// actually request the mode if it has changed:
if (hdl->conn_mgr_info->curr_requested_state != state) {
PBL_LOG(LOG_LEVEL_INFO, "LE: Requesting state %d for %d secs, due to %u",
state, secs_til_max_latency, responsible_consumer);
gap_le_connect_params_request(hdl, state);
}
// remove a watchdog timer if it was already scheduled and schedule a new one
RegularTimerInfo *watchdog_cb_info = &hdl->conn_mgr_info->watchdog_cb_info;
if (regular_timer_is_scheduled(watchdog_cb_info)) {
regular_timer_remove_callback(watchdog_cb_info);
}
// don't start the watchdog timer if we have entered the lowest power mode or
// if we want to run at the specified rate indefinitely
if ((state != ResponseTimeMax) && (secs_til_max_latency != MAX_PERIOD_RUN_FOREVER)) {
watchdog_cb_info->cb = prv_bt_le_gateway_response_latency_watchdog_cb;
watchdog_cb_info->cb_data = hdl;
// wait an extra second since the multisecond callback will fire somewhere
// between 0 and 1 seconds from now and we want to make sure the interval
// we are currently running at actually expires
regular_timer_add_multisecond_callback(
watchdog_cb_info, secs_til_max_latency + 1);
}
hdl->conn_mgr_info->curr_requested_state = state;
}
static void prv_bt_le_gateway_response_latency_watchdog_handler(void *data) {
bt_lock();
GAPLEConnection *hdl = (GAPLEConnection *)data;
// Let's make sure our connection handle is still valid in case we
// disconnected before this CB had a chance to execute
if (!gap_le_connection_is_valid(hdl)) {
goto unlock;
}
ConnectionMgrInfo *conn_mgr_info = hdl->conn_mgr_info;
// if we are executing this cb, we have timed out running at the currently
// selected state so check and see what consumer timeouts have expired
ConnectionStateRequest *curr_request = conn_mgr_info->requests;
uint32_t curr_ticks = rtc_get_ticks();
while (curr_request != NULL) {
ConnectionStateRequest *next =
(ConnectionStateRequest *)list_get_next(&curr_request->list_node);
if (conn_mgr_info->curr_requested_state == curr_request->req_state) {
if (curr_ticks >= curr_request->timeout) {
list_remove(&curr_request->list_node, (ListNode **)&conn_mgr_info->requests, NULL);
kernel_free(curr_request);
}
}
curr_request = next;
}
// Note: As an optimization, we could track how long we have been in a lower
// latency state and subtract that from higher latency requests, but most of
// the time we should be in the maximum latency (low power) state anyway
// get & set the new state
prv_handle_response_latency_for_le_conn(hdl);
unlock:
bt_unlock();
}
static void prv_bt_le_gateway_response_latency_watchdog_cb(void *data) {
// offload handling onto KernelBG so we don't stall the timer thread
// trying to get the bt lock
system_task_add_callback(prv_bt_le_gateway_response_latency_watchdog_handler, data);
}
static bool prv_find_source(ListNode *found_node, void *data) {
return (((ConnectionStateRequest *)found_node)->consumer == (BtConsumer)data);
}
/*
* Exported APIs
*/
void conn_mgr_set_ble_conn_response_time(
GAPLEConnection *hdl, BtConsumer consumer, ResponseTimeState state,
uint16_t max_period_secs) {
conn_mgr_set_ble_conn_response_time_ext(hdl, consumer, state, max_period_secs, NULL);
}
void conn_mgr_set_ble_conn_response_time_ext(
GAPLEConnection *hdl, BtConsumer consumer, ResponseTimeState state,
uint16_t max_period_secs, ResponsivenessGrantedHandler granted_handler) {
ConnectionMgrInfo *conn_mgr_info;
if (!hdl || !((conn_mgr_info = hdl->conn_mgr_info))) {
PBL_LOG(LOG_LEVEL_ERROR, "GAP Handle not properly initialized");
return;
}
bt_lock();
// remove the watchdog timer if it was already scheduled since we are
// going to recompute
RegularTimerInfo *watchdog_cb_info = &hdl->conn_mgr_info->watchdog_cb_info;
if (regular_timer_is_scheduled(watchdog_cb_info)) {
regular_timer_remove_callback(watchdog_cb_info);
}
ConnectionStateRequest *consumer_request =
(ConnectionStateRequest *)list_find((ListNode *)conn_mgr_info->requests,
prv_find_source, (void *)consumer);
bool is_already_granted = (gap_le_connect_params_get_actual_state(hdl) >= state);
if (consumer_request == NULL) {
if (state == ResponseTimeMax) {
// No changes: there was no previous node and the new state is the default "low power" one.
goto handle_current_state;
}
// create node
consumer_request = kernel_malloc_check(sizeof(ConnectionStateRequest));
list_init(&consumer_request->list_node);
conn_mgr_info->requests = (ConnectionStateRequest *)list_prepend(
&conn_mgr_info->requests->list_node, &consumer_request->list_node);
}
// If the consumer requests to go back to low power (ResponseTimeMax), wait a little longer
// before actually going back. This prevents rapid back-n-forths between low power and fast modes,
// that can happen especially in a chain of operations, for example, the resource & bin put-bytes
// sessions to install an app.
if (state == ResponseTimeMax) {
// Keep the existing node in the list for the duration of our "activity timeout". It will be
// cleaned up automatically by the watchdog timer.
max_period_secs = BT_CONN_MGR_INACTIVITY_TIMEOUT_SECS;
state = consumer_request->req_state;
}
// populate node with new info. If it was previously set we override it
consumer_request->timeout = rtc_get_ticks() + max_period_secs * RTC_TICKS_HZ;
consumer_request->req_state = state;
consumer_request->consumer = consumer;
consumer_request->granted_handler = is_already_granted ? NULL : granted_handler;
handle_current_state:
if (is_already_granted && granted_handler) {
prv_schedule_granted_handler(granted_handler);
}
prv_handle_response_latency_for_le_conn(hdl);
bt_unlock();
}
//! expects that the bt lock is held
ConnectionMgrInfo * bt_conn_mgr_info_init(void) {
ConnectionMgrInfo *newinfo = kernel_malloc_check(sizeof(ConnectionMgrInfo));
*newinfo = (ConnectionMgrInfo) {
.curr_requested_state = ResponseTimeMax,
};
return newinfo;
}
//! expects that the bt_lock is held
void bt_conn_mgr_info_deinit(ConnectionMgrInfo **info) {
// If we have any callbacks scheduled for this device, take them out
RegularTimerInfo *watchdog_cb_info = &(*info)->watchdog_cb_info;
if (regular_timer_is_scheduled(watchdog_cb_info)) {
regular_timer_remove_callback(watchdog_cb_info);
}
ListNode *curr_request = (ListNode *)(*info)->requests;
while (curr_request != NULL) {
ListNode *temp = list_get_next(curr_request);
list_remove(curr_request, NULL, NULL);
kernel_free(curr_request);
curr_request = temp;
}
kernel_free(*info);
*info = NULL;
}
void command_change_le_mode(char *mode) {
// assume we only have one connection for debug
GAPLEConnection *conn_hdl = gap_le_connection_any();
ResponseTimeState state = atoi(mode);
conn_mgr_set_ble_conn_response_time(
conn_hdl, BtConsumerPrompt, state, MAX_PERIOD_RUN_FOREVER);
}
static TimerID s_chaos_monkey_timer;
static ResponseTimeState s_chaos_monkey_last_state;
static void prv_mode_chaos_monkey_stop(void) {
new_timer_delete(s_chaos_monkey_timer);
s_chaos_monkey_timer = TIMER_INVALID_ID;
}
static void prv_mode_chaos_monkey_callback(void *data) {
bt_lock();
GAPLEConnection *hdl = (GAPLEConnection *) data;
if (s_chaos_monkey_timer == TIMER_INVALID_ID) {
goto unlock;
}
if (!gap_le_connection_is_valid(hdl)) {
prv_mode_chaos_monkey_stop();
goto unlock;
}
ResponseTimeState requested_state;
do {
requested_state = bounded_rand_int(ResponseTimeMax, ResponseTimeMin);
} while (requested_state == s_chaos_monkey_last_state);
s_chaos_monkey_last_state = requested_state;
conn_mgr_set_ble_conn_response_time(hdl, BtConsumerPrompt,
requested_state, MAX_PERIOD_RUN_FOREVER);
const uint32_t delay_ms = bounded_rand_int(1, 3000);
PBL_LOG(LOG_LEVEL_DEBUG, "Mode chaos monkey: next change=%"PRIu32"ms", delay_ms);
new_timer_start(s_chaos_monkey_timer, delay_ms, prv_mode_chaos_monkey_callback, data, 0);
unlock:
bt_unlock();
}
void command_le_mode_chaos_monkey(char *enabled_str) {
bool new_enabled = atoi(enabled_str);
bool is_enabled = (s_chaos_monkey_timer != TIMER_INVALID_ID);
if (new_enabled == is_enabled) {
return;
}
bt_lock();
if (new_enabled) {
GAPLEConnection *conn_hdl = gap_le_connection_any();
if (conn_hdl) {
s_chaos_monkey_timer = new_timer_create();
prv_mode_chaos_monkey_callback(conn_hdl);
}
} else {
prv_mode_chaos_monkey_stop();
}
bt_unlock();
}
ResponseTimeState conn_mgr_get_latency_for_le_connection(
GAPLEConnection *hdl, uint16_t *secs_to_wait) {
bt_lock_assert_held(true);
return prv_determine_latency_for_connection(
hdl->conn_mgr_info->requests, secs_to_wait, NULL);
}