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,16 @@
/*
* 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.
*/

209
src/fw/resource/resource.c Normal file
View file

@ -0,0 +1,209 @@
/*
* 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 "resource.h"
#include "resource_storage.h"
#include "resource_storage_builtin.h"
#include "resource_storage_flash.h"
#include "process_management/app_manager.h"
#include "drivers/flash.h"
#include "flash_region/flash_region.h"
#include "kernel/pbl_malloc.h"
#include "os/mutex.h"
#include "services/normal/process_management/app_storage.h"
#include "system/logging.h"
#include "system/passert.h"
// TODO: this may be replaced once apps become more dynamic
typedef struct {
ListNode list_node;
uint32_t id;
ResourceStoreEntry stored_resource;
} CachedResource;
PebbleRecursiveMutex *s_resource_mutex = NULL;
static CachedResource *s_resource_list = NULL;
static bool prv_resource_filter(ListNode *found_node, void *data) {
CachedResource *resource = (CachedResource *)found_node;
uint32_t resource_id = (uint32_t)data;
return (resource->id == resource_id);
}
static void prv_get_resource(ResAppNum app_num, uint32_t id, ResourceStoreEntry *entry) {
if (id < 1) {
*entry = (ResourceStoreEntry){0};
return;
}
mutex_lock_recursive(s_resource_mutex);
ListNode *node;
if (app_num == SYSTEM_APP &&
(node = list_find((ListNode *)s_resource_list, prv_resource_filter, (void *)(uintptr_t)id))) {
mutex_unlock_recursive(s_resource_mutex);
*entry = ((CachedResource *)node)->stored_resource;
return;
}
resource_storage_get_resource(app_num, id, entry);
mutex_unlock_recursive(s_resource_mutex);
}
//! initialize components needed for one apps resources
bool resource_init_app(ResAppNum app_num, const ResourceVersion *expected_version) {
// resource_id is ignored in this case, so we set it to 0
mutex_lock_recursive(s_resource_mutex);
bool rv = resource_storage_check(app_num, 0, expected_version);
mutex_unlock_recursive(s_resource_mutex);
return rv;
}
void resource_init(void) {
// see if there's a system bank waiting to be loaded
resource_storage_init();
s_resource_mutex = mutex_create_recursive();
}
uint32_t resource_get_and_cache(ResAppNum app_num, uint32_t resource_id) {
PBL_ASSERTN(app_num == SYSTEM_APP);
// get from resource store
mutex_lock_recursive(s_resource_mutex);
ResourceStoreEntry res;
resource_storage_get_resource(app_num, resource_id, &res);
if (res.id < 1) {
mutex_unlock_recursive(s_resource_mutex);
return 0;
}
// check if we already have something in cache for this resource
CachedResource *cached_resource = (CachedResource *)list_find((ListNode *)s_resource_list,
prv_resource_filter, (void *)(uintptr_t)resource_id);
if (cached_resource == NULL) {
cached_resource = kernel_malloc_check(sizeof(CachedResource));
*cached_resource = (CachedResource){};
cached_resource->id = resource_id;
s_resource_list = (CachedResource *)list_prepend((ListNode *)s_resource_list,
(ListNode *)cached_resource);
}
cached_resource->stored_resource = res;
mutex_unlock_recursive(s_resource_mutex);
return resource_id;
}
size_t resource_load_byte_range_system(ResAppNum app_num, uint32_t resource_id,
uint32_t offset, uint8_t *buffer, size_t num_bytes) {
PBL_ASSERTN(buffer);
if (!num_bytes) {
return 0;
}
mutex_lock_recursive(s_resource_mutex);
ResourceStoreEntry resource;
prv_get_resource(app_num, resource_id, &resource);
if (resource.id < 1) {
mutex_unlock_recursive(s_resource_mutex);
return 0;
}
if (offset + num_bytes > resource.length) {
if (offset >= resource.length) {
// Can't recover from trying to read from beyond the resource. Read nothing.
mutex_unlock_recursive(s_resource_mutex);
return 0;
}
// We want to stop the FW from doing this, so we added an assert
// but in the name of backwards compatibility, we let the app misbehave
num_bytes = resource.length - offset;
PBL_LOG(LOG_LEVEL_DEBUG, "Tried to read past end of resource, reading %d bytes",
(int)num_bytes);
}
size_t bytes_read = resource_storage_read(&resource, offset, buffer, num_bytes);
mutex_unlock_recursive(s_resource_mutex);
return bytes_read;
}
size_t resource_size(ResAppNum app_num, uint32_t resource_id) {
mutex_lock_recursive(s_resource_mutex);
ResourceStoreEntry resource;
prv_get_resource(app_num, resource_id, &resource);
mutex_unlock_recursive(s_resource_mutex);
return resource.length;
}
bool resource_bytes_are_readonly(void *bytes) {
return resource_storage_builtin_bytes_are_readonly(bytes) ||
resource_storage_flash_bytes_are_readonly(bytes);
}
const uint8_t *resource_get_readonly_bytes(ResAppNum app_num, uint32_t resource_id,
size_t *num_bytes_out, bool has_privileged_access) {
// we don't support memory-mapping for resources that don't belong to the system
if (app_num != SYSTEM_APP) {
return NULL;
}
mutex_lock_recursive(s_resource_mutex);
// FIXME PBL-28781: This operation touches flash. Even though this is the cleanest approach
// to detect if the resource is a builtin, it is a slow one. We should instead only search
// in the builtin table for the resource_ids and if there are no matches, bail early.
ResourceStoreEntry resource;
prv_get_resource(app_num, resource_id, &resource);
mutex_unlock_recursive(s_resource_mutex);
if (num_bytes_out) {
*num_bytes_out = resource.length;
}
return resource.impl->readonly_bytes(&resource, has_privileged_access);
}
ResourceVersion resource_get_version(ResAppNum app_num, uint32_t resource_id) {
mutex_lock_recursive(s_resource_mutex);
ResourceVersion v = resource_storage_get_version(app_num, resource_id);
mutex_unlock_recursive(s_resource_mutex);
return v;
}
ResourceVersion resource_get_system_version(void) {
return resource_get_version(0, 0);
}
bool resource_is_valid(ResAppNum app_num, uint32_t resource_id) {
mutex_lock_recursive(s_resource_mutex);
bool rv = resource_storage_check(app_num, resource_id, NULL /* No expected version */);
if (rv) {
ResourceStoreEntry entry;
prv_get_resource(app_num, resource_id, &entry);
rv = (entry.id != 0);
}
mutex_unlock_recursive(s_resource_mutex);
return rv;
}
bool resource_version_matches(const ResourceVersion *v1, const ResourceVersion *v2) {
return (v1->crc == v2->crc);
}

106
src/fw/resource/resource.h Normal file
View file

