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 @@
Flash Memory Drivers
--------------------
Flash memory access is exposed through two separate APIs. The main API, defined
in `flash.h` and implemented in `flash_api.c`, is used almost everywhere. There
is also an alternate API used exclusively for performing core dumps, implemented
in `cd_flash_driver.c`. The alternate API is carefully implemented to be usable
regardless of how messed up the system state is in by not relying on any OS
services.
The flash APIs are written to be agnostic to the specific type of flash used.
They are written against the generic low-level driver interface defined in
`flash_impl.h`. Each low-level driver is specific to a combination of
flash memory part and microcontroller interface. The low-level drivers make no
use of OS services so that they can be used for both the main and core dump
driver APIs.

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.
*/
#include <inttypes.h>
#include "drivers/flash.h"
#include "drivers/flash/flash_impl.h"
#include "drivers/watchdog.h"
#include "flash_region/flash_region.h"
#include "kernel/util/delay.h"
#include "kernel/core_dump_private.h"
//! We have our own flash driver for coredump support because it must not use
//! any FreeRTOS constructs & we want to keep it as simple as possible. In
//! addition we want the flexibility to be able to reset the flash driver to
//! get it into a working state
void cd_flash_init(void) {
// Reset & (re)initialize the flash HW
flash_impl_init(true /* coredump_mode */);
// Protect the PRF region from writes
flash_impl_write_protect(
FLASH_REGION_SAFE_FIRMWARE_BEGIN,
(FLASH_REGION_SAFE_FIRMWARE_END - SECTOR_SIZE_BYTES));
}
void cd_flash_erase_region(uint32_t start_addr, uint32_t total_bytes) {
CD_ASSERTN(((start_addr & SUBSECTOR_ADDR_MASK) == start_addr) &&
((total_bytes & SUBSECTOR_ADDR_MASK) == total_bytes));
while (total_bytes > 0) {
watchdog_feed();
uint32_t erase_size = 0;
if (((start_addr & SECTOR_ADDR_MASK) == start_addr) &&
(total_bytes >= SECTOR_SIZE_BYTES)) {
erase_size = SECTOR_SIZE_BYTES;
flash_impl_erase_sector_begin(start_addr);
} else if ((start_addr & SUBSECTOR_ADDR_MASK) == start_addr &&
(total_bytes >= SUBSECTOR_SIZE_BYTES)) {
erase_size = SUBSECTOR_SIZE_BYTES;
flash_impl_erase_subsector_begin(start_addr);
} else {
// Unaligned start address or unaligned erase size
CD_ASSERTN(0);
}
status_t status;
while ((status = flash_impl_get_erase_status()) == E_BUSY) delay_us(100);
CD_ASSERTN(status == S_SUCCESS);
total_bytes -= erase_size;
start_addr += erase_size;
}
watchdog_feed();
}
uint32_t cd_flash_write_bytes(const void *buffer_ptr, uint32_t start_addr,
const uint32_t buffer_size) {
CD_ASSERTN(((start_addr + buffer_size) <= CORE_DUMP_FLASH_END) &&
(int)start_addr >= CORE_DUMP_FLASH_START);
const uint8_t *buffer = buffer_ptr;
uint32_t remaining = buffer_size;
while (remaining) {
int written = flash_impl_write_page_begin(buffer, start_addr, remaining);
CD_ASSERTN(PASSED(written));
status_t status;
while ((status = flash_impl_get_write_status()) == E_BUSY) {
delay_us(10);
}
CD_ASSERTN(PASSED(status));
buffer += written;
start_addr += written;
remaining -= written;
}
return buffer_size;
}
void cd_flash_read_bytes(void* buffer_ptr, uint32_t start_addr,
uint32_t buffer_size) {
flash_impl_read_sync(buffer_ptr, start_addr, buffer_size);
}

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");
}

View file

