mirror of
https://github.com/google/pebble.git
synced 2025-07-04 22:00:38 -04:00
768 lines
27 KiB
C
768 lines
27 KiB
C
/*
|
|
* 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.
|
|
*/
|
|
|
|
|
|
// This app only makes sense on Snowy, as it uses addresses and sector sizes that only make sense
|
|
// on our parallel flash hardware
|
|
#if CAPABILITY_USE_PARALLEL_FLASH
|
|
|
|
#include <inttypes.h>
|
|
|
|
#include "mfg_flash_test.h"
|
|
|
|
#include "drivers/flash.h"
|
|
#include "drivers/task_watchdog.h"
|
|
#include "flash_region/flash_region.h"
|
|
#include "system/logging.h"
|
|
#include "kernel/pbl_malloc.h"
|
|
|
|
|
|
static const uint8_t data_pattern = 0xAA;
|
|
static const uint8_t test_pattern = 0x55;
|
|
static volatile bool enable_flash_test = false;
|
|
|
|
static FlashTestErrorType prv_read_verify_byte(uint32_t read_addr,
|
|
uint8_t expected_val,
|
|
FlashTestErrorType err_code,
|
|
uint8_t bitpos,
|
|
bool disp_logs) {
|
|
uint8_t read_buffer = 0;
|
|
flash_read_bytes((uint8_t *)&read_buffer, read_addr, sizeof(read_buffer));
|
|
|
|
if (disp_logs) {
|
|
PBL_LOG(LOG_LEVEL_DEBUG, ">> Reading Addr 0x%"PRIx32" value is 0x%"PRIx8,
|
|
read_addr, read_buffer);
|
|
}
|
|
|
|
if (read_buffer != expected_val) {
|
|
switch (err_code) {
|
|
case FLASH_TEST_ERR_ERASE:
|
|
PBL_LOG(LOG_LEVEL_DEBUG, "ERROR: Did not successfully erase the sector");
|
|
break;
|
|
case FLASH_TEST_ERR_STUCK_AT_HIGH:
|
|
PBL_LOG(LOG_LEVEL_DEBUG, "ERROR: Address bit %d stuck at high", bitpos);
|
|
break;
|
|
case FLASH_TEST_ERR_STUCK_AT_LOW:
|
|
PBL_LOG(LOG_LEVEL_DEBUG, "ERROR: Address bit %d stuck at low or shorted", bitpos);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
return err_code;
|
|
}
|
|
|
|
return FLASH_TEST_SUCCESS;
|
|
}
|
|
|
|
static FlashTestErrorType prv_read_verify_halfword(uint32_t read_addr,
|
|
uint16_t expected_val,
|
|
FlashTestErrorType err_code,
|
|
bool disp_logs) {
|
|
uint16_t read_buffer = 0;
|
|
flash_read_bytes((uint8_t *)&read_buffer, read_addr, sizeof(read_buffer));
|
|
|
|
if (disp_logs) {
|
|
PBL_LOG(LOG_LEVEL_DEBUG, ">> Reading Addr 0x%"PRIx32" value is 0x%"PRIx16,
|
|
read_addr, read_buffer);
|
|
}
|
|
|
|
if (read_buffer != expected_val) {
|
|
switch (err_code) {
|
|
case FLASH_TEST_ERR_ERASE:
|
|
PBL_LOG(LOG_LEVEL_DEBUG, "ERROR: Did not successfully erase the sector");
|
|
break;
|
|
case FLASH_TEST_ERR_DATA_WRITE:
|
|
PBL_LOG(LOG_LEVEL_DEBUG, "ERROR: Did not successfully write the data");
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
return err_code;
|
|
}
|
|
|
|
return FLASH_TEST_SUCCESS;
|
|
}
|
|
|
|
static FlashTestErrorType prv_write_read_verify_byte(uint32_t write_addr,
|
|
uint8_t write_val,
|
|
uint8_t expected_val,
|
|
bool disp_logs) {
|
|
uint8_t write_buffer = write_val;
|
|
uint8_t read_buffer = 0;
|
|
// Write test pattern
|
|
if (disp_logs) {
|
|
PBL_LOG(LOG_LEVEL_DEBUG, ">> Writing Addr 0x%"PRIx32" to value 0x%"PRIx8,
|
|
write_addr, write_val);
|
|
}
|
|
flash_write_bytes((uint8_t *)&write_buffer, write_addr, sizeof(write_buffer));
|
|
|
|
// Confirm write took place
|
|
read_buffer = 0;
|
|
flash_read_bytes((uint8_t*) &read_buffer, write_addr, sizeof(read_buffer));
|
|
|
|
if (disp_logs) {
|
|
PBL_LOG(LOG_LEVEL_DEBUG, ">> Reading Addr 0x%"PRIx32" value is 0x%"PRIx8,
|
|
write_addr, read_buffer);
|
|
}
|
|
|
|
if (read_buffer != expected_val) {
|
|
PBL_LOG(LOG_LEVEL_DEBUG, "ERROR: Did not successfully write the data");
|
|
return FLASH_TEST_ERR_DATA_WRITE;
|
|
}
|
|
|
|
return FLASH_TEST_SUCCESS;
|
|
}
|
|
|
|
static FlashTestErrorType prv_write_read_verify_halfword(uint32_t write_addr,
|
|
uint16_t write_val,
|
|
uint16_t expected_val,
|
|
bool disp_logs) {
|
|
uint16_t write_buffer = write_val;
|
|
uint16_t read_buffer = 0;
|
|
|
|
// Write test pattern
|
|
if (disp_logs) {
|
|
PBL_LOG(LOG_LEVEL_DEBUG, ">> Writing Addr 0x%"PRIx32" to value 0x%"PRIx16,
|
|
write_addr, write_val);
|
|
}
|
|
|
|
flash_write_bytes((uint8_t *)&write_buffer, write_addr, sizeof(write_buffer));
|
|
|
|
// Confirm write took place
|
|
read_buffer = 0;
|
|
flash_read_bytes((uint8_t*) &read_buffer, write_addr, sizeof(read_buffer));
|
|
|
|
if (disp_logs) {
|
|
PBL_LOG(LOG_LEVEL_DEBUG, ">> Reading Addr 0x%"PRIx32" value is 0x%"PRIx16,
|
|
write_addr, read_buffer);
|
|
}
|
|
|
|
if (read_buffer != expected_val) {
|
|
PBL_LOG(LOG_LEVEL_DEBUG, "ERROR: Did not successfully write the data");
|
|
return FLASH_TEST_ERR_DATA_WRITE;
|
|
}
|
|
|
|
return FLASH_TEST_SUCCESS;
|
|
}
|
|
|
|
#define VERIFY_TEST_STATUS(status) \
|
|
do { \
|
|
if (status != FLASH_TEST_SUCCESS) { return status; } \
|
|
} while(0)
|
|
|
|
/***********************************************************/
|
|
/******************* DATA Test Functions *******************/
|
|
/***********************************************************/
|
|
static FlashTestErrorType prv_run_data_test(void) {
|
|
FlashTestErrorType status = FLASH_TEST_SUCCESS;
|
|
|
|
// Test each data bit using walking 1's method by toggling each data line
|
|
PBL_LOG(LOG_LEVEL_DEBUG, ">START - DATA TEST 1: Data bus test");
|
|
|
|
// Initialize region that is to be written
|
|
uint16_t data_buffer = 0x0;
|
|
uint8_t bitpos = 0;
|
|
|
|
uint16_t read_buffer = 0x0;
|
|
// Ensure within test data region and aligned to sector boundary
|
|
uint32_t addr_region = (FLASH_TEST_ADDR_START + SECTOR_SIZE_BYTES) & SECTOR_ADDR_MASK;
|
|
|
|
// Loop on each data bit - erase the sector, then write the next data value and verify that data
|
|
// was written
|
|
for (data_buffer = 1; data_buffer != 0; data_buffer <<= 1) {
|
|
read_buffer = 0;
|
|
flash_read_bytes((uint8_t*) &read_buffer, addr_region, sizeof(read_buffer));
|
|
PBL_LOG(LOG_LEVEL_DEBUG, ">> Reading Addr 0x%"PRIx32" value is 0x%"PRIx16,
|
|
addr_region, read_buffer);
|
|
|
|
if (read_buffer != 0xFFFF) {
|
|
// Erase sector only if necessary
|
|
flash_erase_sector_blocking(addr_region);
|
|
flash_read_bytes((uint8_t*) &read_buffer, addr_region, sizeof(read_buffer));
|
|
PBL_LOG(LOG_LEVEL_DEBUG, ">> Reading Addr 0x%"PRIx32" value is 0x%"PRIx16,
|
|
addr_region, read_buffer);
|
|
}
|
|
|
|
// All data should be set to 0xFFFF upon erase
|
|
if (read_buffer != 0xFFFF) {
|
|
PBL_LOG(LOG_LEVEL_DEBUG, "ERROR: Did not successfully erase the sector");
|
|
return FLASH_TEST_ERR_ERASE;
|
|
}
|
|
|
|
// Read and compare data that was written
|
|
status = prv_write_read_verify_halfword(addr_region, data_buffer, data_buffer, true);
|
|
if (status != 0) {
|
|
PBL_LOG(LOG_LEVEL_DEBUG, "ERROR: Data bit %d not returning correct data value", bitpos);
|
|
return status;
|
|
}
|
|
|
|
bitpos++;
|
|
addr_region += 4; // increment to the next address to avoid extra erases
|
|
}
|
|
|
|
PBL_LOG(LOG_LEVEL_DEBUG, ">PASS - DATA TEST 1: Data bus test");
|
|
return status;
|
|
}
|
|
|
|
/***********************************************************/
|
|
/******************* Addr Test Functions *******************/
|
|
/***********************************************************/
|
|
// Write the test byte 0xAA at each power-of-two offset within the flash test range. If necessary,
|
|
// erase the sector that the byte resides in first.
|
|
// The base address always gets erased. If skip_base_addr is true, we leave it at 0xFF (erased)
|
|
// otherwise we write 0xAA to that address as well.
|
|
static FlashTestErrorType write_initial_pattern(bool display_logs, bool skip_base_addr,
|
|
uint32_t* erase_addr) {
|
|
FlashTestErrorType status = FLASH_TEST_SUCCESS;
|
|
uint32_t base_addr = FLASH_TEST_ADDR_START;
|
|
uint32_t test_addr = base_addr;
|
|
uint32_t addr_mask = FLASH_TEST_ADDR_MSK;
|
|
uint8_t read_buffer = 0;
|
|
uint32_t bit_offset;
|
|
|
|
if (display_logs) { PBL_LOG(LOG_LEVEL_DEBUG, ">>> Initializing data patterns..."); }
|
|
if (display_logs) { PBL_LOG(LOG_LEVEL_DEBUG, ">>> Erasing sectors..."); }
|
|
if (erase_addr) {
|
|
// only erase the specific erase address
|
|
if (display_logs) {
|
|
PBL_LOG(LOG_LEVEL_DEBUG, ">> Erasing Addr 0x%"PRIx32, *erase_addr);
|
|
}
|
|
flash_erase_sector_blocking(*erase_addr);
|
|
} else {
|
|
// Erase the addresses within test range that we will touch. These are addresses with
|
|
// power-of-two offsets
|
|
for (bit_offset = 0; (bit_offset == 0) || (bit_offset & addr_mask); bit_offset <<= 1) {
|
|
|
|
if (bit_offset > base_addr) {
|
|
test_addr = bit_offset;
|
|
} else {
|
|
test_addr = base_addr + bit_offset;
|
|
}
|
|
if (test_addr >= FLASH_TEST_ADDR_END) {
|
|
break;
|
|
}
|
|
|
|
// skip erasing of unnecessary overlapping sectors
|
|
if ((test_addr >= base_addr + SECTOR_SIZE_BYTES) ||
|
|
(test_addr == base_addr + 1) || (test_addr == base_addr)) {
|
|
// check if byte location is already 0xFF or default data pattern, then skip erase
|
|
// Always erase base address
|
|
|
|
flash_read_bytes((uint8_t*)&read_buffer, test_addr, sizeof(read_buffer));
|
|
PBL_LOG(LOG_LEVEL_DEBUG, ">> Testing Addr 0x%"PRIx32", value:0x%x", test_addr, read_buffer);
|
|
|
|
if (((read_buffer != 0xFF) && (read_buffer != 0xAA)) || (test_addr == base_addr)) {
|
|
if (display_logs) {
|
|
PBL_LOG(LOG_LEVEL_DEBUG, ">> Erasing Addr 0x%"PRIx32, test_addr);
|
|
}
|
|
flash_erase_sector_blocking(test_addr);
|
|
|
|
// Verify data was erased
|
|
status = prv_read_verify_byte(test_addr, 0xFF, FLASH_TEST_ERR_ERASE, 0, display_logs);
|
|
VERIFY_TEST_STATUS(status);
|
|
}
|
|
}
|
|
|
|
// After the base address, go up by power of 2's
|
|
if (bit_offset == 0) {
|
|
bit_offset = 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (display_logs) { PBL_LOG(LOG_LEVEL_DEBUG, ">>> Erasing sectors...complete"); }
|
|
|
|
// Write default data pattern to each power-of-two offset within the test region
|
|
for (bit_offset = 1; (bit_offset & addr_mask) != 0; bit_offset <<= 1) {
|
|
if (bit_offset > base_addr) {
|
|
test_addr = bit_offset;
|
|
} else {
|
|
test_addr = base_addr + bit_offset;
|
|
}
|
|
if (test_addr >= FLASH_TEST_ADDR_END) {
|
|
break;
|
|
}
|
|
|
|
// Write default data pattern to address if necessary
|
|
status = prv_read_verify_byte(test_addr, data_pattern, FLASH_TEST_ERR_SKIP, 0, display_logs);
|
|
if (status != FLASH_TEST_SUCCESS) {
|
|
// Write default data pattern to address
|
|
status = prv_write_read_verify_byte(test_addr, data_pattern, data_pattern, display_logs);
|
|
VERIFY_TEST_STATUS(status);
|
|
}
|
|
}
|
|
|
|
if (!skip_base_addr) {
|
|
test_addr = base_addr;
|
|
|
|
// Read initial value
|
|
read_buffer = 0;
|
|
flash_read_bytes((uint8_t*) &read_buffer, test_addr, sizeof(read_buffer));
|
|
if (display_logs) {
|
|
PBL_LOG(LOG_LEVEL_DEBUG, ">> Reading Addr 0x%"PRIx32" value is 0x%"PRIx8,
|
|
test_addr, read_buffer);
|
|
}
|
|
|
|
// Write data pattern
|
|
status = prv_write_read_verify_byte(test_addr, data_pattern, data_pattern, display_logs);
|
|
VERIFY_TEST_STATUS(status);
|
|
}
|
|
|
|
if (display_logs) { PBL_LOG(LOG_LEVEL_DEBUG, ">>> Initializing data patterns...complete"); }
|
|
|
|
return FLASH_TEST_SUCCESS;
|
|
}
|
|
|
|
static FlashTestErrorType prv_run_addr_test (void) {
|
|
uint32_t base_addr = FLASH_TEST_ADDR_START;
|
|
uint32_t test_addr = base_addr;
|
|
uint32_t addr_mask = FLASH_TEST_ADDR_MSK;
|
|
uint8_t read_buffer = 0;
|
|
uint32_t bit_offset;
|
|
uint32_t test_offset = 0;
|
|
FlashTestErrorType status = FLASH_TEST_SUCCESS;
|
|
|
|
///////////////////////////////////////////////////
|
|
/// Test 1: Check for address bits stuck at high
|
|
///////////////////////////////////////////////////
|
|
PBL_LOG(LOG_LEVEL_DEBUG, ">START - ADDR TEST 1: Check for address bits stuck at high");
|
|
|
|
// Write data pattern (0xAA) to each power-of-2 offset within the flash
|
|
status = write_initial_pattern(true /*display_logs*/, false /*skip_base_addr*/,
|
|
NULL /*erase_addr*/);
|
|
VERIFY_TEST_STATUS(status);
|
|
|
|
// offset of 0
|
|
test_addr = base_addr + test_offset;
|
|
|
|
// Read initial value
|
|
read_buffer = 0;
|
|
flash_read_bytes((uint8_t*) &read_buffer, test_addr, sizeof(read_buffer));
|
|
PBL_LOG(LOG_LEVEL_DEBUG, ">> Reading Addr 0x%"PRIx32" value is 0x%"PRIx8,
|
|
test_addr, read_buffer);
|
|
|
|
// Write test pattern to address 0
|
|
// After writing test pattern, data should be 0x00 since initial value was 0xAA and 0x55 was written
|
|
status = prv_write_read_verify_byte(test_addr, test_pattern, 0x00, true /*display_logs*/);
|
|
VERIFY_TEST_STATUS(status);
|
|
|
|
|
|
// Check if any of the address bits are stuck at high. If they are, then the previous write to
|
|
// address 0 would have trashed the data at one of the other addresses
|
|
uint8_t base_addr_pos = 0;
|
|
uint8_t bitpos;
|
|
bool stuck_at_high = false;
|
|
for (bit_offset = 1, bitpos = 0; bit_offset & addr_mask; bit_offset <<= 1, bitpos++) {
|
|
if (bit_offset > base_addr) {
|
|
test_addr = bit_offset;
|
|
} else if (bit_offset == base_addr) {
|
|
base_addr_pos = bitpos;
|
|
// Skip base address check - that is done later
|
|
PBL_LOG(LOG_LEVEL_DEBUG, "Skip base address bit position %d", bitpos);
|
|
bitpos++;
|
|
continue;
|
|
} else {
|
|
test_addr = base_addr + bit_offset;
|
|
}
|
|
|
|
if (test_addr >= FLASH_TEST_ADDR_END) {
|
|
PBL_LOG(LOG_LEVEL_DEBUG, "Skipping test address 0x%"PRIx32" which is out of range",
|
|
test_addr);
|
|
break;
|
|
}
|
|
|
|
|
|
// If test_pattern was written over the data_pattern, then return data should be 0 since
|
|
// data cannot transition from 0 to 1 without an erase; else it will be initial data_pattern
|
|
status = prv_read_verify_byte(test_addr, data_pattern, FLASH_TEST_ERR_STUCK_AT_HIGH, bitpos,
|
|
true);
|
|
if (status != FLASH_TEST_SUCCESS) {
|
|
stuck_at_high = true;
|
|
}
|
|
}
|
|
|
|
// Special case - test bit for base address
|
|
// - Use an address between FLASH_TEST_ADDR_START and base_addr
|
|
PBL_LOG(LOG_LEVEL_DEBUG, ">> Testing special case for base address bit %d", base_addr_pos);
|
|
// Read initial value
|
|
test_addr = FLASH_REGION_FILESYSTEM_BEGIN;
|
|
uint32_t special_case_addr = test_addr | base_addr;
|
|
if ((test_addr >= base_addr) || (special_case_addr > FLASH_TEST_ADDR_END)) {
|
|
PBL_LOG(LOG_LEVEL_DEBUG, "ERROR: Cannot test address bit for base_addr");
|
|
return FLASH_TEST_ERR_ADDR_RANGE;
|
|
}
|
|
|
|
// erase (base_addr | test_addr) and start of test space
|
|
flash_erase_sector_blocking(test_addr);
|
|
flash_erase_sector_blocking(special_case_addr);
|
|
|
|
// Verify erase took place
|
|
status = prv_read_verify_byte(test_addr, 0xFF, FLASH_TEST_ERR_ERASE, 0, true);
|
|
VERIFY_TEST_STATUS(status);
|
|
|
|
// Verify erase took place
|
|
status = prv_read_verify_byte(special_case_addr, 0xFF, FLASH_TEST_ERR_ERASE, 0, true);
|
|
VERIFY_TEST_STATUS(status);
|
|
|
|
// Write test pattern to the test address
|
|
// Data should be set to test_pattern since existing data should be 0xFF and we are writing
|
|
// test_pattern
|
|
status = prv_write_read_verify_byte(test_addr, test_pattern, test_pattern, true);
|
|
VERIFY_TEST_STATUS(status);
|
|
|
|
// Confirm write into base_addr did not take place
|
|
// If test_pattern was written over the data_pattern, then return data should be 0 since
|
|
// data cannot transition from 0 to 1 without an erase
|
|
status = prv_read_verify_byte(special_case_addr, 0xFF, FLASH_TEST_ERR_STUCK_AT_HIGH,
|
|
base_addr_pos, true);
|
|
if (status != FLASH_TEST_SUCCESS) {
|
|
stuck_at_high = true;
|
|
}
|
|
|
|
// If any bits are stuck at high, return error
|
|
if (stuck_at_high) {
|
|
return FLASH_TEST_ERR_STUCK_AT_HIGH;
|
|
}
|
|
|
|
PBL_LOG(LOG_LEVEL_DEBUG, ">PASS - ADDR TEST 1: Check for address bits stuck at high");
|
|
|
|
|
|
|
|
/////////////////////////////////////////////////////////////
|
|
/// Test 2: Check for address bits stuck at low or shorted
|
|
/////////////////////////////////////////////////////////////
|
|
PBL_LOG(LOG_LEVEL_DEBUG, ">START - ADDR TEST 2: Check for address bits stuck at low or shorted");
|
|
|
|
// NOTE that the previous test only modified the data at base_addr and left all other
|
|
// power-of-2 addresses with the data pattern in them. The write_initial_pattern() method
|
|
// will skip erasing a sector if all of the power of 2 addresses within it still have the
|
|
// data pattern, so only the first sector will end up being re-erased.
|
|
status = write_initial_pattern(true /*display_logs*/, false /*skip_base_addr*/,
|
|
NULL /*erase_addr*/);
|
|
VERIFY_TEST_STATUS(status);
|
|
|
|
bool stuck_at_low = false;
|
|
for (test_offset = 1, bitpos=0; test_offset & addr_mask; test_offset <<= 1, bitpos++) {
|
|
|
|
if (test_offset >= base_addr) {
|
|
test_addr = test_offset;
|
|
} else {
|
|
test_addr = base_addr + test_offset;
|
|
}
|
|
if (test_addr >= FLASH_TEST_ADDR_END) {
|
|
break;
|
|
}
|
|
|
|
// Skip base address
|
|
if (test_addr == base_addr) {
|
|
continue;
|
|
}
|
|
PBL_LOG(LOG_LEVEL_DEBUG, ">> Testing Stuck at Low at Addr 0x%"PRIx32, test_addr);
|
|
|
|
// After we write test_pattern, data should be set to 0x00 since existing data should be 0xAA
|
|
// and we are writing 0x55
|
|
status = prv_write_read_verify_byte(test_addr, test_pattern, 0x00, false);
|
|
VERIFY_TEST_STATUS(status);
|
|
|
|
// read base address to insure that it wasn't modified due to a stuck at zero in an address
|
|
// bit
|
|
status = prv_read_verify_byte(base_addr, data_pattern, FLASH_TEST_ERR_STUCK_AT_LOW, bitpos,
|
|
false);
|
|
if (status != FLASH_TEST_SUCCESS) {
|
|
stuck_at_low = true;
|
|
}
|
|
|
|
// Check if any other address bits are shorted with our test bit. If shorted, then we would
|
|
// read 0 from the address bit which is shorted with the test one.
|
|
// We only have to check shorts with higher address bits, since we've already checked for
|
|
// shorts from the lower address bits to this one.
|
|
uint8_t bitpos2 = bitpos+1;
|
|
for (bit_offset = test_offset << 1; bit_offset & addr_mask; bit_offset <<= 1, bitpos2++) {
|
|
// skip same offset
|
|
if (bit_offset == test_offset) {
|
|
continue;
|
|
}
|
|
|
|
uint32_t test_addr2;
|
|
if (bit_offset >= base_addr) {
|
|
test_addr2 = bit_offset;
|
|
} else {
|
|
test_addr2 = base_addr + bit_offset;
|
|
}
|
|
if (test_addr2 >= FLASH_TEST_ADDR_END) {
|
|
break;
|
|
}
|
|
|
|
status = prv_read_verify_byte(test_addr2, data_pattern, FLASH_TEST_ERR_STUCK_AT_LOW, bitpos2,
|
|
false /*display_logs*/);
|
|
if (status != FLASH_TEST_SUCCESS) {
|
|
stuck_at_low = true;
|
|
}
|
|
}
|
|
|
|
if (stuck_at_low) {
|
|
// Restore data back to original if stuck at low occurred
|
|
status = write_initial_pattern(false /*display_logs*/, false /*skip_base_addr*/,
|
|
NULL /*base_addr*/);
|
|
}
|
|
|
|
VERIFY_TEST_STATUS(status);
|
|
}
|
|
|
|
if (stuck_at_low) {
|
|
return FLASH_TEST_ERR_STUCK_AT_LOW;
|
|
}
|
|
|
|
PBL_LOG(LOG_LEVEL_DEBUG, ">PASS - ADDR TEST 2: Check for address bits stuck at low or shorted");
|
|
|
|
return FLASH_TEST_SUCCESS;
|
|
}
|
|
|
|
/***********************************************************/
|
|
/******************* Stress Test Functions *****************/
|
|
/***********************************************************/
|
|
#define FLASH_TEST_STRESS_ADDR1 0x00A5A5A5
|
|
#define FLASH_TEST_STRESS_DATA1 0x5A5A
|
|
#define FLASH_TEST_STRESS_ADDR2 0x00CA5A5A
|
|
#define FLASH_TEST_STRESS_DATA2 0xA5A5
|
|
static FlashTestErrorType setup_stress_addr_test(void) {
|
|
FlashTestErrorType status = FLASH_TEST_SUCCESS;
|
|
|
|
// Read/Write from address 1
|
|
uint32_t stress_addr1 = FLASH_TEST_STRESS_ADDR1;
|
|
uint16_t stress_data1 = FLASH_TEST_STRESS_DATA1;
|
|
|
|
// Read/Write from address 2
|
|
uint32_t stress_addr2 = FLASH_TEST_STRESS_ADDR2;
|
|
uint16_t stress_data2 = FLASH_TEST_STRESS_DATA2;
|
|
|
|
if ((stress_addr1 < FLASH_REGION_FILESYSTEM_BEGIN) ||
|
|
(stress_addr1 >= FLASH_REGION_FILESYSTEM_END)) {
|
|
PBL_LOG(LOG_LEVEL_DEBUG, "ERROR: Invalid range");
|
|
return FLASH_TEST_ERR_ADDR_RANGE;
|
|
}
|
|
|
|
if ((stress_addr2 < FLASH_REGION_FILESYSTEM_BEGIN) ||
|
|
(stress_addr2 >= FLASH_REGION_FILESYSTEM_END)) {
|
|
PBL_LOG(LOG_LEVEL_DEBUG, "ERROR: Invalid range");
|
|
return FLASH_TEST_ERR_ADDR_RANGE;
|
|
}
|
|
|
|
// Erase sectors
|
|
flash_erase_sector_blocking(stress_addr1);
|
|
status = prv_read_verify_halfword(stress_addr1, 0xFFFF, FLASH_TEST_ERR_ERASE, false);
|
|
VERIFY_TEST_STATUS(status);
|
|
flash_erase_sector_blocking(stress_addr2);
|
|
status = prv_read_verify_halfword(stress_addr2, 0xFFFF, FLASH_TEST_ERR_ERASE, false);
|
|
VERIFY_TEST_STATUS(status);
|
|
|
|
// Write data to stress address locations
|
|
PBL_LOG(LOG_LEVEL_DEBUG, ">> Writing Addr 0x%"PRIx32" to value 0x%"PRIx16,
|
|
stress_addr1, stress_data1);
|
|
flash_write_bytes((uint8_t *)&stress_data1, stress_addr1, sizeof(stress_data1));
|
|
|
|
PBL_LOG(LOG_LEVEL_DEBUG, ">> Writing Addr 0x%"PRIx32" to value 0x%"PRIx16,
|
|
stress_addr2, stress_data2);
|
|
flash_write_bytes((uint8_t *)&stress_data2, stress_addr2, sizeof(stress_data2));
|
|
|
|
return FLASH_TEST_SUCCESS;
|
|
}
|
|
|
|
// Run address read/write stess test - if iterations is 0, then stop only when button is pushed;
|
|
// else go until iterations hit
|
|
static FlashTestErrorType prv_run_stress_addr_test(uint32_t iterations) {
|
|
PBL_LOG(LOG_LEVEL_DEBUG, ">START - STRESS TEST 1");
|
|
|
|
uint16_t halfwordcount = 0;
|
|
unsigned int iteration_count = 0;
|
|
|
|
// Read/Write from address 1
|
|
uint32_t stress_addr1 = FLASH_TEST_STRESS_ADDR1;
|
|
uint16_t stress_data1 = FLASH_TEST_STRESS_DATA1;
|
|
|
|
// Read/Write from adress 2
|
|
uint32_t stress_addr2 = FLASH_TEST_STRESS_ADDR2;
|
|
uint16_t stress_data2 = FLASH_TEST_STRESS_DATA2;
|
|
|
|
FlashTestErrorType status = setup_stress_addr_test();
|
|
if (status != FLASH_TEST_SUCCESS) {
|
|
return status;
|
|
}
|
|
|
|
// Keep going until DOWN button is pushed or iterations reached
|
|
while(((iterations == 0) && enable_flash_test) ||
|
|
((iterations > 0) && (iteration_count < iterations))) {
|
|
// Confirm write took place - data should now be set to stress_data1
|
|
status = prv_read_verify_halfword(stress_addr1, stress_data1, FLASH_TEST_ERR_DATA_WRITE, false);
|
|
VERIFY_TEST_STATUS(status);
|
|
halfwordcount++;
|
|
|
|
// Confirm write took place - data should now be set to stress_data2
|
|
status = prv_read_verify_halfword(stress_addr2, stress_data2, FLASH_TEST_ERR_DATA_WRITE, false);
|
|
VERIFY_TEST_STATUS(status);
|
|
halfwordcount++;
|
|
|
|
if (halfwordcount*2 % (256*1024) == 0) {
|
|
// Reading flash words (which are 16 bits) hence double
|
|
if (iterations) {
|
|
PBL_LOG(LOG_LEVEL_DEBUG, ">> Read 256KB, iteration: %d of %"PRId32,
|
|
iteration_count, iterations);
|
|
} else {
|
|
PBL_LOG(LOG_LEVEL_DEBUG, ">> Read 256KB, iteration: %d", iteration_count);
|
|
}
|
|
}
|
|
|
|
iteration_count++;
|
|
}
|
|
|
|
PBL_LOG(LOG_LEVEL_DEBUG, "Ran %d iterations", iteration_count);
|
|
PBL_LOG(LOG_LEVEL_DEBUG, ">PASS - STRESS TEST 1");
|
|
|
|
return FLASH_TEST_SUCCESS;
|
|
}
|
|
|
|
/***********************************************************/
|
|
/******************* Perf Data Test Functions **************/
|
|
/***********************************************************/
|
|
|
|
#define COUNTER_START \
|
|
uint32_t _start = *((volatile uint32_t *)0xE0001004);\
|
|
uint32_t _tot = 0
|
|
#define COUNTER_STOP \
|
|
uint32_t _end = *((volatile uint32_t *)0xE0001004)
|
|
#define COUNTER_PRINT(x) \
|
|
do { \
|
|
if (_end > _start) { \
|
|
_tot += (_end - _start); \
|
|
} else { \
|
|
_tot += (UINT32_MAX - _start) + _end; \
|
|
} \
|
|
PBL_LOG(LOG_LEVEL_DEBUG, "Read %lu bytes %lu ticks %lu us", x, _tot, _tot / 64); \
|
|
} while (0)
|
|
|
|
#define SWAP(a, b) \
|
|
do { \
|
|
uint32_t temp = a; \
|
|
a = b; \
|
|
b = temp; \
|
|
} while (0)
|
|
|
|
// Run performance test to measure data access times
|
|
#define DWT_CTRL_ADDR 0xE0001000
|
|
#define DWT_CYCCNT_ADDR 0xE0001004
|
|
#define MAX_READ_BUFF_SIZE 4096 // 4KB
|
|
static FlashTestErrorType prv_run_perf_data_test(void) {
|
|
uint8_t *read_buffer = (uint8_t *) app_malloc(MAX_READ_BUFF_SIZE);
|
|
if (!read_buffer) {
|
|
PBL_LOG(LOG_LEVEL_DEBUG, "ERROR: Not enough memory to run test");
|
|
return FLASH_TEST_ERR_OOM;
|
|
}
|
|
|
|
uint32_t addr = FLASH_TEST_ADDR_START;
|
|
volatile uint32_t *ptr = (uint32_t *) DWT_CTRL_ADDR;
|
|
for (uint32_t num_bytes = 1; num_bytes <= MAX_READ_BUFF_SIZE; num_bytes<<=1) {
|
|
// Run test three times and print out the median throughput
|
|
uint32_t ticks[3] = {0, 0, 0};
|
|
for (uint8_t repeat = 0; repeat < 3; repeat++) {
|
|
*ptr = *ptr & 0xFFFFFFFE;
|
|
*((volatile uint32_t *)DWT_CYCCNT_ADDR) = 0;
|
|
*ptr = *ptr | 0x1;
|
|
COUNTER_START;
|
|
flash_read_bytes((uint8_t *)&read_buffer[0], addr, num_bytes);
|
|
COUNTER_STOP;
|
|
COUNTER_PRINT(num_bytes);
|
|
ticks[repeat] = _tot;
|
|
}
|
|
|
|
// Do a simple sort
|
|
if (ticks[0] > ticks[1]) {
|
|
SWAP(ticks[0], ticks[1]);
|
|
}
|
|
if (ticks[1] > ticks[2]) {
|
|
SWAP(ticks[1], ticks[2]);
|
|
if (ticks[0] > ticks[1]) {
|
|
SWAP(ticks[0], ticks[1]);
|
|
}
|
|
}
|
|
|
|
PBL_LOG(LOG_LEVEL_DEBUG, "Read %lu bytes, median throughput %lu KBps", num_bytes, (num_bytes * 1000 * 64 / ticks[1]));
|
|
}
|
|
|
|
app_free(read_buffer);
|
|
return FLASH_TEST_SUCCESS;
|
|
}
|
|
|
|
/***********************************************************/
|
|
/******************* Wrapper Functions *********************/
|
|
/***********************************************************/
|
|
void stop_flash_test_case( void ) {
|
|
enable_flash_test = false;
|
|
}
|
|
|
|
FlashTestErrorType run_flash_test_case(FlashTestCaseType test_case_num, uint32_t iterations) {
|
|
FlashTestErrorType status = FLASH_TEST_SUCCESS;
|
|
|
|
// Disable watchdog if enabled
|
|
bool previous_task_watchdog_state = task_watchdog_mask_get(pebble_task_get_current());
|
|
if (previous_task_watchdog_state) {
|
|
task_watchdog_mask_clear(pebble_task_get_current());
|
|
}
|
|
|
|
enable_flash_test = true;
|
|
|
|
// Schedule test to run
|
|
switch (test_case_num) {
|
|
case FLASH_TEST_CASE_RUN_DATA_TEST:
|
|
status = prv_run_data_test();
|
|
break;
|
|
case FLASH_TEST_CASE_RUN_ADDR_TEST:
|
|
status = prv_run_addr_test();
|
|
break;
|
|
case FLASH_TEST_CASE_RUN_STRESS_ADDR_TEST:
|
|
status = prv_run_stress_addr_test(iterations);
|
|
break;
|
|
case FLASH_TEST_CASE_RUN_PERF_DATA_TEST:
|
|
status = prv_run_perf_data_test();
|
|
break;
|
|
case FLASH_TEST_CASE_RUN_SWITCH_MODE_ASYNC:
|
|
case FLASH_TEST_CASE_RUN_SWITCH_MODE_SYNC_BURST:
|
|
flash_switch_mode(test_case_num - FLASH_TEST_CASE_RUN_SWITCH_MODE_ASYNC);
|
|
status = FLASH_TEST_SUCCESS;
|
|
break;
|
|
default:
|
|
status = FLASH_TEST_ERR_UNSUPPORTED;
|
|
break;
|
|
}
|
|
|
|
enable_flash_test = false;
|
|
|
|
if (status == FLASH_TEST_SUCCESS) {
|
|
PBL_LOG(LOG_LEVEL_DEBUG, ">>>>>PASS FLASH TEST CASE %d<<<<<", test_case_num);
|
|
}
|
|
else {
|
|
PBL_LOG(LOG_LEVEL_DEBUG, ">>>>>FAIL FLASH TEST CASE %d, Status: %d<<<<<", test_case_num, status);
|
|
}
|
|
|
|
// Re-enable watchdog state if previously enabled
|
|
if (previous_task_watchdog_state) {
|
|
task_watchdog_bit_set(pebble_task_get_current());
|
|
task_watchdog_mask_set(pebble_task_get_current());
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
#endif // CAPABILITY_USE_PARALLEL_FLASH
|