@ -0,0 +1,106 @@
/*
* 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 <stdint.h>
#include <stdbool.h>
#include <stdlib.h>
#include "applib/applib_resource.h"
#include "util/attributes.h"
//! @addtogroup Foundation
//! @{
//! @addtogroup Resources
//! @{
// TODO: find a cleaner way to do this
typedef uint32_t ResAppNum;
// Needs to be a #define so it can be used in static initializers
#define SYSTEM_APP ((ResAppNum)0)
//! The version information baked into every binary resource pack.
typedef struct PACKED {
//! The crc of the resource pack between content_start and last_used. See check_bank_crc for how this is calculated.
uint32_t crc;
//! Just an identifier, not actually compared to anything.
uint32_t timestamp;
} ResourceVersion;
//! Types used by pfs_watch_resource()
typedef void (*ResourceChangedCallback)(void *data);
typedef void *ResourceCallbackHandle;
// inits system resources, and sets app resources to an unloaded state
void resource_init(void);
//! @param app_num the resource app to initialize
//! @param version Optional parameter that indicates which version information we should check
// against. If NULL is provided no version check is performed.
//! @return True if the resources are valid, false otherwise.
bool resource_init_app(ResAppNum app_num, const ResourceVersion *version);
size_t resource_size(ResAppNum app_num, uint32_t resource_id);
//! Check that a resource id actually exists
bool resource_is_valid(ResAppNum app_num, uint32_t resource_id);
//! @internal
uint32_t resource_get_and_cache(ResAppNum app_num, uint32_t resource_id);
//! @internal
//! @param buffer[out] a buffer to load the data into. Must be at least max_length in bytes.
//! @return Number of bytes actually read. Should be num_bytes for a successful read.
// NOTE: Many things don't properly check the return of this.
size_t resource_load_byte_range_system(ResAppNum app_num, uint32_t resource_id,
uint32_t start_offset, uint8_t *data, size_t num_bytes);
//! @internal
//! Gets a pointer to a data of a built-in resource or memory-addressable resource if possible
const uint8_t *resource_get_readonly_bytes(ResAppNum app_num, uint32_t resource_id,
size_t *num_bytes_out, bool has_privileged_access);
//! @internal
//! True, if given pointer maps to a built-in resource or memory-addressable read-only resource
bool resource_bytes_are_readonly(void *bytes);
//! @internal
//! Retrieve the version of the currently loaded system resources
ResourceVersion resource_get_system_version(void);
//! @internal
//! Retrieve the version of the resource
ResourceVersion resource_get_version(ResAppNum app_num, uint32_t resource_id);
//! @internal
//! Check that two versions are identical
bool resource_version_matches(const ResourceVersion *v1, const ResourceVersion *v2);
//
//! Watch a resource. The callback is called whenever the given resource is modified.
//! NOTE: This currently only supports file-based resources. If the resource is not
//! file based, then a NULL PFSCallbackHandle will be returned.
ResourceCallbackHandle resource_watch(ResAppNum app_num, uint32_t resource_id,
ResourceChangedCallback callback, void* data);
//! Stop watching a resource.
void resource_unwatch(ResourceCallbackHandle cb_handle);
//! @} // end addtogroup Resources
//! @} // end addtogroup Foundation

View file

@ -0,0 +1,11 @@
// This order is significant.
// The first implementation that matches the resource/store query gets it.
#ifndef RECOVERY_FW
RESOURCE_IMPL(g_app_file_impl)
RESOURCE_IMPL(g_builtin_impl)
RESOURCE_IMPL(g_file_impl)
RESOURCE_IMPL(g_system_bank_impl)
#else
RESOURCE_IMPL(g_builtin_impl)
#endif

View file

@ -0,0 +1,41 @@
/*
* 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 "resource_mapped.h"
#include "drivers/flash.h"
#include "system/passert.h"
#if CAPABILITY_HAS_MAPPABLE_FLASH
static uint32_t s_mapped_refcount_for_task[NumPebbleTask];
void resource_mapped_use(PebbleTask task) {
// We keep track of the refcount per task so we can cleanup all resources for a task
s_mapped_refcount_for_task[task]++;
flash_use();
}
void resource_mapped_release(PebbleTask task) {
PBL_ASSERTN(s_mapped_refcount_for_task[task] != 0);
s_mapped_refcount_for_task[task]--;
flash_release_many(1);
}
void resource_mapped_release_all(PebbleTask task) {
flash_release_many(s_mapped_refcount_for_task[task]);
s_mapped_refcount_for_task[task] = 0;
}
#endif

View file

@ -0,0 +1,24 @@
/*
* 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 "kernel/pebble_tasks.h"
void resource_mapped_use(PebbleTask task);
void resource_mapped_release(PebbleTask task);
void resource_mapped_release_all(PebbleTask task);

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 "resource_storage.h"
#include "resource_storage_impl.h"
#include <stdio.h>
#include <string.h>
#include "services/normal/filesystem/app_file.h"
#include "system/hexdump.h"
#include "system/logging.h"
#include "system/passert.h"
#include "system/version.h"
#include "util/math.h"
#include "util/size.h"
static const ResourceStoreImplementation *s_resource_store_impls[] = {
#define RESOURCE_IMPL(impl) &impl,
#include "resource_impl.def"
#undef RESOURCE_IMPL
};
// Check if our offset+length is within the resource entry's bounds.
// Truncate the length if we overrun the ending.
static uint32_t prv_check_resource_bounds(ResourceStoreEntry *entry, uint32_t store_offset,
size_t num_bytes) {
// If we haven't had the length set yet, just assume we're ok.
if (entry->length == ENTRY_LENGTH_UNSET) {
return num_bytes;
}
uint32_t resource_offset = store_offset - entry->offset;
if (entry->length < resource_offset) {
PBL_LOG(LOG_LEVEL_ERROR, "Resource offset past its own ending.");
return 0;
} else if ((resource_offset + num_bytes) > entry->length) {
PBL_LOG(LOG_LEVEL_ERROR, "offset + length > resource size, truncated.");
return entry->length - resource_offset;
} else {
return num_bytes;
}
}
static uint32_t prv_read(ResourceStoreEntry *entry, uint32_t offset, void *data, size_t num_bytes) {
num_bytes = prv_check_resource_bounds(entry, offset, num_bytes);
if (!num_bytes) {
return 0;
}
return entry->impl->read(entry, offset, data, num_bytes);
}
static void prv_get_manifest(ResourceStoreEntry *entry, ResourceManifest *manifest) {
if (prv_read(entry, 0, manifest, sizeof(ResourceManifest)) != sizeof(ResourceManifest)) {
*manifest = (ResourceManifest){0};
}
}
static bool prv_read_res_table_entry(ResTableEntry *res_entry, ResourceStoreEntry *entry,
uint32_t index) {
uint32_t addr = MANIFEST_SIZE + index * TABLE_ENTRY_SIZE;
return prv_read(entry, addr, res_entry, sizeof(ResTableEntry)) == sizeof(ResTableEntry);
}
static uint32_t prv_get_length(ResourceStoreEntry *entry) {
return entry->impl->get_length(entry);
}
// entry_offset is the offset of the resource of interest.
// If we're doing the whole store, that ends up being 0.
static uint32_t prv_get_crc(ResourceStoreEntry *entry, uint32_t num_bytes, uint32_t entry_offset) {
num_bytes = prv_check_resource_bounds(entry, entry_offset, num_bytes);
if (!num_bytes) {
return 0xFFFFFFFF;
}
return entry->impl->get_crc(entry, num_bytes, entry_offset);
}
T_STATIC uint32_t prv_get_store_length(ResourceStoreEntry *entry, ResourceManifest *manifest) {
// Get the resource entry for the last entry
ResTableEntry res_entry = {0};
if (!prv_read_res_table_entry(&res_entry, entry, manifest->num_resources - 1)) {
return 0;
}
// Get the full ending offset of the last resource.
uint32_t resource_end_offset = res_entry.offset + res_entry.length;
// Add the store's metadata size to the end address of the last resource.
uint32_t store_length = resource_end_offset + resource_store_get_metadata_size(entry);
// Catch an overflow if the store is enormous (unlikely unless corrupted)
if (store_length < resource_end_offset) {
// overflow
PBL_LOG(LOG_LEVEL_ERROR, "Overflow while validating resource");
return 0;
}
// Make sure the store's calculated length is not past the end of the store.
if (prv_get_length(entry) < store_length) {
return 0;
}
// Return the length of the store's resource data
return resource_end_offset;
}
static bool prv_validate_store(ResourceManifest *manifest, ResourceStoreEntry *entry,
ResAppNum app_num) {
uint32_t num_bytes;
if ((num_bytes = prv_get_store_length(entry, manifest)) == 0) {
PBL_LOG(LOG_LEVEL_WARNING, "Resource table check failed. Table or manifest may be corrupted");
return false;
}
uint32_t calculated_crc = prv_get_crc(entry, num_bytes, 0);
if (calculated_crc != manifest->version.crc) {
PBL_LOG(LOG_LEVEL_WARNING, "Resource crc mismatch for app %"PRIu32".", app_num);
PBL_LOG(LOG_LEVEL_WARNING, "0x%"PRIx32" != 0x%"PRIx32, calculated_crc, manifest->version.crc);
PBL_LOG(LOG_LEVEL_WARNING, "PBL-28517: If you see this please let Brad know");
const uint32_t calculated_crc_again = prv_get_crc(entry, num_bytes, 0);
PBL_LOG(LOG_LEVEL_WARNING, "Num bytes is %"PRIu32, num_bytes);
PBL_LOG(LOG_LEVEL_WARNING, "Calculated the CRC again, got 0x%"PRIx32, calculated_crc_again);
return calculated_crc_again == manifest->version.crc;
}
return true;
}
static void prv_get_store_entry(ResAppNum app_num, uint32_t resource_id,
ResourceStoreEntry *entry) {
*entry = (ResourceStoreEntry){
.id = resource_id,
.length = ENTRY_LENGTH_UNSET,
};
for (unsigned int i = 0; i < ARRAY_LENGTH(s_resource_store_impls); i++) {
entry->impl = s_resource_store_impls[i];
PBL_ASSERTN(entry->impl->find_resource);
if (entry->impl->find_resource(entry, app_num, resource_id)) {
return;
}
}
PBL_LOG(LOG_LEVEL_WARNING,
"get_store_entry(%"PRIu32",%"PRIu32") failed to find appropriate store",
app_num, resource_id);
entry->impl = NULL;
}
static bool prv_validate_entry(ResourceStoreEntry *entry, ResourceManifest *manifest,
uint32_t resource_id) {
if (entry->id > manifest->num_resources) {
PBL_LOG(LOG_LEVEL_DEBUG, "Out of bound resource %"PRId32" vs %"PRId32,
entry->id, manifest->num_resources);
return false;
}
ResTableEntry table_entry = {0};
if (!prv_read_res_table_entry(&table_entry, entry, entry->id - 1)) {
return false;
}
if (entry->id != table_entry.resource_id) {
PBL_LOG(LOG_LEVEL_ERROR, "Resource table entry for %" PRIx32 " is corrupt!"
"(%"PRIx32" != %"PRIx32")", resource_id, entry->id, table_entry.resource_id);
return false;
}
const uint32_t resource_crc = prv_get_crc(entry, table_entry.length, table_entry.offset);
if (resource_crc != table_entry.crc) {
PBL_LOG(LOG_LEVEL_DEBUG, "Bad resource CRC for %" PRIx32 ", %" PRIx32
" vs %" PRIx32, resource_id, resource_crc, table_entry.crc);
return false;
}
return true;
}
uint32_t resource_store_get_metadata_size(ResourceStoreEntry *entry) {
return entry->impl->metadata_size(entry);
}
void resource_storage_clear(ResAppNum app_num) {
ResourceStoreEntry entry;
prv_get_store_entry(app_num, 0, &entry);
if (entry.impl) {
entry.impl->clear(&entry);
}
}
static bool prv_get_manifest_by_id(ResAppNum app_num, uint32_t resource_id,
ResourceManifest *manifest) {
ResourceStoreEntry entry;
prv_get_store_entry(app_num, resource_id, &entry);
if (!entry.impl) {
return false;
}
prv_get_manifest(&entry, manifest);
return true;
}
ResourceVersion resource_storage_get_version(ResAppNum app_num, uint32_t resource_id) {
ResourceManifest manifest;
if (!prv_get_manifest_by_id(app_num, resource_id, &manifest)) {
return (ResourceVersion) {0};
}
return manifest.version;
}
uint32_t resource_storage_get_num_entries(ResAppNum app_num, uint32_t resource_id) {
ResourceManifest manifest;
if (!prv_get_manifest_by_id(app_num, resource_id, &manifest)) {
return 0;
}
return manifest.num_resources;
}
// if resource_id == 0 then check all of resource storage, else just validate
// that the resource requested is valid
bool resource_storage_check(ResAppNum app_num, uint32_t resource_id,
const ResourceVersion *expected_version) {
ResourceStoreEntry entry;
prv_get_store_entry(app_num, resource_id, &entry);
if (!entry.impl) {
return false;
}
return entry.impl->check(app_num, resource_id, &entry, expected_version);
}
void resource_storage_init(void) {
for (unsigned int i = 0; i < ARRAY_LENGTH(s_resource_store_impls); i++) {
if (s_resource_store_impls[i]->init) {
s_resource_store_impls[i]->init();
}
}
}
uint32_t resource_storage_read(ResourceStoreEntry *entry, uint32_t offset, void *data,
size_t num_bytes) {
return prv_read(entry, offset + entry->offset, data, num_bytes);
}
void resource_storage_get_resource(ResAppNum app_num, uint32_t resource_id,
ResourceStoreEntry *entry) {
prv_get_store_entry(app_num, resource_id, entry);
if (!entry->impl) {
*entry = (ResourceStoreEntry){0};
return;
}
if (!entry->impl->get_resource(entry)) {
*entry = (ResourceStoreEntry){0};
return;
}
PBL_ASSERTN(entry->length != ENTRY_LENGTH_UNSET);
}
ResourceCallbackHandle resource_watch(ResAppNum app_num, uint32_t resource_id,
ResourceChangedCallback callback, void* data) {
ResourceStoreEntry entry;
prv_get_store_entry(app_num, resource_id, &entry);
if (!entry.impl) {
return NULL;
}
return entry.impl->watch(&entry, callback, data);
}
void resource_unwatch(ResourceCallbackHandle cb_handle) {
#ifndef RECOVERY_FW
// TODO: Support unwatching not-files.
g_file_impl.unwatch(cb_handle);
#endif
}
void resource_storage_get_file_name(char *name, size_t buf_length, ResAppNum resource_bank) {
app_file_name_make(name, buf_length, resource_bank, APP_RESOURCES_FILENAME_SUFFIX,
strlen(APP_RESOURCES_FILENAME_SUFFIX));
}
void resource_storage_generic_init(void) {
}
void resource_storage_generic_clear(ResourceStoreEntry *entry) {
}
bool resource_storage_generic_check(ResAppNum app_num, uint32_t resource_id,
ResourceStoreEntry *entry,
const ResourceVersion *expected_version) {
ResourceManifest manifest;
prv_get_manifest(entry, &manifest);
if (expected_version && !resource_version_matches(&manifest.version, expected_version)) {
PBL_LOG(LOG_LEVEL_WARNING, "expected version <%#010"PRIx32", %"PRIu32">,",
expected_version->crc, expected_version->timestamp);
PBL_LOG(LOG_LEVEL_WARNING, "got <%#010"PRIx32", %"PRIu32">,",
manifest.version.crc, manifest.version.timestamp);
return false;
}
if (manifest.num_resources == 0) {
// no resources, no need to read anything more
return true;
}
if (resource_id == 0) {
return (prv_validate_store(&manifest, entry, app_num));
} else if (!prv_validate_entry(entry, &manifest, resource_id)) {
PBL_LOG(LOG_LEVEL_WARNING, "Resource %"PRId32" check for App %"PRIu32" failed",
resource_id, app_num);
return false;
}
return true;
}
uint32_t resource_storage_generic_metadata_size(ResourceStoreEntry *entry) {
return RESOURCE_STORE_METADATA_BYTES;
}
bool resource_storage_generic_get_resource(ResourceStoreEntry *entry) {
ResourceManifest manifest;
prv_get_manifest(entry, &manifest);
if (entry->id > manifest.num_resources) {
return false;
}
ResTableEntry table_entry;
if (!prv_read_res_table_entry(&table_entry, entry, entry->id - 1)) {
return false;
}
if ((table_entry.resource_id != entry->id) ||
(table_entry.length == 0)) {
// empty resource
return false;
}
entry->offset = resource_store_get_metadata_size(entry) + table_entry.offset;
entry->length = table_entry.length;
return true;
}
uint32_t resource_storage_generic_get_length(ResourceStoreEntry *entry) {
return entry->length;
}
uint32_t resource_storage_generic_get_crc(ResourceStoreEntry *entry, uint32_t num_bytes,
uint32_t entry_offset) {
return 0;
}
uint32_t resource_storage_generic_write(ResourceStoreEntry *entry, uint32_t offset, void *data,
size_t num_bytes) {
return 0;
}
ResourceCallbackHandle resource_storage_generic_watch(ResourceStoreEntry *entry,
ResourceChangedCallback callback,
void* data) {
PBL_LOG(LOG_LEVEL_WARNING, "resource_watch not supported for resource type %d.",
entry->impl->type);
return NULL;
}
bool resource_storage_generic_unwatch(ResourceCallbackHandle cb_handle) {
return false;
}

View file

@ -0,0 +1,100 @@
/*
* 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 <inttypes.h>
#include <stdbool.h>
#include <stdlib.h>
#include "resource.h"
typedef enum {
InvalidResourceStore = 0,
/* System Bank */
ResourceStoreTypeSystemBank,
/* App Banks in PFS */
ResourceStoreTypeAppFile,
/* Baked in FW. E.g. Fallback Font */
ResourceStoreTypeBuiltIn,
/* Filesystem stored resources */
ResourceStoreTypeFile,
} ResourceStoreType;
struct ResourceStoreImplementation;
typedef struct {
uint32_t id; // Used when the store implementation needs to permute the resource_id
const struct ResourceStoreImplementation *impl;
uint32_t offset;
uint32_t length;
const void *store_data;
} ResourceStoreEntry;
// Used to flag that the ResourceStoreEntry hasn't had its length filled yet.
#define ENTRY_LENGTH_UNSET ((uint32_t)~0)
// The filename suffix we use to represent a resource file
#define APP_RESOURCES_FILENAME_SUFFIX "res"
typedef struct ResourceStoreImplementation {
ResourceStoreType type;
// None of these callbacks may be NULL. There are generic implementations of most that can be
// used if nothing 'unique' needs to be done.
void (*init)(void);
void (*clear)(ResourceStoreEntry *entry);
// if resource_id == 0 then check all of resource storage, else just validate
// that the resource requested is valid
bool (*check)(ResAppNum app_num, uint32_t resource_id, ResourceStoreEntry *entry,
const ResourceVersion *expected_version);
uint32_t (*metadata_size)(ResourceStoreEntry *entry);
bool (*find_resource)(ResourceStoreEntry *entry, ResAppNum app_num, uint32_t resource_id);
bool (*get_resource)(ResourceStoreEntry *entry);
uint32_t (*get_length)(ResourceStoreEntry *entry);
uint32_t (*get_crc)(ResourceStoreEntry *entry, uint32_t num_bytes, uint32_t entry_offset);
uint32_t (*write)(ResourceStoreEntry *entry, uint32_t offset, void *data, size_t num_bytes);
uint32_t (*read)(ResourceStoreEntry *entry, uint32_t offset, void *data, size_t num_bytes);
const uint8_t *(*readonly_bytes)(ResourceStoreEntry *entry, bool has_privileged_access);
ResourceCallbackHandle (*watch)(ResourceStoreEntry *entry, ResourceChangedCallback callback,
void* data);
bool (*unwatch)(ResourceCallbackHandle cb_handle);
} ResourceStoreImplementation;
void resource_storage_init(void);
void resource_storage_clear(ResAppNum app_num);
bool resource_storage_check(ResAppNum app_num, uint32_t resource_id,
const ResourceVersion *expected_version);
ResourceVersion resource_storage_get_version(ResAppNum app_num, uint32_t resource_id);
uint32_t resource_storage_get_num_entries(ResAppNum app_num, uint32_t resource_id);
void resource_storage_get_resource(ResAppNum app_num, uint32_t resource_id,
ResourceStoreEntry *entry);
uint32_t resource_storage_read(ResourceStoreEntry *entry, uint32_t offset, void *data,
size_t num_bytes);
uint32_t resource_store_get_metadata_size(ResourceStoreEntry *entry);
// TODO PBL-21009: Move this somewhere else.
void resource_storage_get_file_name(char *name, size_t buf_length, ResAppNum app_num);

