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,599 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "drivers/flash.h"
#include "drivers/flash/flash_internal.h"
#include <inttypes.h>
#include <stdbool.h>
#include <stdint.h>
#include "drivers/flash/flash_impl.h"
#include "drivers/task_watchdog.h"
#include "drivers/watchdog.h"
#include "flash_region/flash_region.h"
#include "kernel/util/stop.h"
#include "os/mutex.h"
#include "os/tick.h"
#include "process_management/worker_manager.h"
#include "services/common/analytics/analytics.h"
#include "services/common/new_timer/new_timer.h"
#include "system/logging.h"
#include "system/passert.h"
#include "kernel/util/sleep.h"
#include "FreeRTOS.h"
#include "semphr.h"
#define MAX_ERASE_RETRIES (3)
static PebbleMutex *s_flash_lock;
static SemaphoreHandle_t s_erase_semphr;
static struct FlashEraseContext {
bool in_progress;
bool suspended;
bool is_subsector;
uint8_t retries;
PebbleTask task;
uint32_t address;
FlashOperationCompleteCb on_complete_cb;
void *cb_context;
uint32_t expected_duration;
} s_erase = { 0 };
static TimerID s_erase_poll_timer;
static TimerID s_erase_suspend_timer;
static uint32_t s_analytics_read_count;
static uint32_t s_analytics_read_bytes_count;
static uint32_t s_analytics_write_bytes_count;
static uint32_t s_system_analytics_read_bytes_count = 0;
static uint32_t s_system_analytics_write_bytes_count = 0;
static uint8_t s_system_analytics_erase_count = 0;
void analytics_external_collect_system_flash_statistics(void) {
analytics_set(ANALYTICS_DEVICE_METRIC_FLASH_READ_BYTES_COUNT,
s_system_analytics_read_bytes_count, AnalyticsClient_System);
analytics_set(ANALYTICS_DEVICE_METRIC_FLASH_WRITE_BYTES_COUNT,
s_system_analytics_write_bytes_count, AnalyticsClient_System);
analytics_set(ANALYTICS_DEVICE_METRIC_FLASH_ERASE_COUNT,
s_system_analytics_erase_count, AnalyticsClient_System);
s_system_analytics_read_bytes_count = 0;
s_system_analytics_write_bytes_count = 0;
s_system_analytics_erase_count = 0;
}
void analytics_external_collect_app_flash_read_stats(void) {
analytics_set(ANALYTICS_APP_METRIC_FLASH_READ_COUNT,
s_analytics_read_count, AnalyticsClient_App);
analytics_set(ANALYTICS_APP_METRIC_FLASH_READ_BYTES_COUNT,
s_analytics_read_bytes_count, AnalyticsClient_App);
analytics_set(ANALYTICS_APP_METRIC_FLASH_WRITE_BYTES_COUNT,
s_analytics_write_bytes_count, AnalyticsClient_App);
// The overhead cost of tracking whether each flash read was due to the foreground
// or background app is large, so the best we can do is to attribute to both of them
if (worker_manager_get_current_worker_md() != NULL) {
analytics_set(ANALYTICS_APP_METRIC_FLASH_READ_COUNT,
s_analytics_read_count, AnalyticsClient_Worker);
analytics_set(ANALYTICS_APP_METRIC_FLASH_READ_BYTES_COUNT,
s_analytics_read_bytes_count, AnalyticsClient_Worker);
analytics_set(ANALYTICS_APP_METRIC_FLASH_WRITE_BYTES_COUNT,
s_analytics_write_bytes_count, AnalyticsClient_Worker);
}
s_analytics_read_count = 0;
s_analytics_read_bytes_count = 0;
s_analytics_write_bytes_count = 0;
}
//! Assumes that s_flash_lock is held.
static status_t prv_try_restart_interrupted_erase(bool is_subsector,
uint32_t addr) {
status_t status = is_subsector? flash_impl_erase_subsector_begin(addr)
: flash_impl_erase_sector_begin(addr);
if (FAILED(status)) {
PBL_LOG(LOG_LEVEL_ERROR, "Got error trying to reissue interrupted erase: "
"%"PRIi32, status);
return status;
}
// Hopefully the task watchdog isn't enabled; this could take a while.
while (1) {
psleep(10);
status = flash_impl_get_erase_status();
if (status != E_BUSY && status != E_AGAIN) {
// Success or failure
return status;
}
}
}
void flash_init(void) {
if (s_flash_lock) {
return; // Already initialized
}
s_flash_lock = mutex_create();
s_erase_semphr = xSemaphoreCreateBinary();
xSemaphoreGive(s_erase_semphr);
s_erase_poll_timer = new_timer_create();
s_erase_suspend_timer = new_timer_create();
flash_erase_init();
mutex_lock(s_flash_lock);
flash_impl_init(false /* coredump_mode */);
uint32_t erase_in_progress_address = 0;
bool is_subsector = false;
if (flash_impl_get_nvram_erase_status(
&is_subsector, &erase_in_progress_address) == S_TRUE) {
// An erase was interrupted by e.g. a crash. Retry the erase so the
// incompletely-erased sector doesn't cause us trouble later on.
PBL_LOG(LOG_LEVEL_WARNING, "Flash erase to 0x%"PRIx32" was interrupted "
"last boot. Restarting the erase...", erase_in_progress_address);
// When an erase is interrupted, subsequent erases of the same sector tend to
// take an exceptionally long time and may even outright fail. Try the erase a
// few times before giving up.
int attempt = 1;
while (true) {
status_t status = prv_try_restart_interrupted_erase(
is_subsector, erase_in_progress_address);
if (PASSED(status)) {
break;
}
PBL_LOG(LOG_LEVEL_ERROR, "Flash erase failed, status %"PRIi32, status);
if (attempt++ >= MAX_ERASE_RETRIES) {
// We've tried all we can. No point in croaking; it's not like a reboot
// is going to fix anything. Best we can do is pretend like nothing is
// wrong and hope for the best.
PBL_LOG(LOG_LEVEL_ERROR, "Giving up on restarting the flash erase.");
break;
}
}
}
flash_impl_clear_nvram_erase_status();
mutex_unlock(s_flash_lock);
}
#if UNITTEST
void flash_api_reset_for_test(void) {
s_erase = (struct FlashEraseContext) {0};
s_flash_lock = NULL;
}
TimerID flash_api_get_erase_poll_timer_for_test(void) {
return s_erase_poll_timer;
}
#endif
//! Assumes that s_flash_lock is held.
static void prv_erase_pause(void) {
if (s_erase.in_progress && !s_erase.suspended) {
// If an erase is in progress, make sure it gets at least a mininum time slice to progress.
// If not, the successive kicking of the suspend timer could starve it out completely
psleep(100);
task_watchdog_bit_set(s_erase.task);
status_t status = flash_impl_erase_suspend(s_erase.address);
PBL_ASSERT(PASSED(status), "Erase suspend failure: %" PRId32, status);
if (status == S_NO_ACTION_REQUIRED) {
// The erase has already completed. No need to resume.
s_erase.in_progress = false;
} else {
s_erase.suspended = true;
}
}
}
//! Assumes that s_flash_lock is held.
static void prv_erase_resume(void) {
if (s_erase.suspended) {
status_t status = flash_impl_erase_resume(s_erase.address);
PBL_ASSERT(PASSED(status), "Erase resume failure: %" PRId32, status);
s_erase.suspended = false;
}
}
static void prv_erase_suspend_timer_cb(void *unused) {
mutex_lock(s_flash_lock);
prv_erase_resume();
mutex_unlock(s_flash_lock);
}
void flash_read_bytes(uint8_t* buffer, uint32_t start_addr,
uint32_t buffer_size) {
mutex_lock(s_flash_lock);
s_analytics_read_count++;
s_analytics_read_bytes_count += buffer_size;
s_system_analytics_read_bytes_count += buffer_size;
// TODO: use DMA when possible
// TODO: be smarter about pausing erases. Some flash chips allow concurrent
// reads while an erase is in progress, as long as the read is to another bank
// than the one being erased.
prv_erase_pause();
new_timer_start(s_erase_suspend_timer, 5, prv_erase_suspend_timer_cb, NULL, 0);
flash_impl_read_sync(buffer, start_addr, buffer_size);
mutex_unlock(s_flash_lock);
}
#ifdef TEST_FLASH_LOCK_PROTECTION
static bool s_assert_write_error = false;
void flash_expect_program_failure(bool expect_failure) {
s_assert_write_error = expect_failure;
}
#endif
void flash_write_bytes(const uint8_t *buffer, uint32_t start_addr,
uint32_t buffer_size) {
mutex_lock(s_flash_lock);
stop_mode_disable(InhibitorFlash); // FIXME: PBL-18028
s_analytics_write_bytes_count += buffer_size;
s_system_analytics_write_bytes_count += buffer_size;
prv_erase_pause();
new_timer_start(s_erase_suspend_timer, 50, prv_erase_suspend_timer_cb, NULL, 0);
while (buffer_size) {
int written = flash_impl_write_page_begin(buffer, start_addr, buffer_size);
PBL_ASSERT(
#ifdef TEST_FLASH_LOCK_PROTECTION
s_assert_write_error ||
#endif
PASSED(written),
"flash_impl_write_page_begin failed: %d", written);
status_t status;
while ((status = flash_impl_get_write_status()) == E_BUSY) {
psleep(0);
}
#ifdef TEST_FLASH_LOCK_PROTECTION
if (s_assert_write_error) {
PBL_ASSERT(FAILED(status), "flash write unexpectedly succeeded: %" PRId32,
status);
} else {
#endif
PBL_ASSERT(PASSED(status), "flash_impl_get_write_status returned %" PRId32,
status);
#ifdef TEST_FLASH_LOCK_PROTECTION
}
#endif
buffer += written;
start_addr += written;
buffer_size -= written;
// Give higher-priority tasks a chance to access the flash in between
// each page write.
// TODO: uncomment the lines below to resolve PBL-17503
// if (buffer_size) {
// mutex_unlock(s_flash_lock);
// mutex_lock(s_flash_lock);
// }
}
stop_mode_enable(InhibitorFlash);
mutex_unlock(s_flash_lock);
}
// Returns 0 if the erase has completed, or a non-zero expected duration (in
// ms) if not. If the erase has not finished (non-zero has been returned), the
// caller is responsible for calling the prv_flash_erase_poll() method until
// the erase completes.
static uint32_t prv_flash_erase_start(uint32_t addr,
FlashOperationCompleteCb on_complete_cb,
void *context,
bool is_subsector,
uint8_t retries) {
xSemaphoreTake(s_erase_semphr, portMAX_DELAY);
mutex_lock(s_flash_lock);
PBL_ASSERTN(s_erase.in_progress == false);
s_erase = (struct FlashEraseContext) {
.in_progress = true,
.task = pebble_task_get_current(),
.retries = retries,
// FIXME: We should just assert that the address is already aligned. If
// someone is depending on this behaviour without already knowing the range
// that's being erased they're going to have a bad time. This will probably
// cause some client fallout though, so tackle this later.
.is_subsector = is_subsector,
.address = is_subsector? flash_impl_get_subsector_base_address(addr)
: flash_impl_get_sector_base_address(addr),
.on_complete_cb = on_complete_cb,
.cb_context = context,
.expected_duration = is_subsector?
flash_impl_get_typical_subsector_erase_duration_ms() :
flash_impl_get_typical_sector_erase_duration_ms(),
};
stop_mode_disable(InhibitorFlash); // FIXME: PBL-18028
status_t status = is_subsector? flash_impl_blank_check_subsector(addr)
: flash_impl_blank_check_sector(addr);
PBL_ASSERT(PASSED(status), "Blank check error: %" PRId32, status);
if (status != S_FALSE) {
stop_mode_enable(InhibitorFlash);
s_erase.in_progress = false;
mutex_unlock(s_flash_lock);
xSemaphoreGive(s_erase_semphr);
// Only run the callback with no locks held so that the callback won't
// deadlock if it kicks off another sector erase.
on_complete_cb(context, S_NO_ACTION_REQUIRED);
return 0;
}
analytics_inc(ANALYTICS_APP_METRIC_FLASH_SUBSECTOR_ERASE_COUNT,
AnalyticsClient_CurrentTask);
s_system_analytics_erase_count++;
status = is_subsector? flash_impl_erase_subsector_begin(addr)
: flash_impl_erase_sector_begin(addr);
if (PASSED(status)) {
flash_impl_set_nvram_erase_status(is_subsector, addr);
mutex_unlock(s_flash_lock);
return (s_erase.expected_duration * 7 / 8);
} else {
stop_mode_enable(InhibitorFlash);
s_erase.in_progress = false;
mutex_unlock(s_flash_lock);
xSemaphoreGive(s_erase_semphr);
// Only run the callback with no locks held so that the callback won't
// deadlock if it kicks off another sector erase.
on_complete_cb(context, status);
return 0;
}
}
// Returns non-zero expected remaining time if the erase has not finished. If the erase
// has finished it will re-enable stop-mode, clear the in_progress flag and call the
// completed callback before returning 0.
static uint32_t prv_flash_erase_poll(void) {
mutex_lock(s_flash_lock);
status_t status = flash_impl_get_erase_status();
bool erase_finished;
struct FlashEraseContext saved_ctx = s_erase;
switch (status) {
case E_BUSY:
case E_AGAIN:
erase_finished = false;
break;
case S_SUCCESS:
default:
// Success or failure; the erase has finished either way.
erase_finished = true;
break;
}
if (erase_finished) {
stop_mode_enable(InhibitorFlash);
s_erase.in_progress = false;
flash_impl_clear_nvram_erase_status();
}
mutex_unlock(s_flash_lock);
if (!erase_finished) {
return s_erase.expected_duration / 8;
}
xSemaphoreGive(s_erase_semphr);
if (status == E_ERROR && saved_ctx.retries < MAX_ERASE_RETRIES) {
// Try issuing the erase again. It might succeed this time around.
PBL_LOG(LOG_LEVEL_DEBUG, "Erase of 0x%"PRIx32" failed (attempt %d)."
" Trying again...", saved_ctx.address, saved_ctx.retries);
return prv_flash_erase_start(
saved_ctx.address, saved_ctx.on_complete_cb, saved_ctx.cb_context,
saved_ctx.is_subsector, saved_ctx.retries + 1);
} else {
// Only run the callback with no locks held so that the callback won't
// deadlock if it kicks off another sector erase.
saved_ctx.on_complete_cb(saved_ctx.cb_context, status);
return 0;
}
}
// Timer callback that checks to see if the erase has finished. Used by the non-blocking
// routines.
static void prv_flash_erase_timer_cb(void *context) {
uint32_t remaining_ms = prv_flash_erase_poll();
if (remaining_ms) {
// Erase is in progress or suspended; poll again later.
new_timer_start(s_erase_poll_timer, remaining_ms, prv_flash_erase_timer_cb, NULL, 0);
}
}
static void prv_flash_erase_async(
uint32_t sector_addr, bool is_subsector, FlashOperationCompleteCb on_complete_cb,
void *context) {
uint32_t remaining_ms = prv_flash_erase_start(sector_addr, on_complete_cb,
context, is_subsector, 0);
if (remaining_ms) {
// Start timer that will periodically check for the erase to complete
new_timer_start(s_erase_poll_timer, remaining_ms, prv_flash_erase_timer_cb, NULL, 0);
}
}
static void prv_blocking_erase_complete(void *context, status_t status) {
PBL_ASSERT(PASSED(status), "Flash erase failure: %" PRId32, status);
}
static void prv_flash_erase_blocking(uint32_t sector_addr, bool is_subsector) {
uint32_t total_time_spent_waiting_ms = 0;
uint32_t remaining_ms = prv_flash_erase_start(
sector_addr, prv_blocking_erase_complete, NULL, is_subsector, 0);
while (remaining_ms) {
psleep(remaining_ms);
total_time_spent_waiting_ms += remaining_ms;
remaining_ms = prv_flash_erase_poll();
// check to see if the cb responsible for resuming erases should have run
// but is blocked. See PBL-25741 for details
uint32_t erase_suspend_time_remaining;
if (new_timer_scheduled(s_erase_suspend_timer, &erase_suspend_time_remaining) &&
(erase_suspend_time_remaining == 0)) {
prv_erase_suspend_timer_cb(NULL);
}
// An erase can take a long time, especially if the erase needs to be
// retried. Appease the watchdog so that it doesn't get angry when an
// erase takes >6 seconds.
//
// After a certain amount of time passes, stop kicking the watchdog. This is to handle a case
// where the erase never finishes or takes an unheard of amount of time to complete. Just let
// the watchdog kill us in this case.
static const uint32_t FLASH_ERASE_BLOCKING_TIMEOUT_MS = 5000;
if (total_time_spent_waiting_ms < FLASH_ERASE_BLOCKING_TIMEOUT_MS) {
#if IS_BIGBOARD
// Our bigboards have had a hard life and they have some fairly abused flash chips, and we
// run into 5+ second erases pretty regularly. We're not holding the flash lock while we're
// doing this, so other threads are allowed to use flash, but it's pretty common to hold
// other locks while waiting for a flash operation to complete, leading to other tasks
// triggering their task watchdogs before this erase completes. Let's kick all watchdogs
// instead. The downside to this is that it may take us longer to detect another thread is
// stuck, but we should still detect it eventually as long as we're not constantly erasing.
task_watchdog_bit_set_all();
#else
// Just kick the watchdog for the current task. This should give us more accurate watchdog
// behaviour and sealed watches haven't been abused as much and shouldn't have extremely
// long erase problems.
task_watchdog_bit_set(pebble_task_get_current());
#endif
}
}
}
void flash_erase_sector(uint32_t sector_addr,
FlashOperationCompleteCb on_complete_cb,
void *context) {
prv_flash_erase_async(sector_addr, false /* is_subsector */, on_complete_cb, context);
}
void flash_erase_subsector(uint32_t sector_addr,
FlashOperationCompleteCb on_complete_cb,
void *context) {
prv_flash_erase_async(sector_addr, true /* is_subsector */, on_complete_cb, context);
}
void flash_erase_sector_blocking(uint32_t sector_addr) {
prv_flash_erase_blocking(sector_addr, false /* is_subsector */);
}
void flash_erase_subsector_blocking(uint32_t subsector_addr) {
prv_flash_erase_blocking(subsector_addr, true /* is_subsector */);
}
void flash_enable_write_protection(void) {
flash_impl_enable_write_protection();
}
void flash_prf_set_protection(bool do_protect) {
status_t status;
mutex_lock(s_flash_lock);
if (do_protect) {
status = flash_impl_write_protect(
FLASH_REGION_SAFE_FIRMWARE_BEGIN,
(FLASH_REGION_SAFE_FIRMWARE_END - SECTOR_SIZE_BYTES));
} else {
status = flash_impl_unprotect();
}
PBL_ASSERT(PASSED(status), "flash_prf_set_protection failed: %" PRId32, status);
mutex_unlock(s_flash_lock);
}
#if 0
void flash_erase_bulk(void) {
mutex_lock(s_flash_lock);
flash_impl_erase_bulk_begin();
while (flash_impl_erase_is_in_progress()) {
psleep(10);
}
mutex_unlock(s_flash_lock);
}
#endif
void flash_sleep_when_idle(bool enable) {
// the S29VS flash automatically enters and exits standby
}
bool flash_get_sleep_when_idle(void) {
return false;
}
bool flash_is_initialized(void) {
return (s_flash_lock != NULL);
}
void flash_stop(void) {
if (!flash_is_initialized()) {
// Not yet initialized, nothing to do.
return;
}
mutex_lock(s_flash_lock);
if (s_erase.in_progress) {
new_timer_stop(s_erase_suspend_timer);
prv_erase_resume();
mutex_unlock(s_flash_lock);
while (__atomic_load_n(&s_erase.in_progress, __ATOMIC_SEQ_CST)) {
psleep(10);
}
}
}
void flash_switch_mode(FlashModeType mode) {
mutex_lock(s_flash_lock);
flash_impl_set_burst_mode(mode == FLASH_MODE_SYNC_BURST);
mutex_unlock(s_flash_lock);
}
uint32_t flash_get_sector_base_address(uint32_t flash_addr) {
return flash_impl_get_sector_base_address(flash_addr);
}
uint32_t flash_get_subsector_base_address(uint32_t flash_addr) {
return flash_impl_get_subsector_base_address(flash_addr);
}
void flash_power_down_for_stop_mode(void) {
flash_impl_enter_low_power_mode();
}
void flash_power_up_after_stop_mode(void) {
flash_impl_exit_low_power_mode();
}
bool flash_sector_is_erased(uint32_t sector_addr) {
return flash_impl_blank_check_sector(flash_impl_get_sector_base_address(sector_addr));
}
bool flash_subsector_is_erased(uint32_t sector_addr) {
return flash_impl_blank_check_subsector(flash_impl_get_subsector_base_address(sector_addr));
}
void flash_use(void) {
mutex_lock(s_flash_lock);
flash_impl_use();
mutex_unlock(s_flash_lock);
}
void flash_release_many(uint32_t num_locks) {
mutex_lock(s_flash_lock);
flash_impl_release_many(num_locks);
mutex_unlock(s_flash_lock);
}
#include "console/prompt.h"
void command_flash_unprotect(void) {
flash_impl_unprotect();
prompt_send_response("OK");
}