Import of the watch repository from Pebble

This commit is contained in:
Matthieu Jeanson 2024-12-12 16:43:03 -08:00 committed by Katharine Berry
commit 3b92768480
10334 changed files with 2564465 additions and 0 deletions

View file

@ -0,0 +1,69 @@
/*
* 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.
*/
#include "accessory_idle_mode.h"
#include "drivers/accessory.h"
#include "mfg/mfg_mode/mfg_factory_mode.h"
#include "services/common/system_task.h"
#include "system/logging.h"
#if PLATFORM_SNOWY || PLATFORM_SPALDING
static const char KNOCKING_CODE[] = "sn0wy";
#elif PLATFORM_SILK
static const char KNOCKING_CODE[] = "s1lk";
#elif PLATFORM_ROBERT
static const char KNOCKING_CODE[] = "r0bert";
#elif PLATFORM_CALCULUS
static const char KNOCKING_CODE[] = "c@lculus";
#else
#error "Unknown platform"
#endif
static void prv_knocking_complete(void *data) {
mfg_enter_mfg_mode_and_launch_app();
}
bool accessory_idle_mode_handle_char(char c) {
// Note: You're in an interrupt here, be careful
static int s_knocking_state = 0;
bool should_context_switch = false;
if (KNOCKING_CODE[s_knocking_state] == c) {
// This character matched! We're now looking for the next character.
++s_knocking_state;
PBL_LOG(LOG_LEVEL_DEBUG, "Idle: <%c> Match! State %u", c, s_knocking_state);
// If we reach the null terminator, we're done!
if (KNOCKING_CODE[s_knocking_state] == 0) {
system_task_add_callback_from_isr(
prv_knocking_complete, NULL, &should_context_switch);
s_knocking_state = 0;
}
} else {
PBL_LOG(LOG_LEVEL_DEBUG, "Idle: <%c> Mismatch!", c);
// Wrong character, reset
s_knocking_state = 0;
}
return should_context_switch;
}

View file

@ -0,0 +1,22 @@
/*
* 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.
*/
#pragma once
#include <stdbool.h>
bool accessory_idle_mode_handle_char(char c);

View file