View file

@ -0,0 +1,120 @@
/*
* 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 "resource_storage_builtin.h"
#include "resource_storage_impl.h"
#include "kernel/memory_layout.h"
#include "kernel/pbl_malloc.h"
#include "services/normal/process_management/app_storage.h"
#include <string.h>
extern const BuiltInResourceData g_builtin_resources[];
extern const uint32_t g_num_builtin_resources;
static uint32_t resource_storage_builtin_read(ResourceStoreEntry *entry, uint32_t offset,
void *data, size_t num_bytes) {
const BuiltInResourceData *builtin = entry->store_data;
if (!builtin) {
return 0;
}
memcpy(data, builtin->address + offset, num_bytes);
return num_bytes;
}
bool resource_storage_builtin_bytes_are_readonly(const void *bytes) {
if (bytes == NULL) {
return false;
}
return memory_layout_is_pointer_in_region(memory_layout_get_microflash_region(), bytes);
}
static const uint8_t *resource_storage_builtin_readonly_bytes(ResourceStoreEntry *entry,
bool has_privileged_access) {
const BuiltInResourceData *builtin = entry->store_data;
if (!builtin) {
return NULL;
}
return builtin->address;
}
static bool resource_storage_builtin_find_resource(ResourceStoreEntry *entry, ResAppNum app_num,
uint32_t resource_id) {
if (app_num != SYSTEM_APP) {
return false;
}
// Story time! This is closely related to PBL-14367
// resource_id == 0 means get the store.
// HOWEVER, both builtin and flash stores respond to (app_num,rsrc_id) == (SYSTEM_APP,*)
// When we ask for (SYSTEM_APP,0), we always want to actually be getting the flash store.
// As a result, we should return false when rsrc_id == 0.
// In the future, we need to change this hideously gross behavior.
// BUTTTTTTT, PRF only has builtin, so we _need_ to say yes on PRF!
if (resource_id == 0) {
#if RECOVERY_FW
return true;
#else
return false;
#endif
}
for (unsigned int i = 0; i < g_num_builtin_resources; ++i) {
if (g_builtin_resources[i].resource_id == resource_id) {
entry->store_data = &g_builtin_resources[i];
return true;
}
}
return false;
}
static bool resource_storage_builtin_get_resource(ResourceStoreEntry *entry) {
const BuiltInResourceData *builtin = entry->store_data;
if (!builtin) {
return false;
}
entry->offset = 0;
entry->length = builtin->num_bytes;
return true;
}
bool resource_storage_builtin_check(ResAppNum app_num, uint32_t resource_id,
ResourceStoreEntry *entry,
const ResourceVersion *expected_version) {
// Builtins don't have manifests and can't be corrupted because they're built into the micro
// flash image.
return true;
}
const ResourceStoreImplementation g_builtin_impl = {
.type = ResourceStoreTypeBuiltIn,
.init = resource_storage_generic_init,
.clear = resource_storage_generic_clear,
.check = resource_storage_builtin_check,
.metadata_size = resource_storage_generic_metadata_size,
.find_resource = resource_storage_builtin_find_resource,
.get_resource = resource_storage_builtin_get_resource,
.get_length = resource_storage_generic_get_length,
.get_crc = resource_storage_generic_get_crc,
.write = resource_storage_generic_write,
.read = resource_storage_builtin_read,
.readonly_bytes = resource_storage_builtin_readonly_bytes,
.watch = resource_storage_generic_watch,
.unwatch = resource_storage_generic_unwatch,
};

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.
*/
#pragma once
#include <stdbool.h>
#include <stdint.h>
//! @file resource_storage_builtin.h
typedef struct {
uint32_t resource_id;
const uint8_t *address;
uint32_t num_bytes;
} BuiltInResourceData;
bool resource_storage_builtin_bytes_are_readonly(const void *bytes);

