From d4ebe64af05e2fa163bcea0dd699ae9c8934cc6f Mon Sep 17 00:00:00 2001 From: Joey Castillo Date: Fri, 6 May 2022 17:06:27 -0400 Subject: [PATCH] add support for a small filesystem on the watch --- apps/eeprom-emulation-upgrade/Makefile | 10 ++ apps/eeprom-emulation-upgrade/app.c | 62 +++++++++ apps/flash-test/Makefile | 13 ++ apps/flash-test/app.c | 131 ++++++++++++++++++ make.mk | 2 + watch-library/hardware/watch/watch_storage.c | 94 +++++++++++++ watch-library/shared/watch/watch.h | 1 + watch-library/shared/watch/watch_storage.h | 92 ++++++++++++ watch-library/simulator/watch/watch_storage.c | 32 +++++ 9 files changed, 437 insertions(+) create mode 100755 apps/eeprom-emulation-upgrade/Makefile create mode 100644 apps/eeprom-emulation-upgrade/app.c create mode 100755 apps/flash-test/Makefile create mode 100644 apps/flash-test/app.c create mode 100644 watch-library/hardware/watch/watch_storage.c create mode 100644 watch-library/shared/watch/watch_storage.h create mode 100644 watch-library/simulator/watch/watch_storage.c diff --git a/apps/eeprom-emulation-upgrade/Makefile b/apps/eeprom-emulation-upgrade/Makefile new file mode 100755 index 0000000..5534c17 --- /dev/null +++ b/apps/eeprom-emulation-upgrade/Makefile @@ -0,0 +1,10 @@ +TOP = ../.. +include $(TOP)/make.mk + +INCLUDES += \ + -I./ + +SRCS += \ + ./app.c + +include $(TOP)/rules.mk diff --git a/apps/eeprom-emulation-upgrade/app.c b/apps/eeprom-emulation-upgrade/app.c new file mode 100644 index 0000000..edd73e4 --- /dev/null +++ b/apps/eeprom-emulation-upgrade/app.c @@ -0,0 +1,62 @@ +#include +#include +#include +#include +#include "watch.h" + +void app_init(void) { +} + +void app_wake_from_backup(void) { +} + +void app_setup(void) { + delay_ms(5000); + while (!(NVMCTRL->INTFLAG.reg & NVMCTRL_INTFLAG_READY)); + uint32_t user_row = (*((uint32_t *)NVMCTRL_AUX0_ADDRESS)); + uint8_t eeprom = (user_row >> NVMCTRL_FUSES_EEPROM_SIZE_Pos) & 7; + printf("User row read successfully: 0x%lx\n", user_row); + + if (eeprom != 1) { + user_row &= ~NVMCTRL_FUSES_EEPROM_SIZE_Msk; + user_row |= NVMCTRL_FUSES_EEPROM_SIZE(1); + if (NVMCTRL->STATUS.reg & NVMCTRL_STATUS_SB) { + printf("Secured bit was set; cannot perform upgrade.\n"); + return; + } + printf("EEPROM configuration was %d.\nApplying change...\n", eeprom); + + uint32_t temp = NVMCTRL->CTRLB.reg; // Backup settings + NVMCTRL->CTRLB.reg = temp | NVMCTRL_CTRLB_CACHEDIS; // Disable Cache + NVMCTRL->STATUS.reg |= NVMCTRL_STATUS_MASK; // Clear error flags + NVMCTRL->ADDR.reg = NVMCTRL_AUX0_ADDRESS / 2; // Set address, command will be issued elsewhere + NVMCTRL->CTRLA.reg = NVMCTRL_CTRLA_CMD_EAR | NVMCTRL_CTRLA_CMDEX_KEY; // Erase the user page + while (!(NVMCTRL->INTFLAG.reg & NVMCTRL_INTFLAG_READY)); // Wait for NVM command to complete + NVMCTRL->STATUS.reg |= NVMCTRL_STATUS_MASK; // Clear error flags + NVMCTRL->ADDR.reg = NVMCTRL_AUX0_ADDRESS / 2; // Set address, command will be issued elsewhere + NVMCTRL->CTRLA.reg = NVMCTRL_CTRLA_CMD_PBC | NVMCTRL_CTRLA_CMDEX_KEY; // Erase the page buffer before buffering new data + while (!(NVMCTRL->INTFLAG.reg & NVMCTRL_INTFLAG_READY)); // Wait for NVM command to complete + NVMCTRL->STATUS.reg |= NVMCTRL_STATUS_MASK; // Clear error flags + NVMCTRL->ADDR.reg = NVMCTRL_AUX0_ADDRESS / 2; // Set address, command will be issued elsewhere + *((uint32_t *)NVMCTRL_AUX0_ADDRESS) = user_row; // write the new fuse values to the memory buffer + NVMCTRL->CTRLA.reg = NVMCTRL_CTRLA_CMD_WAP | NVMCTRL_CTRLA_CMDEX_KEY; // Write the user page + NVMCTRL->CTRLB.reg = temp; // Restore settings + + printf("Done! Resetting...\n"); + delay_ms(1000); + NVIC_SystemReset(); + } else { + printf("EEPROM configuration was %d (8192 bytes). Upgrade successful!\n", eeprom); + } + printf("%d %d\n", eeprom, NVMCTRL->PARAM.bit.RWWEEP); +} + +void app_prepare_for_standby(void) { +} + +void app_wake_from_standby(void) { +} + +bool app_loop(void) { + return true; +} diff --git a/apps/flash-test/Makefile b/apps/flash-test/Makefile new file mode 100755 index 0000000..1e55226 --- /dev/null +++ b/apps/flash-test/Makefile @@ -0,0 +1,13 @@ +TOP = ../.. +include $(TOP)/make.mk + +INCLUDES += \ + -I$(TOP)/littlefs/ \ + -I./ + +SRCS += \ + $(TOP)/littlefs/lfs.c \ + $(TOP)/littlefs/lfs_util.c \ + ./app.c + +include $(TOP)/rules.mk diff --git a/apps/flash-test/app.c b/apps/flash-test/app.c new file mode 100644 index 0000000..17b1d0a --- /dev/null +++ b/apps/flash-test/app.c @@ -0,0 +1,131 @@ +#include +#include +#include +#include +#include "watch.h" +#include "lfs.h" +#include "hpl_flash.h" + +int lfs_storage_read(const struct lfs_config *cfg, lfs_block_t block, lfs_off_t off, void *buffer, lfs_size_t size); +int lfs_storage_prog(const struct lfs_config *cfg, lfs_block_t block, lfs_off_t off, const void *buffer, lfs_size_t size); +int lfs_storage_erase(const struct lfs_config *cfg, lfs_block_t block); +int lfs_storage_sync(const struct lfs_config *cfg); + +int lfs_storage_read(const struct lfs_config *cfg, lfs_block_t block, lfs_off_t off, void *buffer, lfs_size_t size) { + (void) cfg; + return !watch_storage_read(block, off, (void *)buffer, size); +} + +int lfs_storage_prog(const struct lfs_config *cfg, lfs_block_t block, lfs_off_t off, const void *buffer, lfs_size_t size) { + (void) cfg; + return !watch_storage_write(block, off, (void *)buffer, size); +} + +int lfs_storage_erase(const struct lfs_config *cfg, lfs_block_t block) { + (void) cfg; + return !watch_storage_erase(block); +} + +int lfs_storage_sync(const struct lfs_config *cfg) { + (void) cfg; + return !watch_storage_sync(); +} + +const struct lfs_config cfg = { + // block device operations + .read = lfs_storage_read, + .prog = lfs_storage_prog, + .erase = lfs_storage_erase, + .sync = lfs_storage_sync, + + // block device configuration + .read_size = 16, + .prog_size = NVMCTRL_PAGE_SIZE, + .block_size = NVMCTRL_ROW_SIZE, + .block_count = NVMCTRL_RWWEE_PAGES / 4, + .cache_size = NVMCTRL_PAGE_SIZE, + .lookahead_size = 16, + .block_cycles = 100, +}; + +lfs_t lfs; +lfs_file_t file; + +static int _traverse_df_cb(void *p, lfs_block_t block){ + (void) block; + uint32_t *nb = p; + *nb += 1; + return 0; +} + +static int get_free_space(void){ + int err; + + uint32_t free_blocks = 0; + err = lfs_fs_traverse(&lfs, _traverse_df_cb, &free_blocks); + if(err < 0){ + return err; + } + + uint32_t available = cfg.block_count * cfg.block_size - free_blocks * cfg.block_size; + + return available; +} + +static void cb_tick(void) { + watch_date_time date_time = watch_rtc_get_date_time(); + if (date_time.unit.second == 0) { + int err = lfs_mount(&lfs, &cfg); + if (err) { + printf("Mount failed: %d\n", err); + } + // read current count + uint32_t loop_count = 0; + lfs_file_open(&lfs, &file, "loop_count", LFS_O_RDWR | LFS_O_CREAT); + lfs_file_read(&lfs, &file, &loop_count, sizeof(loop_count)); + + // update loop count + loop_count += 1; + lfs_file_rewind(&lfs, &file); + lfs_file_write(&lfs, &file, &loop_count, sizeof(loop_count)); + + // remember the storage is not updated until the file is closed successfully + lfs_file_close(&lfs, &file); + + // release any resources we were using + lfs_unmount(&lfs); + + // print the boot count + printf("loop_count: %ld\n", loop_count); + printf("free space: %d\n", get_free_space()); + } +} + +void app_init(void) { +} + +void app_wake_from_backup(void) { +} + +void app_setup(void) { + // mount the filesystem + int err = lfs_mount(&lfs, &cfg); + + // reformat if we can't mount the filesystem + // this should only happen on the first boot + if (err) { + lfs_format(&lfs, &cfg); + lfs_mount(&lfs, &cfg); + } + watch_rtc_register_tick_callback(cb_tick); +} + +void app_prepare_for_standby(void) { +} + +void app_wake_from_standby(void) { +} + +bool app_loop(void) { + return true; +} diff --git a/make.mk b/make.mk index ac5be77..07c6933 100644 --- a/make.mk +++ b/make.mk @@ -87,6 +87,7 @@ SRCS += \ $(TOP)/watch-library/hardware/watch/watch_i2c.c \ $(TOP)/watch-library/hardware/watch/watch_spi.c \ $(TOP)/watch-library/hardware/watch/watch_uart.c \ + $(TOP)/watch-library/hardware/watch/watch_storage.c \ $(TOP)/watch-library/hardware/watch/watch_deepsleep.c \ $(TOP)/watch-library/hardware/watch/watch_private.c \ $(TOP)/watch-library/hardware/watch/watch.c \ @@ -158,6 +159,7 @@ SRCS += \ $(TOP)/watch-library/simulator/watch/watch_i2c.c \ $(TOP)/watch-library/simulator/watch/watch_spi.c \ $(TOP)/watch-library/simulator/watch/watch_uart.c \ + $(TOP)/watch-library/simulator/watch/watch_storage.c \ $(TOP)/watch-library/simulator/watch/watch_deepsleep.c \ $(TOP)/watch-library/simulator/watch/watch_private.c \ $(TOP)/watch-library/simulator/watch/watch.c \ diff --git a/watch-library/hardware/watch/watch_storage.c b/watch-library/hardware/watch/watch_storage.c new file mode 100644 index 0000000..0cd0448 --- /dev/null +++ b/watch-library/hardware/watch/watch_storage.c @@ -0,0 +1,94 @@ +#include +#include +#include +#include "watch_storage.h" + +#define RWWEE_ADDR_START NVMCTRL_RWW_EEPROM_ADDR +#define RWWEE_ADDR_END (NVMCTRL_RWW_EEPROM_ADDR + NVMCTRL_PAGE_SIZE * NVMCTRL_RWWEE_PAGES) +#define NVM_MEMORY ((volatile uint16_t *)FLASH_ADDR) + +static bool _is_valid_address(uint32_t addr, uint32_t size) { + if ((addr < NVMCTRL_RWW_EEPROM_ADDR) || (addr > (NVMCTRL_RWW_EEPROM_ADDR + NVMCTRL_PAGE_SIZE * NVMCTRL_RWWEE_PAGES))) { + return false; + } + if ((addr + size > (NVMCTRL_RWW_EEPROM_ADDR + NVMCTRL_PAGE_SIZE * NVMCTRL_RWWEE_PAGES))) { + return false; + } + + return true; +} + +bool watch_storage_read(uint32_t row, uint32_t offset, uint8_t *buffer, uint32_t size) { + uint32_t address = RWWEE_ADDR_START + row * NVMCTRL_ROW_SIZE + offset; + if (!_is_valid_address(address, size)) return false; + + uint32_t nvm_address = address / 2; + uint32_t i; + uint16_t data; + + watch_storage_sync(); + + if (address % 2) { + data = NVM_MEMORY[nvm_address++]; + buffer[0] = data >> 8; + i = 1; + } else { + i = 0; + } + + while (i < size) { + data = NVM_MEMORY[nvm_address++]; + buffer[i] = (data & 0xFF); + if (i < (size - 1)) { + buffer[i + 1] = (data >> 8); + } + i += 2; + } + return true; +} + +bool watch_storage_write(uint32_t row, uint32_t offset, const uint8_t *buffer, uint32_t size) { + uint32_t address = RWWEE_ADDR_START + row * NVMCTRL_ROW_SIZE + offset; + if (!_is_valid_address(address, size)) return false; + + watch_storage_sync(); + + uint32_t nvm_address = address / 2; + uint16_t i, data; + + hri_nvmctrl_write_CTRLA_reg(NVMCTRL, NVMCTRL_CTRLA_CMD_PBC | NVMCTRL_CTRLA_CMDEX_KEY); + watch_storage_sync(); + + for (i = 0; i < size; i += 2) { + data = buffer[i]; + if (i < NVMCTRL_PAGE_SIZE - 1) { + data |= (buffer[i + 1] << 8); + } + NVM_MEMORY[nvm_address++] = data; + } + hri_nvmctrl_write_ADDR_reg(NVMCTRL, address / 2); + hri_nvmctrl_write_CTRLA_reg(NVMCTRL, NVMCTRL_CTRLA_CMD_RWWEEWP | NVMCTRL_CTRLA_CMDEX_KEY); + + return true; +} + +bool watch_storage_erase(uint32_t row) { + uint32_t address = RWWEE_ADDR_START + row * NVMCTRL_ROW_SIZE; + if (!_is_valid_address(address, NVMCTRL_ROW_SIZE)) return false; + + watch_storage_sync(); + hri_nvmctrl_write_ADDR_reg(NVMCTRL, address / 2); + hri_nvmctrl_write_CTRLA_reg(NVMCTRL, NVMCTRL_CTRLA_CMD_RWWEEER | NVMCTRL_CTRLA_CMDEX_KEY); + + return true; +} + +bool watch_storage_sync(void) { + while (!hri_nvmctrl_get_interrupt_READY_bit(NVMCTRL)) { + // wait for flash to become ready + } + + hri_nvmctrl_clear_STATUS_reg(NVMCTRL, NVMCTRL_STATUS_MASK); + + return true; +} diff --git a/watch-library/shared/watch/watch.h b/watch-library/shared/watch/watch.h index ce85eed..1dd8e7f 100644 --- a/watch-library/shared/watch/watch.h +++ b/watch-library/shared/watch/watch.h @@ -64,6 +64,7 @@ #include "watch_i2c.h" #include "watch_spi.h" #include "watch_uart.h" +#include "watch_storage.h" #include "watch_deepsleep.h" #include "watch_private.h" diff --git a/watch-library/shared/watch/watch_storage.h b/watch-library/shared/watch/watch_storage.h new file mode 100644 index 0000000..6026cbc --- /dev/null +++ b/watch-library/shared/watch/watch_storage.h @@ -0,0 +1,92 @@ +/* + * MIT License + * + * Copyright (c) 2020 Joey Castillo + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef _WATCH_STORAGE_H_INCLUDED +#define _WATCH_STORAGE_H_INCLUDED +////< @file watch_storage.h + +#include "watch.h" + +#ifndef NVMCTRL_ROW_SIZE +#define NVMCTRL_ROW_SIZE 256 +#endif +#ifndef NVMCTRL_PAGE_SIZE +#define NVMCTRL_PAGE_SIZE 64 +#endif +#ifndef NVMCTRL_RWWEE_PAGES +#define NVMCTRL_RWWEE_PAGES 128 +#endif + +/** @addtogroup storage Flash Storage + * @brief This section covers functions related to the SAM L22's 8 kilobyte EEPROM emulation area. + * @details The SAM L22 inside Sensor Watch has a 256 kilobyte Flash memory array that can be + * programmed with whatever data we want. We use most of it to store the bootloader + * and the application code that runs on your wrist. The bootloader region is read-only, + * and the main application area is only writable by the bootloader (when you drag new + * code onto the WATCHBOOT drive). However! there's also a special 8 kilobyte region + * at the end of the Flash memory called the EEPROM Emulation Area. This EEPROM emulation + * area can be written or erased while the main Flash array is being read. This makes it + * super easy to work with, and useful for storing a small amount of non-volatile data that + * persists across reboots, even when power is lost. + * The functions in this section are very basic, and only cover reading and writing data + * in this area. The region is laid out as 32 rows consisting of 4 pages of 64 bytes. + * 32*4*64 = 8192 bytes. The area can be written one page at a time, but it can only be + * erased one row at a time. You can read at arbitrary word-aligned offsets within a row. + * + * ┌──────────────┬──────────────┬──────────────┬──────────────┐ + * Row 0 │ 64 bytes │ 64 bytes │ 64 bytes │ 64 bytes │ + * ├──────────────┼──────────────┼──────────────┼──────────────┤ + * Row 1 │ 64 bytes │ 64 bytes │ 64 bytes │ 64 bytes │ + * ├──────────────┼──────────────┼──────────────┼──────────────┤ + * ... │ │ │ │ │ + * ├──────────────┼──────────────┼──────────────┼──────────────┤ + * Row 31 │ 64 bytes │ 64 bytes │ 64 bytes │ 64 bytes │ + * └──────────────┴──────────────┴──────────────┴──────────────┘ + */ +/// @{ +/** @brief Reads a range of bytes from the storage area. + * @param row The row you want to read. + * @param offset The offset from the beginning of the row. + * @param buffer A buffer of at least `size` bytes. + * @param size The number of bytes you wish to read. + */ +bool watch_storage_read(uint32_t row, uint32_t offset, uint8_t *buffer, uint32_t size); + +/** @brief Writes bytes to a page in the storage area. Note that the row should already be erased before writing. + * @param row The row containing the page you want to write. + * @param offset The offset from the beginning of the row. Must be a multiple of 64. + * @param buffer The buffer containing the bytes you wish to set. + * @param size The number of bytes you wish to write. + */ +bool watch_storage_write(uint32_t row, uint32_t offset, const uint8_t *buffer, uint32_t size); + +/** @brief Erases a row in the storage area, setting all its bytes to 0xFF. + * @param row The row you want to erase. + */ +bool watch_storage_erase(uint32_t row); + +/** @brief Waits for any pending writes to complete. + */ +bool watch_storage_sync(void); +/// @} +#endif diff --git a/watch-library/simulator/watch/watch_storage.c b/watch-library/simulator/watch/watch_storage.c new file mode 100644 index 0000000..2701180 --- /dev/null +++ b/watch-library/simulator/watch/watch_storage.c @@ -0,0 +1,32 @@ +#include +#include +#include +#include "watch_storage.h" + +uint8_t storage[NVMCTRL_ROW_SIZE * NVMCTRL_RWWEE_PAGES]; + +bool watch_storage_read(uint32_t row, uint32_t offset, uint8_t *buffer, uint32_t size) { + // printf("read row %ld offset %ld size %ld\n", row, offset, size); + memcpy(buffer, storage + row * NVMCTRL_ROW_SIZE + offset, size); + + return true; +} + +bool watch_storage_write(uint32_t row, uint32_t offset, const uint8_t *buffer, uint32_t size) { + // printf("write row %ld offset %ld size %ld\n", row, offset, size); + memcpy(storage + row * NVMCTRL_ROW_SIZE + offset, buffer, size); + + return true; +} + +bool watch_storage_erase(uint32_t row) { + // printf("erase row %ld\n", row); + memset(storage + row * NVMCTRL_ROW_SIZE, 0xff, NVMCTRL_ROW_SIZE); + + return true; +} + +bool watch_storage_sync(void) { + // nothing to do here! + return true; +}