@ -0,0 +1,606 @@
/*
* 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.
*/
#include "accessory_imaging.h"
#include "console/prompt.h"
#include "drivers/accessory.h"
#include "drivers/flash.h"
#include "flash_region/flash_region.h"
#include "kernel/core_dump.h"
#include "kernel/core_dump_private.h"
#include "mfg/mfg_mode/mfg_factory_mode.h"
#include "resource/resource_storage_flash.h"
#include "services/common/new_timer/new_timer.h"
#include "services/common/system_task.h"
#include "services/prf/accessory/accessory_manager.h"
#include "system/bootbits.h"
#include "system/logging.h"
#include "system/passert.h"
#include "system/reset.h"
#include "util/crc32.h"
#include "util/hdlc.h"
#include "util/attributes.h"
#include "util/math.h"
#include <stdint.h>
#include <inttypes.h>
#include <stdbool.h>
#define TIMEOUT_MS (3000)
#define VERSION (1)
#define NUM_RX_BUFFERS (3)
#define MAX_DATA_LENGTH (2048)
#define CHECKSUM_LENGTH (4)
#define MAX_FRAME_LENGTH (MAX_DATA_LENGTH + sizeof(ImagingHeader) + CHECKSUM_LENGTH)
// flags
#define FLAG_IS_SERVER (1 << 0)
#define FLAG_VERSION (VERSION << 1)
// opcodes
#define OPCODE_PING (0x01)
#define OPCODE_DISCONNECT (0x02)
#define OPCODE_RESET (0x03)
#define OPCODE_FLASH_GEOMETRY (0x11)
#define OPCODE_FLASH_ERASE (0x12)
#define OPCODE_FLASH_WRITE (0x13)
#define OPCODE_FLASH_CRC (0x14)
#define OPCODE_FLASH_FINALIZE (0x15)
#define OPCODE_FLASH_READ (0x16)
// flash regions
#define REGION_PRF (0x01)
#define REGION_RESOURCES (0x02)
#define REGION_FW_SCRATCH (0x03)
#define REGION_PFS (0x04)
#define REGION_COREDUMP (0x05)
// flash read flags
#define FLASH_READ_FLAG_ALL_SAME (1 << 0)
typedef struct PACKED {
uint8_t flags;
uint8_t opcode;
} ImagingHeader;
typedef struct PACKED {
uint8_t region;
} FlashGeometryRequest;
typedef struct PACKED {
uint8_t region;
uint32_t address;
uint32_t length;
} FlashGeometryResponse;
typedef struct PACKED {
uint32_t address;
uint32_t length;
} FlashEraseRequest;
typedef struct PACKED {
uint32_t address;
uint32_t length;
uint8_t complete;
} FlashEraseResponse;
typedef struct PACKED {
uint32_t address;
uint8_t data[];
} FlashWriteRequest;
typedef struct PACKED {
uint32_t address;
uint32_t length;
} FlashReadRequest;
typedef struct PACKED {
uint32_t address;
uint32_t length;
} FlashCRCRequest;
typedef struct PACKED {
uint32_t address;
uint32_t length;
uint32_t crc;
} FlashCRCResponse;
typedef struct PACKED {
uint8_t region;
} FlashFinalizeRequest;
typedef FlashFinalizeRequest FlashFinalizeResponse; // they are currently the same
typedef struct {
bool is_free;
bool is_valid;
HdlcStreamingContext hdlc_ctx;
uint32_t index;
union {
struct PACKED {
ImagingHeader header;
uint8_t payload[MAX_DATA_LENGTH];
uint32_t checksum;
} frame;
uint8_t data[MAX_FRAME_LENGTH];
};
uint32_t checksum;
} ReceiveBuffer;
static bool s_enabled;
static TimerID s_timeout_timer;
static ReceiveBuffer s_buffers[NUM_RX_BUFFERS];
static ReceiveBuffer *s_curr_buf;
static bool s_flash_erase_in_progress;
static int s_no_buffer_count;
static int s_dropped_char_count;
static void prv_timeout_timer_cb(void *context);
// Helper functions
////////////////////////////////////////////////////////////////////
static void prv_reset_buffer(ReceiveBuffer *buffer) {
hdlc_streaming_decode_reset(&buffer->hdlc_ctx);
buffer->index = 0;
buffer->checksum = CRC32_INIT;
buffer->is_valid = true;
buffer->is_free = true;
}
// Start / stop
////////////////////////////////////////////////////////////////////
static void prv_start(void) {
s_curr_buf = NULL;
s_no_buffer_count = 0;
s_dropped_char_count = 0;
for (int i = 0; i < NUM_RX_BUFFERS; ++i) {
prv_reset_buffer(&s_buffers[i]);
}
accessory_manager_set_state(AccessoryInputStateImaging);
accessory_use_dma(true);
s_timeout_timer = new_timer_create();
new_timer_start(s_timeout_timer, TIMEOUT_MS, prv_timeout_timer_cb, NULL, 0 /* flags */);
PBL_LOG(LOG_LEVEL_DEBUG, "Starting accessory imaging");
}
static void prv_stop(void *context) {
if (s_no_buffer_count > 0) {
PBL_LOG(LOG_LEVEL_ERROR, "Ran out of buffers %d times and dropped %d bytes while imaging",
s_no_buffer_count, s_dropped_char_count);
}
flash_prf_set_protection(true);
accessory_use_dma(false);
accessory_manager_set_state(AccessoryInputStateMfg);
new_timer_delete(s_timeout_timer);
PBL_LOG(LOG_LEVEL_DEBUG, "Stopping accessory imaging");
}
static void prv_timeout_timer_cb(void *context) {
system_task_add_callback(prv_stop, NULL);
}
// Sending
////////////////////////////////////////////////////////////////////
static void prv_encode_and_send_data(const void *data, uint32_t length) {
const uint8_t *data_bytes = data;
for (uint32_t i = 0; i < length; i++) {
uint8_t byte = data_bytes[i];
if (hdlc_encode(&byte)) {
accessory_send_byte(HDLC_ESCAPE);
}
accessory_send_byte(byte);
}
}
static void prv_send_frame(uint8_t opcode, const void *payload, uint32_t length) {
accessory_disable_input();
accessory_send_byte(HDLC_FLAG);
// send the header
const ImagingHeader header = {
.flags = FLAG_IS_SERVER | FLAG_VERSION,
.opcode = opcode
};
prv_encode_and_send_data(&header, sizeof(header));
// send the pyaload
prv_encode_and_send_data(payload, length);
// send the checksum
uint32_t checksum = CRC32_INIT;
checksum = crc32(checksum, &header, sizeof(header));
if (payload && length) {
checksum = crc32(checksum, payload, length);
}
prv_encode_and_send_data(&checksum, sizeof(checksum));
accessory_send_byte(HDLC_FLAG);
accessory_enable_input();
}
// Request processing
////////////////////////////////////////////////////////////////////
static void prv_erase_complete(void *ignored, status_t result) {
s_flash_erase_in_progress = false;
}
static bool prv_is_erased(uint32_t addr, uint32_t length) {
const uint32_t sectors_to_erase = (length + SECTOR_SIZE_BYTES - 1) / SECTOR_SIZE_BYTES;
for (uint32_t sector = 0; sector < sectors_to_erase; sector++) {
if (!flash_sector_is_erased(sector * SECTOR_SIZE_BYTES + addr)) {
return false;
}
}
return true;
}
static void prv_handle_ping_request(const void *payload, uint32_t length) {
// echo it back
prv_send_frame(OPCODE_PING, payload, length);
}
static void prv_handle_disconnect_request(const void *payload, uint32_t length) {
if (length) {
// should be 0
PBL_LOG(LOG_LEVEL_ERROR, "Invalid length (%"PRIu32")", length);
return;
}
prv_send_frame(OPCODE_DISCONNECT, NULL, 0);
prv_stop(NULL);
}
static void prv_handle_reset_request(const void *payload, uint32_t length) {
if (length) {
// should be 0
PBL_LOG(LOG_LEVEL_ERROR, "Invalid length (%"PRIu32")", length);
return;
}
PBL_LOG(LOG_LEVEL_WARNING, "Got reset request");
prv_send_frame(OPCODE_RESET, NULL, 0);
prv_stop(NULL);
system_reset();
}
static bool prv_coredump_flash_base(uint32_t *addr, uint32_t *size) {
CoreDumpFlashHeader flash_hdr;
CoreDumpFlashRegionHeader region_hdr;
uint32_t max_last_used = 0;
uint32_t base_address;
uint32_t last_used_idx = 0;
// First, see if the flash header has been put in place
flash_read_bytes((uint8_t *)&flash_hdr, CORE_DUMP_FLASH_START, sizeof(flash_hdr));
if ((flash_hdr.magic != CORE_DUMP_FLASH_HDR_MAGIC) ||
(flash_hdr.unformatted == CORE_DUMP_ALL_UNFORMATTED)) {
return false;
}
// Find the region with the highest last_used count
for (unsigned int i = 0; i < CORE_DUMP_MAX_IMAGES; i++) {
if (flash_hdr.unformatted & (1 << i)) {
continue;
}
base_address = core_dump_get_slot_address(i);
flash_read_bytes((uint8_t *)&region_hdr, base_address, sizeof(region_hdr));
if (region_hdr.last_used > max_last_used) {
max_last_used = region_hdr.last_used;
last_used_idx = i;
}
}
if (max_last_used == 0) {
return false;
}
*addr = core_dump_get_slot_address(last_used_idx);
if (core_dump_size(*addr, size) != S_SUCCESS) {
return false;
}
*addr += sizeof(CoreDumpFlashRegionHeader);
return true;
}
static void prv_handle_flash_geometry_request(const void *payload, uint32_t length) {
if (length != sizeof(FlashGeometryRequest)) {
PBL_LOG(LOG_LEVEL_ERROR, "Invalid length (%"PRIu32")", length);
return;
}
const FlashGeometryRequest *request = payload;
FlashGeometryResponse response = {
.region = request->region
};
if (request->region == REGION_PRF) {
// assume we're about to write to this region, so unlock it
flash_prf_set_protection(false);
response.address = FLASH_REGION_SAFE_FIRMWARE_BEGIN;
response.length = FLASH_REGION_SAFE_FIRMWARE_END - response.address;
} else if (request->region == REGION_RESOURCES) {
const SystemResourceBank *bank = resource_storage_flash_get_unused_bank();
response.address = bank->begin;
response.length = bank->end - response.address;
} else if (request->region == REGION_FW_SCRATCH) {
response.address = FLASH_REGION_FIRMWARE_SCRATCH_BEGIN;
response.length = FLASH_REGION_FIRMWARE_SCRATCH_END - response.address;
} else if (request->region == REGION_PFS) {
response.address = FLASH_REGION_FILESYSTEM_BEGIN;
response.length = FLASH_REGION_FILESYSTEM_END - response.address;
} else if (request->region == REGION_COREDUMP) {
if (!prv_coredump_flash_base(&response.address, &response.length)) {
response.address = 0;
response.length = 0;
}
} else {
PBL_LOG(LOG_LEVEL_ERROR, "Invalid region (%"PRIu8")", request->region);
}
prv_send_frame(OPCODE_FLASH_GEOMETRY, &response, sizeof(response));
}
static void prv_handle_flash_erase_request(const void *payload, uint32_t length) {
if (length != sizeof(FlashEraseRequest)) {
PBL_LOG(LOG_LEVEL_ERROR, "Invalid length (%"PRIu32")", length);
return;
}
const FlashEraseRequest *request = payload;
FlashEraseResponse response = {
.address = request->address,
.length = request->length
};
bool start_erase = false;
if (s_flash_erase_in_progress) {
response.complete = 0;
} else if (prv_is_erased(request->address, request->length)) {
response.complete = 1;
} else {
response.complete = 0;
start_erase = true;
}
prv_send_frame(OPCODE_FLASH_ERASE, &response, sizeof(response));
// start the erase after sending the response
if (start_erase) {
uint32_t end_address = request->address + request->length;
s_flash_erase_in_progress = true;
flash_erase_optimal_range(
request->address, request->address, end_address,
(end_address + SECTOR_SIZE_BYTES - 1) & SECTOR_ADDR_MASK,
prv_erase_complete, NULL);
}
}
static void prv_handle_flash_write_request(const void *payload, uint32_t length) {
if (length < sizeof(FlashWriteRequest)) {
PBL_LOG(LOG_LEVEL_ERROR, "Invalid length (%"PRIu32")", length);
return;
}
const FlashWriteRequest *request = payload;
length -= offsetof(FlashWriteRequest, data);
flash_write_bytes(request->data, request->address, length);
}
static void prv_handle_flash_read_request(const void *payload, uint32_t length) {
if (length < sizeof(FlashReadRequest)) {
PBL_LOG(LOG_LEVEL_ERROR, "Invalid length (%"PRIu32")", length);
return;
}
const FlashReadRequest *request = payload;
if (request->length > MAX_DATA_LENGTH) {
PBL_LOG(LOG_LEVEL_ERROR, "Invalid request length (%"PRIu32")", request->length);
}
// leave 1 byte at the start for flags
static uint8_t buffer[1 + MAX_DATA_LENGTH];
flash_read_bytes(&buffer[1], request->address, request->length);
bool is_all_same = true;
uint8_t same_byte = buffer[1];
for (uint32_t i = 1; i < request->length; i++) {
if (buffer[i + 1] != same_byte) {
is_all_same = false;
break;
}
}
// As an optimization, if all the bytes are the same, we set a flag and just send a single byte.
buffer[0] = is_all_same ? FLASH_READ_FLAG_ALL_SAME : 0; // flags
const uint32_t frame_length = is_all_same ? 2 : request->length + 1;
prv_send_frame(OPCODE_FLASH_READ, buffer, frame_length);
}
static void prv_handle_flash_crc_request(const void *payload, uint32_t length) {
// there can be 1 or more payloads
if (!length || (length % sizeof(FlashCRCRequest))) {
PBL_LOG(LOG_LEVEL_ERROR, "Invalid length (%"PRIu32")", length);
return;
}
const FlashCRCRequest *request = payload;
const uint32_t num_entries = length / sizeof(FlashCRCRequest);
// this is just static cause it's potentially too big to put on the stack
static FlashCRCResponse response[MAX_DATA_LENGTH / sizeof(FlashCRCResponse)];
for (uint32_t i = 0; i < num_entries; i++) {
const FlashCRCRequest *entry = &request[i];
response[i] = (FlashCRCResponse) {
.address = entry->address,
.length = entry->length,
.crc = flash_crc32(entry->address, entry->length)
};
}
prv_send_frame(OPCODE_FLASH_CRC, &response, sizeof(FlashCRCResponse) * num_entries);
}
static void prv_handle_flash_finalize_request(const void *payload, uint32_t length) {
if (length != sizeof(FlashFinalizeRequest)) {
PBL_LOG(LOG_LEVEL_ERROR, "Invalid length (%"PRIu32")", length);
return;
}
const FlashFinalizeRequest *request = payload;
FlashFinalizeResponse response = {
.region = request->region
};
if (request->region == REGION_PRF) {
flash_prf_set_protection(true);
} else if (request->region == REGION_RESOURCES) {
boot_bit_set(BOOT_BIT_NEW_SYSTEM_RESOURCES_AVAILABLE);
} else if (request->region == REGION_FW_SCRATCH) {
boot_bit_set(BOOT_BIT_NEW_FW_AVAILABLE);
} else if (request->region == REGION_PFS) {
// Do nothing!
} else if (request->region == REGION_COREDUMP) {
// Do nothing!
} else {
PBL_LOG(LOG_LEVEL_ERROR, "Invalid region (%"PRIu8")", request->region);
}
prv_send_frame(OPCODE_FLASH_FINALIZE, &response, sizeof(response));
}
static void prv_process_frame(void *context) {
ReceiveBuffer *buf = context;
const ImagingHeader *header = &buf->frame.header;
const void *payload = buf->frame.payload;
const uint32_t payload_length = buf->index - sizeof(ImagingHeader) - CHECKSUM_LENGTH;
PBL_ASSERTN(payload_length <= MAX_DATA_LENGTH);
// sanity check
if (header->flags & FLAG_IS_SERVER) {
PBL_LOG(LOG_LEVEL_ERROR, "Got frame from server (loopback?)");
prv_reset_buffer(buf);
return;
}
// reset the timeout timer
new_timer_start(s_timeout_timer, TIMEOUT_MS, prv_timeout_timer_cb, NULL, 0 /* flags */);
// look at the opcode and handle this message
if (header->opcode == OPCODE_PING) {
prv_handle_ping_request(payload, payload_length);
} else if (header->opcode == OPCODE_DISCONNECT) {
prv_handle_disconnect_request(payload, payload_length);
} else if (header->opcode == OPCODE_RESET) {
prv_handle_reset_request(payload, payload_length);
} else if (header->opcode == OPCODE_FLASH_GEOMETRY) {
prv_handle_flash_geometry_request(payload, payload_length);
} else if (header->opcode == OPCODE_FLASH_ERASE) {
prv_handle_flash_erase_request(payload, payload_length);
} else if (header->opcode == OPCODE_FLASH_WRITE) {
prv_handle_flash_write_request(payload, payload_length);
} else if (header->opcode == OPCODE_FLASH_READ) {
prv_handle_flash_read_request(payload, payload_length);
} else if (header->opcode == OPCODE_FLASH_CRC) {
prv_handle_flash_crc_request(payload, payload_length);
} else if (header->opcode == OPCODE_FLASH_FINALIZE) {
prv_handle_flash_finalize_request(payload, payload_length);
} else {
PBL_LOG(LOG_LEVEL_ERROR, "Got unexpected opcode (0x%x)", header->opcode);
}
prv_reset_buffer(buf);
}
// Receiving (ISR-based)
////////////////////////////////////////////////////////////////////
static bool prv_handle_data(uint8_t data) {
bool should_context_switch = false;
bool hdlc_err;
bool should_store;
bool is_complete = hdlc_streaming_decode(&s_curr_buf->hdlc_ctx, &data, &should_store, &hdlc_err);
if (hdlc_err) {
s_curr_buf->is_valid = false;
} else if (is_complete) {
if (s_curr_buf->is_valid && s_curr_buf->checksum == CRC32_RESIDUE && s_curr_buf->index) {
// queue up processing of this frame and clear s_curr_buf so we'll switch to a new one
system_task_add_callback_from_isr(prv_process_frame, s_curr_buf, &should_context_switch);
} else {
prv_reset_buffer(s_curr_buf);
}
s_curr_buf = NULL;
} else if (should_store && s_curr_buf->is_valid) {
if (s_curr_buf->index < MAX_FRAME_LENGTH) {
// store this byte
s_curr_buf->data[(s_curr_buf->index)++] = data;
s_curr_buf->checksum = crc32(s_curr_buf->checksum, &data, 1);
} else {
// too long!
s_curr_buf->is_valid = false;
}
}
return should_context_switch;
}
bool accessory_imaging_handle_char(char c) {
static bool has_no_buffer = false;
if (!s_curr_buf) {
// find a buffer to write into
for (int i = 0; i < NUM_RX_BUFFERS; ++i) {
if (s_buffers[i].is_free) {
s_buffers[i].is_free = false;
s_curr_buf = &s_buffers[i];
break;
}
}
if (!s_curr_buf) {
// no available buffer :(
if (!has_no_buffer) {
s_no_buffer_count++;
}
has_no_buffer = true;
s_dropped_char_count++;
return false;
}
}
has_no_buffer = false;
return prv_handle_data((uint8_t)c);
}
// Other exported functions
////////////////////////////////////////////////////////////////////
void accessory_imaging_enable(void) {
s_enabled = true;
}
void command_accessory_imaging_start(void) {
if (!s_enabled) {
prompt_send_response("Command not available.");
}
prv_start();
}

