mirror of
https://github.com/google/pebble.git
synced 2025-11-20 06:20:56 -05:00
Import of the watch repository from Pebble
This commit is contained in:
599
src/fw/drivers/flash/flash_api.c
Normal file
599
src/fw/drivers/flash/flash_api.c
Normal 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");
|
||||
}
|
||||
Reference in New Issue
Block a user