Files
pebble/src/fw/services/common/legacy/registry_private.c
Josh Soref de2ef95804 spelling: beginning
Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>
2025-01-28 21:19:00 -05:00

352 lines
12 KiB
C

/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "drivers/flash.h"
#include "flash_region/flash_region.h"
#include "services/common/legacy/registry_private.h"
#include "system/logging.h"
#include "system/passert.h"
#include <string.h>
#include <stdbool.h>
// Header patterns must precede any registry written to flash.
// They are used to indicate if a registry is active. Only one registry should be
// active at a time
static const uint8_t s_active_header[] = {0xff, 0x00, 0xff};
static const uint8_t s_inactive_header[] = {0x00, 0x00, 0x00};
////////////////////////////////////////////////////////////
// Functions for manipulating the cursor in flash
////////////////////////////////////////////////////////////
static int round_up_to_nearest_subsector(uint32_t addr) {
// - 1 because if we're currently on a subsector border we don't want to round up to the
// next one.
return (addr + SUBSECTOR_SIZE_BYTES - 1) & SUBSECTOR_ADDR_MASK;
}
static bool is_cursor_at_active_registry(uint32_t cursor) {
uint8_t header[REGISTRY_HEADER_SIZE_BYTES];
flash_read_bytes(header, cursor, REGISTRY_HEADER_SIZE_BYTES);
return memcmp(header, s_active_header, REGISTRY_HEADER_SIZE_BYTES) == 0;
}
static uint32_t get_next_cursor_position(uint32_t old_address, Registry* registry) {
const uint32_t new_address =
round_up_to_nearest_subsector(old_address + registry->total_buffer_size_bytes);
const bool is_space_for_registry =
new_address + registry->total_buffer_size_bytes < registry->cursor->end;
if (is_space_for_registry) {
return new_address;
}
return registry->cursor->begin;
}
static void move_cursor_to_active_registry(Registry* registry) {
// Search for an active registry, starting at the start of the registry sector
// in flash (`REGISTRY_FLASH_BEGIN`).
// If cursor loops back to the start of the registry sector, then the
// entire sector has been scanned and no registry was found. In that case, leave
// the cursor in an invalid position (address 0xffff...)
uint32_t temp_address = registry->cursor->begin;
registry->cursor->address = FLASH_CURSOR_UNINITIALIZED;
do {
if (is_cursor_at_active_registry(temp_address)) {
registry->cursor->address = temp_address;
break;
}
temp_address = get_next_cursor_position(temp_address, registry);
} while (temp_address != registry->cursor->begin);
}
////////////////////////////////////////////////////////////
// Registry data structure
////////////////////////////////////////////////////////////
static int registry_get_next_available_index(Registry* registry) {
for (int i = 0; i < registry->num_records; i++) {
Record* r = registry->records + i;
if (r->active == false) {
return i;
}
}
return -1;
}
int registry_private_add(const char* key, const uint8_t key_length, const uint8_t* uuid,
uint8_t description, const uint8_t* value, uint8_t value_length, Registry* registry) {
if (value_length >= MAX_VALUE_SIZE_BYTES) {
PBL_LOG(LOG_LEVEL_WARNING, "Length of record value exceeds maximum length.");
return -1;
}
if (key_length > MAX_KEY_SIZE_BYTES) {
PBL_LOG(LOG_LEVEL_WARNING, "Length of record key exceeds maximum length.");
return -1;
}
Record* r = registry_private_get(key, key_length, uuid, registry);
if (r) {
if (r->value_length == value_length &&
r->description == description &&
memcmp(r->value, value, value_length) == 0) {
PBL_LOG(LOG_LEVEL_DEBUG, "Key & value already exist.");
return 0;
}
PBL_LOG(LOG_LEVEL_DEBUG, "Key already exists. Updating record.");
r->description = description;
memcpy(r->value, value, value_length);
r->value_length = value_length;
registry->is_different_from_flash = true;
return 0;
}
int idx = registry_get_next_available_index(registry);
if (idx < 0) {
PBL_LOG(LOG_LEVEL_WARNING, "Registry full.");
return -1;
}
PBL_ASSERTN(idx >= 0 && idx < registry->num_records);
r = registry->records + idx;
r->active = true;
strncpy(r->key, key, key_length);
r->key[MAX_KEY_SIZE_BYTES - 1] = '\0';
r->key_length = key_length;
memcpy(r->uuid, uuid, UUID_SIZE_BYTES);
r->description = description;
memcpy(r->value, value, value_length);
r->value_length = value_length;
registry->is_different_from_flash = true;
PBL_LOG(LOG_LEVEL_DEBUG, "Writing new key: %s", r->key);
return 0;
}
void registry_record_print(Record* r) {
PBL_LOG_VERBOSE("Active:\n\t");
if (r->active) {
PBL_LOG_VERBOSE("True");
} else {
PBL_LOG_VERBOSE("False");
}
PBL_LOG_VERBOSE("\n");
PBL_LOG_VERBOSE("Key is:\n\t");
for (int i = 0; i < r->key_length; i++) {
PBL_LOG_VERBOSE("'%c'", r->key[i]);
}
PBL_LOG_VERBOSE("\n");
PBL_LOG_VERBOSE("UUID is:\n\t");
for (int i = 0; i < UUID_SIZE_BYTES; i++) {
PBL_LOG_VERBOSE("%#.2hx, ",r->uuid[i]);
}
PBL_LOG_VERBOSE("\n");
PBL_LOG_VERBOSE("Description is:\n\t%#.2hx\n", r->description);
PBL_LOG_VERBOSE("Value is:\n\t");
for (int i = 0; i < r->value_length; i++) {
PBL_LOG_VERBOSE("%#.2hx, ",r->value[i]);
}
PBL_LOG_VERBOSE("\n");
}
static bool record_compare(Record* r, const char* key, const uint8_t key_length, const uint8_t* uuid) {
if ((r->key_length == key_length) &&
(memcmp(r->uuid, uuid, UUID_SIZE_BYTES) == 0) &&
(memcmp(r->key, key, r->key_length) == 0)) {
return true;
}
return false;
}
static int record_get_index(const char* key, const uint8_t key_length, const uint8_t* uuid, Registry* registry) {
for (int i = 0; i < registry->num_records; i++) {
Record* r = registry->records + i;
if (r->active) {
if (record_compare(r, key, key_length, uuid)) {
return i;
}
}
}
return -1;
}
Record* registry_private_get(const char* key, const uint8_t key_length,
const uint8_t* uuid, Registry* registry) {
const int idx = record_get_index(key, key_length, uuid, registry);
PBL_ASSERTN(idx < registry->num_records);
if (idx >= 0) {
return registry->records + idx;
}
return NULL;
}
void registry_private_remove_all(const uint8_t* uuid, Registry* registry) {
for (int i = 0; i < registry->num_records; i++) {
Record* r = registry->records + i;
if (r->active) {
bool uuid_match = memcmp(r->uuid, uuid, UUID_SIZE_BYTES) == 0;
if (uuid_match) {
r->active = false;
registry->is_different_from_flash = true;
}
}
}
}
int registry_private_remove(const char* key, const uint8_t key_length, const uint8_t* uuid,
Registry* registry) {
const int idx = record_get_index(key, key_length, uuid, registry);
PBL_ASSERTN(idx < registry->num_records);
if (idx >= 0) {
Record* r = registry->records + idx;
r->active = false;
registry->is_different_from_flash = true;
return 0;
}
return -1;
}
////////////////////////////////////////////////////////////
// Read and write from flash
////////////////////////////////////////////////////////////
static void registry_set_header(uint32_t cursor, bool active) {
const uint8_t* header = s_inactive_header;
if (active) {
header = s_active_header;
}
flash_write_bytes(header, cursor, REGISTRY_HEADER_SIZE_BYTES);
}
//! Verifies the flash cursor is at the active registry (as initialized by `registry_init()`.
//! Cursor must be subsector aligned and within the bounds of REGISTRY_FLASH_BEGIN and
//! REGISTRY_FLASH_END.
static void prv_assert_valid_cursor(const RegistryCursor *cursor) {
const bool is_addr_subsector_aligned = (cursor->address % (SUBSECTOR_SIZE_BYTES)) == 0;
PBL_ASSERTN(is_addr_subsector_aligned &&
cursor->address >= cursor->begin &&
cursor->address < cursor->end);
}
void registry_private_read_from_flash(Registry* registry) {
prv_assert_valid_cursor(registry->cursor);
uint32_t registry_addr = registry->cursor->address + REGISTRY_HEADER_SIZE_BYTES;
flash_read_bytes((uint8_t*)registry->records, registry_addr, registry->registry_size_bytes);
registry->is_different_from_flash = false;
}
static void prv_write_next_registry(Registry* registry) {
// Compute the addresses of the subsectors where the next registry will
// be stored in flash; erase those subsectors
const uint32_t next_start_address = get_next_cursor_position(registry->cursor->address, registry);
const uint32_t next_end_address =
round_up_to_nearest_subsector(next_start_address + registry->total_buffer_size_bytes);
flash_region_erase_optimal_range(next_start_address, next_start_address,
next_end_address, next_end_address);
// Write the next header + content
registry_set_header(next_start_address, true);
const uint32_t data_start_address = next_start_address + REGISTRY_HEADER_SIZE_BYTES;
flash_write_bytes((uint8_t*)registry->records, data_start_address,
registry->registry_size_bytes);
registry->cursor->address = next_start_address;
}
void registry_private_write_to_flash(Registry* registry) {
// If the flash cursor is 0 (if no registry currently exists in flash), then set
// the flash cursor to REGISTRY_FLASH_BEGIN.
//
// Write a registry to flash by erasing the following subsectors, writing the
// registry to those subsectors. Then, set the current registry to be
// inactive and advance the flash cursor.
// Cursor not initialized, no registry exists
if (registry->cursor->address == (unsigned)FLASH_CURSOR_UNINITIALIZED) {
registry->cursor->address = registry->cursor->begin;
}
PBL_LOG(LOG_LEVEL_DEBUG, "Writing registry to flash...");
prv_assert_valid_cursor(registry->cursor);
// Mark the previous registry as invalid
registry_set_header(registry->cursor->address, false);
// Erase the spot for the next registry and write to it
prv_write_next_registry(registry);
registry->is_different_from_flash = false;
}
void registry_private_init(Registry* registry) {
// This fills the statically allocated registry with zeroes, tests that
// the registry is large enough to fit in memory using PBL_ASSERT and initializes
// the `flash cursor`---the flash address of the active registry's header.
//
// Registries are stored in flash with a preceeding three-byte header. This
// header is used to identify if the registry is active. The pattern 0xff,
// 0x00, 0xff indicates a registry is active, 0x00, 0x00, 0x00 indicates a
// registry is inactive. One registry should be active at any time. If no
// active registry can be found when `registry_init()` is called, an empty registry
// is written.
//
// The flash cursor starts at the beginning of the registry's SPIFlash
// address (`REGISTRY_FLASH_BEGIN`), and is incremented to the next completely
// empty subsector every time the registry is written to flash.
memset(registry->records, 0, registry->registry_size_bytes);
// Test that there is enough space in SPIFlash to write the registry
int flash_space_available_bytes = registry->cursor->end - registry->cursor->begin;
// Registry is too large to store in SPIFlash: allocate more space
PBL_ASSERTN((signed)registry->registry_size_bytes < flash_space_available_bytes);
move_cursor_to_active_registry(registry);
// Write empty registry if one does not exist
if (registry->cursor->address == (unsigned)FLASH_CURSOR_UNINITIALIZED) {
registry_private_write_to_flash(registry);
} else {
registry_private_read_from_flash(registry);
}
prv_assert_valid_cursor(registry->cursor);
}