View file

@ -0,0 +1,23 @@
/*
* 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.
*/
#pragma once
#include <stdbool.h>
bool accessory_imaging_handle_char(char c);
void accessory_imaging_enable(void);

View file

@ -0,0 +1,132 @@
/*
* 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.
*/
#include "accessory_manager.h"
#include "accessory_idle_mode.h"
#include "accessory_imaging.h"
#include "accessory_mfg_mode.h"
#include "drivers/accessory.h"
#include "system/logging.h"
#include "os/mutex.h"
static AccessoryInputState s_input_state = AccessoryInputStateIdle;
static PebbleMutex *s_state_mutex;
void accessory_manager_init(void) {
s_state_mutex = mutex_create();
}
bool accessory_manager_handle_character_from_isr(char c) {
// NOTE: THIS IS RUN WITHIN AN ISR
switch (s_input_state) {
case AccessoryInputStateMfg:
return accessory_mfg_mode_handle_char(c);
case AccessoryInputStateIdle:
return accessory_idle_mode_handle_char(c);
case AccessoryInputStateImaging:
return accessory_imaging_handle_char(c);
case AccessoryInputStateMic:
// fallthrough
default:
break;
}
return false;
}
bool accessory_manager_handle_break_from_isr(void) {
// NOTE: THIS IS RUN WITHIN AN ISR
switch (s_input_state) {
case AccessoryInputStateIdle:
case AccessoryInputStateMic:
case AccessoryInputStateMfg:
case AccessoryInputStateImaging:
// fallthrough
default:
break;
}
return false;
}
// Valid state transitions are:
// +-----+
// | IMG |
// +-----+
// ^
// |
// v
// +------+ +-----+ +-----+
// | Idle |<-->| MFG |<-->| MIC |
// +------+ +-----+ +-----+
static bool prv_is_valid_state_transition(AccessoryInputState new_state) {
if (s_input_state == AccessoryInputStateIdle) {
return new_state == AccessoryInputStateMfg;
} else if (s_input_state == AccessoryInputStateMfg) {
return (new_state == AccessoryInputStateIdle) ||
(new_state == AccessoryInputStateImaging) ||
(new_state == AccessoryInputStateMic);
} else if (s_input_state == AccessoryInputStateImaging) {
return new_state == AccessoryInputStateMfg;
} else if (s_input_state == AccessoryInputStateMic) {
return new_state == AccessoryInputStateMfg;
}
return false;
}
// The accessory state is used to differentiate between different consumers of the accessory port.
// Before a consumer uses the accessory port, it must set its state and return the state to idle
// once it has finished. No other consumer will be permitted to use the accessory port until the
// state is returned to idle.
bool accessory_manager_set_state(AccessoryInputState state) {
mutex_lock(s_state_mutex);
if (!prv_is_valid_state_transition(state)) {
// the state is already set by somebody else
mutex_unlock(s_state_mutex);
return false;
}
s_input_state = state;
switch (s_input_state) {
case AccessoryInputStateMfg:
accessory_enable_input();
accessory_set_baudrate(AccessoryBaud115200);
accessory_set_power(false);
accessory_mfg_mode_start();
break;
case AccessoryInputStateIdle:
// restore accessory to default state
accessory_enable_input();
accessory_set_baudrate(AccessoryBaud115200);
accessory_set_power(false);
break;
case AccessoryInputStateImaging:
accessory_enable_input();
accessory_set_baudrate(AccessoryBaud921600);
accessory_set_power(false);
break;
case AccessoryInputStateMic:
// fallthrough
default:
break;
}
mutex_unlock(s_state_mutex);
PBL_LOG(LOG_LEVEL_DEBUG, "Setting accessory state to %u", state);
return true;
}