@ -0,0 +1,83 @@
/*
* 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 "kernel/pbl_malloc.h"
#include "system/logging.h"
#include "util/crc32.h"
#include "util/legacy_checksum.h"
#include <stdint.h>
static size_t prv_allocate_crc_buffer(void **buffer) {
// Try to allocate a big buffer for reading flash data. If we can't,
// use a smaller one.
unsigned int chunk_size = 1024;
*buffer = kernel_malloc(chunk_size);
if (!*buffer) {
PBL_LOG(LOG_LEVEL_WARNING, "Insufficient memory for a large CRC buffer, going slow");
chunk_size = 128;
*buffer = kernel_malloc_check(chunk_size);
}
return chunk_size;
}
uint32_t flash_crc32(uint32_t flash_addr, uint32_t num_bytes) {
void *buffer;
unsigned int chunk_size = prv_allocate_crc_buffer(&buffer);
uint32_t crc = CRC32_INIT;
while (num_bytes > chunk_size) {
flash_read_bytes(buffer, flash_addr, chunk_size);
crc = crc32(crc, buffer, chunk_size);
num_bytes -= chunk_size;
flash_addr += chunk_size;
}
flash_read_bytes(buffer, flash_addr, num_bytes);
crc = crc32(crc, buffer, num_bytes);
kernel_free(buffer);
return crc;
}
uint32_t flash_calculate_legacy_defective_checksum(uint32_t flash_addr,
uint32_t num_bytes) {
void *buffer;
unsigned int chunk_size = prv_allocate_crc_buffer(&buffer);
LegacyChecksum checksum;
legacy_defective_checksum_init(&checksum);
while (num_bytes > chunk_size) {
flash_read_bytes(buffer, flash_addr, chunk_size);
legacy_defective_checksum_update(&checksum, buffer, chunk_size);
num_bytes -= chunk_size;
flash_addr += chunk_size;
}
flash_read_bytes(buffer, flash_addr, num_bytes);
legacy_defective_checksum_update(&checksum, buffer, num_bytes);
kernel_free(buffer);
return legacy_defective_checksum_finish(&checksum);
}

View file

@ -0,0 +1,143 @@
/*
* 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 "flash_region/flash_region.h"
#include "services/common/new_timer/new_timer.h"
#include "system/passert.h"
#include "util/attributes.h"
#include "util/math.h"
#include "FreeRTOS.h"
#include "semphr.h"
#include <inttypes.h>
static SemaphoreHandle_t s_erase_mutex = NULL;
static struct FlashRegionEraseState {
uint32_t next_erase_addr;
uint32_t end_addr;
FlashOperationCompleteCb on_complete;
void *on_complete_context;
} s_erase_state;
static void prv_erase_next_async(void *ignored);
T_STATIC void prv_lock_erase_mutex(void);
T_STATIC void prv_unlock_erase_mutex(void);
#if !UNITTEST
void flash_erase_init(void) {
s_erase_mutex = xSemaphoreCreateBinary();
xSemaphoreGive(s_erase_mutex);
}
static void prv_lock_erase_mutex(void) {
xSemaphoreTake(s_erase_mutex, portMAX_DELAY);
}
static void prv_unlock_erase_mutex(void) {
xSemaphoreGive(s_erase_mutex);
}
#endif
static void prv_async_erase_done_cb(void *ignored, status_t result) {
if (PASSED(result) &&
s_erase_state.next_erase_addr < s_erase_state.end_addr) {
// Chain the next erase from a new callback to prevent recursion (and the
// potential for a stack overflow) if the flash_erase_sector calls the
// completion callback asynchronously.
if (!new_timer_add_work_callback(prv_erase_next_async, NULL)) {
PBL_LOG(LOG_LEVEL_ERROR, "Failed to enqueue callback; aborting erase");
prv_unlock_erase_mutex();
s_erase_state.on_complete(s_erase_state.on_complete_context, E_INTERNAL);
}
} else {
prv_unlock_erase_mutex();
s_erase_state.on_complete(s_erase_state.on_complete_context, result);
}
}
static void prv_erase_next_async(void *ignored) {
uint32_t addr = s_erase_state.next_erase_addr;
if ((addr & ~SECTOR_ADDR_MASK) == 0 &&
addr + SECTOR_SIZE_BYTES <= s_erase_state.end_addr) {
s_erase_state.next_erase_addr += SECTOR_SIZE_BYTES;
flash_erase_sector(addr, prv_async_erase_done_cb, NULL);
} else {
// Fall back to a subsector erase
s_erase_state.next_erase_addr += SUBSECTOR_SIZE_BYTES;
PBL_ASSERTN(s_erase_state.next_erase_addr <= s_erase_state.end_addr);
flash_erase_subsector(addr, prv_async_erase_done_cb, NULL);
}
}
void flash_erase_optimal_range(
uint32_t min_start, uint32_t max_start, uint32_t min_end, uint32_t max_end,
FlashOperationCompleteCb on_complete, void *context) {
PBL_ASSERTN(((min_start & (~SUBSECTOR_ADDR_MASK)) == 0) &&
((max_end & (~SUBSECTOR_ADDR_MASK)) == 0) &&
(min_start <= max_start) &&
(max_start <= min_end) &&
(min_end <= max_end));
// We want to erase the sector that starts immediately below max_start but
// after min_start. If no sector boundary exists between the two, we need to
// start erasing sectors after min_start and backfill with subsector erases.
int32_t sector_start = (max_start & SECTOR_ADDR_MASK);
int32_t subsector_start = (max_start & SUBSECTOR_ADDR_MASK);
if (sector_start < (int32_t) min_start) {
sector_start += SECTOR_SIZE_BYTES;
}
// We want to erase ending after min_end but before max_end. If that ends
// running past the end of max_end, we need to erase starting with the sector
// before and fill in with subsector erases.
int32_t sector_end = ((min_end - 1) & SECTOR_ADDR_MASK) + SECTOR_SIZE_BYTES;
int32_t subsector_end =
((min_end - 1) & SUBSECTOR_ADDR_MASK) + SUBSECTOR_SIZE_BYTES;
if (sector_end > (int32_t) max_end) {
sector_end -= SECTOR_SIZE_BYTES;
}
int32_t start_addr = MIN(sector_start, subsector_start);
int32_t end_addr = MAX(sector_end, subsector_end);
if (sector_start >= sector_end) {
// Can't erase any full sectors; just erase subsectors the whole way.
start_addr = subsector_start;
end_addr = subsector_end;
}
if (start_addr == end_addr) {
// Nothing to do!
on_complete(context, S_NO_ACTION_REQUIRED);
return;
}
prv_lock_erase_mutex();
s_erase_state = (struct FlashRegionEraseState) {
.next_erase_addr = start_addr,
.end_addr = end_addr,
.on_complete = on_complete,
.on_complete_context = context,
};
prv_erase_next_async(NULL);
}

View file

@ -0,0 +1,322 @@
/*
* 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 <stddef.h>
#include <stdint.h>
#include "system/status_codes.h"
//! Flash Low-Level API
//!
//! Unless otherwise specified, this API is non-reentrant. It is unsafe to
//! call a function in one thread while another function is being executed in a
//! second thread, and it is unsafe to call these functions from within a
//! flash_impl callback.
typedef uint32_t FlashAddress;
//! Initialize the low-level flash implementation and hardware into a known
//! state where it is ready to accept commands.
//!
//! This function configures microcontroller peripherals. It should be guarded
//! with periph_config_acquire_lock/periph_config_release_lock.
//!
//! @param coredump_mode True if we need this flash driver to not rely on any other system
//! services such as FreeRTOS being available because we're in the middle
//! of a core dump. This may result in slower operations.
status_t flash_impl_init(bool coredump_mode);
//! Enable or disable synchronous burst mode, if supported.
//!
//! Burst mode is disabled whenever \ref flash_impl_init is called.
//!
//! The result is undefined if this function is called while any other flash
//! operation is in progress.
status_t flash_impl_set_burst_mode(bool enable);
//! Return the base address of the sector overlapping the given address.
//!
//! This function is reentrant.
FlashAddress flash_impl_get_sector_base_address(FlashAddress addr);
//! Return the base address of the subsector overlapping the given address.
//!
//! This function is reentrant.
FlashAddress flash_impl_get_subsector_base_address(FlashAddress addr);
//! Query the flash hardware for its capacity in bytes.
size_t flash_impl_get_capacity(void);
//! Enter a low-power state.
//!
//! Once in a low-power mode, all operations may fail until
//! \ref flash_impl_exit_low_power_mode is called. This function is idempotent.
status_t flash_impl_enter_low_power_mode(void);
//! Exit a low-power state.
//!
//! Return the flash to a fully operational mode. This may be a time-intensive
//! operation. This function is idempotent.
status_t flash_impl_exit_low_power_mode(void);
//! Read data into a buffer.
//!
//! The result is undefined if this function is called while a write or erase is
//! in progress.
status_t flash_impl_read_sync(void *buffer, FlashAddress addr, size_t len);
//! Initiate a DMA-accelerated flash read.
//!
//! The caller must ensure that the DMA transfer will not be interfered with
//! by any clock changes or stoppages externally. (read: inhibit stop mode)
//!
//! This function will return immediately once the transfer has begun.
//! \ref flash_impl_on_read_dma_complete_from_isr will be called from an
//! interrupt context to signal that the transfer has completed. The effect of
//! calling flash_impl_read_dma_begin a second time while another DMA transfer
//! is currently in progress is undefined.
//!
//! The result is undefined if this function is called while a write or erase is
//! in progress.
status_t flash_impl_read_dma_begin(void *buffer, FlashAddress addr,
size_t len);
//! Called from an interrupt context when the DMA read has completed. It is
//! guaranteed that the call is made from an interrupt of low enough priority
//! that RTOS API calls are safe to use, and that it is a tail-call from the end
//! of the implementation's ISR (read: portEND_SWITCHING_ISR is permissible).
//!
//! @param result S_SUCCESS iff the read completed successfully.
extern void flash_impl_on_read_dma_complete_from_isr(status_t result);
//! If the flash part requires write protection to be explicitly enabled, enable it.
void flash_impl_enable_write_protection(void);
//! Write protect a region of flash. Only one region may be protected at any
//! given time.
//!
//! The result is undefined if this function is called while a write or erase is
//! in progress.
status_t flash_impl_write_protect(FlashAddress start_sector,
FlashAddress end_sector);
//! Remove write protection.
//!
//! The result is undefined if this function is called while a write or erase is
//! in progress.
status_t flash_impl_unprotect(void);
//! Write a page of bytes to flash.
//!
//! @param buffer The source buffer.
//! @param addr Destination flash address.
//! @param len Length to write.
//! @return The number of bytes that will be written to flash, assuming that the
//! write completes successfully, or a StatusCode error if there was an
//! error starting the write operation.
//!
//! Each call to flash_impl_write_page_begin begins a single flash write
//! operation, writing the maximum amount of data supported by the hardware in
//! a single operation. Multiple page writes may be required to write a complete
//! buffer to flash.
//!
//! Example usage:
//! \code
//! while (len) {
//! int written = flash_impl_write_page_begin(buffer, addr, len));
//! if (written < 0) {
//! // Handle error
//! }
//! status_t status;
//! while ((status = flash_impl_get_write_status()) == E_AGAIN) {
//! continue;
//! }
//! if (status != S_SUCCESS) {
//! // Handle error
//! }
//! buffer += written;
//! addr += written;
//! len -= written;
//! }
//! \endcode
//!
//! The result is undefined if this function is called while a read or erase is
//! in progress. It is an error to call this function while a write is
//! in progress or suspended.
int flash_impl_write_page_begin(const void *buffer, FlashAddress addr,
size_t len);
//! Poll the status of a flash page write.
//!
//! @return S_SUCCESS if the write has succeeded, E_ERROR if the write has
//! failed, E_BUSY if the write is still in progress or E_AGAIN if the
//! write is suspended.
status_t flash_impl_get_write_status(void);
//! Suspend an in-progress write so that reads and erases are permitted.
//!
//! @param addr The address passed to the \ref flash_impl_write_page_begin
//! call which initiated the write being suspended.
status_t flash_impl_write_suspend(FlashAddress addr);
//! Resume a previously-suspended write.
//!
//! @param addr The address passed to \ref flash_impl_write_suspend.
//!
//! The result is undefined if this function is called while a read or write is
//! in progress.
status_t flash_impl_write_resume(FlashAddress addr);
//! Erase the subsector which overlaps the given address.
//!
//! The result is undefined if this function is called while a read or write is
//! in progress. It is an error to call this function while an erase is
//! suspended.
status_t flash_impl_erase_subsector_begin(FlashAddress subsector_addr);
//! Erase the sector which overlaps the given address.
//!
//! The result is undefined if this function is called while a read or write is
//! in progress. It is an error to call this function while an erase is
//! suspended.
status_t flash_impl_erase_sector_begin(FlashAddress sector_addr);
//! Erase the entire flash.
//!
//! The result is undefined if this function is called while a read or write is
//! in progress. It is an error to call this function while an erase is
//! suspended.
status_t flash_impl_erase_bulk_begin(void);
//! Poll the status of a flash erase.
//!
//! @return S_SUCCESS if the erase has succeeded, E_ERROR if the erase has
//! failed, E_BUSY if the erase is still in progress or E_AGAIN if the
//! erase is suspended.
status_t flash_impl_get_erase_status(void);
//! Returns the typical duration of a subsector erase, in milliseconds.
//!
//! This function is reentrant.
uint32_t flash_impl_get_typical_subsector_erase_duration_ms(void);
//! Returns the typical duration of a sector erase, in milliseconds.
//!
//! This function is reentrant.
uint32_t flash_impl_get_typical_sector_erase_duration_ms(void);
//! Suspend an in-progress erase so that reads and writes are permitted.
//!
//! @param addr The sector address passed to the
//! \ref flash_impl_erase_subsector_begin or
//! \ref flash_impl_erase_sector_begin call which initiated the erase being
//! suspended.
//!
//! @return S_SUCCESS if the erase has been suspended, S_NO_ACTION_REQUIRED if
//! there was no erase in progress at the time, or an error code.
status_t flash_impl_erase_suspend(FlashAddress addr);
//! Resume a previously-suspended erase.
//!
//! @param addr The address passed to \ref flash_impl_erase_suspend.
//!
//! The result is undefined if this function is called while a read or write is
//! in progress.
status_t flash_impl_erase_resume(FlashAddress addr);
//! Check whether the subsector overlapping the specified address is blank
//! (reads as all 1's).
//!
//! @param addr An address within the subsector being checked.
//!
//! @return S_TRUE if blank, S_FALSE if any bit in the sector has been
//! programmed, or E_BUSY if another flash operation is in progress.
//!
//! This operation is hardware-accelerated if possible. This operation may not
//! be performed if any reads, writes, or erases are in progress or suspended,
//! and this operation cannot be suspended once initiated. The result is
//! undefined if any other flash operation is initiated or in progress while a
//! blank check operation is in progress.
//!
//! @warning This function may return S_TRUE on a subsector where an erase
//! operation was terminated prematurely. While such a subsector may
//! read back as blank, data loss may occur and writes may fail if the
//! subsector is not erased fully before it is written to.
status_t flash_impl_blank_check_subsector(FlashAddress addr);
//! Check whether the sector overlapping the specified address is blank (reads
//! as all 1's).
//!
//! @param addr An address within the sector being checked.
//!
//! @return S_TRUE if blank, S_FALSE if any bit in the sector has been
//! programmed, or E_BUSY if another flash operation is in progress.
//!
//! This operation is hardware-accelerated if possible. This operation may not
//! be performed if any reads, writes, or erases are in progress or suspended,
//! and this operation cannot be suspended once initiated. The result is
//! undefined if any other flash operation is initiated or in progress while a
//! blank check operation is in progress.
//!
//! @warning This function may return S_TRUE on a sector where an erase
//! operation was terminated prematurely. While such a sector may read
//! back as blank, data loss may occur and writes may fail if the
//! sector is not erased fully before it is written to.
status_t flash_impl_blank_check_sector(FlashAddress addr);
//! Save the address of an erase in progress to a nonvolatile location. The
//! erase address, along with the fact that an erase is in progress, must be
//! able to survive a system crash and reboot.
//!
//! @note Writing this data to the same flash array that is being erased is
//! almost certainly a bad idea.
//!
//! @param is_subsector True if the erase is a subsector.
//!
//! @param addr The address being erased.
//!
//! @return S_SUCCESS if the data was successfully stored.
status_t flash_impl_set_nvram_erase_status(bool is_subsector,
FlashAddress addr);
//! Save to a nonvolatile location the fact that no erase is in progress.
//!
//! @return S_SUCCESS if the status was successfully stored.
status_t flash_impl_clear_nvram_erase_status(void);
//! Retrieve the erase status previously set by
//! flash_impl_set_nvram_erase_status or flash_impl_clear_nvram_erase_status.
//!
//! @param [out] is_subsector The value of is_subsector passed to the most
//! most recent call to flash_impl_set_nvram_erase_status if the status was
//! not subsequently cleared by flash_impl_clear_nvram_erase_status. The
//! pointer should not be written to if the erase status was cleared.
//!
//! @param [out] addr The address passed to the most recent call to
//! flash_impl_set_nvram_erase_status if the status was not subsequently
//! cleared by flash_impl_clear_nvram_erase_status. The address should not be
//! written to if the erase status was cleared.
//!
//! @return S_TRUE if an erase was in progress; S_FALSE otherwise.
status_t flash_impl_get_nvram_erase_status(bool *is_subsector,
FlashAddress *addr);
void flash_impl_use(void);
void flash_impl_release(void);
void flash_impl_release_many(uint32_t num_locks);

View file

@ -0,0 +1,19 @@
/*
* 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
void flash_erase_init(void);

View file

@ -0,0 +1,236 @@
/*
* 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 <inttypes.h>
#include "drivers/flash.h"
#include "drivers/flash/micron_n25q/flash_private.h"
#include "drivers/watchdog.h"
#include "flash_region/flash_region.h"
#include "kernel/util/delay.h"
#include "util/math.h"
#include "kernel/core_dump_private.h"
//! We have our own flash driver for coredump support because it must not use
//! any FreeRTOS constructs & we want to keep it as simple as possible. In
//! addition we want the flexibility to be able to reset the flash driver to
//! get it into a working state
static void prv_flash_start_cmd(void) {
GPIO_ResetBits(FLASH_GPIO, FLASH_PIN_SCS);
}
static void prv_flash_end_cmd(void) {
GPIO_SetBits(FLASH_GPIO, FLASH_PIN_SCS);
// 50ns required between SCS going high and low again, so just delay here to be safe
delay_us(1);
}
static uint8_t prv_flash_send_and_receive_byte(uint8_t byte) {
// Ensure that there are no other write operations in progress
while (SPI_I2S_GetFlagStatus(FLASH_SPI, SPI_I2S_FLAG_TXE) == RESET) {
continue;
}
// Send the byte on the SPI bus
SPI_I2S_SendData(FLASH_SPI, byte);
// Wait for the response byte to be received
while (SPI_I2S_GetFlagStatus(FLASH_SPI, SPI_I2S_FLAG_RXNE) == RESET) {
continue;
}
// Return the byte
return SPI_I2S_ReceiveData(FLASH_SPI);
}
static uint8_t prv_flash_read_next_byte(void) {
uint8_t result = prv_flash_send_and_receive_byte(FLASH_CMD_DUMMY);
return result;
}
static void prv_flash_wait_for_write_bounded(volatile int cycles_to_wait) {
prv_flash_start_cmd();
prv_flash_send_and_receive_byte(FLASH_CMD_READ_STATUS_REG);
uint8_t status_register = 0;
do {
if (cycles_to_wait-- < 1) {
break;
}
status_register = prv_flash_read_next_byte();
} while (status_register & 0x1);
prv_flash_end_cmd();
}
// Init the flash hardware
void cd_flash_init(void) {
// Enable the SPI clock
RCC_APB2PeriphClockCmd(FLASH_SPI_CLOCK, ENABLE);
// Enable the GPIO clock
uint8_t idx = ((((uint32_t)FLASH_GPIO) - AHB1PERIPH_BASE) / 0x0400);
SET_BIT(RCC->AHB1ENR, (0x1 << idx));
// Init the flash hardware
flash_hw_init();
// Make sure we are not in deep sleep
prv_flash_start_cmd();
prv_flash_send_and_receive_byte(FLASH_CMD_WAKE);
prv_flash_end_cmd();
// See if we can successfully access the flash
// TODO: Will we successfully recover if the flash HW was left midstream in a command from
// before?
prv_flash_wait_for_write_bounded(64000000);
prv_flash_start_cmd();
prv_flash_send_and_receive_byte(FLASH_CMD_READ_ID);
uint32_t manufacturer = prv_flash_read_next_byte();
uint32_t type = prv_flash_read_next_byte();
uint32_t capacity = prv_flash_read_next_byte();
prv_flash_end_cmd();
// If we can't ready the flash info correctly, bail
CD_ASSERTN(manufacturer == 0x20 && type == 0xbb && (capacity >= 0x16));
}
static void prv_flash_write_enable(void) {
prv_flash_start_cmd();
prv_flash_send_and_receive_byte(FLASH_CMD_WRITE_ENABLE);
prv_flash_end_cmd();
}
static void prv_flash_send_24b_address(uint32_t start_addr) {
// Ensure the high bits are not set.
prv_flash_send_and_receive_byte((start_addr & 0xFF0000) >> 16);
prv_flash_send_and_receive_byte((start_addr & 0x00FF00) >> 8);
prv_flash_send_and_receive_byte((start_addr & 0x0000FF));
}
static void prv_flash_wait_for_write(void) {
prv_flash_start_cmd();
prv_flash_send_and_receive_byte(FLASH_CMD_READ_STATUS_REG);
uint8_t status_register = 0;
do {
status_register = prv_flash_read_next_byte();
} while (status_register & 0x1);
prv_flash_end_cmd();
}
static void prv_flash_write_page(const uint8_t* buffer, uint32_t start_addr, uint16_t buffer_size) {
// Ensure that we're not trying to write more data than a single page (256 bytes)
CD_ASSERTN(buffer_size <= FLASH_PAGE_SIZE);
CD_ASSERTN(buffer_size);
// Writing a zero-length buffer is a no-op.
if (buffer_size < 1) {
return;
}
prv_flash_write_enable();
prv_flash_start_cmd();
prv_flash_send_and_receive_byte(FLASH_CMD_PAGE_PROGRAM);
prv_flash_send_24b_address(start_addr);
while (buffer_size--) {
prv_flash_send_and_receive_byte(*buffer);
buffer++;
}
prv_flash_end_cmd();
prv_flash_wait_for_write();
}
void cd_flash_read_bytes(void* buffer_ptr, uint32_t start_addr, uint32_t buffer_size) {
uint8_t* buffer = buffer_ptr;
prv_flash_wait_for_write();
prv_flash_start_cmd();
prv_flash_send_and_receive_byte(FLASH_CMD_READ);
prv_flash_send_24b_address(start_addr);
while (buffer_size--) {
*buffer = prv_flash_read_next_byte();
buffer++;
}
prv_flash_end_cmd();
}
uint32_t cd_flash_write_bytes(const void* buffer_ptr, uint32_t start_addr, uint32_t buffer_size) {
CD_ASSERTN((start_addr + buffer_size <= CORE_DUMP_FLASH_END) &&
(int)start_addr >= CORE_DUMP_FLASH_START);
const uint8_t* buffer = buffer_ptr;
const uint32_t total_bytes = buffer_size;
uint32_t first_page_available_bytes = FLASH_PAGE_SIZE - (start_addr % FLASH_PAGE_SIZE);
uint32_t bytes_to_write = MIN(buffer_size, first_page_available_bytes);
while (bytes_to_write) {
prv_flash_write_page(buffer, start_addr, bytes_to_write);
start_addr += bytes_to_write;
buffer += bytes_to_write;
buffer_size -= bytes_to_write;
bytes_to_write = MIN(buffer_size, FLASH_PAGE_SIZE);
}
watchdog_feed();
return total_bytes;
}
static void prv_flash_erase_sector(uint32_t sector_addr) {
prv_flash_write_enable();
prv_flash_start_cmd();
prv_flash_send_and_receive_byte(FLASH_CMD_ERASE_SECTOR);
prv_flash_send_24b_address(sector_addr);
prv_flash_end_cmd();
prv_flash_wait_for_write();
}
static void prv_flash_erase_subsector(uint32_t sector_addr) {
prv_flash_write_enable();
prv_flash_start_cmd();
prv_flash_send_and_receive_byte(FLASH_CMD_ERASE_SUBSECTOR);
prv_flash_send_24b_address(sector_addr);
prv_flash_end_cmd();
prv_flash_wait_for_write();
}
// Erase a region comprised of 1 or more sub-sectors. This will erase sectors at a time if
// the address and size allow.
void cd_flash_erase_region(uint32_t start_addr, uint32_t total_bytes) {
CD_ASSERTN(((start_addr & SUBSECTOR_ADDR_MASK) == start_addr)
&& ((total_bytes & SUBSECTOR_ADDR_MASK) == total_bytes));
while (total_bytes > 0) {
if (((start_addr & SECTOR_ADDR_MASK) == start_addr) && (total_bytes >= SECTOR_SIZE_BYTES)) {
prv_flash_erase_sector(start_addr);
total_bytes -= SECTOR_SIZE_BYTES;
start_addr += SECTOR_SIZE_BYTES;
} else {
prv_flash_erase_subsector(start_addr);
total_bytes -= SUBSECTOR_SIZE_BYTES;
start_addr += SUBSECTOR_SIZE_BYTES;
}
watchdog_feed();
}
}

View file

@ -0,0 +1,700 @@
/*
* 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 <inttypes.h>
#include <stdbool.h>
#include <stdio.h>
#include "board/board.h"
#include "drivers/dma.h"
#include "drivers/flash.h"
#include "drivers/flash/micron_n25q/flash_private.h"
#include "kernel/util/stop.h"
#include "process_management/worker_manager.h"
#include "services/common/analytics/analytics.h"
#include "os/mutex.h"
#include "kernel/util/delay.h"
#include "system/logging.h"
#include "system/passert.h"
#include "util/math.h"
#include "FreeRTOS.h"
#include "semphr.h"
/*
* Each peripheral has a dma channel / stream it works with
* c.f. section 9.3.3 in stm32 reference manual
*/
/* RX DMA */
static DMA_Stream_TypeDef* FLASH_DMA_STREAM = DMA2_Stream0;
static const uint32_t FLASH_DMA_CHANNEL = DMA_Channel_3;
static const uint32_t FLASH_DMA_IRQn = DMA2_Stream0_IRQn;
static const uint32_t FLASH_DATA_REGISTER_ADDR = (uint32_t)&(SPI1->DR);
/* TX DMA */
static DMA_Stream_TypeDef* FLASH_TX_DMA_STREAM = DMA2_Stream3;
static const uint32_t FLASH_TX_DMA_CHANNEL = DMA_Channel_3;
static uint32_t analytics_read_count;
static uint32_t analytics_read_bytes_count;
static uint32_t analytics_write_bytes_count;
void analytics_external_collect_system_flash_statistics(void) {
// TODO: Add support back to tintin
}
void analytics_external_collect_app_flash_read_stats(void) {
analytics_set(ANALYTICS_APP_METRIC_FLASH_READ_COUNT, analytics_read_count, AnalyticsClient_App);
analytics_set(ANALYTICS_APP_METRIC_FLASH_READ_BYTES_COUNT, analytics_read_bytes_count, AnalyticsClient_App);
analytics_set(ANALYTICS_APP_METRIC_FLASH_WRITE_BYTES_COUNT, 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, analytics_read_count, AnalyticsClient_Worker);
analytics_set(ANALYTICS_APP_METRIC_FLASH_READ_BYTES_COUNT, analytics_read_bytes_count, AnalyticsClient_Worker);
analytics_set(ANALYTICS_APP_METRIC_FLASH_WRITE_BYTES_COUNT, analytics_write_bytes_count, AnalyticsClient_Worker);
}
analytics_read_count = 0;
analytics_read_bytes_count = 0;
analytics_write_bytes_count = 0;
}
struct FlashState {
bool enabled;
bool sleep_when_idle;
bool deep_sleep;
PebbleMutex * mutex;
SemaphoreHandle_t dma_semaphore;
} s_flash_state;
static void flash_deep_sleep_enter(void);
static void flash_deep_sleep_exit(void);
void assert_usable_state(void) {
PBL_ASSERTN(s_flash_state.mutex != 0);
}
static void enable_flash_dma_clock(void) {
// TINTINHACK: Rather than update this file to use the new DMA driver, just rely on the fact that
// this is the only consumer of DMA2.
periph_config_enable(DMA2, RCC_AHB1Periph_DMA2);
}
static void disable_flash_dma_clock(void) {
// TINTINHACK: Rather than update this file to use the new DMA driver, just rely on the fact that
// this is the only consumer of DMA2.
periph_config_disable(DMA2, RCC_AHB1Periph_DMA2);
}
static void setup_dma_read(uint8_t *buffer, int size) {
DMA_InitTypeDef dma_config;
DMA_DeInit(FLASH_DMA_STREAM);
DMA_DeInit(FLASH_TX_DMA_STREAM);
/* RX DMA config */
DMA_StructInit(&dma_config);
dma_config.DMA_Channel = FLASH_DMA_CHANNEL;
dma_config.DMA_DIR = DMA_DIR_PeripheralToMemory;
dma_config.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
dma_config.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
dma_config.DMA_Mode = DMA_Mode_Normal;
dma_config.DMA_PeripheralBaseAddr = FLASH_DATA_REGISTER_ADDR;
dma_config.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
dma_config.DMA_MemoryInc = DMA_MemoryInc_Enable;
dma_config.DMA_Priority = DMA_Priority_High;
dma_config.DMA_FIFOMode = DMA_FIFOMode_Disable;
dma_config.DMA_MemoryBurst = DMA_MemoryBurst_Single;
dma_config.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;
dma_config.DMA_Memory0BaseAddr = (uint32_t)buffer;
dma_config.DMA_BufferSize = size;
DMA_Init(FLASH_DMA_STREAM, &dma_config);
/* TX DMA config */
dma_config.DMA_Channel = FLASH_TX_DMA_CHANNEL;
dma_config.DMA_DIR = DMA_DIR_MemoryToPeripheral;
dma_config.DMA_PeripheralBaseAddr = FLASH_DATA_REGISTER_ADDR;
dma_config.DMA_MemoryInc = DMA_MemoryInc_Disable;
dma_config.DMA_Priority = DMA_Priority_High;
dma_config.DMA_Memory0BaseAddr = (uint32_t)&FLASH_CMD_DUMMY;
dma_config.DMA_BufferSize = size;
DMA_Init(FLASH_TX_DMA_STREAM, &dma_config);
/* Setup DMA interrupts */
NVIC_InitTypeDef nvic_config;
nvic_config.NVIC_IRQChannel = FLASH_DMA_IRQn;
nvic_config.NVIC_IRQChannelPreemptionPriority = 0x0f;
nvic_config.NVIC_IRQChannelSubPriority = 0x00;
nvic_config.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&nvic_config);
DMA_ITConfig(FLASH_DMA_STREAM, DMA_IT_TC, ENABLE);
// enable the DMA stream to start the transfer
SPI_I2S_DMACmd(FLASH_SPI, SPI_I2S_DMAReq_Tx | SPI_I2S_DMAReq_Rx, ENABLE);
}
static void do_dma_transfer(void) {
xSemaphoreTake(s_flash_state.dma_semaphore, portMAX_DELAY);
stop_mode_disable(InhibitorFlash);
DMA_Cmd(FLASH_DMA_STREAM, ENABLE);
DMA_Cmd(FLASH_TX_DMA_STREAM, ENABLE);
xSemaphoreTake(s_flash_state.dma_semaphore, portMAX_DELAY);
stop_mode_enable(InhibitorFlash);
xSemaphoreGive(s_flash_state.dma_semaphore);
}
void DMA2_Stream0_IRQHandler(void) {
if (DMA_GetITStatus(FLASH_DMA_STREAM, DMA_IT_TCIF3)) {
DMA_ClearITPendingBit(FLASH_DMA_STREAM, DMA_IT_TCIF3);
NVIC_DisableIRQ(FLASH_DMA_IRQn);
signed portBASE_TYPE was_higher_priority_task_woken = pdFALSE;
xSemaphoreGiveFromISR(s_flash_state.dma_semaphore, &was_higher_priority_task_woken);
portEND_SWITCHING_ISR(was_higher_priority_task_woken);
return; //notreached
}
}
static void flash_deep_sleep_enter(void) {
assert_usable_state();
if (!s_flash_state.deep_sleep) {
flash_start_cmd();
flash_send_and_receive_byte(FLASH_CMD_DEEP_SLEEP);
flash_end_cmd();
// guarantee we have actually transitioned to deep sleep
delay_us(5);
s_flash_state.deep_sleep = true;
}
}
static void flash_deep_sleep_exit(void) {
assert_usable_state();
if (s_flash_state.deep_sleep) {
flash_start_cmd();
flash_send_and_receive_byte(FLASH_CMD_WAKE);
flash_end_cmd();
// wait a sufficient amount of time to enter standby mode
// It appears violating these timing conditions can lead to
// random bit corruptions on flash writes!
delay_us(100);
s_flash_state.deep_sleep = false;
}
}
void handle_sleep_when_idle_begin(void) {
if (s_flash_state.sleep_when_idle) {
flash_deep_sleep_exit();
}
}
void flash_power_down_for_stop_mode(void) {
if (s_flash_state.sleep_when_idle) {
if (s_flash_state.enabled) {
enable_flash_spi_clock();
flash_deep_sleep_enter();
disable_flash_spi_clock();
}
}
}
void flash_power_up_after_stop_mode(void) {
// no need here as this platform doesn't support memory-mappable flash
}
uint32_t flash_get_sector_base_address(uint32_t addr) {
return addr & ~(SECTOR_SIZE_BYTES - 1);
}
// This simply issues a command to read a specific register
static uint8_t prv_flash_get_register(uint8_t command) {
flash_start_cmd();
flash_send_and_receive_byte(command);
uint8_t reg = flash_read_next_byte();
flash_end_cmd();
return reg;
}
// This will read the flag status register and check it for the SectorLockStatus flag
void prv_check_protection_flag() {
uint8_t flag_status_register = prv_flash_get_register(FLASH_CMD_READ_FLAG_STATUS_REG);
// assert if we found the flag to be enabled
PBL_ASSERTN(!(flag_status_register & N25QFlagStatusBit_SectorLockStatus));
}
// This will clear the protection flag error from a previous error.
// We call this because the error bits persist across reboots
static void prv_clear_flag_status_register(void) {
flash_start_cmd();
flash_send_and_receive_byte(FLASH_CMD_CLEAR_FLAG_STATUS_REG);
flash_end_cmd();
}
/**
* Write up to 1 page (256B) of data to flash. start_addr DOES NOT
* need to be paged aligned. When writing into the middle of a page
* (addr & 0xFFF > 0), overrunning the length of the page will cause
* the write to "wrap around" and will modify (i.e. corrupt) data
* stored before the starting address within the page.
*
*/
static void flash_write_page(const uint8_t* buffer, uint32_t start_addr, uint16_t buffer_size) {
// Ensure that we're not trying to write more data than a single page (256 bytes)
PBL_ASSERTN(buffer_size <= FLASH_PAGE_SIZE);
PBL_ASSERTN(buffer_size);
mutex_assert_held_by_curr_task(s_flash_state.mutex, true /* is_held */);
// Writing a zero-length buffer is a no-op.
if (buffer_size < 1) {
return;
}
flash_write_enable();
flash_start_cmd();
flash_send_and_receive_byte(FLASH_CMD_PAGE_PROGRAM);
flash_send_24b_address(start_addr);
while (buffer_size--) {
flash_send_and_receive_byte(*buffer);
buffer++;
}
flash_end_cmd();
flash_wait_for_write();
prv_check_protection_flag();
}
// Public interface
// From here on down, make sure you're taking the s_flash_state.mutex before doing anything to the SPI peripheral.
void flash_enable_write_protection(void) {
return;
}
void flash_lock(void) {
mutex_lock(s_flash_state.mutex);
}
void flash_unlock(void) {
mutex_unlock(s_flash_state.mutex);
}
bool flash_is_enabled(void) {
return (s_flash_state.enabled);
}
void flash_init(void) {
if (s_flash_state.mutex != 0) {
return; // Already initialized.
}
s_flash_state.mutex = mutex_create();
vSemaphoreCreateBinary(s_flash_state.dma_semaphore);
flash_lock();
enable_flash_spi_clock();
flash_start();
s_flash_state.enabled = true;
s_flash_state.sleep_when_idle = false;
// Assume that last time we shut down we were asleep. Come back out.
s_flash_state.deep_sleep = true;
flash_deep_sleep_exit();
prv_clear_flag_status_register();
disable_flash_spi_clock();
flash_unlock();
flash_whoami();
PBL_LOG_VERBOSE("Detected SPI Flash Size: %u bytes", flash_get_size());
}
void flash_stop(void) {
if (s_flash_state.mutex == NULL) {
return;
}
flash_lock();
s_flash_state.enabled = false;
flash_unlock();
}
void flash_read_bytes(uint8_t* buffer, uint32_t start_addr, uint32_t buffer_size) {
if (!buffer_size) {
return;
}
assert_usable_state();
flash_lock();
if (!s_flash_state.enabled) {
flash_unlock();
return;
}
analytics_read_count++;
analytics_read_bytes_count += buffer_size;
power_tracking_start(PowerSystemFlashRead);
enable_flash_spi_clock();
handle_sleep_when_idle_begin();
flash_wait_for_write();
flash_start_cmd();
flash_send_and_receive_byte(FLASH_CMD_READ);
flash_send_24b_address(start_addr);
// There is delay associated with setting up the stm32 dma, using FreeRTOS
// sempahores, handling ISRs, etc. Thus for short reads, the cost of using
// DMA is far more expensive than the read being performed. Reads greater
// than 34 was empirically determined to be the point at which using the DMA
// engine is advantageous
#if !defined(TARGET_QEMU)
const uint32_t num_reads_dma_cutoff = 34;
#else
// We are disabling DMA reads when running under QEMU for now because they are not reliable.
const uint32_t num_reads_dma_cutoff = buffer_size + 1;
#endif
if (buffer_size < num_reads_dma_cutoff) {
while (buffer_size--) {
*buffer = flash_read_next_byte();
buffer++;
}
} else {
enable_flash_dma_clock();
setup_dma_read(buffer, buffer_size);
do_dma_transfer();
disable_flash_dma_clock();
}
flash_end_cmd();
disable_flash_spi_clock();
power_tracking_stop(PowerSystemFlashRead);
flash_unlock();
}
void flash_write_bytes(const uint8_t* buffer, uint32_t start_addr, uint32_t buffer_size) {
if (!buffer_size) {
return;
}
PBL_ASSERTN((start_addr + buffer_size) <= BOARD_NOR_FLASH_SIZE);
assert_usable_state();
flash_lock();
if (!s_flash_state.enabled) {
flash_unlock();
return;
}
analytics_write_bytes_count += buffer_size;
power_tracking_start(PowerSystemFlashWrite);
enable_flash_spi_clock();
handle_sleep_when_idle_begin();
uint32_t first_page_available_bytes = FLASH_PAGE_SIZE - (start_addr % FLASH_PAGE_SIZE);
uint32_t bytes_to_write = MIN(buffer_size, first_page_available_bytes);
if (first_page_available_bytes < FLASH_PAGE_SIZE) {
PBL_LOG_VERBOSE("Address is not page-aligned; first write will be %"PRId32"B at address 0x%"PRIX32,
first_page_available_bytes, start_addr);
}
while (bytes_to_write) {
flash_write_page(buffer, start_addr, bytes_to_write);
start_addr += bytes_to_write;
buffer += bytes_to_write;
buffer_size -= bytes_to_write;
bytes_to_write = MIN(buffer_size, FLASH_PAGE_SIZE);
}
disable_flash_spi_clock();
power_tracking_stop(PowerSystemFlashWrite);
flash_unlock();
}
void flash_erase_subsector_blocking(uint32_t subsector_addr) {
assert_usable_state();
PBL_LOG(LOG_LEVEL_DEBUG, "Erasing subsector 0x%"PRIx32" (0x%"PRIx32" - 0x%"PRIx32")",
subsector_addr,
subsector_addr & SUBSECTOR_ADDR_MASK,
(subsector_addr & SUBSECTOR_ADDR_MASK) + SUBSECTOR_SIZE_BYTES);
flash_lock();
if (!s_flash_state.enabled) {
flash_unlock();
return;
}
analytics_inc(ANALYTICS_APP_METRIC_FLASH_SUBSECTOR_ERASE_COUNT, AnalyticsClient_CurrentTask);
power_tracking_start(PowerSystemFlashErase);
enable_flash_spi_clock();
handle_sleep_when_idle_begin();
flash_write_enable();
flash_start_cmd();
flash_send_and_receive_byte(FLASH_CMD_ERASE_SUBSECTOR);
flash_send_24b_address(subsector_addr);
flash_end_cmd();
flash_wait_for_write();
prv_check_protection_flag();
disable_flash_spi_clock();
power_tracking_stop(PowerSystemFlashErase);
flash_unlock();
}
void flash_erase_sector_blocking(uint32_t sector_addr) {
assert_usable_state();
PBL_LOG(LOG_LEVEL_DEBUG, "Erasing sector 0x%"PRIx32" (0x%"PRIx32" - 0x%"PRIx32")",
sector_addr,
sector_addr & SECTOR_ADDR_MASK,
(sector_addr & SECTOR_ADDR_MASK) + SECTOR_SIZE_BYTES);
if (flash_sector_is_erased(sector_addr)) {
PBL_LOG(LOG_LEVEL_DEBUG, "Sector %#"PRIx32" already erased", sector_addr);
return;
}
flash_lock();
if (!flash_is_enabled()) {
flash_unlock();
return;
}
power_tracking_start(PowerSystemFlashErase);
enable_flash_spi_clock();
handle_sleep_when_idle_begin();
flash_write_enable();
flash_start_cmd();
flash_send_and_receive_byte(FLASH_CMD_ERASE_SECTOR);
flash_send_24b_address(sector_addr);
flash_end_cmd();
flash_wait_for_write();
prv_check_protection_flag();
disable_flash_spi_clock();
power_tracking_stop(PowerSystemFlashErase);
flash_unlock();
}
// It is dangerous to leave this built in by default.
#if 0
void flash_erase_bulk(void) {
assert_usable_state();
flash_lock();
if (!s_flash_state.enabled) {
flash_unlock();
return;
}
flash_prf_set_protection(false);
power_tracking_start(PowerSystemFlashErase);
enable_flash_spi_clock();
handle_sleep_when_idle_begin();
flash_write_enable();
flash_start_cmd();
flash_send_and_receive_byte(FLASH_CMD_ERASE_BULK);
flash_end_cmd();
flash_wait_for_write();
flash_prf_set_protection(true);
disable_flash_spi_clock();
power_tracking_stop(PowerSystemFlashErase);
flash_unlock();
}
#endif
void flash_sleep_when_idle(bool enable) {
if (enable == s_flash_state.sleep_when_idle) {
return;
}
flash_lock();
if (!s_flash_state.enabled) {
flash_unlock();
return;
}
enable_flash_spi_clock();
s_flash_state.sleep_when_idle = enable;
if (enable) {
if (!s_flash_state.deep_sleep) {
flash_deep_sleep_enter();
}
} else {
if (s_flash_state.deep_sleep) {
flash_deep_sleep_exit();
}
}
disable_flash_spi_clock();
flash_unlock();
}
bool flash_get_sleep_when_idle(void) {
bool result;
flash_lock();
result = s_flash_state.deep_sleep;
flash_unlock();
return result;
}
void debug_flash_dump_registers(void) {
#ifdef PBL_LOG_ENABLED
flash_lock();
if (!s_flash_state.enabled) {
flash_unlock();
return;
}
enable_flash_spi_clock();
handle_sleep_when_idle_begin();
uint8_t status_register = prv_flash_get_register(FLASH_CMD_READ_STATUS_REG);
uint8_t lock_register = prv_flash_get_register(FLASH_CMD_READ_LOCK_REGISTER);
uint8_t flag_status_register = prv_flash_get_register(FLASH_CMD_READ_FLAG_STATUS_REG);
uint8_t nonvolatile_config_register =
prv_flash_get_register(FLASH_CMD_READ_NONVOLATILE_CONFIG_REGISTER);
uint8_t volatile_config_register =
prv_flash_get_register(FLASH_CMD_READ_VOLATILE_CONFIG_REGISTER);
disable_flash_spi_clock();
flash_unlock();
PBL_LOG(LOG_LEVEL_DEBUG, "Status Register: 0x%x", status_register);
PBL_LOG(LOG_LEVEL_DEBUG, "Lock Register: 0x%x", lock_register);
PBL_LOG(LOG_LEVEL_DEBUG, "Flag Status Register: 0x%x", flag_status_register);
PBL_LOG(LOG_LEVEL_DEBUG, "Nonvolatile Configuration Register: 0x%x", nonvolatile_config_register);
PBL_LOG(LOG_LEVEL_DEBUG, "Volatile Configuration Register: 0x%x", volatile_config_register);
#endif
}
bool flash_is_initialized(void) {
return (s_flash_state.mutex != 0);
}
size_t flash_get_size(void) {
uint32_t spi_flash_id = flash_whoami();
if (!check_whoami(spi_flash_id)) {
// Zero bytes is the best size to report if the flash is corrupted
return 0;
}
// capcity_megabytes = 2^(capacity in whoami)
uint32_t capacity = spi_flash_id & 0x000000FF;
// get the capacity of the flash in bytes
return 1 << capacity;
}
void flash_prf_set_protection(bool do_protect) {
assert_usable_state();
flash_lock();
if (!s_flash_state.enabled) {
flash_unlock();
return;
}
enable_flash_spi_clock();
handle_sleep_when_idle_begin();
flash_write_enable();
const uint32_t start_addr = FLASH_REGION_SAFE_FIRMWARE_BEGIN;
const uint32_t end_addr = FLASH_REGION_SAFE_FIRMWARE_END;
const uint8_t lock_bits = do_protect ? N25QLockBit_SectorWriteLock : 0;
for (uint32_t addr = start_addr; addr < end_addr; addr += SECTOR_SIZE_BYTES) {
flash_start_cmd();
flash_send_and_receive_byte(FLASH_CMD_WRITE_LOCK_REGISTER);
flash_send_24b_address(addr);
flash_send_and_receive_byte(lock_bits);
flash_end_cmd();
}
disable_flash_spi_clock();
flash_unlock();
}
void flash_erase_sector(uint32_t sector_addr,
FlashOperationCompleteCb on_complete_cb,
void *context) {
// TODO: implement nonblocking erase
flash_erase_sector_blocking(sector_addr);
on_complete_cb(context, S_SUCCESS);
}
void flash_erase_subsector(uint32_t sector_addr,
FlashOperationCompleteCb on_complete_cb,
void *context) {
// TODO: implement nonblocking erase
flash_erase_subsector_blocking(sector_addr);
on_complete_cb(context, S_SUCCESS);
}

