mirror of
https://github.com/google/pebble.git
synced 2026-02-15 09:56:51 -05:00
341 lines
11 KiB
C
341 lines
11 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.
|
|
*/
|
|
|
|
#include "flash_diagnostic_app.h"
|
|
|
|
#include <stdio.h>
|
|
|
|
#include "applib/app.h"
|
|
#include "applib/ui/simple_menu_layer.h"
|
|
#include "applib/ui/ui.h"
|
|
#include "applib/ui/window.h"
|
|
#include "drivers/flash.h"
|
|
#include "drivers/task_watchdog.h"
|
|
#include "flash_region/flash_region.h"
|
|
#include "kernel/pbl_malloc.h"
|
|
#include "kernel/pebble_tasks.h"
|
|
#include "process_state/app_state/app_state.h"
|
|
#include "resource/resource_ids.auto.h"
|
|
#include "resource/resource_storage_flash.h"
|
|
#include "system/logging.h"
|
|
#include "system/passert.h"
|
|
#include "kernel/util/sleep.h"
|
|
#include "util/size.h"
|
|
|
|
#define NUM_REGIONS ARRAY_LENGTH(s_flash_regions)
|
|
|
|
#define FILE_WRITE_STRESS (NUM_REGIONS)
|
|
#define FILE_SUBSECTOR_STRESS (FILE_WRITE_STRESS + 1)
|
|
#define NUM_STRESS_TESTS (FILE_SUBSECTOR_STRESS - NUM_REGIONS + 1)
|
|
|
|
#define NUM_MENU_ITEMS (NUM_REGIONS + NUM_STRESS_TESTS)
|
|
|
|
struct Region {
|
|
char *name;
|
|
uint32_t begin;
|
|
uint32_t end;
|
|
};
|
|
|
|
static struct Region s_flash_regions[] = {
|
|
{
|
|
"System Resources",
|
|
FLASH_REGION_SYSTEM_RESOURCES_BANK_0_BEGIN,
|
|
FLASH_REGION_SYSTEM_RESOURCES_BANK_0_END
|
|
},
|
|
{
|
|
"System Resources",
|
|
FLASH_REGION_SYSTEM_RESOURCES_BANK_1_BEGIN,
|
|
FLASH_REGION_SYSTEM_RESOURCES_BANK_1_END
|
|
},
|
|
{
|
|
"File System",
|
|
FLASH_REGION_FILESYSTEM_BEGIN,
|
|
FLASH_REGION_FILESYSTEM_END
|
|
},
|
|
};
|
|
|
|
typedef struct {
|
|
Window window;
|
|
SimpleMenuLayer menu_layer;
|
|
SimpleMenuSection menu_section;
|
|
SimpleMenuItem menu_items[NUM_MENU_ITEMS];
|
|
} FlashDiagAppData;
|
|
|
|
typedef struct {
|
|
Window window;
|
|
TextLayer *text_layer;
|
|
int stress_iteration;
|
|
int stress_index;
|
|
} FlashStressWindow;
|
|
|
|
|
|
static bool check_region_erased(struct Region region) {
|
|
PBL_LOG_SYNC(LOG_LEVEL_INFO, "Checking Erase ...");
|
|
bool success = true;
|
|
for (uint32_t i = region.begin; i < region.end; i += sizeof(uint32_t)) {
|
|
uint32_t read = 0;
|
|
flash_read_bytes((uint8_t *)&read, i, sizeof(read));
|
|
if (read != 0xffffffff) {
|
|
PBL_LOG_SYNC(LOG_LEVEL_INFO, ">>>> Address 0x%lx failed to erase: 0x%lx", i, read);
|
|
success = false;
|
|
}
|
|
}
|
|
|
|
return success;
|
|
}
|
|
|
|
// region: region to check (and possibly write)
|
|
// use_rand: write random values
|
|
// perform_writes: perform writes if true, else see if the region reads as 0
|
|
static bool check_region_write(struct Region region, bool use_rand,
|
|
bool perform_writes) {
|
|
bool success = true;
|
|
uint32_t write_rand = (use_rand && perform_writes) ? rand() : 0;
|
|
|
|
PBL_LOG_SYNC(LOG_LEVEL_INFO, "%sChecking 0x%lx over 0x%lx 0x%lx",
|
|
(perform_writes) ? "Writing and " : "", write_rand, region.begin,
|
|
region.end);
|
|
|
|
for (uint32_t i = region.begin; i < region.end; i += sizeof(uint32_t)) {
|
|
uint32_t write = write_rand;
|
|
uint32_t read = 0xffff;
|
|
if (perform_writes) {
|
|
flash_write_bytes((uint8_t *)&write, i, sizeof(write));
|
|
}
|
|
flash_read_bytes((uint8_t *)&read, i, sizeof(read));
|
|
if (read != write) {
|
|
PBL_LOG_SYNC(LOG_LEVEL_INFO, ">>>> Address 0x%lx failed to write: 0x%lx 0x%lx",
|
|
i, read, write);
|
|
success = false;
|
|
}
|
|
}
|
|
|
|
return success;
|
|
}
|
|
|
|
// Writes 0's to the first half of a flash sector and confirms that everything
|
|
// reads as 0. Then uses 8 subsector erases to erase the second half of the
|
|
// sector. Then re-reads the first half to see if any bits have flipped
|
|
static bool check_subsector_bitflip(struct Region region) {
|
|
#if !CAPABILITY_USE_PARALLEL_FLASH
|
|
bool success = true;
|
|
|
|
if (((region.end - region.begin) % (64 * 1024)) != 0) {
|
|
PBL_LOG(LOG_LEVEL_WARNING, "Test only works on 64k aligned regions");
|
|
return (false);
|
|
}
|
|
|
|
const uint32_t block_size = 64 * 1024;
|
|
const uint32_t write_size = 32 * 1024;
|
|
const uint32_t subsector_size = 4 * 1024;
|
|
|
|
for (uint32_t i = region.begin; i < region.end; i += block_size) {
|
|
|
|
struct Region write_region;
|
|
write_region.begin = i;
|
|
write_region.end = i + write_size;
|
|
|
|
if (!check_region_write(write_region, false, true)) {
|
|
success = false;
|
|
break;
|
|
}
|
|
|
|
uint32_t subsec_begin = block_size - write_size;
|
|
PBL_ASSERTN((subsec_begin % (32*1024)) == 0);
|
|
|
|
for (uint32_t subsec = subsec_begin; subsec < block_size;
|
|
subsec += subsector_size) {
|
|
uint32_t erase = subsec + i;
|
|
PBL_ASSERTN((erase % (4 * 1024)) == 0);
|
|
PBL_LOG_SYNC(LOG_LEVEL_INFO, "Subsector Erase of 0x%lx", erase);
|
|
flash_erase_subsector_blocking(erase);
|
|
}
|
|
|
|
if (!check_region_write(write_region, false, false)) {
|
|
success = false;
|
|
break;
|
|
}
|
|
psleep(5);
|
|
}
|
|
|
|
return (success);
|
|
#else
|
|
PBL_LOG_SYNC(LOG_LEVEL_INFO, "Test not supported for parallel flash");
|
|
return (false);
|
|
#endif
|
|
}
|
|
|
|
static void menu_select_callback(int index, void *data) {
|
|
struct Region region = s_flash_regions[index];
|
|
PBL_LOG(LOG_LEVEL_INFO, ">>>> Erase %s", region.name);
|
|
flash_region_erase_optimal_range(region.begin, region.begin, region.end, region.end);
|
|
PBL_LOG(LOG_LEVEL_INFO, ">>>> Checking '%s' is erased", region.name);
|
|
check_region_erased(region);
|
|
PBL_LOG(LOG_LEVEL_INFO, ">>>> Checking '%s' can write", region.name);
|
|
check_region_write(region, false, true);
|
|
PBL_LOG(LOG_LEVEL_INFO, ">>>> Done!");
|
|
}
|
|
|
|
FlashStressWindow stress_data;
|
|
static bool abort_stress_test;
|
|
static void update_text(int iter, int tot, bool failed) {
|
|
static char status[50];
|
|
|
|
snprintf(status, sizeof(status), "%d / %d %s", iter, tot,
|
|
failed ? "Failed Out" : "Complete");
|
|
text_layer_set_text(stress_data.text_layer, (char *)status);
|
|
}
|
|
|
|
static void app_timer_cb(void *data) {
|
|
static const int num_stress_iters = 1000;
|
|
struct Region region = s_flash_regions[2];
|
|
PBL_LOG(LOG_LEVEL_INFO, ">>>> %s %d", "Test Loop", stress_data.stress_iteration);
|
|
|
|
PBL_LOG(LOG_LEVEL_INFO, "Erasing 0x%lx to 0x%lx", region.begin, region.end);
|
|
flash_region_erase_optimal_range(region.begin, region.begin, region.end, region.end);
|
|
|
|
bool failed = true;
|
|
if (stress_data.stress_index == FILE_WRITE_STRESS) {
|
|
failed = (!check_region_erased(region) || !check_region_write(region, true, true));
|
|
} else if (stress_data.stress_index == FILE_SUBSECTOR_STRESS) {
|
|
failed = (!check_region_erased(region) || !check_subsector_bitflip(region));
|
|
} else {
|
|
PBL_LOG(LOG_LEVEL_WARNING, "Unknown stress test %d!", stress_data.stress_index);
|
|
}
|
|
|
|
if (!abort_stress_test) {
|
|
update_text(++stress_data.stress_iteration, num_stress_iters, failed);
|
|
|
|
if (!failed && (stress_data.stress_iteration < num_stress_iters)) {
|
|
app_timer_register(1000, app_timer_cb, NULL); // allow for animation to complete
|
|
} else { // clean up state
|
|
flash_region_erase_optimal_range(region.begin, region.begin, region.end, region.end);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void stress_window_load(Window *data) {
|
|
Layer *layer = window_get_root_layer(data);
|
|
const int16_t width = layer->frame.size.w - ACTION_BAR_WIDTH - 3;
|
|
|
|
stress_data.text_layer = text_layer_create(GRect(4, 44, width, 60));
|
|
TextLayer *text_layer = stress_data.text_layer;
|
|
text_layer_set_font(text_layer, fonts_get_system_font(FONT_KEY_GOTHIC_28_BOLD));
|
|
text_layer_set_background_color(text_layer, GColorClear);
|
|
layer_add_child(layer, text_layer_get_layer(text_layer));
|
|
|
|
text_layer_set_text(stress_data.text_layer, "Starting Stress Test");
|
|
abort_stress_test = false;
|
|
app_timer_register(500, app_timer_cb, NULL);
|
|
};
|
|
|
|
static void stress_window_unload(Window *data) {
|
|
abort_stress_test = true;
|
|
}
|
|
|
|
static void file_system_stress_callback(int index, void *data) {
|
|
stress_data.stress_iteration = 0;
|
|
stress_data.stress_index = index;
|
|
window_init(&stress_data.window, WINDOW_NAME("Stress Test"));
|
|
window_set_user_data(&stress_data.window, &stress_data);
|
|
window_set_window_handlers(&stress_data.window, &(WindowHandlers) {
|
|
.load = stress_window_load,
|
|
.unload = stress_window_unload
|
|
});
|
|
app_window_stack_push(&stress_data.window, true);
|
|
}
|
|
|
|
static void populate_menu(SimpleMenuSection *menu_section, SimpleMenuItem *menu_items) {
|
|
for (unsigned int i = 0; i < NUM_REGIONS; ++i) {
|
|
menu_items[i] = (SimpleMenuItem) {
|
|
.title = s_flash_regions[i].name,
|
|
.callback = menu_select_callback,
|
|
};
|
|
}
|
|
menu_items[FILE_WRITE_STRESS] = (SimpleMenuItem) {
|
|
.title = "File Stress",
|
|
.callback = file_system_stress_callback,
|
|
};
|
|
menu_items[FILE_SUBSECTOR_STRESS] = (SimpleMenuItem) {
|
|
.title = "Subsector Stress",
|
|
.callback = file_system_stress_callback,
|
|
};
|
|
|
|
menu_section->num_items = NUM_MENU_ITEMS;
|
|
menu_section->items = menu_items;
|
|
menu_section->title = "Flash Regions";
|
|
}
|
|
|
|
static void prv_window_load(Window *window) {
|
|
FlashDiagAppData *data = window_get_user_data(window);
|
|
populate_menu(&data->menu_section, data->menu_items);
|
|
Layer *root_layer = window_get_root_layer(window);
|
|
const GRect *bounds = &root_layer->bounds;
|
|
simple_menu_layer_init(&data->menu_layer, bounds, window, &data->menu_section, 1, NULL);
|
|
layer_add_child(root_layer, simple_menu_layer_get_layer(&data->menu_layer));
|
|
}
|
|
|
|
static void push_window(FlashDiagAppData *data) {
|
|
Window *window = &data->window;
|
|
window_init(window, WINDOW_NAME("Flash Diagnostic"));
|
|
window_set_user_data(window, data);
|
|
window_set_window_handlers(window, &(WindowHandlers) {
|
|
.load = prv_window_load,
|
|
});
|
|
const bool animated = true;
|
|
app_window_stack_push(window, animated);
|
|
}
|
|
|
|
////////////////////
|
|
// App boilerplate
|
|
static void handle_init(void) {
|
|
FlashDiagAppData *data = (FlashDiagAppData*) app_malloc_check(sizeof(FlashDiagAppData));
|
|
if (data == NULL) {
|
|
PBL_CROAK("Out of memory");
|
|
}
|
|
app_state_set_user_data(data);
|
|
push_window(data);
|
|
}
|
|
|
|
static void handle_deinit(void) {
|
|
FlashDiagAppData *data = app_state_get_user_data();
|
|
simple_menu_layer_deinit(&data->menu_layer);
|
|
app_free(data);
|
|
}
|
|
|
|
static void s_main(void) {
|
|
if (resource_storage_flash_get_unused_bank()->begin ==
|
|
s_flash_regions[0].begin) {
|
|
s_flash_regions[0].name = "Unused Resources";
|
|
} else {
|
|
s_flash_regions[1].name = "Unused Resources";
|
|
}
|
|
|
|
handle_init();
|
|
|
|
app_event_loop();
|
|
|
|
handle_deinit();
|
|
}
|
|
|
|
const PebbleProcessMd* flash_diagnostic_app_get_info() {
|
|
static const PebbleProcessMdSystem s_flash_diagnostic_app_info = {
|
|
.common.main_func = s_main,
|
|
.name = "Flash Diagnostic"
|
|
};
|
|
return (const PebbleProcessMd*) &s_flash_diagnostic_app_info;
|
|
}
|