View file

@ -0,0 +1,29 @@
/*
* 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.
*/
#pragma once
#include <stdbool.h>
typedef enum {
AccessoryInputStateIdle,
AccessoryInputStateMfg,
AccessoryInputStateMic,
AccessoryInputStateImaging,
} AccessoryInputState;
void accessory_manager_init(void);
bool accessory_manager_set_state(AccessoryInputState state);

View file

@ -0,0 +1,86 @@
/*
* 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.
*/
#include "console/prompt.h"
#include "drivers/accessory.h"
#include "services/common/system_task.h"
#include "system/logging.h"
#include "util/likely.h"
#include "util/math.h"
#include <stdint.h>
#include <string.h>
static void prv_command_response_callback(const char* response) {
accessory_send_data((const uint8_t*) response, strlen(response));
accessory_send_data((const uint8_t*) "\r\n", sizeof(char) * 2);
}
static void prv_display_prompt(void) {
accessory_send_data((const uint8_t*) ">", sizeof(char));
}
static PromptContext s_prompt_context = {
.response_callback = prv_command_response_callback,
.command_complete_callback = prv_display_prompt,
};
static void prv_execute_command(void *data) {
PromptContext *prompt_context = (PromptContext *)data;
// Copy the command and append a NULL so we can print it for debugging purposes
char buffer[40];
size_t cropped_length = MIN(sizeof(buffer) - 1, prompt_context->write_index);
memcpy(buffer, prompt_context->buffer, cropped_length);
buffer[cropped_length] = 0;
PBL_LOG(LOG_LEVEL_DEBUG, "Exec command <%s>", buffer);
prompt_context_execute(prompt_context);
}
void accessory_mfg_mode_start(void) {
#ifdef DISABLE_PROMPT
return;
#else
prv_display_prompt();
#endif
}
bool accessory_mfg_mode_handle_char(char c) {
// Note: You're in an interrupt here, be careful
#if DISABLE_PROMPT
return false;
#else
if (UNLIKELY(prompt_command_is_executing())) {
return false;
}
bool should_context_switch = false;
if (LIKELY(c >= 0x20 && c < 127)) {
prompt_context_append_char(&s_prompt_context, c);
} else if (UNLIKELY(c == 0xd)) { // Enter key
system_task_add_callback_from_isr(
prv_execute_command, &s_prompt_context, &should_context_switch);
} else if (UNLIKELY(c == 0x3)) { // CTRL-C
// FIXME: Clean this up so this logic doesn't need to be duplicated here
s_prompt_context.write_index = 0;
prv_display_prompt();
}
return should_context_switch;
#endif
}