View file

@ -0,0 +1,228 @@
/*
* 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 "kernel/util/delay.h"
#include "system/passert.h"
#include "util/units.h"
#include <inttypes.h>
#include <stdbool.h>
#include <stdio.h>
#include "drivers/flash/micron_n25q/flash_private.h"
void enable_flash_spi_clock(void) {
periph_config_enable(FLASH_SPI, FLASH_SPI_CLOCK);
}
void disable_flash_spi_clock(void) {
periph_config_disable(FLASH_SPI, FLASH_SPI_CLOCK);
}
// IMPORTANT: This method is also used by the core dump logic in order to re-initialize the flash hardware
// to prepare for writing the core dump. For this reason, it can NOT use any FreeRTOS functions, mess with
// the interrupt priority, primask, etc.
void flash_hw_init(void) {
// Connect PA5 to SPI1_SCLK
GPIO_PinAFConfig(FLASH_GPIO, GPIO_PinSource5, GPIO_AF_SPI1);
// Connect PA6 to SPI1_MISO
GPIO_PinAFConfig(FLASH_GPIO, GPIO_PinSource6, GPIO_AF_SPI1);
// Connect PA7 to SPI1_MOSI
GPIO_PinAFConfig(FLASH_GPIO, GPIO_PinSource7, GPIO_AF_SPI1);
GPIO_InitTypeDef gpio_cfg;
gpio_cfg.GPIO_OType = GPIO_OType_PP;
gpio_cfg.GPIO_PuPd = GPIO_PuPd_NOPULL;
gpio_cfg.GPIO_Mode = GPIO_Mode_AF;
gpio_cfg.GPIO_Speed = GPIO_Speed_50MHz;
gpio_cfg.GPIO_Pin = FLASH_PIN_MISO | FLASH_PIN_MOSI;
GPIO_Init(FLASH_GPIO, &gpio_cfg);
// Configure the SCLK pin to have a weak pull-down to put it in a known state
// when SCS is toggled
gpio_cfg.GPIO_PuPd = GPIO_PuPd_DOWN;
gpio_cfg.GPIO_Pin = FLASH_PIN_SCLK;
GPIO_Init(FLASH_GPIO, &gpio_cfg);
// Configure SCS to be controlled in software; pull up to high when inactive
gpio_cfg.GPIO_Mode = GPIO_Mode_OUT;
gpio_cfg.GPIO_Pin = FLASH_PIN_SCS;
gpio_cfg.GPIO_PuPd = GPIO_PuPd_UP;
GPIO_Init(FLASH_GPIO, &gpio_cfg);
// Set up a SPI bus on SPI1
SPI_InitTypeDef spi_cfg;
SPI_I2S_DeInit(FLASH_SPI);
spi_cfg.SPI_Direction = SPI_Direction_2Lines_FullDuplex;
spi_cfg.SPI_Mode = SPI_Mode_Master;
spi_cfg.SPI_DataSize = SPI_DataSize_8b;
spi_cfg.SPI_CPOL = SPI_CPOL_Low;
spi_cfg.SPI_CPHA = SPI_CPHA_1Edge;
spi_cfg.SPI_NSS = SPI_NSS_Soft;
spi_cfg.SPI_BaudRatePrescaler = spi_find_prescaler(MHZ_TO_HZ(54), FLASH_SPI_CLOCK_PERIPH); // max read freq for the flash
spi_cfg.SPI_FirstBit = SPI_FirstBit_MSB;
spi_cfg.SPI_CRCPolynomial = 7;
SPI_Init(FLASH_SPI, &spi_cfg);
SPI_Cmd(FLASH_SPI, ENABLE);
}
void flash_start(void) {
periph_config_acquire_lock();
gpio_use(FLASH_GPIO);
// Init the hardware
flash_hw_init();
gpio_release(FLASH_GPIO);
periph_config_release_lock();
}
void flash_start_cmd(void) {
gpio_use(FLASH_GPIO);
GPIO_ResetBits(FLASH_GPIO, FLASH_PIN_SCS);
gpio_release(FLASH_GPIO);
}
void flash_end_cmd(void) {
gpio_use(FLASH_GPIO);
GPIO_SetBits(FLASH_GPIO, FLASH_PIN_SCS);
gpio_release(FLASH_GPIO);
// 50ns required between SCS going high and low again, so just delay here to be safe
delay_us(1);
}
uint8_t flash_send_and_receive_byte(uint8_t byte) {
// Ensure that there are no other write operations in progress
while (SPI_I2S_GetFlagStatus(FLASH_SPI, SPI_I2S_FLAG_TXE) == RESET);
// Send the byte on the SPI bus
SPI_I2S_SendData(FLASH_SPI, byte);
// Wait for the response byte to be received
while (SPI_I2S_GetFlagStatus(FLASH_SPI, SPI_I2S_FLAG_RXNE) == RESET);
// Return the byte
return SPI_I2S_ReceiveData(FLASH_SPI);
}
void flash_write_enable(void) {
flash_start_cmd();
flash_send_and_receive_byte(FLASH_CMD_WRITE_ENABLE);
flash_end_cmd();
}
void flash_send_24b_address(uint32_t start_addr) {
// Ensure the high bits are not set.
PBL_ASSERTN(!(start_addr & 0xFF000000));
flash_send_and_receive_byte((start_addr & 0xFF0000) >> 16);
flash_send_and_receive_byte((start_addr & 0x00FF00) >> 8);
flash_send_and_receive_byte((start_addr & 0x0000FF));
}
uint8_t flash_read_next_byte(void) {
uint8_t result = flash_send_and_receive_byte(FLASH_CMD_DUMMY);
return result;
}
void flash_wait_for_write_bounded(volatile int cycles_to_wait) {
flash_start_cmd();
flash_send_and_receive_byte(FLASH_CMD_READ_STATUS_REG);
uint8_t status_register = 0;
do {
if (cycles_to_wait-- < 1) {
break;
}
status_register = flash_read_next_byte();
} while (status_register & N25QStatusBit_WriteInProgress);
flash_end_cmd();
}
void flash_wait_for_write(void) {
flash_start_cmd();
flash_send_and_receive_byte(FLASH_CMD_READ_STATUS_REG);
uint8_t status_register = 0;
do {
status_register = flash_read_next_byte();
} while (status_register & N25QStatusBit_WriteInProgress);
flash_end_cmd();
}
bool flash_sector_is_erased(uint32_t sector_addr) {
const uint32_t bufsize = 128;
uint8_t buffer[bufsize];
sector_addr &= SECTOR_ADDR_MASK;
for (uint32_t offset = 0; offset < SECTOR_SIZE_BYTES; offset += bufsize) {
flash_read_bytes(buffer, sector_addr + offset, bufsize);
for (uint32_t i = 0; i < bufsize; i++) {
if (buffer[i] != 0xff) {
return false;
}
}
}
return true;
}
uint32_t flash_whoami(void) {
assert_usable_state();
flash_lock();
if (!flash_is_enabled()) {
flash_unlock();
return 0;
}
enable_flash_spi_clock();
handle_sleep_when_idle_begin();
flash_wait_for_write_bounded(64000000);
flash_start_cmd();
flash_send_and_receive_byte(FLASH_CMD_READ_ID);
uint32_t manufacturer = flash_read_next_byte();
uint32_t type = flash_read_next_byte();
uint32_t capacity = flash_read_next_byte();
flash_end_cmd();
disable_flash_spi_clock();
flash_unlock();
return ((manufacturer << 16) | (type << 8) | capacity);
}
bool check_whoami(uint32_t spi_flash_id) {
return spi_flash_id == EXPECTED_SPI_FLASH_ID_32MBIT ||
spi_flash_id == EXPECTED_SPI_FLASH_ID_64MBIT;
}
bool flash_is_whoami_correct(void) {
uint32_t spi_flash_id = flash_whoami();
return check_whoami(spi_flash_id);
}
void flash_switch_mode(FlashModeType mode) {
}

View file

@ -0,0 +1,115 @@
/*
* 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 "board/board.h"
#include "drivers/gpio.h"
#include "drivers/periph_config.h"
#include "drivers/spi.h"
#include "flash_region/flash_region.h"
#include "system/passert.h"
#include "system/logging.h"
#define STM32F2_COMPATIBLE
#define STM32F4_COMPATIBLE
#include <mcu.h>
#include "debug/power_tracking.h"
/* GPIO */
static GPIO_TypeDef* const FLASH_GPIO = GPIOA;
/* SPI */
static SPI_TypeDef* const FLASH_SPI = SPI1;
static const uint32_t FLASH_SPI_CLOCK = RCC_APB2Periph_SPI1;
static const SpiPeriphClock FLASH_SPI_CLOCK_PERIPH = SpiPeriphClockAPB2;
/* Pin Defintions */
static const uint32_t FLASH_PIN_SCS = GPIO_Pin_4;
static const uint32_t FLASH_PIN_SCLK = GPIO_Pin_5;
static const uint32_t FLASH_PIN_MISO = GPIO_Pin_6;
static const uint32_t FLASH_PIN_MOSI = GPIO_Pin_7;
/* Flash SPI commands */
static const uint8_t FLASH_CMD_WRITE_ENABLE = 0x06;
static const uint8_t FLASH_CMD_WRITE_DISABLE = 0x04;
static const uint8_t FLASH_CMD_READ_STATUS_REG = 0x05;
static const uint8_t FLASH_CMD_READ_FLAG_STATUS_REG = 0x70;
static const uint8_t FLASH_CMD_CLEAR_FLAG_STATUS_REG = 0x50;
static const uint8_t FLASH_CMD_READ = 0x03;
static const uint8_t FLASH_CMD_READ_ID = 0x9F;
static const uint8_t FLASH_CMD_PAGE_PROGRAM = 0x02;
static const uint8_t FLASH_CMD_ERASE_SUBSECTOR = 0x20;
static const uint8_t FLASH_CMD_ERASE_SECTOR = 0xD8;
static const uint8_t FLASH_CMD_ERASE_BULK = 0xC7;
static const uint8_t FLASH_CMD_DEEP_SLEEP = 0xB9;
static const uint8_t FLASH_CMD_WAKE = 0xAB;
static const uint8_t FLASH_CMD_DUMMY = 0xA9;
static const uint8_t FLASH_CMD_WRITE_LOCK_REGISTER = 0xE5;
static const uint8_t FLASH_CMD_READ_LOCK_REGISTER = 0xE8;
static const uint8_t FLASH_CMD_READ_NONVOLATILE_CONFIG_REGISTER = 0xB5;
static const uint8_t FLASH_CMD_READ_VOLATILE_CONFIG_REGISTER = 0x85;
static const uint16_t FLASH_PAGE_SIZE = 0x100;
typedef enum N25QFlagStatusBit {
// Bit 0 is reserved
N25QFlagStatusBit_SectorLockStatus = (1 << 1),
N25QFlagStatusBit_ProgramSuspended = (1 << 2),
N25QFlagStatusBit_VppStatus = (1 << 3),
N25QFlagStatusBit_ProgramStatus = (1 << 4),
N25QFlagStatusBit_EraseStatus = (1 << 5),
N25QFlagStatusBit_EraseSuspended = (1 << 6),
N25QFlagStatusBit_DeviceReady = (1 << 7),
} N25QFlagStatusBit;
typedef enum N25QStatusBit {
N25QStatusBit_WriteInProgress = (1 << 0),
N25QStatusBit_WriteEnableLatch = (1 << 1),
N25QStatusBit_BlockProtect0 = (1 << 2),
N25QStatusBit_BlockProtect1 = (1 << 3),
N25QStatusBit_BlockProtect2 = (1 << 4),
N25QStatusBit_ProtectTopBottom = (1 << 5),
// Bit 6 is reserved
N25QStatusBit_StatusRegisterWrite = (1 << 7),
} N25QStatusBit;
typedef enum N25QLockBit {
N25QLockBit_SectorWriteLock = (1 << 0),
N25QLockBit_SectorLockDown = (1 << 1),
// Bits 2-7 are reserved
} N25QLockBit;
// Method shared with flash.c and the core dump logic in core_dump.c
void flash_hw_init(void);
void assert_usable_state(void);
void flash_lock(void);
void flash_unlock(void);
bool flash_is_enabled(void);
void handle_sleep_when_idle_begin(void);
void enable_flash_spi_clock(void);
void disable_flash_spi_clock(void);
void flash_start(void);
void flash_start_cmd(void);
void flash_end_cmd(void);
uint8_t flash_send_and_receive_byte(uint8_t byte);
void flash_write_enable(void);
void flash_send_24b_address(uint32_t start_addr);
uint8_t flash_read_next_byte(void);
void flash_wait_for_write_bounded(volatile int cycles_to_wait);
void flash_wait_for_write(void);
bool check_whoami(uint32_t spi_flash_id);
bool flash_is_whoami_correct(void);