View file

@ -0,0 +1,276 @@
/*
* 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 "resource_storage_impl.h"
#include "resource_storage_file.h"
#include "kernel/util/sleep.h"
#include "services/normal/filesystem/pfs.h"
#include "system/logging.h"
#include <stdint.h>
extern const FileResourceData g_file_resource_stores[];
extern const uint32_t g_num_file_resource_stores;
// Common helpers functions
//
// These functions are highly coupled to the ones that call them but they're just for code
// deduplication and not actually intended to be reusable or provide encapsulation.
static uint32_t prv_file_common_get_length_and_close(int fd) {
if (fd < 0) {
return 0;
}
uint32_t length = pfs_get_file_size(fd);
pfs_close(fd);
return length;
}
static uint32_t prv_file_common_get_crc(int fd, uint32_t num_bytes, uint32_t entry_offset) {
if (fd < 0) {
return 0xFFFFFFFF;
}
uint32_t crc = pfs_crc_calculate_file(fd, RESOURCE_STORE_METADATA_BYTES + entry_offset,
num_bytes);
pfs_close(fd);
return crc;
}
static uint32_t prv_file_common_read(int fd, uint32_t offset, void *data, size_t num_bytes) {
if (fd < 0) {
return 0;
}
int bytes_read = 0;
if (pfs_seek(fd, offset, FSeekSet) >= 0) {
bytes_read = pfs_read(fd, data, num_bytes);
// prevent invalid resource API return values
if (bytes_read < 0) {
bytes_read = 0;
}
}
pfs_close(fd);
return bytes_read;
}
///////////////////////////////////////////////////////////////////////////////
// ResourceStoreTypeFile implementation
static int prv_file_open_by_name(const char *name, uint8_t op_flags) {
int fd = pfs_open(name, op_flags, FILE_TYPE_STATIC, 0);
if ((fd < 0) && (fd != E_DOES_NOT_EXIST)) {
PBL_LOG(LOG_LEVEL_WARNING, "Could not open resource pfs file <%s>, fd: %d", name, fd);
}
return fd;
}
static int prv_file_open(ResourceStoreEntry *entry, uint8_t op_flags) {
return prv_file_open_by_name(((FileResourceData *) entry->store_data)->name, op_flags);
}
static uint32_t resource_storage_file_get_length(ResourceStoreEntry *entry) {
const uint8_t op_flags = OP_FLAG_READ | OP_FLAG_SKIP_HDR_CRC_CHECK | OP_FLAG_USE_PAGE_CACHE;
return prv_file_common_get_length_and_close(prv_file_open(entry, op_flags));
}
static uint32_t resource_storage_file_get_crc(ResourceStoreEntry *entry, uint32_t num_bytes,
uint32_t entry_offset) {
const uint8_t op_flags = OP_FLAG_READ;
return prv_file_common_get_crc(prv_file_open(entry, op_flags), num_bytes, entry_offset);
}
static uint32_t resource_storage_file_read(ResourceStoreEntry *entry, uint32_t offset, void *data,
size_t num_bytes) {
const uint8_t op_flags = OP_FLAG_READ | OP_FLAG_SKIP_HDR_CRC_CHECK | OP_FLAG_USE_PAGE_CACHE;
return prv_file_common_read(prv_file_open(entry, op_flags), offset, data, num_bytes);
}
static const uint8_t *resource_storage_file_readonly_bytes_unsupported(ResourceStoreEntry *entry,
bool has_privileged_access) {
return NULL;
}
static bool resource_storage_file_find_resource(ResourceStoreEntry *entry, ResAppNum app_num,
uint32_t resource_id) {
if (app_num != SYSTEM_APP) {
return false;
}
for (unsigned int i = 0; i < g_num_file_resource_stores; ++i) {
if (g_file_resource_stores[i].first_resource_id > resource_id) {
break;
} else if (g_file_resource_stores[i].last_resource_id >= resource_id) {
const FileResourceData *file = &g_file_resource_stores[i];
entry->store_data = file;
entry->id -= file->resource_id_offset;
return true;
}
}
return false;
}
static ResourceCallbackHandle resource_storage_file_watch(ResourceStoreEntry *entry,
ResourceChangedCallback callback,
void* data) {
const FileResourceData *file = entry->store_data;
if (!file) {
return NULL;
}
PFSCallbackHandle cb_handle = pfs_watch_file(file->name, callback, FILE_CHANGED_EVENT_ALL, data);
return cb_handle;
}
static bool resource_storage_file_unwatch(ResourceCallbackHandle cb_handle) {
pfs_unwatch_file(cb_handle);
return true;
}
static void resource_storage_file_init(void) {
// Make sure the files we have are valid
for (unsigned int i = 0; i < g_num_file_resource_stores; ++i) {
// The only way we can check this file is valid is by making sure each resource in each file
// is valid.
// Get the length of the file to see if we're checking a large file
const char *name = g_file_resource_stores[i].name;
const uint8_t op_flags = OP_FLAG_READ | OP_FLAG_SKIP_HDR_CRC_CHECK | OP_FLAG_USE_PAGE_CACHE;
const int fd = prv_file_open_by_name(name, op_flags);
const uint32_t file_length = prv_file_common_get_length_and_close(fd);
PBL_LOG(LOG_LEVEL_INFO, "File %s has length %"PRIu32, name, file_length);
// Now check each entry in the file
for (uint32_t resource_id = g_file_resource_stores[i].first_resource_id;
resource_id <= g_file_resource_stores[i].last_resource_id; resource_id++) {
// TODO PBL-21402
if (!resource_storage_check(SYSTEM_APP, resource_id, NULL)) {
PBL_LOG(LOG_LEVEL_ERROR, "System resource file %"PRIu32" corrupt!!!", resource_id);
}
const uint32_t large_file_size_threshold = 200 * 1024;
if (file_length > large_file_size_threshold) {
// If this file is over 200KB, it's going to take a while to CRC. Let's sleep a bit
// between entries so we don't starve out our background task. See PBL-24560 for a real
// long term fix.
psleep(5);
}
}
}
}
const ResourceStoreImplementation g_file_impl = {
.type = ResourceStoreTypeFile,
.init = resource_storage_file_init,
.clear = resource_storage_generic_clear,
.check = resource_storage_generic_check,
.metadata_size = resource_storage_generic_metadata_size,
.find_resource = resource_storage_file_find_resource,
.get_resource = resource_storage_generic_get_resource,
.get_length = resource_storage_file_get_length,
.get_crc = resource_storage_file_get_crc,
.write = resource_storage_generic_write,
.read = resource_storage_file_read,
.readonly_bytes = resource_storage_file_readonly_bytes_unsupported,
.watch = resource_storage_file_watch,
.unwatch = resource_storage_file_unwatch,
};
///////////////////////////////////////////////////////////////////////////////
// ResourceStoreTypeAppFile implementation
static int prv_app_file_open(ResourceStoreEntry *entry, uint8_t op_flags) {
ResAppNum app_num = (ResAppNum)entry->store_data;
if (app_num == SYSTEM_APP) {
return -1;
}
char filename[APP_RESOURCE_FILENAME_MAX_LENGTH + 1]; // extra for null terminator
resource_storage_get_file_name(filename, sizeof(filename), app_num);
int fd = pfs_open(filename, op_flags, FILE_TYPE_STATIC, 0);
if ((fd < 0) && (fd != E_DOES_NOT_EXIST)) {
PBL_LOG(LOG_LEVEL_WARNING, "Could not open resource pfs file <%s>, fd: %d", filename, fd);
}
return fd;
}
static bool resource_storage_app_file_find_resource(ResourceStoreEntry *entry, ResAppNum app_num,
uint32_t resource_id) {
if (app_num == SYSTEM_APP) {
return false;
}
// Need to cast to uintptr_t first to make test compiling on 64-bit happy
entry->store_data = (void*)(uintptr_t)app_num;
return true;
}
static void resource_storage_app_file_clear(ResourceStoreEntry *entry) {
ResAppNum app_num = (ResAppNum)entry->store_data;
if (app_num == SYSTEM_APP) {
return;
}
char filename[APP_RESOURCE_FILENAME_MAX_LENGTH + 1]; // extra for null terminator
resource_storage_get_file_name(filename, sizeof(filename), app_num);
pfs_remove(filename);
}
static uint32_t resource_storage_app_file_get_length(ResourceStoreEntry *entry) {
const uint8_t op_flags = OP_FLAG_READ | OP_FLAG_SKIP_HDR_CRC_CHECK | OP_FLAG_USE_PAGE_CACHE;
return prv_file_common_get_length_and_close(prv_app_file_open(entry, op_flags));
}
static uint32_t resource_storage_app_file_get_crc(ResourceStoreEntry *entry, uint32_t num_bytes,
uint32_t entry_offset) {
const uint8_t op_flags = OP_FLAG_READ;
return prv_file_common_get_crc(prv_app_file_open(entry, op_flags), num_bytes, entry_offset);
}
static uint32_t resource_storage_app_file_read(ResourceStoreEntry *entry, uint32_t offset,
void *data, size_t num_bytes) {
const uint8_t op_flags = OP_FLAG_READ | OP_FLAG_SKIP_HDR_CRC_CHECK | OP_FLAG_USE_PAGE_CACHE;
return prv_file_common_read(prv_app_file_open(entry, op_flags), offset, data, num_bytes);
}
const ResourceStoreImplementation g_app_file_impl = {
.type = ResourceStoreTypeAppFile,
.init = resource_storage_generic_init,
.clear = resource_storage_app_file_clear,
.check = resource_storage_generic_check,
.metadata_size = resource_storage_generic_metadata_size,
.find_resource = resource_storage_app_file_find_resource,
.get_resource = resource_storage_generic_get_resource,
.get_length = resource_storage_app_file_get_length,
.get_crc = resource_storage_app_file_get_crc,
.write = resource_storage_generic_write,
.read = resource_storage_app_file_read,
.readonly_bytes = resource_storage_file_readonly_bytes_unsupported,
.watch = resource_storage_generic_watch,
.unwatch = resource_storage_generic_unwatch,
};

View file

@ -0,0 +1,31 @@
/*
* 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 "resource_storage.h"
//! @file resource_storage_file.h
typedef struct {
uint32_t first_resource_id;
uint32_t last_resource_id;
uint32_t resource_id_offset;
const char *name;
} FileResourceData;
// TODO PBL-21009: Move this somewhere else.
#define APP_RESOURCE_FILENAME_MAX_LENGTH 24

View file

@ -0,0 +1,211 @@
/*
* 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 "resource_storage_flash.h"
#include "resource_storage_impl.h"
#include "drivers/flash.h"
#include "kernel/pbl_malloc.h"
#include "resource/resource_version.auto.h"
#include "services/normal/process_management/app_storage.h"
#include "system/bootbits.h"
#include "system/logging.h"
#include "util/size.h"
#include <stdlib.h>
static const SystemResourceBank s_resource_banks[] = {
{
.begin = FLASH_REGION_SYSTEM_RESOURCES_BANK_0_BEGIN,
.end = FLASH_REGION_SYSTEM_RESOURCES_BANK_0_END,
},
{
.begin = FLASH_REGION_SYSTEM_RESOURCES_BANK_1_BEGIN,
.end = FLASH_REGION_SYSTEM_RESOURCES_BANK_1_END,
},
};
//! Index into s_resource_banks
static unsigned int s_active_bank = 0;
#define BANK s_resource_banks[s_active_bank]
//! Set to true if we've scanned the available resource banks and determined one of them had valid
//! resources in it.
static bool s_valid_resources_found = false;
const ResourceStoreImplementation g_system_bank_impl;
static void resource_storage_system_bank_init(void) {
boot_bit_clear(BOOT_BIT_NEW_SYSTEM_RESOURCES_AVAILABLE);
ResourceStoreEntry entry = {
.id = 0, // resource id 0 means the store itself
.impl = &g_system_bank_impl,
.length = ENTRY_LENGTH_UNSET
};
// Increment s_active_bank and call resource_storage_generic_check for each value to find
// a bank that's valid.
for (s_active_bank = 0; s_active_bank < ARRAY_LENGTH(s_resource_banks); ++s_active_bank) {
PBL_LOG(LOG_LEVEL_INFO, "Checking bank %u for system resources", s_active_bank);
if (resource_storage_generic_check(SYSTEM_APP, 0, &entry, &SYSTEM_RESOURCE_VERSION)) {
PBL_LOG(LOG_LEVEL_INFO, "Valid system resources found!");
s_valid_resources_found = true;
return;
}
}
// Welp, we found nothing. Leave s_valid_resources_found as false and when resource_storage_check
// is called as part of system_resource_init we'll complain and handle missing resources.
}
// TODO PBL-21009: Move this somewhere else.
const SystemResourceBank *resource_storage_flash_get_unused_bank(void) {
size_t unused_bank_index;
if (s_valid_resources_found) {
unused_bank_index = (s_active_bank + 1) % ARRAY_LENGTH(s_resource_banks);
} else {
static int s_unused_bank_index = -1;
if (s_unused_bank_index == -1) {
// A crude form of wear levelling to try and keep BB2s in infra happy
//
// For real watches, the only time this should happen is during initial onboarding. (If we
// are in normal FW, one of the resource banks _must_ be valid.) We only call this once
// because we want to target the same bank when both are unused so features like resumable
// resource updates work as expected. We reset the bank on boot to make our watches a little
// more resilient to the scenario where one of the resource banks has gone completely bad
s_unused_bank_index = rand() % ARRAY_LENGTH(s_resource_banks);
}
unused_bank_index = s_unused_bank_index;
}
return &s_resource_banks[unused_bank_index];
}
static uint32_t resource_storage_system_bank_metadata_size(ResourceStoreEntry *entry) {
return SYSTEM_STORE_METADATA_BYTES;
}
// PBL-28517 investigation
extern uint8_t pbl_28517_flash_impl_get_status_register(uint32_t sector_addr);
static uint32_t resource_storage_system_bank_get_crc(ResourceStoreEntry *entry, uint32_t num_bytes,
uint32_t entry_offset) {
#if (PLATFORM_SNOWY || PLATFORM_SPALDING) && !RELEASE && !UNITTEST
// PBL-28517 investigation
if (entry_offset == 0) {
// We're calculating the CRC of the whole bank. Before we do this, let's save the status
// register for each sector so we can see if the flash is in a funny state.
for (int i = 0; (i * SECTOR_SIZE_BYTES) < (int) num_bytes; ++i) {
const uint32_t addr = (BANK.begin + (i * SECTOR_SIZE_BYTES)) & SECTOR_ADDR_MASK;
uint8_t status_reg = pbl_28517_flash_impl_get_status_register(addr);
uint32_t crc = flash_calculate_legacy_defective_checksum(addr, SECTOR_SIZE_BYTES);
PBL_LOG(LOG_LEVEL_DEBUG, "PBL-28517 Sector 0x%"PRIx32" Status 0x%"PRIx8" CRC 0x%"PRIx32,
addr, status_reg, crc);
}
}
#endif
uint32_t start_offset = resource_store_get_metadata_size(entry) + entry_offset;
return flash_calculate_legacy_defective_checksum(
BANK.begin + start_offset, num_bytes);
}
static uint32_t resource_storage_system_bank_read(ResourceStoreEntry *entry, uint32_t offset,
void *data, size_t num_bytes) {
flash_read_bytes(data, BANK.begin + offset, num_bytes);
return num_bytes;
}
#if CAPABILITY_HAS_MAPPABLE_FLASH
bool resource_storage_flash_bytes_are_readonly(const void *bytes) {
return (bytes > (void *)FLASH_MEMORY_MAPPABLE_ADDRESS) &&
(bytes < (void *)(FLASH_MEMORY_MAPPABLE_ADDRESS + FLASH_MEMORY_MAPPABLE_SIZE));
}
static const uint8_t *resource_storage_system_bank_readonly_bytes(ResourceStoreEntry *entry,
bool has_privileged_access) {
if (!has_privileged_access) {
return NULL;
}
return (uint8_t *)(uintptr_t)(FLASH_MEMORY_MAPPABLE_ADDRESS + BANK.begin + entry->offset);
}
#else
bool resource_storage_flash_bytes_are_readonly(const void *bytes) {
return false;
}
static const uint8_t *resource_storage_system_bank_readonly_bytes(ResourceStoreEntry *entry,
bool has_privileged_access) {
return NULL;
}
#endif // CAPABILITY_HAS_MAPPABLE_FLASH
static void resource_storage_system_bank_clear(ResourceStoreEntry *entry) {
uint8_t buffer[MANIFEST_SIZE] = {0};
flash_write_bytes(buffer, BANK.begin, MANIFEST_SIZE);
}
bool resource_storage_system_bank_check(ResAppNum app_num, uint32_t resource_id,
ResourceStoreEntry *entry,
const ResourceVersion *expected_version) {
if (!s_valid_resources_found) {
// We determined that we had no valid banks during init(), return false.
return false;
}
// Are we checking the store itself?
if (resource_id == 0) {
// We've already verified that the bank was good at init(), just return true.
return true;
}
// We're checking a specific resource, delegate this to the generic method.
return resource_storage_generic_check(app_num, resource_id, entry, expected_version);
}
static bool resource_storage_system_bank_find_resource(ResourceStoreEntry *entry,
ResAppNum app_num, uint32_t resource_id) {
return app_num == SYSTEM_APP && s_valid_resources_found;
}
const ResourceStoreImplementation g_system_bank_impl = {
.type = ResourceStoreTypeSystemBank,
.init = resource_storage_system_bank_init,
.clear = resource_storage_system_bank_clear,
.check = resource_storage_system_bank_check,
.metadata_size = resource_storage_system_bank_metadata_size,
.find_resource = resource_storage_system_bank_find_resource,
.get_resource = resource_storage_generic_get_resource,
.get_length = resource_storage_generic_get_length,
.get_crc = resource_storage_system_bank_get_crc,
.write = resource_storage_generic_write,
.read = resource_storage_system_bank_read,
.readonly_bytes = resource_storage_system_bank_readonly_bytes,
.watch = resource_storage_generic_watch,
.unwatch = resource_storage_generic_unwatch,
};

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.
*/
#pragma once
#include "resource_storage.h"
//! @file resource_storage_flash.h
//!
//! Functions for flash-based resource storage implementations
typedef struct SystemResourceBank {
uint32_t begin;
uint32_t end;
} SystemResourceBank;
//! Get the extents of a resource storage bank which is not currently in use by
//! the system.
// TODO PBL-21009: Move this somewhere else.
const SystemResourceBank *resource_storage_flash_get_unused_bank(void);
bool resource_storage_flash_bytes_are_readonly(const void *bytes);