View file

@ -0,0 +1,26 @@
/*
* 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.
*/
#pragma once
#include <stdbool.h>
//! Call this as you're just entering manufacturing mode to do initalial setup.
void accessory_mfg_mode_start(void);
//! Called on an ISR to handle a character from the accessory connector.
bool accessory_mfg_mode_handle_char(char c);

View file

@ -0,0 +1,74 @@
/*
* 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.
*/
#include "services/common/analytics/analytics.h"
//! Stub for PRF
void analytics_init(void) {
}
void analytics_set(AnalyticsMetric metric, int64_t value, AnalyticsClient client) {
}
void analytics_max(AnalyticsMetric metric, int64_t val, AnalyticsClient client) {
}
void analytics_inc(AnalyticsMetric metric, AnalyticsClient client) {
}
void analytics_add(AnalyticsMetric metric, int64_t amount, AnalyticsClient client) {
}
void analytics_stopwatch_start(AnalyticsMetric metric, AnalyticsClient client) {
}
void analytics_stopwatch_start_at_rate(AnalyticsMetric metric,
uint32_t count_per_sec,
AnalyticsClient client) {
}
void analytics_stopwatch_stop(AnalyticsMetric metric) {
}
void analytics_event_app_oom(AnalyticsEvent type,
uint32_t requested_size, uint32_t total_size,
uint32_t total_free, uint32_t largest_free_block) {
}
void analytics_event_app_launch(const Uuid *uuid) {
}
void analytics_event_bt_connection_or_disconnection(AnalyticsEvent type, uint8_t reason) {
}
void analytics_event_bt_error(AnalyticsEvent type, uint32_t error) {
}
void analytics_event_bt_cc2564x_lockup_error(void) {
}
void analytics_event_bt_app_launch_error(uint8_t gatt_error) {
}
void analytics_event_session_close(bool is_system_session, const Uuid *optional_app_uuid,
CommSessionCloseReason reason, uint16_t session_duration_mins) {
}
void analytics_event_bt_le_disconnection(uint8_t reason, uint8_t remote_bt_version,
uint16_t remote_bt_company_id,
uint16_t remote_bt_subversion) {
}

View file

@ -0,0 +1,35 @@
/*
* 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.
*/
#include "services/common/analytics/analytics.h"
#include "services/common/analytics/analytics_event.h"
//! Stub for PRF
void sys_analytics_set(AnalyticsMetric metric, uint64_t value, AnalyticsClient client) {
}
void sys_analytics_add(AnalyticsMetric metric, uint64_t increment, AnalyticsClient client) {
}
void sys_analytics_inc(AnalyticsMetric metric, AnalyticsClient client) {
}
void sys_analytics_max(AnalyticsMetric metric, int64_t val, AnalyticsClient client) {
}
void sys_analytics_logging_log_event(AnalyticsEventBlob *event_blob) {
}

View file