View file

@ -0,0 +1,210 @@
/*
* 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 "board/board.h"
#include "drivers/flash/flash_impl.h"
#include "drivers/flash/qspi_flash.h"
#include "drivers/flash/qspi_flash_part_definitions.h"
#include "flash_region/flash_region.h"
#include "system/passert.h"
#include "system/status_codes.h"
#include "util/math.h"
#define STM32F4_COMPATIBLE
#define STM32F7_COMPATIBLE
#include <mcu.h>
static QSPIFlashPart QSPI_FLASH_PART = {
.instructions = {
.fast_read = 0x0B,
.fast_read_ddr = 0x0D,
.page_program = 0x02,
.erase_sector_4k = 0x20,
.erase_block_64k = 0xD8,
.write_enable = 0x06,
.write_disable = 0x04,
.read_status = 0x05,
.read_flag_status = 0x70,
.erase_suspend = 0x75,
.erase_resume = 0x7A,
.enter_low_power = 0xB9,
.exit_low_power = 0xAB,
.enter_quad_mode = 0x35,
.reset_enable = 0x66,
.reset = 0x99,
.qspi_id = 0xAF,
.block_lock = 0xE5,
.block_lock_status = 0xE8,
},
.status_bit_masks = {
.busy = 1 << 0,
.write_enable = 1 << 1,
},
.flag_status_bit_masks = {
.erase_suspend = 1 << 6,
},
.dummy_cycles = {
.fast_read = 10,
.fast_read_ddr = 8,
},
.block_lock = {
.has_lock_data = true,
.lock_data = 0x1,
.locked_check = 0x1,
},
.reset_latency_ms = 51,
.suspend_to_read_latency_us = 0,
.standby_to_low_power_latency_us = 3,
.low_power_to_standby_latency_us = 30,
.supports_fast_read_ddr = true,
#if BOARD_ROBERT_BB || BOARD_ROBERT_BB2 || BOARD_CUTTS_BB
.qspi_id_value = 0x19BB20,
.name = "MT25Q256",
#elif BOARD_ROBERT_EVT
.qspi_id_value = 0x18BB20,
.name = "MT25Q128",
#else
#error "Unsupported board"
#endif
};
bool flash_check_whoami(void) {
return qspi_flash_check_whoami(QSPI_FLASH);
}
FlashAddress flash_impl_get_sector_base_address(FlashAddress addr) {
return (addr & SECTOR_ADDR_MASK);
}
FlashAddress flash_impl_get_subsector_base_address(FlashAddress addr) {
return (addr & SUBSECTOR_ADDR_MASK);
}
void flash_impl_enable_write_protection(void) {
}
status_t flash_impl_write_protect(FlashAddress start_sector, FlashAddress end_sector) {
FlashAddress block_addr = start_sector;
while (block_addr <= end_sector) {
uint32_t block_size;
if (WITHIN(block_addr, SECTOR_SIZE_BYTES, BOARD_NOR_FLASH_SIZE - SECTOR_SIZE_BYTES - 1)) {
// Middle of flash has 64k lock units
block_addr = flash_impl_get_sector_base_address(block_addr);
block_size = SECTOR_SIZE_BYTES;
} else {
// Start and end of flash have 1 sector of 4k lock units
block_addr = flash_impl_get_subsector_base_address(block_addr);
block_size = SUBSECTOR_SIZE_BYTES;
}
const status_t sc = qspi_flash_lock_sector(QSPI_FLASH, block_addr);
if (FAILED(sc)) {
return sc;
}
block_addr += block_size;
}
return S_SUCCESS;
}
status_t flash_impl_unprotect(void) {
// No way to unprotect all of flash. This requires a full reset of the mt25q
qspi_flash_init(QSPI_FLASH, &QSPI_FLASH_PART, qspi_flash_is_in_coredump_mode(QSPI_FLASH));
return S_SUCCESS;
}
static void prv_set_high_drive_strength(void) {
// Match the impedance of the traces (~50 ohms) by configuring the drive strength of the data
// output pins on the MT25Q to the 45 ohm setting This avoids signal integreity issues. This is
// done by setting bits 2:0 in the "Enhanced Volatile Configuration Register" to 101b.
const uint8_t read_instruction = 0x65;
const uint8_t write_instruction = 0x61;
const uint8_t mask = 0x7;
const uint8_t value = 0x5;
qspi_flash_ll_set_register_bits(QSPI_FLASH, read_instruction, write_instruction, value, mask);
}
status_t flash_impl_init(bool coredump_mode) {
qspi_flash_init(QSPI_FLASH, &QSPI_FLASH_PART, coredump_mode);
#if BOARD_CUTTS_BB || BOARD_ROBERT_BB || BOARD_ROBERT_EVT
prv_set_high_drive_strength();
#endif
return S_SUCCESS;
}
status_t flash_impl_get_erase_status(void) {
return qspi_flash_is_erase_complete(QSPI_FLASH);
}
status_t flash_impl_erase_subsector_begin(FlashAddress subsector_addr) {
return qspi_flash_erase_begin(QSPI_FLASH, subsector_addr, true /* is_subsector */);
}
status_t flash_impl_erase_sector_begin(FlashAddress sector_addr) {
return qspi_flash_erase_begin(QSPI_FLASH, sector_addr, false /* !is_subsector */);
}
status_t flash_impl_erase_suspend(FlashAddress sector_addr) {
return qspi_flash_erase_suspend(QSPI_FLASH, sector_addr);
}
status_t flash_impl_erase_resume(FlashAddress sector_addr) {
qspi_flash_erase_resume(QSPI_FLASH, sector_addr);
return S_SUCCESS;
}
status_t flash_impl_read_sync(void *buffer_ptr, FlashAddress start_addr, size_t buffer_size) {
PBL_ASSERT(buffer_size > 0, "flash_impl_read_sync() called with 0 bytes to read");
qspi_flash_read_blocking(QSPI_FLASH, start_addr, buffer_ptr, buffer_size);
return S_SUCCESS;
}
int flash_impl_write_page_begin(const void *buffer, const FlashAddress start_addr, size_t len) {
return qspi_flash_write_page_begin(QSPI_FLASH, buffer, start_addr, len);
}
status_t flash_impl_get_write_status(void) {
return qspi_flash_get_write_status(QSPI_FLASH);
}
status_t flash_impl_enter_low_power_mode(void) {
qspi_flash_set_lower_power_mode(QSPI_FLASH, true);
return S_SUCCESS;
}
status_t flash_impl_exit_low_power_mode(void) {
qspi_flash_set_lower_power_mode(QSPI_FLASH, false);
return S_SUCCESS;
}
status_t flash_impl_set_burst_mode(bool burst_mode) {
// NYI
return S_SUCCESS;
}
status_t flash_impl_blank_check_sector(FlashAddress addr) {
return qspi_flash_blank_check(QSPI_FLASH, addr, false /* !is_subsector */);
}
status_t flash_impl_blank_check_subsector(FlashAddress addr) {
return qspi_flash_blank_check(QSPI_FLASH, addr, true /* is_subsector */);
}
uint32_t flash_impl_get_typical_sector_erase_duration_ms(void) {
return 150;
}
uint32_t flash_impl_get_typical_subsector_erase_duration_ms(void) {
return 50;
}

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.
*/
#include "board/board.h"
#include "drivers/flash/flash_impl.h"
#include "drivers/flash/qspi_flash.h"
#include "drivers/flash/qspi_flash_part_definitions.h"
#include "flash_region/flash_region.h"
#include "system/passert.h"
#include "system/status_codes.h"
#include "system/version.h"
#include "util/math.h"
#define STM32F4_COMPATIBLE
#define STM32F7_COMPATIBLE
#include <mcu.h>
static QSPIFlashPart QSPI_FLASH_PART = {
.instructions = {
.fast_read = 0x0B,
.page_program = 0x02,
.erase_sector_4k = 0x20,
.erase_block_64k = 0xD8,
.write_enable = 0x06,
.write_disable = 0x04,
.read_status = 0x05,
.read_flag_status = 0x2B,
.erase_suspend = 0xB0,
.erase_resume = 0x30,
.enter_low_power = 0xB9,
.exit_low_power = 0xAB,
.enter_quad_mode = 0x35,
.reset_enable = 0x66,
.reset = 0x99,
.qspi_id = 0xAF,
.block_lock = 0x36,
.block_lock_status = 0x3C,
.block_unlock_all = 0x98,
.write_protection_enable = 0x68,
.read_protection_status = 0x2B,
},
.status_bit_masks = {
.busy = 1 << 0,
.write_enable = 1 << 1,
},
.flag_status_bit_masks = {
.erase_suspend = 1 << 3,
},
.dummy_cycles = {
.fast_read = 4,
},
.block_lock = {
.has_lock_data = false,
.locked_check = 0xff,
.protection_enabled_mask = (1 << 7),
},
.reset_latency_ms = 13,
.suspend_to_read_latency_us = 20,
.standby_to_low_power_latency_us = 10,
.low_power_to_standby_latency_us = 30,
.supports_fast_read_ddr = false,
.qspi_id_value = 0x3725c2,
.name = "MX25U64",
};
//! Any PRF built after this timestamp supports mx25u flash protection
#define MIN_PRF_TIMESTAMP_SUPPORTING_PROTECTION (1466531458)
//! True if the installed PRF version supports flash protection
static bool s_flash_protection_supported = false;
bool flash_check_whoami(void) {
return qspi_flash_check_whoami(QSPI_FLASH);
}
FlashAddress flash_impl_get_sector_base_address(FlashAddress addr) {
return (addr & SECTOR_ADDR_MASK);
}
FlashAddress flash_impl_get_subsector_base_address(FlashAddress addr) {
return (addr & SUBSECTOR_ADDR_MASK);
}
static bool prv_prf_supports_flash_protection(void) {
#if IS_BIGBOARD
// Bigboards should always exercise flash protection
return true;
#else
FirmwareMetadata prf;
if (!version_copy_recovery_fw_metadata(&prf)) {
return false;
}
return (prf.version_timestamp > MIN_PRF_TIMESTAMP_SUPPORTING_PROTECTION);
#endif
}
void flash_impl_enable_write_protection(void) {
s_flash_protection_supported = prv_prf_supports_flash_protection();
if (s_flash_protection_supported) {
// Ensure that write protection is enabled on the mx25u
if (qspi_flash_write_protection_enable(QSPI_FLASH) == S_SUCCESS) {
// after flash protection is enabled, full array is locked. Unlock it.
qspi_flash_unlock_all(QSPI_FLASH);
}
}
}
status_t flash_impl_write_protect(FlashAddress start_sector, FlashAddress end_sector) {
if (!s_flash_protection_supported) {
return S_SUCCESS; // If not supported, pretend protection succeeded.
}
FlashAddress block_addr = start_sector;
while (block_addr <= end_sector) {
uint32_t block_size;
if (WITHIN(block_addr, SECTOR_SIZE_BYTES, BOARD_NOR_FLASH_SIZE - SECTOR_SIZE_BYTES - 1)) {
// Middle of flash has 64k lock units
block_addr = flash_impl_get_sector_base_address(block_addr);
block_size = SECTOR_SIZE_BYTES;
} else {
// Start and end of flash have 1 sector of 4k lock units
block_addr = flash_impl_get_subsector_base_address(block_addr);
block_size = SUBSECTOR_SIZE_BYTES;
}
const status_t sc = qspi_flash_lock_sector(QSPI_FLASH, block_addr);
if (FAILED(sc)) {
return sc;
}
block_addr += block_size;
}
return S_SUCCESS;
}
status_t flash_impl_unprotect(void) {
return qspi_flash_unlock_all(QSPI_FLASH);
}
status_t flash_impl_init(bool coredump_mode) {
qspi_flash_init(QSPI_FLASH, &QSPI_FLASH_PART, coredump_mode);
qspi_flash_unlock_all(QSPI_FLASH);
return S_SUCCESS;
}
status_t flash_impl_get_erase_status(void) {
return qspi_flash_is_erase_complete(QSPI_FLASH);
}
status_t flash_impl_erase_subsector_begin(FlashAddress subsector_addr) {
return qspi_flash_erase_begin(QSPI_FLASH, subsector_addr, true /* is_subsector */);
}
status_t flash_impl_erase_sector_begin(FlashAddress sector_addr) {
return qspi_flash_erase_begin(QSPI_FLASH, sector_addr, false /* !is_subsector */);
}
status_t flash_impl_erase_suspend(FlashAddress sector_addr) {
return qspi_flash_erase_suspend(QSPI_FLASH, sector_addr);
}
status_t flash_impl_erase_resume(FlashAddress sector_addr) {
qspi_flash_erase_resume(QSPI_FLASH, sector_addr);
return S_SUCCESS;
}
status_t flash_impl_read_sync(void *buffer_ptr, FlashAddress start_addr, size_t buffer_size) {
PBL_ASSERT(buffer_size > 0, "flash_impl_read_sync() called with 0 bytes to read");
qspi_flash_read_blocking(QSPI_FLASH, start_addr, buffer_ptr, buffer_size);
return S_SUCCESS;
}
int flash_impl_write_page_begin(const void *buffer, const FlashAddress start_addr, size_t len) {
return qspi_flash_write_page_begin(QSPI_FLASH, buffer, start_addr, len);
}
status_t flash_impl_get_write_status(void) {
return qspi_flash_get_write_status(QSPI_FLASH);
}
status_t flash_impl_enter_low_power_mode(void) {
qspi_flash_set_lower_power_mode(QSPI_FLASH, true);
return S_SUCCESS;
}
status_t flash_impl_exit_low_power_mode(void) {
qspi_flash_set_lower_power_mode(QSPI_FLASH, false);
return S_SUCCESS;
}
status_t flash_impl_set_burst_mode(bool burst_mode) {
// NYI
return S_SUCCESS;
}
status_t flash_impl_blank_check_sector(FlashAddress addr) {
return qspi_flash_blank_check(QSPI_FLASH, addr, false /* !is_subsector */);
}
status_t flash_impl_blank_check_subsector(FlashAddress addr) {
return qspi_flash_blank_check(QSPI_FLASH, addr, true /* is_subsector */);
}
uint32_t flash_impl_get_typical_sector_erase_duration_ms(void) {
return 400;
}
uint32_t flash_impl_get_typical_subsector_erase_duration_ms(void) {
return 40;
}

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.
*/
#include "drivers/flash/flash_impl.h"
#include "system/passert.h"
#include "system/rtc_registers.h"
#define STM32F2_COMPATIBLE
#define STM32F4_COMPATIBLE
#define STM32F7_COMPATIBLE
#include <mcu.h>
#include <stdbool.h>
#include <stdint.h>
// RTC backup registers are reset to zero on a cold boot (power up from a dead
// battery) so the value stored in the backup register corresponding to no erase
// in progress must also be zero. We need to use a nonzero value to store an
// erase to sector zero, so we can't simply store just the address.
//
// We want to store whether an erase is in progress (1 bit), whether the erase
// is for a sector or a subsector (1 bit), and the address being erased (32
// bits) in a single 32-bit RTC register. Since we can't magically compress 34
// bits into 32, we'll need to play some tricks. The address is going to almost
// certainly be less than 32 bits long; we aren't going to be using
// gigabyte-sized flash memories any time soon (at least not with this
// homegrown API), leaving bits free on the high end.
#define ERASE_IN_PROGRESS 0x80000000
#define ERASE_IS_SUBSECTOR 0x40000000
#define ERASE_FLAGS_MASK (ERASE_IN_PROGRESS | ERASE_IS_SUBSECTOR)
#define ERASE_ADDRESS_MASK 0x3FFFFFFF
status_t flash_impl_set_nvram_erase_status(bool is_subsector,
FlashAddress addr) {
PBL_ASSERTN((addr & ERASE_FLAGS_MASK) == 0); // "Flash address too large to store"
uint32_t reg = addr | ERASE_IN_PROGRESS;
if (is_subsector) {
reg |= ERASE_IS_SUBSECTOR;
}
RTC_WriteBackupRegister(RTC_BKP_FLASH_ERASE_PROGRESS, reg);
return S_SUCCESS;
}
status_t flash_impl_clear_nvram_erase_status(void) {
RTC_WriteBackupRegister(RTC_BKP_FLASH_ERASE_PROGRESS, 0);
return S_SUCCESS;
}
status_t flash_impl_get_nvram_erase_status(bool *is_subsector,
FlashAddress *addr) {
uint32_t reg = RTC_ReadBackupRegister(RTC_BKP_FLASH_ERASE_PROGRESS);
if (reg == 0) {
return S_FALSE;
}
*addr = reg & ERASE_ADDRESS_MASK;
*is_subsector = (reg & ERASE_IS_SUBSECTOR) != 0;
return S_TRUE;
}