View file

@ -0,0 +1,87 @@
/*
* 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 "util/attributes.h"
#include "resource.h"
#include "resource_storage.h"
//! @file resource_storage_impl.h
//!
//! Shared functionality that all the different ResourceStoreImplemention's need.
// TODO PBL-21382: Abstract these details out of the resource storage implementation.
// Apart from builtins which do not have a header at all, the resource stores
// are structured as follow:
//
// +----------------------------------------------------------------+
// | ResourceManifest | ResTableEntry (n-times) | Raw resource data |
// +----------------------------------------------------------------+
//
// Each ResTableEntry contains metadata about resources, and an offset in the
// raw resource data blob.
//
// More info at:
// https://pebbletechnology.atlassian.net/wiki/display/DEV/Pebble+Resource+Pack+Format
//! Actually baked into the flash storage format.
//! Do not change this without changing the associated tooling!
typedef struct PACKED {
uint32_t num_resources;
ResourceVersion version;
} ResourceManifest;
//! Actually baked into the flash storage format.
//! Do not change this without changing the associated tooling!
typedef struct PACKED {
uint32_t resource_id;
uint32_t offset;
uint32_t length;
uint32_t crc;
} ResTableEntry;
#define MAX_RESOURCES_PER_STORE 256
#define MAX_RESOURCES_FOR_SYSTEM_STORE 512
#define MANIFEST_SIZE (sizeof(ResourceManifest))
#define TABLE_ENTRY_SIZE (sizeof(ResTableEntry))
#define RESOURCE_STORE_METADATA_BYTES \
(MANIFEST_SIZE + MAX_RESOURCES_PER_STORE * TABLE_ENTRY_SIZE)
#define SYSTEM_STORE_METADATA_BYTES \
(MANIFEST_SIZE + MAX_RESOURCES_FOR_SYSTEM_STORE * TABLE_ENTRY_SIZE)
void resource_storage_generic_init(void);
void resource_storage_generic_clear(ResourceStoreEntry *entry);
bool resource_storage_generic_check(ResAppNum app_num, uint32_t resource_id,
ResourceStoreEntry *entry,
const ResourceVersion *expected_version);
uint32_t resource_storage_generic_metadata_size(ResourceStoreEntry *entry);
bool resource_storage_generic_get_resource(ResourceStoreEntry *entry);
uint32_t resource_storage_generic_get_length(ResourceStoreEntry *entry);
uint32_t resource_storage_generic_get_crc(ResourceStoreEntry *entry, uint32_t num_bytes,
uint32_t entry_offset);
uint32_t resource_storage_generic_write(ResourceStoreEntry *entry, uint32_t offset, void *data,
size_t num_bytes);
ResourceCallbackHandle resource_storage_generic_watch(ResourceStoreEntry *entry,
ResourceChangedCallback callback,
void* data);
bool resource_storage_generic_unwatch(ResourceCallbackHandle cb_handle);
#define RESOURCE_IMPL(impl) extern const ResourceStoreImplementation impl;
#include "resource_impl.def"
#undef RESOURCE_IMPL

View file

@ -0,0 +1,93 @@
/*
* 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 "resource.h"
#include "resource_mapped.h"
#include "process_management/app_manager.h"
#include "kernel/memory_layout.h"
#include "syscall/syscall_internal.h"
#include "system/logging.h"
//! @file resource_syscalls.c
//! The landing place for untrusted code to use resources.
DEFINE_SYSCALL(size_t, sys_resource_size, ResAppNum app_num, uint32_t resource_id) {
if (PRIVILEGE_WAS_ELEVATED) {
if (pebble_task_get_current() == PebbleTask_Worker) {
// Not allowed from workers
syscall_failed();
}
}
return resource_size(app_num, resource_id);
}
DEFINE_SYSCALL(size_t, sys_resource_load_range, ResAppNum app_num,
uint32_t id, uint32_t start_offset, uint8_t *data, size_t num_bytes) {
if (PRIVILEGE_WAS_ELEVATED) {
if (pebble_task_get_current() == PebbleTask_Worker) {
// Not allowed from workers
syscall_failed();
}
syscall_assert_userspace_buffer(data, num_bytes);
}
return resource_load_byte_range_system(app_num, id, start_offset, data, num_bytes);
}
DEFINE_SYSCALL(bool, sys_resource_bytes_are_readonly, void *ptr) {
return resource_bytes_are_readonly(ptr);
}
DEFINE_SYSCALL(const uint8_t *, sys_resource_read_only_bytes, ResAppNum app_num, uint32_t
resource_id, size_t *num_bytes_out) {
bool caller_is_privileged = true;
if (PRIVILEGE_WAS_ELEVATED) {
caller_is_privileged = false;
if (pebble_task_get_current() == PebbleTask_Worker) {
// Not allowed from workers
syscall_failed();
}
// num_bytes_out is optional, so it's perfectly safe for an app to pass in NULL here.
if (num_bytes_out) {
syscall_assert_userspace_buffer(num_bytes_out, sizeof(*num_bytes_out));
}
}
return resource_get_readonly_bytes(app_num, resource_id, num_bytes_out, caller_is_privileged);
}
DEFINE_SYSCALL(bool, sys_resource_is_valid, ResAppNum app_num, uint32_t resource_id) {
return resource_is_valid(app_num, resource_id);
}
DEFINE_SYSCALL(uint32_t, sys_resource_get_and_cache, ResAppNum app_num, uint32_t resource_id) {
return resource_get_and_cache(app_num, resource_id);
}
DEFINE_SYSCALL(void, sys_resource_mapped_use) {
PebbleTask task = pebble_task_get_current();
resource_mapped_use(task);
}
DEFINE_SYSCALL(void, sys_resource_mapped_release) {
PebbleTask task = pebble_task_get_current();
resource_mapped_release(task);
}

View file

@ -0,0 +1,123 @@
/*
* 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 "system_resource.h"
#include "applib/fonts/fonts.h"
#include "applib/graphics/text_resources.h"
#include "kernel/event_loop.h"
#include "kernel/memory_layout.h"
#include "kernel/panic.h"
#include "kernel/pbl_malloc.h"
#include "kernel/util/fw_reset.h"
#include "pebble_errors.h"
#include "resource/resource.h"
#include "resource/resource_storage.h"
#include "syscall/syscall_internal.h"
#include "system/logging.h"
#include "system/passert.h"
#include "system/testinfra.h"
#include "util/size.h"
#include "resource/resource_ids.auto.h"
#include "resource/resource_version.auto.h"
#include "font_resource_table.auto.h"
void system_resource_init(void) {
if (!resource_init_app(SYSTEM_APP, &SYSTEM_RESOURCE_VERSION)) {
// System resources are missing!
#if defined(IS_BIGBOARD)
pbl_log(LOG_LEVEL_ERROR, __FILE_NAME__, __LINE__,
"System resources are missing or corrupt, time to sad watch");
launcher_panic(ERROR_BAD_RESOURCES);
#else
PBL_LOG(LOG_LEVEL_ERROR, "System resources are missing or corrupt! Going to PRF");
fw_reset_into_prf();
#endif
}
}
bool system_resource_is_valid(void) {
return resource_init_app(SYSTEM_APP, &SYSTEM_RESOURCE_VERSION);
}
#define NUM_SYSTEM_FONTS ARRAY_LENGTH(s_font_resource_keys)
// Total number of fonts = NUM_SYSTEM_FONTS + 1 for the fallback font
FontInfo s_system_fonts_info_table[NUM_SYSTEM_FONTS + 1] KERNEL_READONLY_DATA;
static GFont prv_load_system_font(const char *font_key) {
if (font_key == NULL) {
PBL_LOG(LOG_LEVEL_DEBUG, "GETTING FALLBACK FONT");
// load fallback font
if (!s_system_fonts_info_table[NUM_SYSTEM_FONTS].loaded) {
PBL_ASSERTN(text_resources_init_font(SYSTEM_APP, RESOURCE_ID_FONT_FALLBACK_INTERNAL, 0,
&s_system_fonts_info_table[NUM_SYSTEM_FONTS]));
}
return &s_system_fonts_info_table[NUM_SYSTEM_FONTS];
}
for (int i = 0; i < (int) NUM_SYSTEM_FONTS; ++i) {
if (0 == strcmp(font_key, s_font_resource_keys[i].key_name)) {
FontInfo *fontinfo = &s_system_fonts_info_table[i];
uint32_t resource = s_font_resource_keys[i].resource_id;
uint32_t extension = s_font_resource_keys[i].extension_id;
// if the font has not been initialized yet
if (!fontinfo->loaded) {
if (!text_resources_init_font(SYSTEM_APP,
resource, extension, &s_system_fonts_info_table[i])) {
// Can't initialize the font for some reason
return NULL;
}
resource_get_and_cache(SYSTEM_APP, resource);
resource_get_and_cache(SYSTEM_APP, extension);
}
return &s_system_fonts_info_table[i];
}
}
// Didn't find the given font, invalid key.
return NULL;
}
GFont system_resource_get_font(const char *font_key) {
GFont result = prv_load_system_font(font_key);
return result;
}
DEFINE_SYSCALL(GFont, sys_font_get_system_font, const char *font_key) {
if (font_key && PRIVILEGE_WAS_ELEVATED) {
if (!memory_layout_is_cstring_in_region(memory_layout_get_app_region(), font_key, 100) &&
!memory_layout_is_cstring_in_region(memory_layout_get_microflash_region(), font_key, 100)) {
PBL_LOG(LOG_LEVEL_ERROR, "Pointer %p not in app or microflash region", font_key);
syscall_failed();
}
}
return system_resource_get_font(font_key);
}
DEFINE_SYSCALL(void, sys_font_reload_font, FontInfo *fontinfo) {
if (PRIVILEGE_WAS_ELEVATED) {
if (!memory_layout_is_pointer_in_region(memory_layout_get_readonly_bss_region(), fontinfo)) {
syscall_failed();
}
}
text_resources_init_font(fontinfo->base.app_num, fontinfo->base.resource_id,
fontinfo->extension.resource_id, fontinfo);
}

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 "applib/fonts/fonts.h"
void system_resource_init(void);
bool system_resource_is_valid(void);
GFont system_resource_get_font(const char *font_key);