@ -0,0 +1,42 @@
/*
* 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.
*/
#include "util/uuid.h"
#include "services/common/analytics/analytics_event.h"
#include "services/common/comm_session/session_internal.h"
//! Stub for PRF
void analytics_event_crash(uint8_t crash_code, uint32_t link_register) {
}
void analytics_event_local_bt_disconnect(uint16_t handle, uint32_t lr) {
}
typedef struct CommSession CommSession;
void analytics_event_put_byte_stats(
CommSession *session, bool crc_good, uint8_t type,
uint32_t bytes_transferred, uint32_t elapsed_time_ms,
uint32_t conn_events, uint32_t sync_errors, uint32_t skip_errors, uint32_t other_errors) {
}
void analytics_event_PPoGATT_disconnect(time_t timestamp, bool successful_reconnect) {
}
void analytics_event_get_bytes_stats(
CommSession *session, uint8_t type, uint32_t bytes_transferred, uint32_t elapsed_time_ms,
uint32_t conn_events, uint32_t sync_errors, uint32_t skip_errors, uint32_t other_errors) {
}

View file

@ -0,0 +1,30 @@
/*
* 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.
*/
#include "services/normal/bluetooth/ble_hrm.h"
bool ble_hrm_is_supported_and_enabled(void) {
return false;
}
void ble_hrm_handle_disconnection(GAPLEConnection *connection) {
}
void ble_hrm_init(void) {
}
void ble_hrm_deinit(void) {
}

View file

