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,80 @@
/*
* 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 "app_file.h"
#include <string.h>
#include "system/passert.h"
#include "resource/resource_storage.h"
void app_file_name_make(char * restrict buffer, size_t buffer_len,
AppInstallId app_id, const char * restrict suffix,
size_t suffix_len) {
PBL_ASSERTN(buffer_len > APP_FILE_NAME_PREFIX_LENGTH + suffix_len);
buffer[0] = '@';
uint32_t unsigned_id = (uint32_t)app_id;
for (int i = 8; i >= 1; --i) {
uint8_t nybble = unsigned_id & 0xf;
if (nybble < 0xa) {
buffer[i] = '0' + nybble;
} else {
buffer[i] = 'a' + (nybble - 0xa);
}
unsigned_id >>= 4;
}
buffer[9] = '/';
memcpy(&buffer[10], suffix, suffix_len);
buffer[APP_FILE_NAME_PREFIX_LENGTH + suffix_len] = '\0';
}
//! Checks whether the given filename is an app file.
bool is_app_file_name(const char *filename) {
bool answer = true;
answer = answer && strlen(filename) > APP_FILE_NAME_PREFIX_LENGTH;
answer = answer && filename[0] == '@' && filename[9] == '/';
for (int i = 1; answer && i <= 8; ++i) {
char c = filename[i];
answer = answer && ((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f'));
}
return answer;
}
//! Checks whether the given filename is an app resource file (suffix = "res")
bool is_app_resource_file_name(const char *filename) {
return is_app_file_name(filename) &&
!strcmp(filename + APP_FILE_NAME_PREFIX_LENGTH, APP_RESOURCES_FILENAME_SUFFIX);
}
//! Parses an app-file name to get the AppInstallId.
//! Assumes the file is indeed an app-file
AppInstallId app_file_parse_app_id(const char *filename) {
return (AppInstallId)strtol(filename + 1, NULL, 16); // + 1 to skip the initial '@'
}
//! Parses an app-file name to get the AppInstallId.
//!
//! @returns INSTALL_ID_INVALID if the filename is not an app-file.
AppInstallId app_file_get_app_id(const char *filename) {
if (!is_app_file_name(filename)) {
return INSTALL_ID_INVALID;
}
return app_file_parse_app_id(filename);
}

View file

@ -0,0 +1,61 @@
/*
* 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.
*/
//! Consistent naming of per-app files.
//!
//! All files which are specific to an app are named with a consistent scheme
//! which identifies the files as belonging to the app. This is done by
//! prefixing the filename with a string based on the AppInstallId. Filenames
//! take the format printf("@%08x/%s", (uint32_t)app_id, suffix) to form a
//! pseudo-directory structure.
//!
//! The prefix is fixed-length to make it simple to generate, parse and
//! identify.
#pragma once
#include <stdbool.h>
#include <stddef.h>
#include "process_management/app_install_types.h"
// The suffix starts at offset 10 in the filename
// '@' + "XXXXXXXX" + '/' : (1 + 8 + 1 = 10)
#define APP_FILE_NAME_PREFIX_LENGTH (10)
//! Make an app-file name from the given app_id and suffix string.
//!
//! @param suffix_len strlen(suffix)
//!
//! @note buffer_len must be > APP_FILE_NAME_PREFIX_LENGTH + suffix_len to fit
//! the full file name including NULL-terminator.
void app_file_name_make(char * restrict buffer, size_t buffer_len,
AppInstallId app_id, const char * restrict suffix,
size_t suffix_len);
//! Checks whether the given filename is an app file.
bool is_app_file_name(const char *filename);
//! Checks whether the given filename is an app resource file (suffix = "res")
bool is_app_resource_file_name(const char *filename);
//! Parses an app-file name to get the AppInstallId.
//! Assumes the file is indeed an app-file
AppInstallId app_file_parse_app_id(const char *filename);
//! Parses an app-file name to get the AppInstallId.
//!
//! @returns INSTALL_ID_INVALID if the filename is not an app-file.
AppInstallId app_file_get_app_id(const char *filename);

View file