View file

@ -0,0 +1,761 @@
/*
* 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 "qspi_flash.h"
#include "qspi_flash_definitions.h"
#include "board/board.h"
#include "drivers/flash/flash_impl.h"
#include "drivers/gpio.h"
#include "drivers/qspi.h"
#include "flash_region/flash_region.h"
#include "kernel/util/delay.h"
#include "kernel/util/sleep.h"
#include "mcu/cache.h"
#include "system/logging.h"
#include "system/passert.h"
#include "system/status_codes.h"
#include "util/math.h"
#define STM32F4_COMPATIBLE
#define STM32F7_COMPATIBLE
#include <mcu.h>
#define FLASH_RESET_WORD_VALUE (0xffffffff)
static void prv_read_register(QSPIFlash *dev, uint8_t instruction, uint8_t *data, uint32_t length) {
qspi_indirect_read_no_addr(dev->qspi, instruction, 0, data, length, false /* !is_ddr */);
}
static void prv_write_cmd_no_addr(QSPIFlash *dev, uint8_t cmd) {
qspi_indirect_write_no_addr(dev->qspi, cmd, NULL, 0);
}
static void prv_write_enable(QSPIFlash *dev) {
prv_write_cmd_no_addr(dev, dev->state->part->instructions.write_enable);
// wait for writing to be enabled
qspi_poll_bit(dev->qspi, dev->state->part->instructions.read_status,
dev->state->part->status_bit_masks.write_enable, true /* set */, QSPI_NO_TIMEOUT);
}
static bool prv_check_whoami(QSPIFlash *dev) {
// The WHOAMI is 3 bytes
const uint32_t whoami_length = 3;
uint32_t read_whoami = 0;
prv_read_register(dev, dev->state->part->instructions.qspi_id, (uint8_t *)&read_whoami,
whoami_length);
if (read_whoami == dev->state->part->qspi_id_value) {
PBL_LOG(LOG_LEVEL_INFO, "Flash is %s", dev->state->part->name);
return true;
} else {
PBL_LOG(LOG_LEVEL_ERROR, "Flash isn't expected %s (whoami: 0x%"PRIx32")",
dev->state->part->name, read_whoami);
return false;
}
qspi_release(dev->qspi);
}
bool qspi_flash_check_whoami(QSPIFlash *dev) {
qspi_use(dev->qspi);
bool result = prv_check_whoami(dev);
qspi_release(dev->qspi);
return result;
}
static void prv_set_fast_read_ddr_enabled(QSPIFlash *dev, bool enabled) {
// If we're supposed to use DDR for fast read, make sure the part can support it
PBL_ASSERTN(!enabled || dev->state->part->supports_fast_read_ddr);
dev->state->fast_read_ddr_enabled = enabled;
}
bool qspi_flash_is_in_coredump_mode(QSPIFlash *dev) {
return dev->state->coredump_mode;
}
void qspi_flash_init(QSPIFlash *dev, QSPIFlashPart *part, bool coredump_mode) {
dev->state->part = part;
dev->state->coredump_mode = coredump_mode;
prv_set_fast_read_ddr_enabled(dev, dev->default_fast_read_ddr_enabled);
qspi_use(dev->qspi);
if (dev->reset_gpio.gpio) {
gpio_output_init(&dev->reset_gpio, GPIO_OType_PP, GPIO_Speed_2MHz);
gpio_output_set(&dev->reset_gpio, false);
}
// Must call quad_enable first, all commands are QSPI
qspi_indirect_write_no_addr_1line(dev->qspi, dev->state->part->instructions.enter_quad_mode);
// Reset the flash to stop any program's or erase in progress from before reboot
prv_write_cmd_no_addr(dev, dev->state->part->instructions.reset_enable);
prv_write_cmd_no_addr(dev, dev->state->part->instructions.reset);
if (coredump_mode) {
delay_us(dev->state->part->reset_latency_ms * 1000);
} else {
psleep(dev->state->part->reset_latency_ms);
}
// Return the flash to Quad SPI mode, all our commands are quad-spi and it'll just cause
// problems/bugs for someone if it comes back in single spi mode
qspi_indirect_write_no_addr_1line(dev->qspi, dev->state->part->instructions.enter_quad_mode);
if (!coredump_mode) {
prv_check_whoami(dev);
}
qspi_release(dev->qspi);
}
status_t qspi_flash_is_erase_complete(QSPIFlash *dev) {
qspi_use(dev->qspi);
uint8_t status_reg;
uint8_t flag_status_reg;
prv_read_register(dev, dev->state->part->instructions.read_status, &status_reg, 1);
prv_read_register(dev, dev->state->part->instructions.read_flag_status, &flag_status_reg, 1);
qspi_release(dev->qspi);
if (status_reg & dev->state->part->status_bit_masks.busy) {
return E_BUSY;
} else if (flag_status_reg & dev->state->part->flag_status_bit_masks.erase_suspend) {
return E_AGAIN;
} else {
return S_SUCCESS;
}
}
status_t qspi_flash_erase_begin(QSPIFlash *dev, uint32_t addr, bool is_subsector) {
uint8_t instruction;
if (is_subsector) {
instruction = dev->state->part->instructions.erase_sector_4k;
} else {
instruction = dev->state->part->instructions.erase_block_64k;
}
qspi_use(dev->qspi);
prv_write_enable(dev);
qspi_indirect_write(dev->qspi, instruction, addr, NULL, 0);
// wait for busy to be set indicating the erase has started
const uint32_t busy_timeout_us = 500;
const bool result = qspi_poll_bit(dev->qspi, dev->state->part->instructions.read_status,
dev->state->part->status_bit_masks.busy, true /* set */,
busy_timeout_us);
qspi_release(QSPI);
return result ? S_SUCCESS : E_ERROR;
}
status_t qspi_flash_erase_suspend(QSPIFlash *dev, uint32_t addr) {
qspi_use(dev->qspi);
uint8_t status_reg;
prv_read_register(dev, dev->state->part->instructions.read_status, &status_reg, 1);
if (!(status_reg & dev->state->part->status_bit_masks.busy)) {
// no erase in progress
qspi_release(QSPI);
return S_NO_ACTION_REQUIRED;
}
prv_write_cmd_no_addr(dev, dev->state->part->instructions.erase_suspend);
qspi_release(QSPI);
if (dev->state->part->suspend_to_read_latency_us) {
delay_us(dev->state->part->suspend_to_read_latency_us);
}
return S_SUCCESS;
}
void qspi_flash_erase_resume(QSPIFlash *dev, uint32_t addr) {
qspi_use(dev->qspi);
prv_write_cmd_no_addr(dev, dev->state->part->instructions.erase_resume);
// wait for the erase_suspend bit to be cleared
qspi_poll_bit(dev->qspi, dev->state->part->instructions.read_flag_status,
dev->state->part->flag_status_bit_masks.erase_suspend, false /* !set */,
QSPI_NO_TIMEOUT);
qspi_release(dev->qspi);
}
static void prv_get_fast_read_params(QSPIFlash *dev, uint8_t *instruction, uint8_t *dummy_cycles,
bool *is_ddr) {
if (dev->state->fast_read_ddr_enabled) {
*instruction = dev->state->part->instructions.fast_read_ddr;
*dummy_cycles = dev->state->part->dummy_cycles.fast_read_ddr;
*is_ddr = true;
} else {
*instruction = dev->state->part->instructions.fast_read;
*dummy_cycles = dev->state->part->dummy_cycles.fast_read;
*is_ddr = false;
}
}
static void prv_read_mmap_with_params(QSPIFlash *dev, uint32_t addr, void *buffer, uint32_t length,
uint8_t instruction, uint8_t dummy_cycles, bool is_ddr) {
qspi_mmap_start(dev->qspi, instruction, addr, dummy_cycles, length, is_ddr);
// Point the buffer at the QSPI region
memcpy(buffer, (uint32_t *)(QSPI_MMAP_BASE_ADDRESS + addr), length);
// stop memory mapped mode
qspi_mmap_stop(dev->qspi);
}
static void prv_read_mmap(QSPIFlash *dev, uint32_t addr, void *buffer, uint32_t length) {
uint8_t instruction;
uint8_t dummy_cycles;
bool is_ddr;
prv_get_fast_read_params(dev, &instruction, &dummy_cycles, &is_ddr);
prv_read_mmap_with_params(dev, addr, buffer, length, instruction,
dummy_cycles, is_ddr);
}
void qspi_flash_read_blocking(QSPIFlash *dev, uint32_t addr, void *buffer, uint32_t length) {
// TODO: Figure out what thresholds we should use when switching between memory mapping, DMA, &
// polling PBL-37438
bool should_use_dma = length > 128 && !dev->state->coredump_mode;
bool should_use_memmap = length > 128;
#if QSPI_DMA_DISABLE
// Known issues with some platforms, see PBL-37278 as an example
should_use_dma = false;
#endif
#if TARGET_QEMU
// QEMU doesn't yet support DMA or memory-mapping
should_use_dma = should_use_memmap = false;
#endif
qspi_use(dev->qspi);
uint8_t instruction;
uint8_t dummy_cycles;
bool is_ddr;
prv_get_fast_read_params(dev, &instruction, &dummy_cycles, &is_ddr);
if (should_use_dma) {
qspi_indirect_read_dma(dev->qspi, instruction, addr, dummy_cycles, buffer, length, is_ddr);
} else if (should_use_memmap) {
prv_read_mmap_with_params(dev, addr, buffer, length, instruction, dummy_cycles, is_ddr);
} else {
qspi_indirect_read(dev->qspi, instruction, addr, dummy_cycles, buffer, length, is_ddr);
}
qspi_release(dev->qspi);
}
int qspi_flash_write_page_begin(QSPIFlash *dev, const void *buffer, uint32_t addr,
uint32_t length) {
const uint32_t offset_in_page = addr % PAGE_SIZE_BYTES;
const uint32_t bytes_in_page = MIN(PAGE_SIZE_BYTES - offset_in_page, length);
qspi_use(dev->qspi);
prv_write_enable(dev);
qspi_indirect_write(dev->qspi, dev->state->part->instructions.page_program, addr, buffer,
bytes_in_page);
qspi_poll_bit(dev->qspi, dev->state->part->instructions.read_status,
dev->state->part->status_bit_masks.busy, false /* !set */, QSPI_NO_TIMEOUT);
qspi_release(dev->qspi);
return bytes_in_page;
}
status_t qspi_flash_get_write_status(QSPIFlash *dev) {
qspi_use(dev->qspi);
uint8_t status_reg;
prv_read_register(dev, dev->state->part->instructions.read_status, &status_reg, 1);
qspi_release(dev->qspi);
return (status_reg & dev->state->part->status_bit_masks.busy) ? E_BUSY : S_SUCCESS;
}
void qspi_flash_set_lower_power_mode(QSPIFlash *dev, bool active) {
qspi_use(dev->qspi);
uint8_t instruction;
uint32_t delay;
if (active) {
instruction = dev->state->part->instructions.enter_low_power;
delay = dev->state->part->standby_to_low_power_latency_us;
} else {
instruction = dev->state->part->instructions.exit_low_power;
delay = dev->state->part->low_power_to_standby_latency_us;
}
prv_write_cmd_no_addr(dev, instruction);
qspi_release(dev->qspi);
if (delay) {
delay_us(delay);
}
}
#if TARGET_QEMU
// While this works with normal hardware, it has a large stack requirment and I can't
// see a compelling reason to use it over the mmap blank check variant
static bool prv_blank_check_poll(QSPIFlash *dev, uint32_t addr, bool is_subsector) {
const uint32_t size_bytes = is_subsector ? SUBSECTOR_SIZE_BYTES : SECTOR_SIZE_BYTES;
const uint32_t BUF_SIZE_BYTES = 128;
const uint32_t BUF_SIZE_WORDS = BUF_SIZE_BYTES / sizeof(uint32_t);
uint32_t buffer[BUF_SIZE_WORDS];
for (uint32_t offset = 0; offset < size_bytes; offset += BUF_SIZE_BYTES) {
flash_impl_read_sync(buffer, addr + offset, BUF_SIZE_BYTES);
for (uint32_t i = 0; i < BUF_SIZE_WORDS; ++i) {
if (buffer[i] != FLASH_RESET_WORD_VALUE) {
return false;
}
}
}
return true;
}
#endif
static bool prv_blank_check_mmap(QSPIFlash *dev, uint32_t addr, bool is_subsector) {
const uint32_t size_bytes = is_subsector ? SUBSECTOR_SIZE_BYTES : SECTOR_SIZE_BYTES;
bool result = true;
uint8_t instruction;
uint8_t dummy_cycles;
bool is_ddr;
prv_get_fast_read_params(dev, &instruction, &dummy_cycles, &is_ddr);
qspi_mmap_start(dev->qspi, instruction, addr, dummy_cycles, size_bytes, is_ddr);
// Point the buffer at the QSPI region
uint32_t const volatile * const buffer = (uint32_t *)(QSPI_MMAP_BASE_ADDRESS + addr);
uint32_t size_words = size_bytes / sizeof(uint32_t);
for (uint32_t i = 0; i < size_words; ++i) {
if (buffer[i] != FLASH_RESET_WORD_VALUE) {
result = false;
break;
}
}
// stop memory mapped mode
qspi_mmap_stop(QSPI);
return result;
}
status_t qspi_flash_blank_check(QSPIFlash *dev, uint32_t addr, bool is_subsector) {
qspi_use(dev->qspi);
#if TARGET_QEMU
// QEMU doesn't support memory-mapping the FLASH
const bool result = prv_blank_check_poll(dev, addr, is_subsector);
#else
const bool result = prv_blank_check_mmap(dev, addr, is_subsector);
#endif
qspi_release(dev->qspi);
return result ? S_TRUE : S_FALSE;
}
void qspi_flash_ll_set_register_bits(QSPIFlash *dev, uint8_t read_instruction,
uint8_t write_instruction, uint8_t value, uint8_t mask) {
// make sure we're not trying to set any bits not within the mask
PBL_ASSERTN((value & mask) == value);
qspi_use(dev->qspi);
// first read the register
uint8_t reg_value;
prv_read_register(dev, read_instruction, &reg_value, 1);
// set the desired bits
reg_value = (reg_value & ~mask) | value;
// enable writing and write the register value
prv_write_cmd_no_addr(dev, dev->state->part->instructions.write_enable);
qspi_indirect_write_no_addr(dev->qspi, write_instruction, &reg_value, 1);
qspi_release(dev->qspi);
}
static bool prv_protection_is_enabled(QSPIFlash *dev) {
uint8_t status;
prv_read_register(dev, dev->state->part->instructions.read_protection_status, &status, 1);
return (status & dev->state->part->block_lock.protection_enabled_mask);
}
status_t qspi_flash_write_protection_enable(QSPIFlash *dev) {
#if TARGET_QEMU
return S_NO_ACTION_REQUIRED;
#endif
qspi_use(dev->qspi);
prv_write_enable(dev);
const bool already_enabled = prv_protection_is_enabled(dev);
if (already_enabled == false) {
PBL_LOG(LOG_LEVEL_INFO, "Enabling flash protection");
// Enable write protection
prv_write_cmd_no_addr(dev, dev->state->part->instructions.write_protection_enable);
// Poll busy status until done
qspi_poll_bit(dev->qspi, dev->state->part->instructions.read_status,
dev->state->part->status_bit_masks.busy, false /* !set */, QSPI_NO_TIMEOUT);
}
qspi_release(dev->qspi);
return (already_enabled) ? S_NO_ACTION_REQUIRED : S_SUCCESS;
}
status_t qspi_flash_lock_sector(QSPIFlash *dev, uint32_t addr) {
#if TARGET_QEMU
return S_SUCCESS;
#endif
qspi_use(dev->qspi);
prv_write_enable(dev);
// Lock or unlock the sector
const uint8_t instruction = dev->state->part->instructions.block_lock;
if (dev->state->part->block_lock.has_lock_data) {
qspi_indirect_write(dev->qspi, instruction, addr, &dev->state->part->block_lock.lock_data, 1);
} else {
qspi_indirect_write(dev->qspi, instruction, addr, NULL, 0);
}
// Poll busy status until done
qspi_poll_bit(dev->qspi, dev->state->part->instructions.read_status,
dev->state->part->status_bit_masks.busy, false /* !set */, QSPI_NO_TIMEOUT);
// Read lock status
uint8_t status;
qspi_indirect_read(dev->qspi, dev->state->part->instructions.block_lock_status,
addr, 0, &status, sizeof(status), false);
qspi_release(dev->qspi);
return (status == dev->state->part->block_lock.locked_check) ? S_SUCCESS : E_ERROR;
}
status_t qspi_flash_unlock_all(QSPIFlash *dev) {
#if TARGET_QEMU
return S_SUCCESS;
#endif
qspi_use(dev->qspi);
prv_write_enable(dev);
prv_write_cmd_no_addr(dev, dev->state->part->instructions.block_unlock_all);
qspi_release(dev->qspi);
return S_SUCCESS;
}
#if !RELEASE
#include "console/prompt.h"
#include "drivers/flash.h"
#include "kernel/pbl_malloc.h"
#include "system/profiler.h"
#include "util/size.h"
static bool prv_flash_read_verify(QSPIFlash *dev, int size, int offset) {
bool success = true;
char *buffer_dma_ptr = kernel_malloc_check(size + offset + 3);
char *buffer_pol = kernel_malloc_check(size + 3);
char *buffer_mmap = kernel_malloc_check(size + 3);
char *buffer_dma = buffer_dma_ptr + offset;
// The buffers need to be different, so when compared against each other we can make
// sure the write functions wrote the same thing.
memset(buffer_dma, 0xA5, size);
memset(buffer_pol, 0xCC, size);
memset(buffer_mmap, 0x33, size);
profiler_start();
prv_read_mmap(dev, 0, buffer_mmap, size);
profiler_stop();
uint32_t mmap_time = profiler_get_total_duration(true);
profiler_start();
uint8_t instruction;
uint8_t dummy_cycles;
bool is_ddr;
prv_get_fast_read_params(dev, &instruction, &dummy_cycles, &is_ddr);
qspi_indirect_read_dma(dev->qspi, instruction, 0, dummy_cycles, buffer_dma, size, is_ddr);
profiler_stop();
uint32_t dma_time = profiler_get_total_duration(true);
profiler_start();
qspi_indirect_read(dev->qspi, instruction, 0, dummy_cycles, buffer_pol, size, is_ddr);
profiler_stop();
uint32_t pol_time = profiler_get_total_duration(true);
if (memcmp(buffer_dma, buffer_pol, size) != 0) {
prompt_send_response("FAILURE: buffer_dma != buffer_pol");
success = false;
}
if (memcmp(buffer_dma, buffer_mmap, size) != 0) {
prompt_send_response("FAILURE: buffer_dma != buffer_mmap");
success = false;
}
const int buf_size = 64;
char buf[buf_size];
prompt_send_response_fmt(buf, buf_size, "Size: %d DMA: %"PRIu32 " POL: %"PRIu32 " MMP: %"PRIu32,
size, dma_time, pol_time, mmap_time);
kernel_free(buffer_dma_ptr);
kernel_free(buffer_pol);
kernel_free(buffer_mmap);
return success;
}
struct FlashReadTestValues {
int size;
int offset;
};
const struct FlashReadTestValues FLASH_READ_TEST_TABLE[] = {
{ .size = 1024, .offset = 0 },
{ .size = 1025, .offset = 0 },
{ .size = 1026, .offset = 0 },
{ .size = 1027, .offset = 0 },
{ .size = 1024, .offset = 1 },
{ .size = 1025, .offset = 2 },
{ .size = 1026, .offset = 3 },
{ .size = 4, .offset = 0 },
{ .size = 20, .offset = 0 },
{ .size = 60, .offset = 0 },
{ .size = 127, .offset = 0 },
{ .size = 128, .offset = 0 },
};
void command_flash_apicheck(const char *len_str) {
QSPIFlash *dev = QSPI_FLASH;
const int buf_size = 64;
char buf[buf_size];
int failures = 0;
int passes = 0;
profiler_init();
prompt_send_response("Check whoami");
if (!qspi_flash_check_whoami(dev)) {
++failures;
prompt_send_response("ERROR: Who am I failed");
} else {
++passes;
}
prompt_send_response("Enter low power mode");
flash_impl_enter_low_power_mode();
// WHOAMI should fail in low-power mode
prompt_send_response("Check whoami, should fail in low power mode");
if (qspi_flash_check_whoami(dev)) {
++failures;
prompt_send_response("ERROR: Who am I failed");
} else {
++passes;
}
prompt_send_response("Exit low power mode");
flash_impl_exit_low_power_mode();
prompt_send_response("Start flash_read_verify test");
qspi_use(QSPI);
const int final_size = atoi(len_str);
// If size is 0 run through a pre-defined table
if (final_size == 0) {
for (unsigned int i = 0; i < ARRAY_LENGTH(FLASH_READ_TEST_TABLE); ++i) {
bool result = prv_flash_read_verify(dev, FLASH_READ_TEST_TABLE[i].size,
FLASH_READ_TEST_TABLE[i].offset);
if (!result) {
++failures;
} else {
++passes;
}
}
} else {
if (prv_flash_read_verify(dev, final_size, 3)) {
++passes;
} else {
++failures;
prompt_send_response("ERROR: flash_read_verify failed");
}
}
qspi_release(QSPI);
bool was_busy = false;
// write a few bytes to the sector we're going to erase so it's not empty
uint8_t dummy_data = 0x55;
flash_write_bytes(&dummy_data, FLASH_REGION_FIRMWARE_SCRATCH_BEGIN, sizeof(dummy_data));
profiler_start();
status_t result = flash_impl_erase_sector_begin(FLASH_REGION_FIRMWARE_SCRATCH_BEGIN);
flash_impl_get_erase_status();
if (result == S_SUCCESS) {
while (flash_impl_get_erase_status() == E_BUSY) {
was_busy = true;
}
}
profiler_stop();
uint32_t duration = profiler_get_total_duration(true);
prompt_send_response_fmt(buf, buf_size, "Erase took: %"PRIu32, duration);
// Fash erases take at least ~100ms, if we're too short we probably didn't erase
const uint32_t min_erase_time = 10000;
if (result != S_SUCCESS) {
++failures;
prompt_send_response_fmt(buf, buf_size,
"FAILURE: erase did not report success %"PRIi32, result);
} else if (was_busy == false) {
++failures;
prompt_send_response("FAILURE: Flash never became busy, but we should be busy for 300ms.");
prompt_send_response("FAILURE: Flash probably never did an erase.");
} else if (duration < min_erase_time) {
++failures;
prompt_send_response("FAILURE: Flash erase completed way to quickly to have succeeded.");
} else {
++passes;
}
// must call blank_check_poll by hand, otherwise we'll get the dma version
profiler_start();
qspi_use(QSPI);
bool is_blank = qspi_flash_blank_check(QSPI_FLASH, FLASH_REGION_FIRMWARE_SCRATCH_BEGIN,
SUBSECTOR_SIZE_BYTES);
qspi_release(QSPI);
profiler_stop();
uint32_t blank = profiler_get_total_duration(true);
prompt_send_response_fmt(buf, buf_size, "Sector blank check via read took: %"PRIu32, blank);
if (is_blank != S_TRUE) {
++failures;
prompt_send_response("FAILURE: sector not blank!?!");
} else {
++passes;
}
profiler_start();
is_blank = flash_impl_blank_check_subsector(FLASH_REGION_FIRMWARE_SCRATCH_BEGIN);
profiler_stop();
blank = profiler_get_total_duration(true);
prompt_send_response_fmt(buf, buf_size, "Subsector blank check via read took: %"PRIu32,
blank);
if (is_blank != S_TRUE) {
++failures;
prompt_send_response("FAILURE: sector not blank!?!");
} else {
++passes;
}
if (failures == 0) {
prompt_send_response_fmt(buf, buf_size, "SUCCESS: run %d tests and all passeed", passes);
}
else {
prompt_send_response_fmt(buf, buf_size, "FAILED: run %d tests and %d failed",
passes + failures,
failures);
}
}
#endif
#if RECOVERY_FW
#include "console/prompt.h"
#include "drivers/flash.h"
#define SIGNAL_TEST_MAGIC_PATTERN (0xA5)
#define TEST_BUFFER_SIZE (1024)
static uint8_t s_test_buffer[TEST_BUFFER_SIZE];
static const uint32_t s_test_addr = FLASH_REGION_FIRMWARE_SCRATCH_END - SECTOR_SIZE_BYTES;
static bool s_signal_test_initialized;
void command_flash_signal_test_init(void) {
// just test one sector, which is probably less than the size of the region
// erase the sector
flash_erase_sector_blocking(s_test_addr);
// set the contents of the sector such that we will end up reading alternating 1s and 0s
memset(s_test_buffer, SIGNAL_TEST_MAGIC_PATTERN, sizeof(s_test_buffer));
flash_write_bytes(s_test_buffer, s_test_addr, sizeof(s_test_buffer));
QSPIFlash *dev = QSPI_FLASH;
// Ensure DDR is disabled for write check
prv_set_fast_read_ddr_enabled(dev, false);
uint8_t instruction;
uint8_t dummy_cycles;
bool is_ddr;
prv_get_fast_read_params(dev, &instruction, &dummy_cycles, &is_ddr);
PBL_ASSERTN(!is_ddr);
qspi_use(QSPI);
qspi_indirect_read(dev->qspi, instruction, s_test_addr, dummy_cycles, s_test_buffer,
sizeof(s_test_buffer), is_ddr);
prv_set_fast_read_ddr_enabled(dev, dev->default_fast_read_ddr_enabled);
qspi_release(QSPI);
bool success = true;
for (uint32_t i = 0; i < sizeof(s_test_buffer); ++i) {
if (s_test_buffer[i] != SIGNAL_TEST_MAGIC_PATTERN) {
success = false;
break;
}
}
if (success) {
prompt_send_response("Done!");
s_signal_test_initialized = true;
} else {
prompt_send_response("ERROR: Data read (SDR mode) did not match data written!");
}
}
void command_flash_signal_test_run(void) {
if (!s_signal_test_initialized) {
prompt_send_response("ERROR: 'flash signal test init' must be run first!");
return;
}
QSPIFlash *dev = QSPI_FLASH;
qspi_use(QSPI);
// set to DDR
prv_set_fast_read_ddr_enabled(dev, true);
// issue the read
uint8_t instruction;
uint8_t dummy_cycles;
bool is_ddr;
prv_get_fast_read_params(dev, &instruction, &dummy_cycles, &is_ddr);
PBL_ASSERTN(is_ddr);
qspi_indirect_read(dev->qspi, instruction, s_test_addr, dummy_cycles, s_test_buffer,
sizeof(s_test_buffer), is_ddr);
bool success = true;
for (uint32_t i = 0; i < sizeof(s_test_buffer); ++i) {
if (s_test_buffer[i] != SIGNAL_TEST_MAGIC_PATTERN) {
success = false;
break;
}
}
// set back to default mode
prv_set_fast_read_ddr_enabled(dev, dev->default_fast_read_ddr_enabled);
qspi_release(QSPI);
if (success) {
prompt_send_response("Ok");
} else {
prompt_send_response("ERROR: Read value didn't match!");
}
}
#endif