@ -0,0 +1,382 @@
/*
* 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.
*/
#include "services/common/bluetooth/bluetooth_persistent_storage.h"
#include "comm/ble/gap_le_connect.h"
#include "comm/ble/gap_le_slave_reconnect.h"
#include "comm/bt_lock.h"
#include "services/common/bluetooth/pairability.h"
#include "services/common/analytics/analytics.h"
#include "services/normal/settings/settings_file.h"
#include "services/common/shared_prf_storage/shared_prf_storage.h"
#include "comm/ble/kernel_le_client/kernel_le_client.h"
#include "system/logging.h"
#include <bluetooth/bluetooth_types.h>
#include <bluetooth/bonding_sync.h>
#include <bluetooth/features.h>
#include <btutil/bt_device.h>
#include <btutil/sm_util.h>
//! This is just an interface for the shared PRF storage
//! These don't matter at all
#define BLE_BONDING_ID (0)
#define BT_CLASSIC_BONDING_ID (1)
///////////////////////////////////////////////////////////////////////////////////////////////////
//! BLE Pairing Info
static void prv_call_ble_bonding_change_handlers(BTBondingID bonding, BtPersistBondingOp op) {
gap_le_connect_handle_bonding_change(bonding, op);
kernel_le_client_handle_bonding_change(bonding, op);
bt_pairability_update_due_to_bonding_change();
}
static BTBondingID prv_bt_persistent_storage_store_ble_pairing(
const SMPairingInfo *new_pairing_info, bool is_gateway, bool requires_address_pinning,
uint8_t flags, const char *device_name, BtPersistBondingOp op) {
if (new_pairing_info && is_gateway) {
shared_prf_storage_store_ble_pairing_data(new_pairing_info, device_name,
requires_address_pinning,
flags);
prv_call_ble_bonding_change_handlers(BLE_BONDING_ID, op);
return BLE_BONDING_ID;
}
return BT_BONDING_ID_INVALID;
}
bool bt_persistent_storage_set_ble_pinned_address(const BTDeviceAddress *addr) {
shared_prf_storage_set_ble_pinned_address(addr);
return true;
}
bool bt_persistent_storage_has_pinned_ble_pairings(void) {
bool requires_address_pinning_out = false;
shared_prf_storage_get_ble_pairing_data(NULL, NULL, &requires_address_pinning_out, NULL);
return requires_address_pinning_out;
}
bool bt_persistent_storage_get_ble_pinned_address(BTDeviceAddress *address_out) {
return shared_prf_storage_get_ble_pinned_address(address_out);
}
BTBondingID bt_persistent_storage_store_ble_pairing(const SMPairingInfo *new_pairing_info,
bool is_gateway, const char *device_name,
bool requires_address_pinning,
uint8_t flags) {
// We only have one slot in PRF and all pairing info (except the device
// name) will arrive in one-shot so anytime this routine gets called it
// means we have 'added' a new pairing
bool is_updating_existing = false;
SMPairingInfo existing_pairing_info;
if (shared_prf_storage_get_ble_pairing_data(&existing_pairing_info, NULL, NULL, NULL)) {
if (sm_is_pairing_info_equal_identity(new_pairing_info, &existing_pairing_info)) {
// Treat re-pairing an existing device as an "update" instead of deletion+addition,
// because there is only one bonding ID that gets re-used, a deletion would otherwise cause a
// disconnection to happen. See PBL-24737.
PBL_LOG(LOG_LEVEL_INFO, "Re-pairing previously paired LE device");
is_updating_existing = true;
} else {
// Since we only have one slot, this means we are about to delete what was
// already there so handle the deletion if a valid pairing was stored
prv_call_ble_bonding_change_handlers(BLE_BONDING_ID, BtPersistBondingOpWillDelete);
}
}
BtPersistBondingOp pairing_op =
is_updating_existing ? BtPersistBondingOpDidChange : BtPersistBondingOpDidAdd;
return (prv_bt_persistent_storage_store_ble_pairing(new_pairing_info, is_gateway,
requires_address_pinning,
flags, device_name, pairing_op));
}
bool bt_persistent_storage_update_ble_device_name(BTBondingID bonding, const char *device_name) {
// A device name has come in, update the name of our currently paired device
SMPairingInfo data = {};
bool requires_address_pinning = false;
uint8_t flags = 0;
if (!shared_prf_storage_get_ble_pairing_data(&data, NULL, &requires_address_pinning, &flags)) {
PBL_LOG(LOG_LEVEL_ERROR, "Tried to store device name, but pairing no longer around.");
return false;
}
// In PRF, only the gateway should get paired, so default to "true":
return (BT_BONDING_ID_INVALID !=
prv_bt_persistent_storage_store_ble_pairing(&data, true /* is_gateway */,
requires_address_pinning, flags,
device_name, BtPersistBondingOpDidChange));
}
static void prv_remove_ble_bonding_from_bt_driver(void) {
if (!bt_ctl_is_bluetooth_running()) {
return;
}
BleBonding bonding = {
.is_gateway = true,
};
if (!shared_prf_storage_get_ble_pairing_data(&bonding.pairing_info, NULL, NULL, NULL)) {
return;
}
bt_driver_handle_host_removed_bonding(&bonding);
}
void bt_persistent_storage_delete_ble_pairing_by_id(BTBondingID bonding) {
prv_remove_ble_bonding_from_bt_driver();
shared_prf_storage_erase_ble_pairing_data();
prv_call_ble_bonding_change_handlers(bonding, BtPersistBondingOpWillDelete);
}
bool bt_persistent_storage_get_ble_pairing_by_id(BTBondingID bonding,
SMIdentityResolvingKey *IRK_out,
BTDeviceInternal *device_out,
char *name_out) {
SMPairingInfo data;
char name[BT_DEVICE_NAME_BUFFER_SIZE];
if (!shared_prf_storage_get_ble_pairing_data(&data, name, NULL, NULL)) {
return false;
}
if (IRK_out) {
*IRK_out = data.irk;
}
if (device_out) {
*device_out = data.identity;
}
if (name_out) {
strncpy(name_out, name, BT_DEVICE_NAME_BUFFER_SIZE);
name_out[BT_DEVICE_NAME_BUFFER_SIZE - 1] = 0;
}
return true;
}
bool bt_persistent_storage_get_ble_pairing_by_addr(const BTDeviceInternal *device,
SMIdentityResolvingKey *IRK_out,
char name[BT_DEVICE_NAME_BUFFER_SIZE]) {
BTDeviceInternal device_out = {};
bool rv = bt_persistent_storage_get_ble_pairing_by_id(BLE_BONDING_ID, IRK_out, &device_out, name);
return (rv && bt_device_equal(&device->opaque, &device_out.opaque));
}
void bt_persistent_storage_set_active_ble_gateway(BTBondingID bonding) {
}
BTBondingID bt_persistent_storage_get_ble_ancs_bonding(void) {
return BLE_BONDING_ID;
}
bool bt_persistent_storage_is_ble_ancs_bonding(BTBondingID bonding) {
return bt_persistent_storage_get_ble_pairing_by_id(BLE_BONDING_ID, NULL, NULL, NULL);
}
bool bt_persistent_storage_has_ble_ancs_bonding(void) {
return bt_persistent_storage_get_ble_pairing_by_id(BLE_BONDING_ID, NULL, NULL, NULL);
}
bool bt_persistent_storage_has_active_ble_gateway_bonding(void) {
return bt_persistent_storage_get_ble_pairing_by_id(BLE_BONDING_ID, NULL, NULL, NULL);
}
void bt_persistent_storage_for_each_ble_pairing(BtPersistBondingDBEachBLE cb, void *context) {
return;
}
void bt_persistent_storage_register_existing_ble_bondings(void) {
BleBonding bonding = {};
uint8_t flags;
if (!shared_prf_storage_get_ble_pairing_data(&bonding.pairing_info, NULL, NULL, &flags)) {
return;
}
bonding.is_gateway = true;
bonding.flags = flags;
bt_driver_handle_host_added_bonding(&bonding);
}
///////////////////////////////////////////////////////////////////////////////////////////////////
//! BT Classic Pairing Info
static void prv_call_bt_classic_bonding_change_handlers(BTBondingID bonding,
BtPersistBondingOp op) {
bt_pairability_update_due_to_bonding_change();
}
BTBondingID bt_persistent_storage_store_bt_classic_pairing(BTDeviceAddress *address,
SM128BitKey *key,
char *name, uint8_t *platform_bits) {
if (address) {
if (key) {
// We should really collect all of the classic info and store once its complete
// However, since platform bits are going to be the last piece collected its ok
// to 0 it out here
uint8_t platform_bits_val = platform_bits ? *platform_bits : 0x00;
shared_prf_storage_store_bt_classic_pairing_data(address, name, key, platform_bits_val);
}
if (platform_bits) {
shared_prf_storage_store_platform_bits(*platform_bits);
}
prv_call_bt_classic_bonding_change_handlers(BT_CLASSIC_BONDING_ID, BtPersistBondingOpDidChange);
return BT_CLASSIC_BONDING_ID;
}
return BT_BONDING_ID_INVALID;
}
void bt_persistent_storage_delete_bt_classic_pairing_by_id(BTBondingID bonding) {
shared_prf_storage_erase_bt_classic_pairing_data();
prv_call_bt_classic_bonding_change_handlers(bonding, BtPersistBondingOpWillDelete);
bt_pairability_update_due_to_bonding_change();
}
void bt_persistent_storage_delete_bt_classic_pairing_by_addr(const BTDeviceAddress *bd_addr) {
if (!bd_addr) {
return;
}
bt_persistent_storage_delete_bt_classic_pairing_by_id(BT_CLASSIC_BONDING_ID);
}
bool bt_persistent_storage_get_bt_classic_pairing_by_id(BTBondingID bonding,
BTDeviceAddress *address_out,
SM128BitKey *link_key_out,
char *name_out,
uint8_t *platform_bits_out) {
BTDeviceAddress addr;
char name[BT_DEVICE_NAME_BUFFER_SIZE];
SM128BitKey link_key;
uint8_t platform_bits;
if (!shared_prf_storage_get_bt_classic_pairing_data(&addr, name, &link_key, &platform_bits)) {
return false;
}
if (address_out) {
*address_out = addr;
}
if (link_key_out) {
*link_key_out = link_key;
}
if (name_out) {
strncpy(name_out, name, BT_DEVICE_NAME_BUFFER_SIZE);
name_out[BT_DEVICE_NAME_BUFFER_SIZE - 1] = 0;
}
if (platform_bits_out) {
*platform_bits_out = platform_bits;
}
return true;
}
BTBondingID bt_persistent_storage_get_bt_classic_pairing_by_addr(BTDeviceAddress* addr_in,
SM128BitKey *link_key_out,
char *name_out,
uint8_t *platform_bits_out) {
if (bt_persistent_storage_get_bt_classic_pairing_by_id(BT_CLASSIC_BONDING_ID, NULL, link_key_out,
name_out, platform_bits_out)) {
return BT_CLASSIC_BONDING_ID;
}
return BT_BONDING_ID_INVALID;
}
bool bt_persistent_storage_has_active_bt_classic_gateway_bonding(void) {
return bt_persistent_storage_get_bt_classic_pairing_by_id(BT_CLASSIC_BONDING_ID,
NULL, NULL, NULL, NULL);
}
void bt_persistent_storage_for_each_bt_classic_pairing(BtPersistBondingDBEachBTClassic cb,
void *context) {
return;
}
///////////////////////////////////////////////////////////////////////////////////////////////////
//! Local Device Info
void bt_persistent_storage_set_active_gateway(BTBondingID bonding) {
return;
}
bool bt_persistent_storage_get_active_gateway(BTBondingID *bonding_out,
BtPersistBondingType *type_out) {
if (bt_persistent_storage_get_bt_classic_pairing_by_id(BT_CLASSIC_BONDING_ID,
NULL, NULL, NULL, NULL)) {
*bonding_out = BT_CLASSIC_BONDING_ID;
*type_out = BtPersistBondingTypeBTClassic;
return true;
} else {
return false;
}
}
bool bt_persistent_storage_is_unfaithful(void) {
return true;
}
void bt_persistent_storage_set_unfaithful(bool is_unfaithful) {
return;
}
bool bt_persistent_storage_get_root_key(SMRootKeyType key_type, SM128BitKey *key_out) {
return shared_prf_storage_get_root_key(key_type, key_out);
}
void bt_persistent_storage_set_root_keys(SM128BitKey *keys_in) {
shared_prf_storage_set_root_keys(keys_in);
}
bool bt_persistent_storage_get_local_device_name(char *local_device_name_out, size_t max_size) {
return shared_prf_storage_get_local_device_name(local_device_name_out, max_size);
}
void bt_persistent_storage_set_local_device_name(char *local_device_name, size_t size) {
shared_prf_storage_set_local_device_name(local_device_name);
}
///////////////////////////////////////////////////////////////////////////////////////////////////
//! Remote Device Info
void bt_persistent_storage_get_cached_system_capabilities(
PebbleProtocolCapabilities *capabilities_out) {
}
void bt_persistent_storage_set_cached_system_capabilities(
const PebbleProtocolCapabilities *capabilities) {
}
///////////////////////////////////////////////////////////////////////////////////////////////////
//! Common
void bt_persistent_storage_init(void) {
}
void bt_persistent_storage_delete_all(void) {
}
void bt_persistent_storage_delete_all_pairings(void) {
bt_persistent_storage_delete_ble_pairing_by_id(BLE_BONDING_ID);
if (bt_driver_supports_bt_classic()) {
bt_persistent_storage_delete_bt_classic_pairing_by_id(BT_CLASSIC_BONDING_ID);
}
}

