mirror of https://github.com/google/pebble
414 lines
14 KiB
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);
|
|
}
|