@ -0,0 +1,215 @@
/*
* 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 "flash_translation.h"
#include "drivers/flash.h"
#include "drivers/task_watchdog.h"
#include "flash_region/filesystem_regions.h"
#include "flash_region/flash_region.h"
#include "services/normal/filesystem/pfs.h"
#include "system/logging.h"
#include "system/passert.h"
#include "util/math.h"
#include "util/size.h"
//! Flash translation operation
typedef enum {
FTLRead,
FTLWrite,
FTLEraseSector,
FTLEraseSubsector
} FTLOperation;
//! Total number of FSRegions listed in s_region_list
static const unsigned int TOTAL_NUM_FLASH_REGIONS = ARRAY_LENGTH(s_region_list);
//! Keeps track of the current total size of our filesystem in bytes.
static uint32_t s_ftl_size;
//! Keeps track of which regions are included in the filesystem.
static unsigned int s_next_region_idx = 0;
//! returns FSRegion size given idx in s_region_list
static uint32_t prv_region_size(int idx) {
return (s_region_list[idx].end - s_region_list[idx].start);
}
//! add all regions temporarily so that PFS can test on these regions
static void prv_layout_version_add_all_regions(bool revert) {
static unsigned int original_idx;
if (!revert) {
original_idx = s_next_region_idx;
s_next_region_idx = TOTAL_NUM_FLASH_REGIONS;
} else {
s_next_region_idx = original_idx;
}
s_ftl_size = 0;
for (unsigned int i = 0; i < s_next_region_idx; i++) {
s_ftl_size += prv_region_size(i);
}
PBL_LOG(LOG_LEVEL_DEBUG, "Filesystem: Temporary size - %"PRId32" Kb", (s_ftl_size / 1024));
pfs_set_size(s_ftl_size, false /* don't erase regions */);
}
//! return a layout version that associates with the labels from above
static uint8_t prv_ftl_get_layout_version(void) {
// add all regions so PFS can know about them temporarily
prv_layout_version_add_all_regions(false);
uint8_t flash_version = 0;
uint32_t known_size = 0;
// iterate through all regions idx > 0 and see if PFS is active in the regions. If yes, increment
// flash version.
for (uint8_t i = flash_version; i < TOTAL_NUM_FLASH_REGIONS; i++) {
if ((prv_region_size(i) == 0) ||
pfs_active_in_region(known_size, known_size + prv_region_size(i))) {
// if active, increment known flash version and increase size to check next region
flash_version = i + 1;
known_size += prv_region_size(i);
} else {
// if not active, break and return known flash version
break;
}
}
// go back to the state we were in before the function
prv_layout_version_add_all_regions(true);
return flash_version;
}
void ftl_add_region(uint32_t region_start, uint32_t region_end, bool erase_new_region) {
// check if this region equals the next region, if so, then add next region
if ((region_start == s_region_list[s_next_region_idx].start) &&
(region_end == s_region_list[s_next_region_idx].end) &&
(s_next_region_idx < TOTAL_NUM_FLASH_REGIONS)) {
s_next_region_idx++;
// failure, should never happen
} else {
PBL_LOG(LOG_LEVEL_WARNING,
"Filesystem: Uh oh, we somehow added regions in the wrong order, %"PRIu32" %"PRIu32,
region_start, region_end);
return;
}
// erase if asked to
if (erase_new_region) {
flash_region_erase_optimal_range_no_watchdog(region_start, region_start,
region_end, region_end);
}
s_ftl_size += (region_end - region_start);
// call back to PFS to make sure it realizes there is more space to place files.
pfs_set_size(s_ftl_size, erase_new_region);
}
void ftl_populate_region_list(void) {
uint8_t flash_layout_version = prv_ftl_get_layout_version();
PBL_LOG(LOG_LEVEL_INFO, "Filesystem: Old Flash Layout Version: %u", flash_layout_version);
for (unsigned int i = s_next_region_idx; i < flash_layout_version; i++) {
ftl_add_region(s_region_list[i].start, s_region_list[i].end, false);
}
// at this point we have found all the regions that already exist on the flash
// so run our cleanup logic in case we rebooted during a filesystem operation
pfs_reboot_cleanup();
for (unsigned int i = s_next_region_idx; i < TOTAL_NUM_FLASH_REGIONS; i++) {
ftl_add_region(s_region_list[i].start, s_region_list[i].end, true);
}
PBL_LOG(LOG_LEVEL_DEBUG, "Filesystem: New size - %"PRId32" Kb", (s_ftl_size / 1024));
}
uint32_t ftl_get_size(void) {
return s_ftl_size;
}
static void prv_ftl_operation(uint8_t *buffer, uint32_t size, uint32_t offset,
FTLOperation operation) {
uint32_t curr_virt_offset_begin = 0;
uint32_t curr_virt_offset_end = 0;
// iterate through all regions and perform read, write, or erase
for (unsigned int idx = 0; (idx < s_next_region_idx) && (size != 0); idx++) {
curr_virt_offset_end += prv_region_size(idx);
if (offset < curr_virt_offset_end) {
uint32_t bytes = MIN(curr_virt_offset_end - offset, size);
if (operation == FTLRead) {
flash_read_bytes(
buffer, s_region_list[idx].start + offset - curr_virt_offset_begin, bytes);
} else if (operation == FTLWrite ) {
flash_write_bytes(
buffer, s_region_list[idx].start + offset - curr_virt_offset_begin, bytes);
} else if (operation == FTLEraseSubsector) {
PBL_ASSERTN(size == SUBSECTOR_SIZE_BYTES);
flash_erase_subsector_blocking(
s_region_list[idx].start + offset - curr_virt_offset_begin);
} else if (operation == FTLEraseSector) {
PBL_ASSERTN(size == SECTOR_SIZE_BYTES);
flash_erase_sector_blocking(
s_region_list[idx].start + offset - curr_virt_offset_begin);
}
size -= bytes;
offset += bytes;
}
curr_virt_offset_begin = curr_virt_offset_end;
}
}
void ftl_read(void *buffer, size_t size, uint32_t offset) {
prv_ftl_operation(buffer, size, offset, FTLRead);
}
void ftl_write(const void *buffer, size_t size, uint32_t offset) {
prv_ftl_operation((void *)buffer, size, offset, FTLWrite);
}
void ftl_erase_sector(uint32_t size, uint32_t offset) {
prv_ftl_operation(NULL /* not needed for erase */, size, offset, FTLEraseSector);
}
void ftl_erase_subsector(uint32_t size, uint32_t offset) {
prv_ftl_operation(NULL /* not needed for erase */, size, offset, FTLEraseSubsector);
}
void ftl_format(void) {
}
//! Only used for tests.
void ftl_force_version(int version_idx) {
s_next_region_idx = version_idx;
s_ftl_size = 0;
for (int i = 0; i < version_idx; i++) {
s_ftl_size += prv_region_size(i);
}
pfs_set_size(s_ftl_size, false);
extern void test_force_recalc_of_gc_region(void);
test_force_recalc_of_gc_region();
}