View file

@ -0,0 +1,22 @@
/*
* 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.
*/
#include "util/uuid.h"
void comm_session_app_session_capabilities_init(void) {
}
void comm_session_app_session_capabilities_evict(const Uuid *app_uuid) {
}

View file

@ -0,0 +1,105 @@
/*
* 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.
*/
#include "idle_watchdog.h"
#include "applib/event_service_client.h"
#include "comm/ble/gap_le_connection.h"
#include "services/common/battery/battery_monitor.h"
#include "services/common/regular_timer.h"
#include "services/common/system_task.h"
#include "system/reboot_reason.h"
#include "kernel/util/standby.h"
#include <bluetooth/classic_connect.h>
#define PRF_IDLE_TIMEOUT_MINUTES 10
static RegularTimerInfo s_is_idle_timer;
static void prv_handle_watchdog_timeout_cb(void *not_used) {
GAPLEConnection *le_connection = gap_le_connection_any();
if (le_connection || bt_driver_classic_is_connected()) {
// We are still connected, don't shut down
return;
}
BatteryChargeState current_state = battery_get_charge_state();
if (current_state.is_plugged) {
// We are plugged in, don't shut down
return;
}
enter_standby(RebootReasonCode_PrfIdle);
}
static void prv_handle_watchdog_timeout(void *not_used) {
system_task_add_callback(prv_handle_watchdog_timeout_cb, NULL);
}
static void prv_start_watchdog(void) {
s_is_idle_timer = (const RegularTimerInfo) {
.cb = prv_handle_watchdog_timeout,
};
regular_timer_add_multiminute_callback(&s_is_idle_timer,
PRF_IDLE_TIMEOUT_MINUTES);
}
void prv_watchdog_feed(PebbleEvent *e, void *context) {
if (regular_timer_is_scheduled(&s_is_idle_timer)) {
prv_start_watchdog();
}
}
void prf_idle_watchdog_start(void) {
// Possible scenario: connect -> 9.9 minutes elapse -> disconnect
// Feeding the watchdog on bt events ensures we don't shutdown after being
// idle for only 0.1 minutes
static EventServiceInfo bt_event_info;
bt_event_info = (EventServiceInfo) {
.type = PEBBLE_BT_CONNECTION_EVENT,
.handler = prv_watchdog_feed,
};
event_service_client_subscribe(&bt_event_info);
// Possible scenario: plug in watch to charge -> 9.9 minutes elapse -> remove watch from charger
// Feeding the watchdog on usb events ensures we don't shutdown as the watch is about to be used
static EventServiceInfo battery_event_info;
battery_event_info = (EventServiceInfo) {
.type = PEBBLE_BATTERY_CONNECTION_EVENT,
.handler = prv_watchdog_feed,
};
event_service_client_subscribe(&battery_event_info);
// The watch is clearly being used if a button was pressed
static EventServiceInfo button_event_info;
button_event_info = (EventServiceInfo) {
.type = PEBBLE_BUTTON_DOWN_EVENT,
.handler = prv_watchdog_feed,
};
event_service_client_subscribe(&button_event_info);
prv_start_watchdog();
}
void prf_idle_watchdog_stop(void) {
if (regular_timer_is_scheduled(&s_is_idle_timer)) {
regular_timer_remove_callback(&s_is_idle_timer);
}
}

View file

@ -0,0 +1,29 @@
/*
* 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.
*/
#pragma once
#include <stdbool.h>
//! Auto-shutdown when idle in PRF to increase the changes of getting Pebbles shipped
//! that have some level of battery charge in them.
//! Start listening for battery connection, bluetooth connection, and button events to feed a
//! watchdog.
void prf_idle_watchdog_start(void);
//! Stop the watchdog. We will no longer reset if events don't occur frequently enough.
void prf_idle_watchdog_stop(void);