View file

@ -0,0 +1,85 @@
/*
* 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>
#include "system/status_codes.h"
typedef const struct QSPIFlash QSPIFlash;
typedef const struct QSPIFlashPart QSPIFlashPart;
//! Initialize the QSPI flash
//! @param coredump_mode If true, don't use anything that might not be available mid-crash, such
//! as FreeRTOS calls or other system services.
void qspi_flash_init(QSPIFlash *dev, QSPIFlashPart *part, bool coredump_mode);
bool qspi_flash_is_in_coredump_mode(QSPIFlash *dev);
//! Check if the WHOAMI matches the expected value
bool qspi_flash_check_whoami(QSPIFlash *dev);
//! Check if an in-progress erase is complete
status_t qspi_flash_is_erase_complete(QSPIFlash *dev);
//! Begin an erase
status_t qspi_flash_erase_begin(QSPIFlash *dev, uint32_t addr, bool is_subsector);
//! Suspend an erase
status_t qspi_flash_erase_suspend(QSPIFlash *dev, uint32_t addr);
//! Resume a suspended erase
void qspi_flash_erase_resume(QSPIFlash *dev, uint32_t addr);
//! Performs a blocking read
void qspi_flash_read_blocking(QSPIFlash *dev, uint32_t addr, void *buffer, uint32_t length);
// Begins a write operation
int qspi_flash_write_page_begin(QSPIFlash *dev, const void *buffer, uint32_t addr, uint32_t length);
//! Gets the status of an in-progress write operation
status_t qspi_flash_get_write_status(QSPIFlash *dev);
//! Sets whether or not the QSPI flash is in low-power mode
void qspi_flash_set_lower_power_mode(QSPIFlash *dev, bool active);
//! Check whether a sector/subsector is blank
status_t qspi_flash_blank_check(QSPIFlash *dev, uint32_t addr, bool is_subsector);
//! Sets the values of the bits (masked by `mask`) in the register (read by `read_instruction` and
//! written via `write_instruction`) to `value`
void qspi_flash_ll_set_register_bits(QSPIFlash *dev, uint8_t read_instruction,
uint8_t write_instruction, uint8_t value, uint8_t mask);
//! Enable write/erase protection on the given QSPI flash part.
//! Requires the `write_protection_enable` and `read_protection_status` instructions.
//! Return value of the `read_protection_status` instruction is checked against
//! `block_lock.protection_enabled_mask` to test for success.
status_t qspi_flash_write_protection_enable(QSPIFlash *dev);
//! Lock the given sector from write/erase operations.
//! Sector locked with the `block_lock` instruction, and confirmed with `block_lock_status`
//! If the `block_lock` instruction requires extra data, `block_lock.has_lock_data`
//! and `block_lock.lock_data` can be used.
//! When checking `block_lock_status`, the returned status value is
//! compared against `block_lock.locked_check`
status_t qspi_flash_lock_sector(QSPIFlash *dev, uint32_t addr);
//! Unlock all sectors so they can be written/erased.
//! Operation is performed by the `block_unlock_all` instruction.
status_t qspi_flash_unlock_all(QSPIFlash *dev);

View file

@ -0,0 +1,37 @@
/*
* 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 "qspi_flash_part_definitions.h"
#include "board/board.h"
#include "drivers/qspi.h"
#include <stdint.h>
typedef struct QSPIFlashState {
QSPIFlashPart *part;
bool coredump_mode;
bool fast_read_ddr_enabled;
} QSPIFlashState;
typedef const struct QSPIFlash {
QSPIFlashState *state;
QSPIPort *qspi;
bool default_fast_read_ddr_enabled;
OutputConfig reset_gpio;
} QSPIFlash;

View file

@ -0,0 +1,73 @@
/*
* 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>
typedef const struct QSPIFlashPart {
struct {
uint8_t fast_read;
uint8_t fast_read_ddr;
uint8_t page_program;
uint8_t erase_sector_4k;
uint8_t erase_block_64k;
uint8_t write_enable;
uint8_t write_disable;
uint8_t read_status;
uint8_t read_flag_status;
uint8_t erase_suspend;
uint8_t erase_resume;
uint8_t enter_low_power;
uint8_t exit_low_power;
uint8_t enter_quad_mode;
uint8_t exit_quad_mode;
uint8_t reset_enable;
uint8_t reset;
uint8_t qspi_id;
uint8_t block_lock;
uint8_t block_lock_status;
uint8_t block_unlock_all;
uint8_t write_protection_enable;
uint8_t read_protection_status;
} instructions;
struct {
uint8_t busy;
uint8_t write_enable;
} status_bit_masks;
struct {
uint8_t erase_suspend;
} flag_status_bit_masks;
struct {
uint8_t fast_read;
uint8_t fast_read_ddr;
} dummy_cycles;
struct {
bool has_lock_data; //<! true ifdata needs to be send along with the block_lock instruction
uint8_t lock_data; //<! The data to be sent on a block_lock command, if has_lock_data is true
uint8_t locked_check; //<! Value block_lock_status instruction should return if sector is locked
uint8_t protection_enabled_mask; //<! Mask read_protection_status instr to check if enabled
} block_lock;
uint32_t reset_latency_ms;
uint32_t suspend_to_read_latency_us;
uint32_t standby_to_low_power_latency_us;
uint32_t low_power_to_standby_latency_us;
bool supports_fast_read_ddr;
uint32_t qspi_id_value;
const char *name;
} QSPIFlashPart;

View file

@ -0,0 +1,936 @@
/*
* 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/flash_impl.h"
#include <stdbool.h>
#include <stdint.h>
#include "drivers/gpio.h"
#include "drivers/periph_config.h"
#include "flash_region/flash_region.h"
#include "system/passert.h"
#include "system/logging.h"
#include "kernel/util/delay.h"
#include "util/math.h"
#include "util/size.h"
#include "util/units.h"
#define STM32F4_COMPATIBLE
#include <mcu.h>
//! This is the memory mapped region that's mapped to the parallel flash.
static const uintptr_t FMC_BANK_1_BASE_ADDRESS = 0x60000000;
//! This is the unit that we use for writing
static const uint32_t PAGE_SIZE_BYTES = 64;
//! Different commands we can send to the flash
typedef enum S29VSCommand {
S29VSCommand_WriteBufferLoad = 0x25,
S29VSCommand_BufferToFlash = 0x29,
S29VSCommand_EraseResume = 0x30,
S29VSCommand_SectorBlank = 0x33,
S29VSCommand_SectorLock = 0x60,
S29VSCommand_SectorLockRangeArg = 0x61,
S29VSCommand_ReadStatusRegister = 0x70,
S29VSCommand_ClearStatusRegister = 0x71,
S29VSCommand_EraseSetup = 0x80,
S29VSCommand_DeviceIDEntry = 0x90,
S29VSCommand_EraseSuspend = 0xB0,
S29VSCommand_ConfigureRegisterEntry = 0xD0,
S29VSCommand_SoftwareReset = 0xF0
} S29VSCommand;
//! Arguments to the S29VSCommand_EraseSetup command
typedef enum S29VSCommandEraseAguments {
S29VSCommandEraseAguments_ChipErase = 0x10,
S29VSCommandEraseAguments_SectorErase = 0x30
} S29VSCommandEraseAguments;
//! The bitset stored in the status register, see prv_read_status_register
typedef enum S29VSStatusBit {
S29VSStatusBit_BankStatus = (1 << 0),
S29VSStatusBit_SectorLockStatus = (1 << 1),
S29VSStatusBit_ProgramSuspended = (1 << 2),
// Bit 3 is reserved
S29VSStatusBit_ProgramStatus = (1 << 4),
S29VSStatusBit_EraseStatus = (1 << 5),
S29VSStatusBit_EraseSuspended = (1 << 6),
S29VSStatusBit_DeviceReady = (1 << 7),
} S29VSStatusBit;
static const uint16_t SPANSION_MANUFACTURER_ID = 0x01;
static const uint16_t MACRONIX_MANUFACTURER_ID = 0xc2;
static const GPIO_InitTypeDef s_default_at_flash_cfg = {
.GPIO_Mode = GPIO_Mode_AF,
.GPIO_Speed = GPIO_Speed_100MHz,
.GPIO_OType = GPIO_OType_PP,
.GPIO_PuPd = GPIO_PuPd_NOPULL
};
static void prv_issue_command_argument(FlashAddress sector_address, uint16_t cmd_arg);
static void prv_issue_command(FlashAddress sector_address, S29VSCommand cmd);
// puts gpios into or out of analog to save power when idle/in use respectively
static void prv_flash_idle_gpios(bool enable_gpios) {
static bool gpios_idled = false;
if (gpios_idled == enable_gpios) {
return;
}
gpios_idled = enable_gpios;
gpio_use(GPIOB);
gpio_use(GPIOD);
gpio_use(GPIOE);
GPIO_InitTypeDef gpio_init;
if (enable_gpios) {
gpio_init = s_default_at_flash_cfg;
} else {
gpio_init = (GPIO_InitTypeDef) {
.GPIO_Mode = GPIO_Mode_AN,
.GPIO_Speed = GPIO_Speed_2MHz,
.GPIO_PuPd = GPIO_PuPd_NOPULL
};
}
// leave RESET_N and CE: they need to retain their state
// Configure the rest as analog inputs to save as much power as possible
// D2 - Reset - GPIO Reset line
// D7 - FMC CE - FMC Chip Enable
gpio_init.GPIO_Pin = GPIO_Pin_7;
GPIO_Init(GPIOB, &gpio_init);
gpio_init.GPIO_Pin = GPIO_Pin_All & (~GPIO_Pin_2) & (~GPIO_Pin_7);
GPIO_Init(GPIOD, &gpio_init);
gpio_init.GPIO_Pin = GPIO_Pin_All & (~GPIO_Pin_0) & (~GPIO_Pin_1);
GPIO_Init(GPIOE, &gpio_init);
gpio_release(GPIOE);
gpio_release(GPIOD);
gpio_release(GPIOB);
}
static uint32_t s_num_flash_uses = 0;
void flash_impl_use(void) {
if (s_num_flash_uses == 0) {
periph_config_enable(FMC_Bank1, RCC_AHB3Periph_FMC); // FIXME
prv_flash_idle_gpios(true);
}
s_num_flash_uses++;
}
void flash_impl_release_many(uint32_t num_locks) {
PBL_ASSERTN(s_num_flash_uses >= num_locks);
s_num_flash_uses -= num_locks;
if (s_num_flash_uses == 0) {
periph_config_disable(FMC_Bank1, RCC_AHB3Periph_FMC); // FIXME
}
}
void flash_impl_release(void) {
flash_impl_release_many(1);
}
static uint16_t flash_s29vs_read_short(FlashAddress addr) {
return *((__IO uint16_t*)(FMC_BANK_1_BASE_ADDRESS + addr));
}
FlashAddress flash_impl_get_sector_base_address(FlashAddress addr) {
if (addr < BOTTOM_BOOT_REGION_END) {
return addr & ~(BOTTOM_BOOT_SECTOR_SIZE - 1);
}
return addr & ~(SECTOR_SIZE_BYTES - 1);
}
FlashAddress flash_impl_get_subsector_base_address(FlashAddress addr) {
return flash_impl_get_sector_base_address(addr);
}
static uint8_t prv_read_status_register(FlashAddress sector_base_addr) {
prv_issue_command(sector_base_addr, S29VSCommand_ReadStatusRegister);
return flash_s29vs_read_short(sector_base_addr);
}
static uint8_t prv_poll_for_ready(FlashAddress sector_base_addr) {
// TODO: We should probably just assert if this takes too long
uint8_t status;
while (((status = prv_read_status_register(sector_base_addr)) &
S29VSStatusBit_DeviceReady) == 0) {
delay_us(10);
}
return (status);
}
//! Issue the second part of a two-cycle command. This is not merged with the
//! prv_issue_command as not all commands have an argument.
//!
//! @param sector_address The address of the start of the sector to write the command to.
//! @param cmd_arg The command argument to write.
static void prv_issue_command_argument(FlashAddress sector_address, uint16_t cmd_arg) {
// The offset in the sector we write the second part of commands to. Note that this is a 16-bit
// word aligned address as opposed to a byte address.
static const uint32_t COMMAND_ARGUMENT_ADDRESS = 0x2AA;
((__IO uint16_t*) (FMC_BANK_1_BASE_ADDRESS + sector_address))[COMMAND_ARGUMENT_ADDRESS] = cmd_arg;
}
//! @param sector_address The address of the start of the sector to write the command to.
//! @param cmd The command to write.
static void prv_issue_command(FlashAddress sector_address, S29VSCommand cmd) {
// The offset in the sector we write the first part of commands to. Note that this is a 16-bit
// word aligned address as opposed to a byte address.
static const uint32_t COMMAND_ADDRESS = 0x555;
((__IO uint16_t*) (FMC_BANK_1_BASE_ADDRESS + sector_address))[COMMAND_ADDRESS] = cmd;
}
static void prv_software_reset(void) {
prv_issue_command(0, S29VSCommand_SoftwareReset);
}
// Note: If this command has been executed at least once, all sectors are
// locked. They then must be unlocked before and relocked after each program
// operation (i.e write or erase). The chip only allows for one sector to be
// unlocked at any given time. For sector ranges which have been protected using
// the "Sector Lock Range Command", this function will have no effect.
static void prv_allow_write_if_sector_is_not_protected(bool lock, uint32_t sector_addr) {
prv_issue_command(0, S29VSCommand_SectorLock);
prv_issue_command_argument(0, S29VSCommand_SectorLock);
int lock_flag = (lock ? 0 : 1) << 7; // set A6 to 0 to lock and 1 to unlock
((__IO uint16_t*) (FMC_BANK_1_BASE_ADDRESS + sector_addr + lock_flag))[0] =
S29VSCommand_SectorLock;
}
static uint16_t prv_read_manufacturer_id(void) {
// Issue the DeviceIDEntry command to change to the ID-CFI Address Map. This means that reading from the bank will
// give us ID-CFI information instead of the normal flash contents. See Table 11.2 (ID/CFI Data) for all the
// content you can read here. Reset the state afterwards to return to the default address map.
flash_impl_use();
prv_issue_command(0, S29VSCommand_DeviceIDEntry);
uint16_t result = flash_s29vs_read_short(0x0);
prv_software_reset();
flash_impl_release();
return result;
}
static uint16_t prv_read_configuration_register(void) {
prv_issue_command(0, S29VSCommand_ConfigureRegisterEntry);
uint16_t result = flash_s29vs_read_short(0x0);
prv_software_reset();
return result;
}
static void prv_write_configuration_register(uint16_t data) {
// See section 5.8.1 of data sheet for command sequence
prv_issue_command(0, S29VSCommand_ConfigureRegisterEntry);
// Cycle 1: SA+Address 555h & Data 25h
// Cycle 2: SA+Address 2AAh & Data 00h
// Cycle 3: SA+Address X00h & PD
// Cycle 4: SA+ Address 555h & Data 29h
prv_issue_command(0, S29VSCommand_WriteBufferLoad);
prv_issue_command_argument(0, 0);
((__IO uint16_t*) (FMC_BANK_1_BASE_ADDRESS))[0] = data;
prv_issue_command(0, S29VSCommand_BufferToFlash);
prv_software_reset();
}
// Use the "Sector Lock Range Command" (section 8.2 of data sheet) to block
// writes or erases to the PRF image residing on the flash. The only way to undo
// this is to issue a HW reset or pull power
static void prv_flash_protect_range(uint32_t start_sector, uint32_t end_sector) {
PBL_ASSERTN(start_sector <= end_sector);
flash_impl_use();
prv_issue_command(0, S29VSCommand_SectorLock);
prv_issue_command_argument(0, S29VSCommand_SectorLock);
start_sector = flash_impl_get_sector_base_address(start_sector);
end_sector = flash_impl_get_sector_base_address(end_sector);
((__IO uint16_t*) (FMC_BANK_1_BASE_ADDRESS + start_sector))[0] =
S29VSCommand_SectorLockRangeArg;
((__IO uint16_t*) (FMC_BANK_1_BASE_ADDRESS + end_sector))[0] =
S29VSCommand_SectorLockRangeArg;
flash_impl_release();
}
void flash_s29vs_hw_init(void) {
// Configure the reset pin (D2)
GPIO_InitTypeDef gpio_init = {
.GPIO_Pin = GPIO_Pin_2,
.GPIO_Mode = GPIO_Mode_OUT,
.GPIO_Speed = GPIO_Speed_100MHz,
.GPIO_OType = GPIO_OType_PP,
.GPIO_PuPd = GPIO_PuPd_NOPULL
};
GPIO_Init(GPIOD, &gpio_init);
GPIO_WriteBit(GPIOD, GPIO_Pin_2, Bit_SET);
// Configure pins relating to the FMC peripheral (30 pins!)
// B7 - FMC AVD - FMC Address Valid aka Latch
// D0-D1, D8-D15, E2-15 - FMC A, AD - FMC Address and Address/Data lines
// D2 - Reset - GPIO Reset line
// D3 - FMC CLK
// D4 - FMC OE - FMC Output Enable
// D5 - FMC WE - FMC Write Enable
// D6 - FMC RDY - FMC Ready line
// D7 - FMC CE - FMC Chip Enable
GPIO_PinAFConfig(GPIOB, GPIO_PinSource7, GPIO_AF_FMC);
gpio_init = s_default_at_flash_cfg;
gpio_init.GPIO_Pin = GPIO_Pin_7;
GPIO_Init(GPIOB, &gpio_init);
for (uint8_t pin_source = 0; pin_source < 16; ++pin_source) {
if (pin_source == 2) {
continue;
}
GPIO_PinAFConfig(GPIOD, pin_source, GPIO_AF_FMC);
}
gpio_init.GPIO_Pin = GPIO_Pin_All & (~GPIO_Pin_2);
GPIO_Init(GPIOD, &gpio_init);
for (uint8_t pin_source = 2; pin_source < 16; ++pin_source) {
GPIO_PinAFConfig(GPIOE, pin_source, GPIO_AF_FMC);
}
gpio_init.GPIO_Pin = GPIO_Pin_All & (~GPIO_Pin_0) & (~GPIO_Pin_1);
GPIO_Init(GPIOE, &gpio_init);
// We have configured the pins, lets perform a full HW reset to put the chip
// in a good state
GPIO_WriteBit(GPIOD, GPIO_Pin_2, Bit_RESET);
delay_us(10); // only needs to be 50ns according to data sheet
GPIO_WriteBit(GPIOD, GPIO_Pin_2, Bit_SET);
delay_us(30); // need 200ns + 10us before CE can be pulled low
flash_impl_set_burst_mode(false);
}
static void prv_flash_reset(void) {
s_num_flash_uses = 0;
gpio_use(GPIOB);
gpio_use(GPIOD);
gpio_use(GPIOE);
flash_impl_use();
flash_s29vs_hw_init();
flash_impl_release();
gpio_release(GPIOE);
gpio_release(GPIOD);
gpio_release(GPIOB);
}
void flash_impl_enable_write_protection(void) {
}
// Protects start_sector - end_sector, inclusive, from any kind of program
// operation
status_t flash_impl_write_protect(FlashAddress start_sector,
FlashAddress end_sector) {
prv_flash_reset();
prv_flash_protect_range(start_sector, end_sector);
return S_SUCCESS;
}
status_t flash_impl_unprotect(void) {
// The only way to undo sector protection is to pull power from the chip or
// issue a hardware reset
prv_flash_reset();
return S_SUCCESS;
}
status_t flash_impl_init(bool coredump_mode) {
// Don't need to do anything to enable coredump mode.
prv_flash_reset();
return S_SUCCESS;
}
status_t flash_impl_get_erase_status(void) {
flash_impl_use();
uint8_t status = prv_read_status_register(0);
flash_impl_release();
if ((status & S29VSStatusBit_DeviceReady) == 0) return E_BUSY;
if ((status & S29VSStatusBit_EraseSuspended) != 0) return E_AGAIN;
if ((status & S29VSStatusBit_EraseStatus) != 0) return E_ERROR;
return S_SUCCESS;
}
status_t flash_impl_erase_subsector_begin(FlashAddress subsector_addr) {
return flash_impl_erase_sector_begin(subsector_addr);
}
status_t flash_impl_erase_sector_begin(FlashAddress sector_addr) {
status_t result = E_UNKNOWN;
// 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.
sector_addr = flash_impl_get_sector_base_address(sector_addr);
flash_impl_use();
prv_issue_command(sector_addr, S29VSCommand_ClearStatusRegister);
// Some sanity checks
{
status_t error = S_SUCCESS;
const uint8_t sr = prv_read_status_register(sector_addr);
if ((sr & S29VSStatusBit_DeviceReady) == 0) {
// Another operation is already in progress.
error = E_BUSY;
} else if (sr & S29VSStatusBit_EraseSuspended) {
// Cannot program while another program operation is suspended.
error = E_INVALID_OPERATION;
}
if (FAILED(error)) {
result = error;
goto done;
}
}
prv_allow_write_if_sector_is_not_protected(false, sector_addr);
prv_issue_command(sector_addr, S29VSCommand_EraseSetup);
prv_issue_command_argument(sector_addr,
S29VSCommandEraseAguments_SectorErase);
prv_allow_write_if_sector_is_not_protected(true, sector_addr);
// Check the status register to make sure that the erase has started.
const uint8_t sr = prv_read_status_register(sector_addr);
if ((sr & S29VSStatusBit_DeviceReady) == 0) {
// Program or erase operation in progress. Is it in the current bank?
result = ((sr & S29VSStatusBit_BankStatus) == 0)? S_SUCCESS : E_BUSY;
} else {
// Operation hasn't started. Something is wrong.
if (sr & S29VSStatusBit_SectorLockStatus) {
// Sector is write-protected.
result = E_INVALID_OPERATION;
} else if (sr & S29VSStatusBit_EraseStatus) {
// Erase failed for some reason.
result = E_ERROR;
} else {
// The erase has either completed in the time between starting the erase
// and polling the status register, or the erase was never started. The
// former case could be due to a context switch at the worst time and
// subsequent task starvation, or being run in QEMU. The latter could be
// due to a software bug or hardware failure. It would be possible to tell
// the two situations apart by performing a blank check, but that takes
// more time than a nonblocking erase should require. Let the upper layers
// verify that the erase succeeded if they care about it.
result = S_SUCCESS;
}
}
done:
flash_impl_release();
return result;
}
status_t flash_impl_erase_suspend(FlashAddress sector_addr) {
status_t status = E_INTERNAL;
sector_addr = flash_impl_get_sector_base_address(sector_addr);
flash_impl_use();
const uint8_t sr = prv_read_status_register(sector_addr);
// Is an operation in progress?
if ((sr & S29VSStatusBit_DeviceReady) != 0) {
// No erase in progress to suspend. Maybe the erase completed before this
// call.
status = S_NO_ACTION_REQUIRED;
} else if ((sr & S29VSStatusBit_BankStatus) != 0) {
// Operation is in a different bank than the given address.
status = E_INVALID_ARGUMENT;
} else {
// All clear.
prv_issue_command(sector_addr, S29VSCommand_EraseSuspend);
if (prv_poll_for_ready(sector_addr) & S29VSStatusBit_EraseSuspended) {
status = S_SUCCESS;
} else {
// The erase must have completed between the status register read and
// the EraseSuspend command.
status = S_NO_ACTION_REQUIRED;
}
}
flash_impl_release();
return status;
}
status_t flash_impl_erase_resume(FlashAddress sector_addr) {
status_t status = E_INTERNAL;
sector_addr = flash_impl_get_sector_base_address(sector_addr);
flash_impl_use();
uint8_t sr = prv_read_status_register(sector_addr);
if ((sr & S29VSStatusBit_DeviceReady) != 0 &&
(sr & S29VSStatusBit_EraseSuspended) != 0) {
prv_issue_command(sector_addr, S29VSCommand_EraseResume);
status = S_SUCCESS;
} else {
// Device busy or no suspended erase to resume.
status = E_INVALID_OPERATION;
}
flash_impl_release();
return status;
}
// It is dangerous to leave this built in by default.
#if 0
status_t flash_impl_erase_bulk_begin(void) {
flash_s29vs_use();
prv_issue_command(0, S29VSCommand_EraseSetup);
prv_issue_command_argument(0, S29VSCommandEraseAguments_ChipErase);
flash_s29vs_release();
}
#endif
static void prv_read_words_pio(uint16_t* buffer, uint16_t* flash_data_region,
uint32_t num_words) {
for (uint32_t words_read = 0; words_read < num_words; words_read++) {
buffer[words_read] = flash_data_region[words_read];
}
}
// Currently this implementation reads halfwords at a time (16-bits). Burst
// length is currently 1 for synchronous reads. This can be optimized in future
// to do larger burst sizes and/or unrolling larger transfer sizes into 32-bit
// reads.
status_t flash_impl_read_sync(void *buffer_ptr, FlashAddress start_addr,
size_t buffer_size) {
uint8_t *buffer = buffer_ptr;
flash_impl_use();
uint32_t flash_data_addr = (FMC_BANK_1_BASE_ADDRESS + start_addr);
bool odd_start_addr = ((start_addr % 2) == 1);
uint32_t bytes_read = 0;
uint16_t* buff_ptr = (uint16_t *)&buffer[bytes_read];
if (odd_start_addr) {
// read first byte into a temporary buffer but read from source on aligned word boundary
uint16_t temp_buffer = *(__IO uint16_t *)(flash_data_addr-1);
buffer[bytes_read++] = (uint8_t)((temp_buffer >> 8) & 0xFF);
}
// At this point, flash_data_addr is now halfword aligned
buff_ptr = (uint16_t *)&buffer[bytes_read];
bool odd_buff_addr = ((((uint32_t)buff_ptr) % 2) == 1);
if (buffer_size - bytes_read >= 2) {
// if at least one halfword to read
if (!odd_buff_addr) {
// Both flash_data_addr and buffer are aligned
uint32_t num_words = (buffer_size - bytes_read) / 2;
prv_read_words_pio(buff_ptr, (uint16_t*)(flash_data_addr + bytes_read), num_words);
bytes_read += num_words*2;
} else {
// Not aligned - read into temporary buffer and copy over
__IO uint16_t *flash_data_region = (__IO uint16_t*)(flash_data_addr + bytes_read);
uint32_t num_words = (buffer_size - bytes_read) / 2;
for (uint32_t words_read = 0; words_read < num_words; words_read++) {
uint16_t temp_buffer = flash_data_region[words_read];
buffer[bytes_read++] = (uint8_t)(temp_buffer & 0xFF);
buffer[bytes_read++] = (uint8_t)((temp_buffer >> 8) & 0xFF);
}
}
}
buff_ptr = (uint16_t *)&buffer[bytes_read];
// See if there are any remaining bytes left - at this point - flash_data_addr is still halfword aligned
if (buffer_size - bytes_read == 1) {
uint16_t temp_buffer = *(__IO uint16_t *)(flash_data_addr + bytes_read);
buffer[bytes_read++] = (uint8_t)(temp_buffer & 0xFF);
} else if (buffer_size - bytes_read != 0) {
// Should not reach here
PBL_LOG(LOG_LEVEL_DEBUG, "Invalid data length read");
}
flash_impl_release();
return S_SUCCESS;
}
int flash_impl_write_page_begin(const void *vp_buffer,
const FlashAddress start_addr, size_t len) {
if (!len) {
return E_INVALID_ARGUMENT;
}
const uint8_t *buffer = vp_buffer;
// Flash write transactions can only write one page at a time, where each
// page is 64 bytes in size. Split up our transactions into pages and then
// write one page.
const uint32_t offset_in_page = start_addr % PAGE_SIZE_BYTES;
const uint32_t bytes_in_page = MIN(PAGE_SIZE_BYTES - offset_in_page, len);
// We're only allowed to write whole 16-bit words during a write operation.
// Therefore we'll need to pad out our write if it's not perfectly aligned at
// the start or the end.
int num_shorts = bytes_in_page / 2;
// 4 cases
// Perfectly aligned - No additional writes
// Unaligned start, even length - Need to pad both ends
// Unaligned start, odd length - Pad the start
// Aligned start, odd length - Pad the end
if (start_addr & 0x1 || bytes_in_page & 0x1) {
++num_shorts;
}
const FlashAddress sector_addr =
flash_impl_get_sector_base_address(start_addr);
flash_impl_use();
prv_issue_command(sector_addr, S29VSCommand_ClearStatusRegister);
// Some sanity checks
{
status_t error = S_SUCCESS;
const uint8_t sr = prv_read_status_register(sector_addr);
if ((sr & S29VSStatusBit_DeviceReady) == 0) {
// Another operation is already in progress.
error = E_BUSY;
} else if (sr & S29VSStatusBit_ProgramSuspended) {
// Cannot program while another program operation is suspended.
error = E_INVALID_OPERATION;
}
if (FAILED(error)) {
flash_impl_release();
return error;
}
}
prv_allow_write_if_sector_is_not_protected(false, sector_addr);
prv_issue_command(sector_addr, S29VSCommand_WriteBufferLoad);
prv_issue_command_argument(sector_addr, num_shorts - 1);
// We're now ready to write the words. Subsequent writes to the sector will
// actually write the data through to the write buffer.
__IO uint16_t *flash_write_dest = (__IO uint16_t*)
(FMC_BANK_1_BASE_ADDRESS + (start_addr & ~0x1));
uint32_t bytes_remaining = bytes_in_page;
// Handle leading byte
if (start_addr & 0x1) {
// Handle a buffer with an unaligned start. Write 0xff for the first byte
// since flash can only flip ones to zeros, and no data will be lost.
const uint16_t first_short_value = 0xFF | ((*buffer) << 8);
*flash_write_dest = first_short_value;
// Now for the rest of the function let's pretend this never happened.
++flash_write_dest;
++buffer;
--bytes_remaining;
}
// Handle body words
for (; bytes_remaining >= 2; bytes_remaining -= 2, buffer += 2) {
uint16_t buffer_word;
memcpy(&buffer_word, buffer, sizeof buffer_word);
*flash_write_dest++ = buffer_word;
}
// Handle trailing byte if present. This will be present if we started out
// aligned and we wrote an odd number of bytes or if we started out unaligned
// and wrote an even number of bytes.
if (bytes_remaining) {
// We need to write only a single byte, but we're only allowed to write
// words. If we write a single byte followed by 0xFFFF, we won't modify the
// second byte as bits are only allowed to be written from 1 -> 0. 1s will
// stay 1s, and 0s will stay 0s.
const uint16_t trailing_short_value = *buffer | 0xFF00;
*flash_write_dest = trailing_short_value;
}
// Buffer writing is complete, issue the buffer to flash command to actually
// commit the changes to memory.
prv_issue_command(sector_addr, S29VSCommand_BufferToFlash);
// Check the status register to make sure that the write has started.
status_t result = E_UNKNOWN;
const uint8_t sr = prv_read_status_register(sector_addr);
if ((sr & S29VSStatusBit_DeviceReady) == 0) {
// Program or erase operation in progress. Is it in the current bank?
result = ((sr & S29VSStatusBit_BankStatus) == 0)? S_SUCCESS : E_BUSY;
} else {
// Operation hasn't started. Something is wrong.
if (sr & S29VSStatusBit_SectorLockStatus) {
// Sector is write-protected.
result = E_INVALID_OPERATION;
} else if (sr & S29VSStatusBit_ProgramStatus) {
// Programming failed for some reason.
result = E_ERROR;
} else {
// The flash never appeared to go busy and there is no error. Either the
// flash write completed between the write command and the status register
// read (inopportune context switch or running in QEMU), or the write
// never started. It's possible to tell them apart by validating that the
// data was actually written to flash, but that adds even more complexity
// to this function. Let the upper layers verify that the write succeeded
// if they are concerned about reliability.
result = S_SUCCESS;
}
}
prv_allow_write_if_sector_is_not_protected(true, sector_addr);
flash_impl_release();
return FAILED(result) ? result : (int)bytes_in_page;
}
status_t flash_impl_get_write_status(void) {
flash_impl_use();
const uint8_t status = prv_read_status_register(0);
flash_impl_release();
if ((status & S29VSStatusBit_DeviceReady) == 0) return E_BUSY;
if ((status & S29VSStatusBit_ProgramSuspended) != 0) return E_AGAIN;
if ((status & S29VSStatusBit_ProgramStatus) != 0) return E_ERROR;
return S_SUCCESS;
}
uint8_t pbl_28517_flash_impl_get_status_register(uint32_t sector_addr) {
flash_impl_use();
const FlashAddress base_addr = flash_impl_get_sector_base_address(sector_addr);
const uint8_t status = prv_read_status_register(base_addr);
flash_impl_release();
return status;
}
status_t flash_impl_enter_low_power_mode(void) {
prv_flash_idle_gpios(false);
return S_SUCCESS;
}
status_t flash_impl_exit_low_power_mode(void) {
// it's ok to access s_num_flash_uses here directly, as only caller enter_stop_mode() is called
// only while interrupts are disabled
prv_flash_idle_gpios(s_num_flash_uses > 0);
return S_SUCCESS;
}
static void prv_switch_flash_mode(FMC_NORSRAMInitTypeDef *nor_init) {
FMC_NORSRAMCmd(FMC_Bank1_NORSRAM1, DISABLE);
FMC_NORSRAMInit(nor_init);
FMC_NORSRAMCmd(FMC_Bank1_NORSRAM1, ENABLE);
}
static uint16_t prv_get_num_wait_cycles(uint32_t flash_clock_freq) {
// wait_cycle table based on frequency (table 7.1)
// NOTE: 27MHZ frequency skipped due to data latency being 4 smaller than the wait_cycle
uint32_t wait_cycle[] = {
40000000 ,
54000000 ,
66000000 ,
80000000 ,
95000000 ,
104000000 ,
120000000
};
// find number wait states based on table
uint32_t wait_state;
for (wait_state = 4; wait_state < (ARRAY_LENGTH(wait_cycle) + 4); wait_state++) {
if (flash_clock_freq < wait_cycle[wait_state-4]) {
break;
}
}
return wait_state;
}
status_t flash_impl_set_burst_mode(bool burst_mode) {
const uint32_t MAX_FREQ = MHZ_TO_HZ(108); // max frequency of the flash 108MHZ
const uint32_t TAVDP_MIN = 60; // min addr setup time in tenths of ns
const uint32_t TADVO_MIN = 40; // min addr hold time in tenths
const uint32_t SETUP_STEP = MHZ_TO_HZ(16); // for data setup equation
const uint16_t WAIT_STATE_MASK = 0x7800; // mask for wait state binary for sync burst
flash_impl_use();
// get system clock tick speed
RCC_ClocksTypeDef clocks;
RCC_GetClocksFreq(&clocks);
uint32_t h_clock = clocks.HCLK_Frequency; // frequency in hertz
uint32_t time_per_cycle = ((uint64_t)(10000000000)) / h_clock; // period in 1/10th ns
FMC_NORSRAMTimingInitTypeDef nor_timing_init = {
// time between address write and address latch (AVD high)
// tAAVDS on datasheet, min 4 ns
//
// AVD low time
// tAVDP on datasheet, min 6 ns
.FMC_AddressSetupTime = (TAVDP_MIN / time_per_cycle) + 1, // give setup of min 6ns
// time between AVD high (address is available) and OE low (memory can write)
// tAVDO on the datasheet, min 4 ns
.FMC_AddressHoldTime = (TADVO_MIN / time_per_cycle) + 1, // gives hold of min 4ns
// time between OE low (memory can write) and valid data being available
// FIXME: optimize this equation
// current linear equation has slope of 1 cycle/SETUP_STEP, with initial value 1
// setupTime based on h_clock frequency
// equation derived from existing working values; 5 at 64Mhz, 8 at 128 Mhz
// the data was then interpolated into a line, with a padded value of 1
.FMC_DataSetupTime = (h_clock / SETUP_STEP) + 1,
// Time between chip selects
// not on the datasheet, picked a random safe number
// FIXME: at high bus frequencies, more than one cycle may be needed
.FMC_BusTurnAroundDuration = 1, // TODO: actually ok? See back-to-back Read/Write Cycle
.FMC_CLKDivision = 15, // Not used for async NOR
.FMC_DataLatency = 15, // Not used for async NOR
.FMC_AccessMode = FMC_AccessMode_A // Only used for ExtendedMode == FMC_ExtendedMode_Enable, which we don't use
};
FMC_NORSRAMInitTypeDef nor_init = {
.FMC_Bank = FMC_Bank1_NORSRAM1,
.FMC_DataAddressMux = FMC_DataAddressMux_Enable,
.FMC_MemoryType = FMC_MemoryType_NOR,
.FMC_MemoryDataWidth = FMC_NORSRAM_MemoryDataWidth_16b,
.FMC_BurstAccessMode = FMC_BurstAccessMode_Disable,
.FMC_AsynchronousWait = FMC_AsynchronousWait_Disable,
.FMC_WaitSignalPolarity = FMC_WaitSignalPolarity_Low,
.FMC_WrapMode = FMC_WrapMode_Disable,
.FMC_WaitSignalActive = FMC_WaitSignalActive_BeforeWaitState,
.FMC_WriteOperation = FMC_WriteOperation_Enable,
.FMC_WaitSignal = FMC_WaitSignal_Enable,
.FMC_ExtendedMode = FMC_ExtendedMode_Disable,
.FMC_WriteBurst = FMC_WriteBurst_Disable,
.FMC_ContinousClock = FMC_CClock_SyncOnly,
.FMC_ReadWriteTimingStruct = &nor_timing_init
};
// configure the peripheral before we try to read from it
prv_switch_flash_mode(&nor_init);
uint16_t configuration_register = prv_read_configuration_register();
// clear bits that are about to be set
configuration_register &= 0x0278; // clear bits [15:10], [8:7], [2:0]
// add one. This way, if (h_clock < MAX_FREQ), only divide by one (use h_clock as is)
// else divide by whatever is needed to be under MAX_FREQ
uint32_t clk_division = (h_clock / (MAX_FREQ + 1)) + 1;
// Update necessary parameters for synchronous modes
if (burst_mode) {
nor_init.FMC_BurstAccessMode = FMC_BurstAccessMode_Enable;
nor_init.FMC_WaitSignalActive = FMC_WaitSignalActive_DuringWaitState;
nor_timing_init.FMC_BusTurnAroundDuration = 1;
// nor_timing_init.FMC_DataSetupTime = 1; // FIXME: originally set to 1 for 64Mhz
// but sync burst was not working at this value;
// commented out so the DataSetupTime for ASYNC (up above) is used instead
// this is to ensure sync_burst works with dynamic changes to h_clk frequency
nor_timing_init.FMC_CLKDivision = clk_division; // divide h_clock if h_clock > 108MHZ
uint16_t wait_state = prv_get_num_wait_cycles(h_clock / clk_division);
// testing shows that a difference of 4 needs to be maintained between wait_state and latency
nor_timing_init.FMC_DataLatency = wait_state - 4;
// Set bits according to value needed - see Table 7.11 in data sheet
// [15] Device Read Mode 0b0 Synchronous Read Mode
// [14:11] Programmable Read Wait States 0bXXXX N wait cycles, wait states set to (N - 2)
// [10] RDY Polarity 0b1 RDY signal is active high (default)
// [8] RDY Timing 0b0 RDY active once cycle before data (default)
// [7] Output Drive Strength 0b0 Full Drive=Current Driver Strength (default)
// [2:0] Burst Length 0b000 Continuous (default)
configuration_register |= 0x400 | (((wait_state - 2) << 11) & (WAIT_STATE_MASK));
} else {
// Set bits according to value needed - see Table 7.11 in data sheet
// [15] Device Read Mode 0b1 Asynchronous Read Mode
// [14:11] Programmable Read Wait States 0b1011 13 wait cycles (default)
// [10] RDY Polarity 0b1 RDY signal is active high (default)
// [8] RDY Timing 0b1 RDY active with data (default)
// [7] Output Drive Strength 0b0 Full Drive=Current Driver Strength (default)
// [2:0] Burst Length 0b000 Continuous (default)
configuration_register |= 0xDD00;
}
prv_write_configuration_register(configuration_register);
prv_switch_flash_mode(&nor_init);
prv_poll_for_ready(0);
flash_impl_release();
return S_SUCCESS;
}
status_t flash_impl_blank_check_sector(FlashAddress addr) {
// FIXME: Blank check operation is only allowed in asynchronous mode. Fall
// back to a software blank check in synchronous mode.
const FlashAddress base_addr = flash_impl_get_sector_base_address(addr);
status_t ret = E_INTERNAL;
flash_impl_use();
uint8_t status = prv_read_status_register(base_addr);
if ((status & S29VSStatusBit_DeviceReady) == 0 ||
(status & (S29VSStatusBit_EraseSuspended |
S29VSStatusBit_ProgramSuspended)) != 0) {
ret = E_BUSY;
goto done;
}
prv_issue_command(base_addr, S29VSCommand_SectorBlank);
status = prv_poll_for_ready(base_addr);
ret = ((status & S29VSStatusBit_EraseStatus) == 0)? S_TRUE : S_FALSE;
done:
flash_impl_release();
return ret;
}
status_t flash_impl_blank_check_subsector(FlashAddress addr) {
return flash_impl_blank_check_sector(addr);
}
bool flash_check_whoami(void) {
uint16_t manufacturer_id = prv_read_manufacturer_id();
PBL_LOG(LOG_LEVEL_DEBUG, "Flash Manufacturer ID: 0x%"PRIx16, manufacturer_id);
return manufacturer_id == SPANSION_MANUFACTURER_ID ||
manufacturer_id == MACRONIX_MANUFACTURER_ID;
}
uint32_t flash_impl_get_typical_sector_erase_duration_ms(void) {
return 800;
}
uint32_t flash_impl_get_typical_subsector_erase_duration_ms(void) {
return 800;
}