mirror of
https://github.com/google/pebble.git
synced 2025-06-21 08:40:36 +00:00
Import of the watch repository from Pebble
This commit is contained in:
commit
3b92768480
10334 changed files with 2564465 additions and 0 deletions
69
src/fw/services/prf/accessory/accessory_idle_mode.c
Normal file
69
src/fw/services/prf/accessory/accessory_idle_mode.c
Normal 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;
|
||||
}
|
||||
|
22
src/fw/services/prf/accessory/accessory_idle_mode.h
Normal file
22
src/fw/services/prf/accessory/accessory_idle_mode.h
Normal 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);
|
||||
|
606
src/fw/services/prf/accessory/accessory_imaging.c
Normal file
606
src/fw/services/prf/accessory/accessory_imaging.c
Normal 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 *)®ion_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();
|
||||
}
|
23
src/fw/services/prf/accessory/accessory_imaging.h
Normal file
23
src/fw/services/prf/accessory/accessory_imaging.h
Normal 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);
|
132
src/fw/services/prf/accessory/accessory_manager.c
Normal file
132
src/fw/services/prf/accessory/accessory_manager.c
Normal 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;
|
||||
}
|
29
src/fw/services/prf/accessory/accessory_manager.h
Normal file
29
src/fw/services/prf/accessory/accessory_manager.h
Normal 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);
|
86
src/fw/services/prf/accessory/accessory_mfg_mode.c
Normal file
86
src/fw/services/prf/accessory/accessory_mfg_mode.c
Normal 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
|
||||
}
|
||||
|
26
src/fw/services/prf/accessory/accessory_mfg_mode.h
Normal file
26
src/fw/services/prf/accessory/accessory_mfg_mode.h
Normal 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);
|
||||
|
74
src/fw/services/prf/analytics/analytics.c
Normal file
74
src/fw/services/prf/analytics/analytics.c
Normal 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) {
|
||||
}
|
35
src/fw/services/prf/analytics/analytics_data_syscalls.c
Normal file
35
src/fw/services/prf/analytics/analytics_data_syscalls.c
Normal 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) {
|
||||
}
|
42
src/fw/services/prf/analytics/analytics_event.c
Normal file
42
src/fw/services/prf/analytics/analytics_event.c
Normal 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) {
|
||||
}
|
30
src/fw/services/prf/bluetooth/ble_hrm_stubs.c
Normal file
30
src/fw/services/prf/bluetooth/ble_hrm_stubs.c
Normal 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) {
|
||||
}
|
382
src/fw/services/prf/bluetooth/bluetooth_persistent_storage.c
Normal file
382
src/fw/services/prf/bluetooth/bluetooth_persistent_storage.c
Normal 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);
|
||||
}
|
||||
}
|
22
src/fw/services/prf/comm_session/app_session_capabilities.c
Normal file
22
src/fw/services/prf/comm_session/app_session_capabilities.c
Normal 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) {
|
||||
}
|
105
src/fw/services/prf/idle_watchdog.c
Normal file
105
src/fw/services/prf/idle_watchdog.c
Normal 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);
|
||||
}
|
||||
}
|
29
src/fw/services/prf/idle_watchdog.h
Normal file
29
src/fw/services/prf/idle_watchdog.h
Normal 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);
|
Loading…
Add table
Add a link
Reference in a new issue