View file

@ -0,0 +1,74 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
//! Flash Translation Layer
//!
//! This module allows our filesystem, PFS, to grow into multiple flash regions while keeping a
//! contiguous virtual address space.
//!
//! On boot, this module checks each region to see if the filesystem is active in said region.
//! If so, it adds the region to the flash translation space and continues processing the
//! remaining regions. If the filesystem was not previously active in the region, then the region
//! is first migrated (by calling the migration function pointer) and is added to the
//! flash translation space.
//! Adds a flash region to the flash translation layer. This increases the overall size of the
//! flash translation space by (region_end - region_start)
//! @param region_start start of the region
//! @param region_end end of the region
//! @param erase_new_region Whether or not to erase the region before adding
void ftl_add_region(uint32_t region_start, uint32_t region_end, bool erase_new_region);
//! Gets the size of the flash translation space.
//! @return - the size of the flash translation space in number of bytes.
uint32_t ftl_get_size(void);
//! Erases a SECTOR in the flash translation space starting at the given virtual flash offset.
//! There is an ASSERT to check if size is exactly the size of the region being erased.
//!
//! @param size size of sector to erase. Should be equal to SUBSECTOR_SIZE_BYTES
//! @param offset virtual flash offset to erase the SUBSECTOR
void ftl_erase_sector(uint32_t size, uint32_t offset);
//! same as ftl_erase_sector except it operates on a SUBSECTOR
void ftl_erase_subsector(uint32_t size, uint32_t offset);
//! Reads the data at the virtual flash address given and writes it to the data buffer.
//! @param buffer The data block to write to
//! @param size The number of bytes from flash to write into buffer (must be <= the size of buffer)
//! @param offset Where to read the bytes from in the virtual flash translation space.
void ftl_read(void *buffer, size_t size, uint32_t offset);
//! Writes the data buffer to the virtual flash address given.
//! @param buffer The data block to write to flash
//! @param size The number of bytes from buffer to write (must be <= the size of buffer)
//! @param offset Where to write the bytes in the virtual flash translation space.
void ftl_write(const void *buffer, size_t size, uint32_t offset);
//! Formats all regions added to the flash translation layer
void ftl_format(void);
//! There are two steps to this function.
//! 1. Add all regions where PFS already exists, and add them to the flash translation layer.
//! 2. Migrate all regions where PFS does NOT exist and add them to the flash translation layer.
void ftl_populate_region_list(void);
void add_initial_space_to_filesystem(void);

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,223 @@
/*
* 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 <stddef.h>
#include "kernel/pebble_tasks.h"
#include "system/status_codes.h"
#include "util/list.h"
//! Exported APIs for the Pebble File System (PFS)
//!
//! Things to note:
//! - All APIs are threadsafe
//! - PFS implements a basic wear-leveling strategy to extend the life of
//! the flash part
//! - PFS allows the allocation of blocks of space which appear to the consumer
//! as a contiguous region. It is up to the consumer to manage how they
//! want to manage the allocated space
//! - Assumes underlying HW is a NOR flash chip. This means that when a
//! 0 bit value is written to a given location, the file needs to be erased
//! or rewritten to change it back to a 1. (pfs_open (i.e OP_FLAG_OVERWRITE)
//! provides a mechanism that consumers can leverage to accomplish this)
//! - Erasing flash sectors is a costly operation (from both a time/power
//! perspective). Care should be taken not to constantly be deleting/creating
//! files
#define OP_FLAG_READ (1 << 0)
#define OP_FLAG_WRITE (1 << 1)
#define OP_FLAG_OVERWRITE (1 << 2)
#define OP_FLAG_SKIP_HDR_CRC_CHECK (1 << 3)
#define OP_FLAG_USE_PAGE_CACHE (1 << 4)
#define FILE_TYPE_STATIC (0xfe)
#define FILE_MAX_NAME_LEN (255)
typedef enum {
FSeekSet,
FSeekCur
} FSeekType;
//! Used by pfs_watch_file to know which events to trigger callbacks on
#define FILE_CHANGED_EVENT_CLOSED (1 << 0)
#define FILE_CHANGED_EVENT_REMOVED (1 << 1)
#define FILE_CHANGED_EVENT_ALL (FILE_CHANGED_EVENT_CLOSED | FILE_CHANGED_EVENT_REMOVED)
//! Types used by pfs_watch_file()
typedef void (*PFSFileChangedCallback)(void *data);
typedef void *PFSCallbackHandle;
//! Used by pfs_list_files() and pfs_remove_files()
typedef bool (*PFSFilenameTestCallback)(const char *name);
//! Format of each entry in the linked list returned by pfs_create_file_list
typedef struct {
ListNode list_node;
char name[];
} PFSFileListEntry;
//! @param name - The name of the file to be opened
//! @param op_flags - The operation to be performed on the file
//!
//! OP_FLAG_READ - Open a file such that pfs_read operations will work. If the
//! file does not exist, opening a file with just this mode set will fail
//!
//! OP_FLAG_WRITE - Creates a file if it does not exist, else opens a file
//! such that pfs_write operations will work. It is up to the user to seek to
//! the desired offset within the file
//!
//! OP_FLAG_OVERWRITE - Provides a safe mechanism to incrementally overwrite
//! a file that already exists with new data. Open will fail if the file does
//! not already exist on flash. The changes for the overwritten file are not
//! committed until the pfs_close is called. Until this time, pfs_open of the
//! 'name' will return a hdl to the original file. This way there is always a
//! valid version of the file which can be read & the caller can copy parts
//! of the orginal file in hunks rather than allocating a lot of RAM.
//!
//! OP_FLAG_SKIP_HDR_CRC_CHECK - For files which are not accessed frequently,
//! it is a good idea to sanity check the on-flash header CRCs to make sure
//! nothing has gone astray. This has performance ramifications if you are
//! doing thousands of opens on the same file so this flags allows consumers
//! to turn the check off
//!
//! OP_FLAG_USE_PAGE_CACHE - Turns on caching for the translation from
//! virtual filesystem pages to their physical address. For large files with
//! a lot of random access this is advantageous because we need to read
//! flash bytes to get to the correct page. Ideally this should only be
//! used for read operations so that heap corruption does not lead to us
//! corrupting a file
//!
//! The following two parameters are only parsed when a file is
//! overwritten/created:
//!
//! @param file_type - The type of file being opened
//! @param start_size - The initial space to be allocated for a file if it is
//! being created
//! @return - status_t error code if the operation failed,
//! else a fd handle >= 0 if operation was successful
extern int pfs_open(const char *name, uint8_t op_flags, uint8_t file_type,
size_t start_size);
//! Writes data to the fd specified. After each write, the internal file offset
//! is moved forward
//! @param fd - The fd to write data to
//! @param buf - The buffer of data to write
//! @param size - The number of bytes from buf to write
//! (must be <= the size of buf)
//! @return - the number of bytes written or a status_t code on error
extern int pfs_write(int fd, const void *buf, size_t size);
//! Reads data from the fd specified. After each read, the internal file offset
//! is moved forward
//! @param fd - the file to read data from
//! @param buf - the buffer to store read data in
//! @param size - the amount of data to read (must be <= the size of buf)
//! @return - the number of bytes read or a status_t code on error
extern int pfs_read(int fd, void *buf, size_t size);
//! Seeks to offset specified
//! Returns the offset forwarded to on success,
//! status_t code < 0 to indicate type of failure
extern int pfs_seek(int fd, int offset, FSeekType seek_type);
//! Frees up internal tracking data associated with a given file.
//! @param fd - the fd to close
//! @return - S_SUCCESS or appropriate error code on failure
extern status_t pfs_close(int fd);
//! calls pfs_close and pfs_remove on a file successively
//! @param fd - the fd describing the file to remove
extern status_t pfs_close_and_remove(int fd);
//! Unlinks a given file from the filesystem
//! @param name - the name of the file to remove
//! @return - S_SUCCESS or appropriate error code on failure
extern status_t pfs_remove(const char *name);
//! Returns the size of the file. (The amount of bytes that can be read out)
extern size_t pfs_get_file_size(int fd);
//! Should only be called before using FS
extern status_t pfs_init(bool run_filesystem_check);
//! Should only be called once after reboot and before any file operations
//! are performed
extern void pfs_reboot_cleanup(void);
//! erases everything on the filesystem & removes any open
//! file entries from the cache
//! Note: assumes that pfs_init was called before this
extern void pfs_format(bool write_erase_headers);
//! Returns the size of the pfs filesystem.
extern uint32_t pfs_get_size(void);
//! Updates the size of the pfs filesystem.
//! @param new_size new size
//! @param new_region_erased if the pages being added have been erased and should be marked as so.
extern void pfs_set_size(uint32_t new_size, bool new_region_erased);
//! Returns true is pfs is active on this device
extern bool pfs_active(void);
//! Returns true is pfs is active in the region
extern bool pfs_active_in_region(uint32_t start_address, uint32_t ending_address);
//! In the case of a file which can actually make use of additional space
//! beyond a certain minimum, this function will return the optimal size
//! that should be used for such a file, in order to use no more sectors
//! than the minimum size would.
extern int pfs_sector_optimal_size(int min_size, int namelen);
//! Returns the number of bytes available on the filesystem
extern uint32_t get_available_pfs_space(void);
//! Watch a file. The callback is called whenever the given file (by name) is closed with
//! modifications or deleted
//! @param filename - name of the file to watch
//! @param callback - function to invoke when file is changed
//! @param event_flags - which events the callback should be triggered on
//! (see FILE_CHANGED_EVENT_ flags defined above)
//! @param data - pointer passed to callback when invoked
//! @return - cb handle to pass into \ref pfs_unwatch_file
PFSCallbackHandle pfs_watch_file(const char* filename, PFSFileChangedCallback callback,
uint8_t event_flags, void* data);
//! Stop watching a file.
void pfs_unwatch_file(PFSCallbackHandle cb_handle);
//! calculate the CRC32 for a given part of a file
extern uint32_t pfs_crc_calculate_file(int fd, uint32_t offset, uint32_t num_bytes);
//! Get a directory listing, calling the filter callback on each filename.
//! Returns linked list of filenames, filtered by the callback.
//! @param callback - name filter function to be called for each filename, or NULL to include
//! all files.
//! @return - pointer to head node of linked list of names, or NULL if no names match
extern PFSFileListEntry *pfs_create_file_list(PFSFilenameTestCallback callback);
//! Delete a directory list returned by pfs_list_files
//! @param callback - callback to be called for on filename
//! @return - pointer to head node of linked list of names, or NULL if no names match
extern void pfs_delete_file_list(PFSFileListEntry *list);
//! Run each filename in the filesystem through the filter callback and delete all files that match
//! @param callback - callback to be called for on filename
extern void pfs_remove_files(PFSFilenameTestCallback callback);