mirror of
https://github.com/google/pebble.git
synced 2025-05-27 13:33:12 +00:00
Import of the watch repository from Pebble
This commit is contained in:
commit
3b92768480
10334 changed files with 2564465 additions and 0 deletions
39
src/fw/mfg/mfg_apps/mfg_app_registry.h
Normal file
39
src/fw/mfg/mfg_apps/mfg_app_registry.h
Normal file
|
@ -0,0 +1,39 @@
|
|||
/*
|
||||
* 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 "process_management/pebble_process_md.h"
|
||||
|
||||
#include "mfg/mfg_mode/mfg_factory_mode.h"
|
||||
#include "mfg/mfg_apps/mfg_display_burnin.h"
|
||||
#include "mfg/mfg_apps/mfg_button_app.h"
|
||||
#include "mfg/mfg_apps/mfg_bt_test_app.h"
|
||||
#include "mfg/mfg_apps/mfg_display_app.h"
|
||||
#include "mfg/mfg_apps/mfg_runin_app.h"
|
||||
#include "mfg/mfg_apps/mfg_func_test.h"
|
||||
|
||||
typedef const struct PebbleProcessMd* (*MfgInitFuncType)(void);
|
||||
|
||||
static const MfgInitFuncType INIT_MFG_FUNCTIONS[] = {
|
||||
&mfg_app_button_test_get_info,
|
||||
&mfg_app_runin_get_info,
|
||||
&mfg_display_burnin_get_app_info,
|
||||
&mfg_app_bt_test_get_info,
|
||||
&mfg_app_lcd_test_black_get_info,
|
||||
&mfg_app_lcd_test_white_get_info,
|
||||
&mfg_app_lcd_test_black_white_border_get_info,
|
||||
&mfg_app_lcd_test_cycle_get_info,
|
||||
&mfg_func_test_get_app_info,
|
||||
};
|
225
src/fw/mfg/mfg_apps/mfg_bt_test_app.c
Normal file
225
src/fw/mfg/mfg_apps/mfg_bt_test_app.c
Normal file
|
@ -0,0 +1,225 @@
|
|||
/*
|
||||
* 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 "mfg_bt_test_app.h"
|
||||
|
||||
#include "applib/app.h"
|
||||
#include "applib/fonts/fonts.h"
|
||||
#include "applib/ui/ui.h"
|
||||
#include "applib/ui/window_private.h"
|
||||
#include "process_state/app_state/app_state.h"
|
||||
#include "process_management/process_manager.h"
|
||||
|
||||
#include "kernel/pbl_malloc.h"
|
||||
#include "kernel/util/sleep.h"
|
||||
#include "services/common/bluetooth/bt_compliance_tests.h"
|
||||
#include "services/common/new_timer/new_timer.h"
|
||||
#include "services/common/system_task.h"
|
||||
#include "system/passert.h"
|
||||
#include "system/reboot_reason.h"
|
||||
#include "system/reset.h"
|
||||
|
||||
EventServiceInfo bt_state_change_event_info;
|
||||
|
||||
typedef enum {
|
||||
BtTestStateInit = 0,
|
||||
BtTestStateStopped,
|
||||
BtTestStateStarting,
|
||||
BtTestStateStopping,
|
||||
BtTestStateStarted,
|
||||
BtTestStateFailed,
|
||||
BtTestStateResetting,
|
||||
|
||||
BtTestStateNumStates,
|
||||
} BtTestState;
|
||||
|
||||
static const char* status_text[] = {
|
||||
[BtTestStateInit] = "Initializing",
|
||||
[BtTestStateStopped] = "Stopped",
|
||||
[BtTestStateStarting] = "Starting",
|
||||
[BtTestStateStopping] = "Stopping",
|
||||
[BtTestStateStarted] = "Started",
|
||||
[BtTestStateFailed] = "Failed",
|
||||
[BtTestStateResetting] = "Resetting",
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
Window window;
|
||||
TextLayer title;
|
||||
TextLayer status;
|
||||
BtTestState test_state;
|
||||
TimerID reset_timer;
|
||||
} AppData;
|
||||
|
||||
static void update_text_layers_callback(void *data) {
|
||||
AppData *app_data = data;
|
||||
TextLayer *status = &app_data->status;
|
||||
text_layer_set_text(status, status_text[app_data->test_state]);
|
||||
}
|
||||
|
||||
static void prv_bt_event_handler(PebbleEvent *e, void* data) {
|
||||
AppData *app_data = (AppData*)data;
|
||||
|
||||
switch (app_data->test_state) {
|
||||
case BtTestStateStarting: {
|
||||
PBL_ASSERTN(!bt_ctl_is_bluetooth_active());
|
||||
if (bt_test_bt_sig_rf_test_mode()) {
|
||||
app_data->test_state = BtTestStateStarted;
|
||||
} else {
|
||||
app_data->test_state = BtTestStateFailed;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case BtTestStateStopping: {
|
||||
PBL_ASSERTN(bt_ctl_is_bluetooth_active());
|
||||
app_data->test_state = BtTestStateStopped;
|
||||
break;
|
||||
}
|
||||
case BtTestStateStopped:
|
||||
case BtTestStateStarted:
|
||||
break;
|
||||
default:
|
||||
WTF;
|
||||
}
|
||||
|
||||
process_manager_send_callback_event_to_process(PebbleTask_App, update_text_layers_callback, (void*)data);
|
||||
}
|
||||
|
||||
static void select_single_click_handler(ClickRecognizerRef recognizer, Window *window) {
|
||||
AppData *data = app_state_get_user_data();
|
||||
BtTestState new_state = data->test_state;
|
||||
|
||||
PBL_ASSERTN(data->test_state < BtTestStateNumStates);
|
||||
|
||||
switch (data->test_state) {
|
||||
case BtTestStateStopped: {
|
||||
new_state = BtTestStateStarting;
|
||||
bt_ctl_set_override_mode(BtCtlModeOverrideStop);
|
||||
break;
|
||||
}
|
||||
case BtTestStateStarted: {
|
||||
new_state = BtTestStateStopping;
|
||||
bt_ctl_set_override_mode(BtCtlModeOverrideRun);
|
||||
break;
|
||||
}
|
||||
case BtTestStateStarting:
|
||||
case BtTestStateStopping:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
data->test_state = new_state;
|
||||
update_text_layers_callback(data);
|
||||
}
|
||||
|
||||
static void bt_test_reset_callback(void *timer_data) {
|
||||
RebootReason reason = { RebootReasonCode_MfgShutdown, 0 };
|
||||
reboot_reason_set(&reason);
|
||||
system_reset();
|
||||
}
|
||||
|
||||
static void back_single_click_handler(ClickRecognizerRef recognizer, Window *window) {
|
||||
AppData *app_data = app_state_get_user_data();
|
||||
|
||||
app_data->test_state = BtTestStateResetting;
|
||||
text_layer_set_text(&app_data->status, status_text[app_data->test_state]);
|
||||
|
||||
if (app_data->reset_timer == TIMER_INVALID_ID) {
|
||||
bool success = false;
|
||||
app_data->reset_timer = new_timer_create();
|
||||
if (app_data->reset_timer == TIMER_INVALID_ID) {
|
||||
success = new_timer_start(app_data->reset_timer, 500, bt_test_reset_callback, app_data, 0 /*flags*/);
|
||||
}
|
||||
|
||||
if (app_data->reset_timer == TIMER_INVALID_ID || !success) {
|
||||
bt_test_reset_callback(app_data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void config_provider(Window *window) {
|
||||
// single click / repeat-on-hold config:
|
||||
window_single_click_subscribe(BUTTON_ID_SELECT, (ClickHandler) select_single_click_handler);
|
||||
window_single_click_subscribe(BUTTON_ID_BACK, (ClickHandler) back_single_click_handler);
|
||||
}
|
||||
|
||||
static void handle_init() {
|
||||
AppData *data = task_malloc_check(sizeof(AppData));
|
||||
|
||||
app_state_set_user_data(data);
|
||||
|
||||
*data = (AppData) {
|
||||
.test_state = BtTestStateInit,
|
||||
.reset_timer = TIMER_INVALID_ID,
|
||||
};
|
||||
|
||||
Window *window = &data->window;
|
||||
window_init(window, "BT Test");
|
||||
|
||||
// want to indicate resetting.
|
||||
window_set_overrides_back_button(window, true);
|
||||
|
||||
TextLayer *title = &data->title;
|
||||
text_layer_init(title, &window->layer.bounds);
|
||||
text_layer_set_font(title, fonts_get_system_font(FONT_KEY_GOTHIC_28_BOLD));
|
||||
text_layer_set_text(title, "BT Test Mode");
|
||||
layer_add_child(&window->layer, &title->layer);
|
||||
|
||||
TextLayer *status = &data->status;
|
||||
text_layer_init(status,
|
||||
&GRect(0, 50, window->layer.bounds.size.w, window->layer.bounds.size.h - 30));
|
||||
text_layer_set_font(status, fonts_get_system_font(FONT_KEY_GOTHIC_24_BOLD));
|
||||
text_layer_set_text(status, status_text[data->test_state]);
|
||||
layer_add_child(&window->layer, &status->layer);
|
||||
|
||||
window_set_click_config_provider(window, (ClickConfigProvider) config_provider);
|
||||
|
||||
app_window_stack_push(window, true /* Animated */);
|
||||
|
||||
bt_state_change_event_info = (EventServiceInfo) {
|
||||
.type = PEBBLE_BT_STATE_EVENT,
|
||||
.handler = prv_bt_event_handler,
|
||||
.context = data,
|
||||
};
|
||||
|
||||
event_service_client_subscribe(&bt_state_change_event_info);
|
||||
bt_ctl_set_override_mode(BtCtlModeOverrideRun);
|
||||
bt_ctl_reset_bluetooth();
|
||||
}
|
||||
|
||||
static void handle_deinit() {
|
||||
AppData *data = app_state_get_user_data();
|
||||
bt_ctl_set_override_mode(BtCtlModeOverrideNone);
|
||||
event_service_client_unsubscribe(&bt_state_change_event_info);
|
||||
task_free(data);
|
||||
}
|
||||
|
||||
static void s_main(void) {
|
||||
handle_init();
|
||||
|
||||
app_event_loop();
|
||||
|
||||
handle_deinit();
|
||||
}
|
||||
|
||||
static const PebbleProcessMdSystem s_mfg_bt_test_info = {
|
||||
.common.main_func = &s_main,
|
||||
.name = "BT Test"
|
||||
};
|
||||
|
||||
const PebbleProcessMd* mfg_app_bt_test_get_info() {
|
||||
return (const PebbleProcessMd*) &s_mfg_bt_test_info;
|
||||
}
|
21
src/fw/mfg/mfg_apps/mfg_bt_test_app.h
Normal file
21
src/fw/mfg/mfg_apps/mfg_bt_test_app.h
Normal file
|
@ -0,0 +1,21 @@
|
|||
/*
|
||||
* 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 "process_management/pebble_process_md.h"
|
||||
|
||||
const PebbleProcessMd* mfg_app_bt_test_get_info();
|
118
src/fw/mfg/mfg_apps/mfg_display_burnin.c
Normal file
118
src/fw/mfg/mfg_apps/mfg_display_burnin.c
Normal file
|
@ -0,0 +1,118 @@
|
|||
/*
|
||||
* 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 "mfg_display_burnin.h"
|
||||
|
||||
#include "applib/app.h"
|
||||
#include "applib/app_timer.h"
|
||||
#include "applib/ui/app_window_stack.h"
|
||||
#include "process_state/app_state/app_state.h"
|
||||
|
||||
#include "drivers/display/display.h"
|
||||
#include "kernel/pbl_malloc.h"
|
||||
#include "util/units.h"
|
||||
|
||||
#include <stddef.h>
|
||||
#include <string.h>
|
||||
|
||||
typedef struct {
|
||||
Window window;
|
||||
Layer background;
|
||||
InverterLayer inverter_layer;
|
||||
uint32_t old_display_hz;
|
||||
} MfgDisplayBurninAppData;
|
||||
|
||||
static void draw_checkerboard(Layer* background, GContext* c) {
|
||||
const int height = background->bounds.size.h;
|
||||
const int width = background->bounds.size.w;
|
||||
for(int y = 0; y < height; y += 4) {
|
||||
for(int x = 0; x < width; x += 4) {
|
||||
graphics_context_set_stroke_color(c, GColorBlack);
|
||||
graphics_draw_pixel(c, GPoint(x,y));
|
||||
graphics_draw_pixel(c, GPoint(x+1,y));
|
||||
graphics_draw_pixel(c, GPoint(x,y+1));
|
||||
graphics_draw_pixel(c, GPoint(x+1,y+1));
|
||||
graphics_draw_pixel(c, GPoint(x+2,y+2));
|
||||
graphics_draw_pixel(c, GPoint(x+3,y+2));
|
||||
graphics_draw_pixel(c, GPoint(x+2,y+3));
|
||||
graphics_draw_pixel(c, GPoint(x+3,y+3));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void handle_timer(void *timer_data) {
|
||||
MfgDisplayBurninAppData *data = app_state_get_user_data();
|
||||
|
||||
layer_set_hidden((Layer*)&data->inverter_layer,
|
||||
!layer_get_hidden((Layer*)&data->inverter_layer));
|
||||
app_timer_register(100 /* milliseconds */, handle_timer, NULL);
|
||||
}
|
||||
|
||||
static void handle_init(void) {
|
||||
MfgDisplayBurninAppData *data = task_malloc_check(sizeof(MfgDisplayBurninAppData));
|
||||
|
||||
app_state_set_user_data(data);
|
||||
|
||||
// Overclock the display to 4MHz make the artifacts issue more likely to happen
|
||||
data->old_display_hz = display_baud_rate_change(MHZ_TO_HZ(4));
|
||||
|
||||
window_init(&data->window, "Display Burnin");
|
||||
window_set_fullscreen(&data->window, true);
|
||||
app_window_stack_push(&data->window, true /* Animated */);
|
||||
|
||||
layer_init(&data->background, &data->window.layer.bounds);
|
||||
data->background.update_proc = (LayerUpdateProc) draw_checkerboard;
|
||||
layer_add_child(&data->window.layer, (Layer*) &data->background);
|
||||
|
||||
inverter_layer_init(&data->inverter_layer, &data->window.layer.bounds);
|
||||
layer_add_child(&data->window.layer, (Layer*) &data->inverter_layer);
|
||||
|
||||
app_timer_register(100 /* milliseconds */, handle_timer, NULL);
|
||||
}
|
||||
|
||||
static void handle_deinit(void) {
|
||||
MfgDisplayBurninAppData *data = app_state_get_user_data();
|
||||
|
||||
display_baud_rate_change(data->old_display_hz);
|
||||
|
||||
task_free(data);
|
||||
}
|
||||
|
||||
static void s_main(void) {
|
||||
handle_init();
|
||||
|
||||
app_event_loop();
|
||||
|
||||
handle_deinit();
|
||||
}
|
||||
|
||||
static const PebbleProcessMdSystem s_mfg_func_test = {
|
||||
.common = {
|
||||
// UUID: 1bef4e93-5ec4-4af8-9eff-196eaf25b92b
|
||||
.uuid = {0x1b, 0xef, 0x4e, 0x93, 0x5e, 0xc4, 0x4a, 0xf8, 0x9e, 0xff, 0x19, 0x6e, 0xaf, 0x25, 0xb9, 0x2b},
|
||||
.main_func = s_main
|
||||
},
|
||||
.name = "Display Burn-in"
|
||||
};
|
||||
|
||||
const Uuid* mfg_display_burnin_get_uuid() {
|
||||
return &s_mfg_func_test.common.uuid;
|
||||
}
|
||||
|
||||
const PebbleProcessMd* mfg_display_burnin_get_app_info() {
|
||||
return (const PebbleProcessMd*) &s_mfg_func_test;
|
||||
}
|
26
src/fw/mfg/mfg_apps/mfg_display_burnin.h
Normal file
26
src/fw/mfg/mfg_apps/mfg_display_burnin.h
Normal file
|
@ -0,0 +1,26 @@
|
|||
/*
|
||||
* 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 "process_management/pebble_process_md.h"
|
||||
#include "applib/ui/ui.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
const PebbleProcessMd* mfg_display_burnin_get_app_info();
|
||||
|
||||
const Uuid* mfg_display_burnin_get_uuid();
|
768
src/fw/mfg/mfg_apps/mfg_flash_test.c
Normal file
768
src/fw/mfg/mfg_apps/mfg_flash_test.c
Normal file
|
@ -0,0 +1,768 @@
|
|||
/*
|
||||
* 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
|
55
src/fw/mfg/mfg_apps/mfg_flash_test.h
Normal file
55
src/fw/mfg/mfg_apps/mfg_flash_test.h
Normal file
|
@ -0,0 +1,55 @@
|
|||
/*
|
||||
* 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 <stdint.h>
|
||||
|
||||
// To add new tests, update the following:
|
||||
// 1. FlashTestCaseType enum
|
||||
// 2. Update run_flash_test_case to include test
|
||||
// 3. Update test_window_load in flash_test.c with test case name
|
||||
// 4. Update flash_test_window_load in flash_test.c with test case menu item
|
||||
// 5. Update prompt_commands.c if necessary
|
||||
typedef enum {
|
||||
FLASH_TEST_CASE_RUN_DATA_TEST = 0,
|
||||
FLASH_TEST_CASE_RUN_ADDR_TEST = 1,
|
||||
FLASH_TEST_CASE_RUN_STRESS_ADDR_TEST = 2,
|
||||
FLASH_TEST_CASE_RUN_PERF_DATA_TEST = 3,
|
||||
FLASH_TEST_CASE_RUN_SWITCH_MODE_ASYNC = 4,
|
||||
FLASH_TEST_CASE_RUN_SWITCH_MODE_SYNC_BURST = 5,
|
||||
// Add new test cases above this line
|
||||
FLASH_TEST_CASE_NUM_MENU_ITEMS
|
||||
} FlashTestCaseType;
|
||||
|
||||
typedef enum {
|
||||
FLASH_TEST_SUCCESS = 0,
|
||||
FLASH_TEST_ERR_OTHER = -1,
|
||||
FLASH_TEST_ERR_ERASE = -2,
|
||||
FLASH_TEST_ERR_DATA_WRITE = -3,
|
||||
FLASH_TEST_ERR_ADDR_RANGE = -4,
|
||||
FLASH_TEST_ERR_STUCK_AT_HIGH = -5,
|
||||
FLASH_TEST_ERR_STUCK_AT_LOW = -6,
|
||||
FLASH_TEST_ERR_OOM = -7,
|
||||
FLASH_TEST_ERR_UNSUPPORTED = -8,
|
||||
FLASH_TEST_ERR_SKIP = -9,
|
||||
} FlashTestErrorType;
|
||||
|
||||
// This function explicitly stop a test case if it is currently running. Currently this only affects
|
||||
// the stress test.
|
||||
extern void stop_flash_test_case( void );
|
||||
|
||||
extern FlashTestErrorType run_flash_test_case(FlashTestCaseType test_case_num, uint32_t iterations);
|
173
src/fw/mfg/mfg_apps/mfg_func_test_battery.h
Normal file
173
src/fw/mfg/mfg_apps/mfg_func_test_battery.h
Normal file
|
@ -0,0 +1,173 @@
|
|||
/*
|
||||
* 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
|
||||
|
||||
// FIXME: Wha? We don't use this file directly.
|
||||
#include "mfg_func_test_buttons.h"
|
||||
|
||||
#include "kernel/events.h"
|
||||
#include "applib/fonts/fonts.h"
|
||||
#include "applib/ui/ui.h"
|
||||
#include "drivers/battery.h"
|
||||
#include "services/common/battery/battery_monitor.h"
|
||||
#include "services/common/new_timer/new_timer.h"
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
typedef struct {
|
||||
MfgFuncTestData *app_data;
|
||||
char text_top[32];
|
||||
TextLayer text_layer_top;
|
||||
TextLayer text_layer_center;
|
||||
char text_volt[16];
|
||||
TextLayer text_layer_volt;
|
||||
PathLayer bolt;
|
||||
TimerID poll_timer_id;
|
||||
} BatteryTestData;
|
||||
|
||||
static void battery_polling_callback(void *timer_data) {
|
||||
BatteryTestData *data = (BatteryTestData*)timer_data;
|
||||
|
||||
sprintf(data->text_volt, "%i mV", battery_get_millivolts());
|
||||
text_layer_set_text(&data->text_layer_volt, data->text_volt);
|
||||
|
||||
const bool is_discharging = !battery_get_charge_state().is_charging;
|
||||
layer_set_hidden(&data->bolt.layer, is_discharging);
|
||||
layer_set_hidden(&data->text_layer_center.layer, !is_discharging);
|
||||
}
|
||||
|
||||
static void stop_battery_polling(BatteryTestData *data) {
|
||||
if (data == NULL || data->poll_timer_id == TIMER_INVALID_ID) {
|
||||
return;
|
||||
}
|
||||
new_timer_delete(data->poll_timer_id);
|
||||
data->poll_timer_id = TIMER_INVALID_ID;
|
||||
}
|
||||
|
||||
static void start_battery_polling(BatteryTestData *data) {
|
||||
if (data == NULL || data->poll_timer_id != TIMER_INVALID_ID) {
|
||||
stop_battery_polling(data);
|
||||
}
|
||||
const uint32_t poll_interval_ms = 300;
|
||||
if (data->poll_timer_id == TIMER_INVALID_ID) {
|
||||
data->poll_timer_id = new_timer_create();
|
||||
}
|
||||
new_timer_start(data->poll_timer_id, poll_interval_ms, battery_polling_callback, data, TIMER_START_FLAG_REPEATING);
|
||||
}
|
||||
|
||||
static void battery_window_button_up(ClickRecognizerRef recognizer, void *context) {
|
||||
BatteryTestData *data = window_get_user_data(context);
|
||||
if (data->app_data->charge_test_done) {
|
||||
const bool animated = false;
|
||||
window_stack_pop(animated);
|
||||
} else {
|
||||
if (battery_get_charge_state().is_charging) {
|
||||
mfg_func_test_append_bits(MfgFuncTestBitChargeTestPassed);
|
||||
data->app_data->charge_test_done = true;
|
||||
stop_battery_polling(data);
|
||||
layer_set_hidden(&data->text_layer_volt.layer, true);
|
||||
layer_set_hidden(&data->bolt.layer, true);
|
||||
text_layer_set_text(&data->text_layer_center, "QC OK!");
|
||||
layer_set_hidden(&data->text_layer_center.layer, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void battery_window_click_config_provider(void *context) {
|
||||
for (ButtonId button_id = BUTTON_ID_BACK; button_id < NUM_BUTTONS; ++button_id) {
|
||||
window_raw_click_subscribe(button_id, NULL, (ClickHandler) battery_window_button_up, context);
|
||||
}
|
||||
}
|
||||
|
||||
static void battery_window_load(Window *window) {
|
||||
BatteryTestData *data = window_get_user_data(window);
|
||||
|
||||
// Layout Battery Test Window:
|
||||
Layer *root = &window->layer;
|
||||
|
||||
char addr_hex_str[BT_ADDR_FMT_BUFFER_SIZE_BYTES];
|
||||
bt_local_id_copy_address_hex_string(addr_hex_str);
|
||||
sprintf(data->text_top, "Quality Test\nMAC: %s", addr_hex_str);
|
||||
|
||||
TextLayer *text_layer_top = &data->text_layer_top;
|
||||
text_layer_init(text_layer_top, &GRect(0, 0, 144, 168));
|
||||
text_layer_set_background_color(text_layer_top, GColorWhite);
|
||||
text_layer_set_text_color(text_layer_top, GColorBlack);
|
||||
text_layer_set_text(text_layer_top, data->text_top);
|
||||
text_layer_set_font(text_layer_top, fonts_get_system_font(FONT_KEY_GOTHIC_18_BOLD));
|
||||
layer_add_child(root, &text_layer_top->layer);
|
||||
|
||||
TextLayer *text_layer_center = &data->text_layer_center;
|
||||
text_layer_init(text_layer_center, &GRect(0, 60, 144, 40));
|
||||
text_layer_set_background_color(text_layer_center, GColorClear);
|
||||
text_layer_set_text_color(text_layer_center, GColorBlack);
|
||||
text_layer_set_text(text_layer_center, "Plug Charger");
|
||||
text_layer_set_font(text_layer_center, fonts_get_system_font(FONT_KEY_GOTHIC_28_BOLD));
|
||||
layer_add_child(root, &text_layer_center->layer);
|
||||
|
||||
TextLayer *text_layer_volt = &data->text_layer_volt;
|
||||
text_layer_init(text_layer_volt, &GRect(0, 128, 144, 40));
|
||||
text_layer_set_background_color(text_layer_volt, GColorBlack);
|
||||
text_layer_set_text_color(text_layer_volt, GColorWhite);
|
||||
text_layer_set_font(text_layer_volt, fonts_get_system_font(FONT_KEY_GOTHIC_28_BOLD));
|
||||
layer_add_child(root, &text_layer_volt->layer);
|
||||
|
||||
PathLayer *bolt = &data->bolt;
|
||||
path_layer_init(bolt, &BOLT_PATH_INFO);
|
||||
path_layer_set_fill_color(bolt, GColorBlack);
|
||||
path_layer_set_stroke_color(bolt, GColorClear);
|
||||
layer_set_frame(&bolt->layer, &GRect(58, 48, 28, 60));
|
||||
layer_set_hidden(&bolt->layer, true);
|
||||
layer_add_child(root, &bolt->layer);
|
||||
}
|
||||
|
||||
static void battery_window_appear(Window *window) {
|
||||
BatteryTestData *data = window_get_user_data(window);
|
||||
start_battery_polling(data);
|
||||
}
|
||||
|
||||
static void battery_window_disappear(Window *window) {
|
||||
BatteryTestData *data = window_get_user_data(window);
|
||||
stop_battery_polling(data);
|
||||
}
|
||||
|
||||
static void push_battery_test_window(MfgFuncTestData *app_data) {
|
||||
static BatteryTestData s_battery_test_data;
|
||||
s_battery_test_data = (BatteryTestData) {
|
||||
.app_data = app_data,
|
||||
.poll_timer_id = TIMER_INVALID_ID,
|
||||
};
|
||||
|
||||
// Battery Charge test window:
|
||||
Window *battery_window = &app_data->battery_window;
|
||||
window_init(battery_window, WINDOW_NAME("Mfg Func Test Battery"));
|
||||
window_set_overrides_back_button(battery_window, true);
|
||||
window_set_user_data(battery_window, app_data);
|
||||
window_set_click_config_provider_with_context(battery_window,
|
||||
(ClickConfigProvider) battery_window_click_config_provider, battery_window);
|
||||
window_set_window_handlers(battery_window, &(WindowHandlers) {
|
||||
.load = battery_window_load,
|
||||
.appear = battery_window_appear,
|
||||
.disappear = battery_window_disappear
|
||||
});
|
||||
window_set_user_data(battery_window, &s_battery_test_data);
|
||||
window_set_fullscreen(battery_window, true);
|
||||
|
||||
const bool animated = false;
|
||||
window_stack_push(battery_window, animated);
|
||||
}
|
||||
|
49
src/fw/mfg/mfg_apps/mfg_func_test_black.h
Normal file
49
src/fw/mfg/mfg_apps/mfg_func_test_black.h
Normal file
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
* 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 "mfg_func_test_buttons.h"
|
||||
#include "applib/ui/ui.h"
|
||||
|
||||
static void black_window_button_up(ClickRecognizerRef recognizer, Window *window) {
|
||||
MfgFuncTestData *app_data = (MfgFuncTestData*)window_get_user_data(window);
|
||||
|
||||
mfg_func_test_append_bits(MfgFuncTestBitBlackTestPassed);
|
||||
app_data->black_test_done = true;
|
||||
vibes_short_pulse();
|
||||
|
||||
const bool animated = false;
|
||||
window_stack_pop(animated);
|
||||
}
|
||||
|
||||
static void black_window_click_config_provider(void *context) {
|
||||
for (ButtonId button_id = BUTTON_ID_BACK; button_id < NUM_BUTTONS; ++button_id) {
|
||||
window_raw_click_subscribe(button_id, NULL, (ClickHandler) black_window_button_up, context);
|
||||
}
|
||||
}
|
||||
|
||||
static void push_black_test_window(MfgFuncTestData *app_data) {
|
||||
Window *black_window = &app_data->black_window;
|
||||
window_init(black_window, WINDOW_NAME("Mfg Func Test Black"));
|
||||
window_set_background_color(black_window, GColorBlack);
|
||||
window_set_click_config_provider_with_context(black_window,
|
||||
(ClickConfigProvider) black_window_click_config_provider, black_window);
|
||||
window_set_user_data(black_window, app_data);
|
||||
window_set_fullscreen(black_window, true);
|
||||
|
||||
const bool animated = false;
|
||||
window_stack_push(black_window, animated);
|
||||
}
|
132
src/fw/mfg/mfg_apps/mfg_func_test_buttons.h
Normal file
132
src/fw/mfg/mfg_apps/mfg_func_test_buttons.h
Normal file
|
@ -0,0 +1,132 @@
|
|||
/*
|
||||
* 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 "drivers/button.h"
|
||||
#include "drivers/backlight.h"
|
||||
|
||||
#include "util/trig.h"
|
||||
|
||||
#include "applib/ui/ui.h"
|
||||
#include "applib/ui/window_private.h"
|
||||
|
||||
#include "applib/fonts/fonts.h"
|
||||
|
||||
typedef struct {
|
||||
MfgFuncTestData *app_data;
|
||||
ButtonId button_id;
|
||||
TextLayer label;
|
||||
PathLayer arrow;
|
||||
} ButtonTestData;
|
||||
|
||||
static const GPathInfo ARROW_PATH_INFO = {
|
||||
.num_points = 7,
|
||||
.points = (GPoint []) {{0, 14}, {29, 14}, {29, 0}, {54, 25}, {29, 50}, {29, 36}, {0, 36}}
|
||||
};
|
||||
|
||||
static const GPathInfo BOLT_PATH_INFO = {
|
||||
.num_points = 6,
|
||||
.points = (GPoint []) {{21, 0}, {14, 26}, {28, 26}, {7, 60}, {14, 34}, {0, 34}}
|
||||
};
|
||||
|
||||
static void move_arrow_to_button(ButtonTestData *data, ButtonId id) {
|
||||
#define ARROW_SIZE {54, 50}
|
||||
static const GRect ARROW_RECTS[] = {
|
||||
{{2, 30}, ARROW_SIZE}, // BACK
|
||||
{{88, 2}, ARROW_SIZE}, // UP
|
||||
{{88, 59}, ARROW_SIZE}, // SELECT
|
||||
{{88, 116}, ARROW_SIZE}, // DOWN
|
||||
};
|
||||
layer_set_frame(&data->arrow.layer, &ARROW_RECTS[id]);
|
||||
gpath_rotate_to(&data->arrow.path, id == BUTTON_ID_BACK ? (TRIG_MAX_ANGLE / 2) : 0);
|
||||
gpath_move_to(&data->arrow.path, id == BUTTON_ID_BACK ? GPoint(54, 50) : GPoint(0, 0));
|
||||
}
|
||||
|
||||
static void button_window_button_up(ClickRecognizerRef recognizer, Window *window) {
|
||||
ButtonTestData *data = window_get_user_data(window);
|
||||
ButtonId button_id = click_recognizer_get_button_id(recognizer);
|
||||
|
||||
if (data->button_id == button_id) {
|
||||
++(data->button_id);
|
||||
if (data->button_id > BUTTON_ID_DOWN) {
|
||||
mfg_func_test_append_bits(MfgFuncTestBitButtonTestPassed);
|
||||
data->app_data->button_test_done = true;
|
||||
data->button_id = BUTTON_ID_BACK;
|
||||
const bool animated = false;
|
||||
window_stack_pop(animated);
|
||||
} else {
|
||||
move_arrow_to_button(data, data->button_id);
|
||||
}
|
||||
backlight_set_brightness(0);
|
||||
}
|
||||
}
|
||||
|
||||
static void button_window_button_down(ClickRecognizerRef recognizer, Window *window) {
|
||||
ButtonTestData *data = window_get_user_data(window);
|
||||
if (data->button_id == click_recognizer_get_button_id(recognizer)) {
|
||||
backlight_set_brightness(0xffff);
|
||||
}
|
||||
}
|
||||
|
||||
static void button_window_click_config_provider(void *context) {
|
||||
for (ButtonId button_id = BUTTON_ID_BACK; button_id < NUM_BUTTONS; ++button_id) {
|
||||
window_raw_click_subscribe(button_id,
|
||||
(ClickHandler) button_window_button_down, (ClickHandler) button_window_button_up, context);
|
||||
}
|
||||
}
|
||||
|
||||
static void button_window_load(Window *window) {
|
||||
ButtonTestData *data = window_get_user_data(window);
|
||||
Layer *root = &window->layer;
|
||||
|
||||
TextLayer *label = &data->label;
|
||||
text_layer_init(label, GRect(0, 0, 144, 40));
|
||||
text_layer_set_background_color(label, GColorClear);
|
||||
text_layer_set_text_color(label, GColorBlack);
|
||||
text_layer_set_text(label, "Press Button");
|
||||
text_layer_set_font(label, fonts_get_system_font(FONT_KEY_GOTHIC_28_BOLD));
|
||||
layer_add_child(root, &label->layer);
|
||||
|
||||
PathLayer *arrow = &data->arrow;
|
||||
path_layer_init(arrow, &ARROW_PATH_INFO);
|
||||
path_layer_set_fill_color(arrow, GColorBlack);
|
||||
path_layer_set_stroke_color(arrow, GColorClear);
|
||||
layer_add_child(root, &arrow->layer);
|
||||
|
||||
move_arrow_to_button(data, data->button_id);
|
||||
}
|
||||
|
||||
static void push_button_test_window(MfgFuncTestData *app_data) {
|
||||
static ButtonTestData s_button_test_data = {
|
||||
.button_id = BUTTON_ID_BACK,
|
||||
};
|
||||
|
||||
s_button_test_data.app_data = app_data;
|
||||
|
||||
Window *button_window = &app_data->button_window;
|
||||
window_init(button_window, WINDOW_NAME("Mfg Func Test Buttons"));
|
||||
window_set_overrides_back_button(button_window, true);
|
||||
window_set_click_config_provider_with_context(button_window,
|
||||
(ClickConfigProvider) button_window_click_config_provider, button_window);
|
||||
window_set_window_handlers(button_window, &(WindowHandlers) {
|
||||
.load = button_window_load
|
||||
});
|
||||
window_set_user_data(button_window, &s_button_test_data);
|
||||
window_set_fullscreen(button_window, true);
|
||||
const bool animated = false;
|
||||
window_stack_push(button_window, animated);
|
||||
}
|
133
src/fw/mfg/mfg_apps/mfg_func_test_version.h
Normal file
133
src/fw/mfg/mfg_apps/mfg_func_test_version.h
Normal file
|
@ -0,0 +1,133 @@
|
|||
/*
|
||||
* 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 "drivers/backlight.h"
|
||||
#include "drivers/button.h"
|
||||
#include "applib/fonts/fonts.h"
|
||||
#include "git_version.auto.h"
|
||||
#include "applib/graphics/utf8.h"
|
||||
#include "resource/resource.h"
|
||||
#include "resource/resource_ids.auto.h"
|
||||
#include "system/bootbits.h"
|
||||
#include "system/logging.h"
|
||||
#include "applib/ui/ui.h"
|
||||
#include "applib/ui/window_private.h"
|
||||
#include "system/version.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <inttypes.h>
|
||||
|
||||
typedef struct {
|
||||
MfgFuncTestData *app_data;
|
||||
TextLayer label;
|
||||
} VersionData;
|
||||
|
||||
static int s_click_count;
|
||||
static char* s_version_str;
|
||||
|
||||
static void version_window_button_up(ClickRecognizerRef recognizer, Window *window) {
|
||||
// The button-up event from launching the QC app will propagate to
|
||||
// this window, so instead we wait for two button-up events before
|
||||
// proceeding
|
||||
if (s_click_count++ > 0) {
|
||||
const bool animated = false;
|
||||
window_stack_pop(animated);
|
||||
}
|
||||
}
|
||||
|
||||
static void version_window_click_config_provider(void *context) {
|
||||
for (ButtonId button_id = BUTTON_ID_BACK; button_id < NUM_BUTTONS; ++button_id) {
|
||||
window_raw_click_subscribe(button_id, NULL, (ClickHandler) version_window_button_up, context);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void version_get_version_str(char* buf, int size) {
|
||||
FirmwareMetadata normal_fw;
|
||||
FirmwareMetadata recovery_fw;
|
||||
|
||||
version_copy_running_fw_metadata(&normal_fw);
|
||||
version_copy_recovery_fw_metadata(&recovery_fw);
|
||||
|
||||
ResourceVersion system_res = resource_get_system_version();
|
||||
|
||||
uint32_t bootloader_version = boot_version_read();
|
||||
uint32_t system_res_version = system_res.crc;
|
||||
|
||||
char* normal_version = (char*) normal_fw.version_short;
|
||||
if (!utf8_is_valid_string(normal_version)) {
|
||||
normal_version = "???";
|
||||
}
|
||||
|
||||
char* recovery_version = (char*) recovery_fw.version_short;
|
||||
if (!utf8_is_valid_string(recovery_version)) {
|
||||
recovery_version = "???";
|
||||
}
|
||||
|
||||
sniprintf(buf, size,
|
||||
"n:%s\nr:%s\nb:0x%"PRIx32"\ns:0x%"PRIx32,
|
||||
normal_version,
|
||||
recovery_version,
|
||||
bootloader_version,
|
||||
system_res_version);
|
||||
}
|
||||
|
||||
static void version_window_load(Window *window) {
|
||||
VersionData *data = window_get_user_data(window);
|
||||
Layer *root = &window->layer;
|
||||
|
||||
s_version_str = (char*) malloc(64);
|
||||
version_get_version_str(s_version_str, 64);
|
||||
|
||||
TextLayer *label = &data->label;
|
||||
text_layer_init(label, GRect(2, 2, 142, 164));
|
||||
text_layer_set_background_color(label, GColorClear);
|
||||
text_layer_set_text_color(label, GColorBlack);
|
||||
text_layer_set_text(label, s_version_str);
|
||||
text_layer_set_font(label, fonts_get_system_font(FONT_KEY_GOTHIC_28_BOLD));
|
||||
layer_add_child(root, &label->layer);
|
||||
|
||||
s_click_count = 0;
|
||||
}
|
||||
|
||||
static void version_window_unload(Window* window) {
|
||||
free(s_version_str);
|
||||
(void)window;
|
||||
}
|
||||
|
||||
static void push_version_window(MfgFuncTestData *app_data) {
|
||||
static VersionData s_version_data;
|
||||
s_version_data.app_data = app_data;
|
||||
|
||||
Window* version_window = &app_data->version_window;
|
||||
window_init(version_window, WINDOW_NAME("Mfg Func Test Version"));
|
||||
window_set_overrides_back_button(version_window, true);
|
||||
window_set_click_config_provider_with_context(version_window,
|
||||
(ClickConfigProvider) version_window_click_config_provider, version_window);
|
||||
window_set_window_handlers(version_window, &(WindowHandlers) {
|
||||
.load = version_window_load,
|
||||
.unload = version_window_unload
|
||||
});
|
||||
window_set_user_data(version_window, &s_version_data);
|
||||
window_set_fullscreen(version_window, true);
|
||||
|
||||
const bool animated = false;
|
||||
window_stack_push(version_window, animated);
|
||||
}
|
||||
|
194
src/fw/mfg/mfg_command.c
Normal file
194
src/fw/mfg/mfg_command.c
Normal file
|
@ -0,0 +1,194 @@
|
|||
/*
|
||||
* 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 "mfg_command.h"
|
||||
|
||||
#include "applib/app_launch_reason.h"
|
||||
#include "applib/app_watch_info.h"
|
||||
#include "console/prompt.h"
|
||||
#include "kernel/util/standby.h"
|
||||
#include "mfg/mfg_info.h"
|
||||
#include "process_management/app_install_manager.h"
|
||||
#include "process_management/app_manager.h"
|
||||
|
||||
void command_enter_standby(void) {
|
||||
enter_standby(RebootReasonCode_MfgShutdown);
|
||||
}
|
||||
|
||||
void command_color_read(void) {
|
||||
char buffer[10];
|
||||
prompt_send_response_fmt(buffer, sizeof(buffer), "%d", mfg_info_get_watch_color());
|
||||
}
|
||||
|
||||
void command_color_write(const char* color_num) {
|
||||
char *end;
|
||||
int color = strtol(color_num, &end, 10);
|
||||
|
||||
if (*end) {
|
||||
prompt_send_response("Invalid color");
|
||||
return;
|
||||
}
|
||||
|
||||
mfg_info_set_watch_color(color);
|
||||
|
||||
const WatchInfoColor written_color = mfg_info_get_watch_color();
|
||||
if (written_color == color) {
|
||||
prompt_send_response("OK");
|
||||
} else {
|
||||
prompt_send_response("ERROR");
|
||||
}
|
||||
}
|
||||
|
||||
void command_disp_offset_read(void) {
|
||||
char buffer[16];
|
||||
prompt_send_response_fmt(buffer, sizeof(buffer), "X: %"PRId16" Y: %"PRId16,
|
||||
mfg_info_get_disp_offsets().x,
|
||||
mfg_info_get_disp_offsets().y);
|
||||
}
|
||||
|
||||
void command_disp_offset_write(const char* offset_x_str, const char* offset_y_str) {
|
||||
char *nonnumeric_x, *nonnumeric_y;
|
||||
int8_t offset_x = strtol(offset_x_str, &nonnumeric_x, 10);
|
||||
if (*nonnumeric_x) {
|
||||
prompt_send_response("Invalid x offset");
|
||||
}
|
||||
|
||||
int8_t offset_y = strtol(offset_y_str, &nonnumeric_y, 10);
|
||||
if (*nonnumeric_y) {
|
||||
prompt_send_response("Invalid y offset");
|
||||
}
|
||||
|
||||
if (!*nonnumeric_x && !*nonnumeric_y) {
|
||||
mfg_info_set_disp_offsets((GPoint) {offset_x, offset_y});
|
||||
}
|
||||
}
|
||||
|
||||
void command_rtcfreq_read(void) {
|
||||
char buffer[10];
|
||||
prompt_send_response_fmt(buffer, sizeof(buffer), "%"PRIu32, mfg_info_get_rtc_freq());
|
||||
}
|
||||
|
||||
void command_rtcfreq_write(const char* rtc_freq_string) {
|
||||
char *end;
|
||||
uint32_t rtc_freq = strtol(rtc_freq_string, &end, 10);
|
||||
|
||||
if (*end) {
|
||||
prompt_send_response("Invalid rtcfreq");
|
||||
return;
|
||||
}
|
||||
|
||||
mfg_info_set_rtc_freq(rtc_freq);
|
||||
}
|
||||
|
||||
void command_model_read(void) {
|
||||
char model_buffer[MFG_INFO_MODEL_STRING_LENGTH];
|
||||
mfg_info_get_model(model_buffer);
|
||||
|
||||
// Just send it straight out, as it's already null-terminated
|
||||
prompt_send_response(model_buffer);
|
||||
}
|
||||
|
||||
void command_model_write(const char* model) {
|
||||
// mfg_info_set_model will truncate if the string is too long, so no need to check
|
||||
mfg_info_set_model(model);
|
||||
char written_model[MFG_INFO_MODEL_STRING_LENGTH];
|
||||
mfg_info_get_model(written_model);
|
||||
if (!strncmp(model, written_model, MFG_INFO_MODEL_STRING_LENGTH)) {
|
||||
prompt_send_response("OK");
|
||||
} else {
|
||||
prompt_send_response("ERROR");
|
||||
}
|
||||
}
|
||||
|
||||
#if BOOTLOADER_TEST_STAGE1
|
||||
#include "bootloader_test_bin.auto.h"
|
||||
|
||||
#include "drivers/flash.h"
|
||||
#include "flash_region/flash_region.h"
|
||||
#include "system/firmware_storage.h"
|
||||
#include "system/bootbits.h"
|
||||
#include "system/logging.h"
|
||||
#include "system/reset.h"
|
||||
#include "util/crc32.h"
|
||||
#include "util/legacy_checksum.h"
|
||||
static void prv_bootloader_test_copy(uint32_t flash_addr, uint32_t flash_end) {
|
||||
const uint8_t *bin;
|
||||
size_t size;
|
||||
|
||||
bin = s_bootloader_test_stage2;
|
||||
size = sizeof(s_bootloader_test_stage2);
|
||||
flash_region_erase_optimal_range(flash_addr,
|
||||
flash_addr,
|
||||
flash_addr+size+sizeof(FirmwareDescription),
|
||||
flash_end);
|
||||
|
||||
FirmwareDescription fw_desc = {
|
||||
.description_length = sizeof(FirmwareDescription),
|
||||
.firmware_length = size,
|
||||
.checksum = 0,
|
||||
};
|
||||
#if CAPABILITY_HAS_DEFECTIVE_FW_CRC
|
||||
fw_desc.checksum = legacy_defective_checksum_memory(bin, size);
|
||||
#else
|
||||
fw_desc.checksum = crc32(CRC32_INIT, bin, size);
|
||||
#endif
|
||||
|
||||
flash_write_bytes((uint8_t *)&fw_desc, flash_addr, sizeof(FirmwareDescription));
|
||||
flash_write_bytes(bin, flash_addr+sizeof(FirmwareDescription), size);
|
||||
}
|
||||
|
||||
#define BLTEST_LOG(x...) pbl_log(LOG_LEVEL_ALWAYS, __FILE__, __LINE__, x)
|
||||
|
||||
void command_bootloader_test(const char *dest_type) {
|
||||
prompt_command_finish();
|
||||
|
||||
BLTEST_LOG("BOOTLOADER TEST STAGE 1");
|
||||
boot_bit_set(BOOT_BIT_FW_STABLE);
|
||||
|
||||
BLTEST_LOG("STAGE 1 -- Setting test boot bits");
|
||||
boot_bit_clear(BOOT_BIT_BOOTLOADER_TEST_A | BOOT_BIT_BOOTLOADER_TEST_B);
|
||||
boot_bit_set(BOOT_BIT_BOOTLOADER_TEST_A);
|
||||
|
||||
bool as_fw = true;
|
||||
if (strcmp(dest_type, "prf") == 0) {
|
||||
as_fw = false;
|
||||
}
|
||||
if (as_fw) {
|
||||
BLTEST_LOG("STAGE 1 -- Copying STAGE 2 to scratch");
|
||||
prv_bootloader_test_copy(FLASH_REGION_FIRMWARE_SCRATCH_BEGIN,
|
||||
FLASH_REGION_FIRMWARE_SCRATCH_END);
|
||||
|
||||
BLTEST_LOG("STAGE 1 -- Marking new FW boot bit");
|
||||
boot_bit_set(BOOT_BIT_NEW_FW_AVAILABLE);
|
||||
} else {
|
||||
BLTEST_LOG("STAGE 1 -- Copying STAGE 2 to PRF");
|
||||
flash_prf_set_protection(false);
|
||||
prv_bootloader_test_copy(FLASH_REGION_SAFE_FIRMWARE_BEGIN, FLASH_REGION_SAFE_FIRMWARE_END);
|
||||
|
||||
BLTEST_LOG("STAGE 1 -- Marking PRF boot bit");
|
||||
boot_bit_set(BOOT_BIT_FORCE_PRF);
|
||||
}
|
||||
|
||||
BLTEST_LOG("STAGE 1 -- Rebooting");
|
||||
RebootReason reason = { RebootReasonCode_PrfReset, 0 };
|
||||
reboot_reason_set(&reason);
|
||||
system_hard_reset();
|
||||
}
|
||||
#else
|
||||
void command_bootloader_test(void) {
|
||||
prompt_send_response("Not configured for bootloader test");
|
||||
}
|
||||
#endif
|
23
src/fw/mfg/mfg_command.h
Normal file
23
src/fw/mfg/mfg_command.h
Normal file
|
@ -0,0 +1,23 @@
|
|||
/*
|
||||
* 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 <string.h>
|
||||
// NB: Factory registry has 10 records statically allocated for it: see registry.c
|
||||
|
||||
void mfg_command_init(void);
|
||||
void mfgdata_read_serial_number(char *serial_number, size_t serial_number_size);
|
52
src/fw/mfg/mfg_info.c
Normal file
52
src/fw/mfg/mfg_info.c
Normal file
|
@ -0,0 +1,52 @@
|
|||
/*
|
||||
* 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 "mfg/mfg_serials.h"
|
||||
|
||||
//! @file mfg_info.c
|
||||
//!
|
||||
//! This file implements mfg_info functions where the storage is the same for both tintin and
|
||||
//! snowy, mostly for things that are stored in OTP. See the tintin/mfg_info.c and
|
||||
//! snowy/mfg_info.c for the board specific implementations.
|
||||
|
||||
void mfg_info_get_serialnumber(char *serial_number, size_t serial_number_size) {
|
||||
strncpy(serial_number, mfg_get_serial_number(), serial_number_size);
|
||||
if (serial_number_size > MFG_SERIAL_NUMBER_SIZE) {
|
||||
// FIXME: manually adding a null-terminator if there is space
|
||||
// strncpy should pad the end of strings with nulls if there is space, but
|
||||
// everywhere that seems to use OTP or registry strings seems to pad the end
|
||||
// with a null-term.
|
||||
// Note: making an assumption here that the serial number is always going to the MFG_SERIAL_NUMBER_SIZE characters
|
||||
serial_number[MFG_SERIAL_NUMBER_SIZE] = '\0';
|
||||
}
|
||||
}
|
||||
|
||||
void mfg_info_get_pcba_serialnumber(char *pcba_serial_number, size_t pcba_serial_number_size) {
|
||||
strncpy(pcba_serial_number, mfg_get_pcba_serial_number(), pcba_serial_number_size);
|
||||
if (pcba_serial_number_size > MFG_PCBA_SERIAL_NUMBER_SIZE) {
|
||||
// same assumption as in mfg_info_get_pcba_serialnumber
|
||||
pcba_serial_number[MFG_PCBA_SERIAL_NUMBER_SIZE] = '\0';
|
||||
}
|
||||
}
|
||||
|
||||
void mfg_info_get_hw_version(char *hw_version, size_t hw_version_size) {
|
||||
strncpy(hw_version, mfg_get_hw_version(), hw_version_size);
|
||||
if (hw_version_size > MFG_HW_VERSION_SIZE) {
|
||||
// same assumption as in mfg_info_get_pcba_serialnumber
|
||||
hw_version[MFG_HW_VERSION_SIZE] = '\0';
|
||||
}
|
||||
}
|
||||
|
98
src/fw/mfg/mfg_info.h
Normal file
98
src/fw/mfg/mfg_info.h
Normal file
|
@ -0,0 +1,98 @@
|
|||
/*
|
||||
* 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
|
||||
|
||||
//! @file mfg_info.h
|
||||
//!
|
||||
//! Stores information about the physical watch that is encoded during the manufacturing process.
|
||||
|
||||
#include "applib/app_watch_info.h"
|
||||
#include "applib/graphics/gtypes.h"
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <string.h>
|
||||
|
||||
// If you give these functions enough space, they will add the null-terminator for you
|
||||
|
||||
void mfg_info_get_serialnumber(char *serial_number, size_t serial_number_size);
|
||||
|
||||
void mfg_info_get_pcba_serialnumber(char *serial_number, size_t serial_number_size);
|
||||
|
||||
void mfg_info_get_hw_version(char *serial_number, size_t serial_number_size);
|
||||
|
||||
//! @addtogroup Foundation
|
||||
//! @{
|
||||
//! @addtogroup WatchInfo
|
||||
//! @{
|
||||
|
||||
//! Provides the color of the watch.
|
||||
//! @return {@link WatchInfoColor} representing the color of the watch.
|
||||
WatchInfoColor mfg_info_get_watch_color(void);
|
||||
|
||||
void mfg_info_set_watch_color(WatchInfoColor color);
|
||||
|
||||
//! @} // end addtogroup WatchInfo
|
||||
//! @} // end addtogroup Foundation
|
||||
|
||||
//! @internal
|
||||
//! Returns the measured frequency of the LSE in mHz.
|
||||
uint32_t mfg_info_get_rtc_freq(void);
|
||||
|
||||
void mfg_info_set_rtc_freq(uint32_t rtc_freq);
|
||||
|
||||
// x offset +/- for display
|
||||
GPoint mfg_info_get_disp_offsets(void);
|
||||
|
||||
void mfg_info_set_disp_offsets(GPoint p);
|
||||
|
||||
//! The number of bytes in our model name, including the null-terminator.
|
||||
#define MFG_INFO_MODEL_STRING_LENGTH 16
|
||||
|
||||
//! Get the model string. Populates a supplied buffer with a null-terminated string.
|
||||
//! @param buffer a character array that's at least MFG_INFO_MODEL_STRING_LENGTH in size
|
||||
void mfg_info_get_model(char* buffer);
|
||||
|
||||
//! Set the model string to a new value.
|
||||
//! @param model A null-terminated string that's at most MFG_INFO_MODEL_STRING_LENGTH bytes in
|
||||
//! length including the null-terminator. Longer strings will be truncated to fit.
|
||||
void mfg_info_set_model(const char* model);
|
||||
|
||||
//! Set or update any constant data that needs to be written at manufacturing
|
||||
//! time but which is not customized to the individual unit.
|
||||
void mfg_info_update_constant_data(void);
|
||||
|
||||
//! @internal
|
||||
bool mfg_info_is_hrm_present(void);
|
||||
|
||||
typedef enum {
|
||||
MfgTest_Vibe,
|
||||
MfgTest_Display,
|
||||
MfgTest_Buttons,
|
||||
MfgTest_ALS,
|
||||
|
||||
MfgTestCount
|
||||
} MfgTest;
|
||||
|
||||
//! Record the pass / fail state of the given test.
|
||||
void mfg_info_write_test_result(MfgTest test, bool pass);
|
||||
|
||||
//! Get the pass / fail state of the given test.
|
||||
bool mfg_info_get_test_result(MfgTest test);
|
||||
|
||||
void mfg_info_write_als_result(uint32_t reading);
|
||||
|
||||
uint32_t mfg_info_get_als_result(void);
|
65
src/fw/mfg/mfg_mode/mfg_factory_mode.c
Normal file
65
src/fw/mfg/mfg_mode/mfg_factory_mode.c
Normal file
|
@ -0,0 +1,65 @@
|
|||
/*
|
||||
* 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 "mfg_factory_mode.h"
|
||||
|
||||
#include "apps/prf_apps/mfg_menu_app.h"
|
||||
#include "board/board.h"
|
||||
#include "kernel/event_loop.h"
|
||||
#include "kernel/low_power.h"
|
||||
#include "process_management/app_manager.h"
|
||||
#include "services/prf/accessory/accessory_manager.h"
|
||||
#include "services/prf/idle_watchdog.h"
|
||||
|
||||
static bool s_mfg_mode = false;
|
||||
|
||||
static void prv_launch_mfg_app(void *data) {
|
||||
// Make sure we can launch our MFG app and subsequent apps.
|
||||
app_manager_set_minimum_run_level(ProcessAppRunLevelNormal);
|
||||
app_manager_launch_new_app(&(AppLaunchConfig) {
|
||||
.md = mfg_menu_app_get_info(),
|
||||
});
|
||||
}
|
||||
|
||||
void mfg_enter_mfg_mode(void) {
|
||||
if (!s_mfg_mode) {
|
||||
s_mfg_mode = true;
|
||||
|
||||
#if CAPABILITY_HAS_ACCESSORY_CONNECTOR
|
||||
accessory_manager_set_state(AccessoryInputStateMfg);
|
||||
#endif
|
||||
|
||||
prf_idle_watchdog_stop();
|
||||
|
||||
low_power_exit();
|
||||
}
|
||||
}
|
||||
|
||||
void mfg_enter_mfg_mode_and_launch_app(void) {
|
||||
if (!s_mfg_mode) {
|
||||
mfg_enter_mfg_mode();
|
||||
launcher_task_add_callback(prv_launch_mfg_app, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
bool mfg_is_mfg_mode(void) {
|
||||
return s_mfg_mode;
|
||||
}
|
||||
|
||||
void command_enter_mfg(void) {
|
||||
mfg_enter_mfg_mode_and_launch_app();
|
||||
}
|
||||
|
28
src/fw/mfg/mfg_mode/mfg_factory_mode.h
Normal file
28
src/fw/mfg/mfg_mode/mfg_factory_mode.h
Normal file
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
* 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>
|
||||
|
||||
//! Enter manufacturing mode, but does not start the manufacturing app.
|
||||
void mfg_enter_mfg_mode(void);
|
||||
|
||||
//! Enter manufacturing mode and also add a launcher task callback to start the MFG Menu App.
|
||||
void mfg_enter_mfg_mode_and_launch_app(void);
|
||||
|
||||
bool mfg_is_mfg_mode(void);
|
||||
|
110
src/fw/mfg/mfg_mode/mfg_selftest.c
Normal file
110
src/fw/mfg/mfg_mode/mfg_selftest.c
Normal file
|
@ -0,0 +1,110 @@
|
|||
/*
|
||||
* 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 "bluetooth/bt_test.h"
|
||||
#include "console/prompt.h"
|
||||
#include "drivers/button.h"
|
||||
#include "drivers/flash.h"
|
||||
#include "drivers/i2c.h"
|
||||
#include "drivers/imu.h"
|
||||
#include "drivers/imu/bmi160/bmi160.h"
|
||||
#include "util/bitset.h"
|
||||
#include "util/size.h"
|
||||
|
||||
#include <string.h>
|
||||
#include <stdbool.h>
|
||||
#include <inttypes.h>
|
||||
|
||||
struct SelfTestCase {
|
||||
char name[16];
|
||||
bool (*func)(void);
|
||||
};
|
||||
|
||||
// TODO: PBL-34018 This file is a mess. We should clean it up to better chose the right functions
|
||||
// and list of test cases depending on the capabilities of the platform.
|
||||
|
||||
// Here's a clever trick: selftest functions which may or may not be
|
||||
// linked into the firmware depending on the ./waf configure settings
|
||||
// (read: IMU) are redeclared as weak so that it is not a linker error
|
||||
// to have missing definitions for these functions. They simply link as
|
||||
// zero (null pointer). This works out perfectly as the selftest code
|
||||
// considers a null function pointer to mean not-implemented, which is
|
||||
// exactly the outcome we want!
|
||||
bool bmi160_query_whoami(void) WEAK;
|
||||
bool bma255_query_whoami(void) WEAK;
|
||||
bool flash_check_whoami(void) WEAK;
|
||||
bool accel_manager_run_selftest(void) WEAK;
|
||||
bool gyro_manager_run_selftest(void) WEAK;
|
||||
bool mag3110_check_whoami(void) WEAK;
|
||||
bool snowy_mag3110_query_whoami(void) WEAK;
|
||||
|
||||
// NULL function pointer means test is not implemented
|
||||
static const struct SelfTestCase s_test_cases[] = {
|
||||
#if PLATFORM_SILK
|
||||
{ "Accel Comm", bma255_query_whoami },
|
||||
#else
|
||||
{ "IMU Comm", bmi160_query_whoami },
|
||||
#endif
|
||||
{ "Accel ST", accel_manager_run_selftest },
|
||||
#if !PLATFORM_SILK
|
||||
{ "Gyro ST", gyro_manager_run_selftest },
|
||||
{ "MAG3110 Comm", mag3110_check_whoami },
|
||||
#endif
|
||||
#if CAPABILITY_HAS_APPLE_MFI
|
||||
{ "Apple ACP I2C", bt_driver_test_mfi_chip_selftest },
|
||||
#endif
|
||||
{ "BT Module", bt_driver_test_selftest },
|
||||
{ "Flash Comm", flash_check_whoami },
|
||||
{ "Buttons", button_selftest },
|
||||
};
|
||||
|
||||
static char* bool_to_pass_fail(bool b) {
|
||||
if (b) {
|
||||
return "PASS";
|
||||
} else {
|
||||
return "FAIL";
|
||||
}
|
||||
}
|
||||
|
||||
//! Runs all the test cases
|
||||
//! @return a bitset of tests that passed or failed
|
||||
uint32_t mfg_selftest(void) {
|
||||
uint32_t result = 0;
|
||||
for (uint32_t i = 0; i < ARRAY_LENGTH(s_test_cases); i++) {
|
||||
const struct SelfTestCase* test = s_test_cases + i;
|
||||
bool test_passed = false;
|
||||
if (test->func != 0) {
|
||||
test_passed = test->func();
|
||||
}
|
||||
bitset32_update(&result, i, test_passed);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void command_selftest(void) {
|
||||
char buffer[32];
|
||||
uint32_t result = mfg_selftest();
|
||||
for (uint32_t i = 0; i < ARRAY_LENGTH(s_test_cases); i++) {
|
||||
const struct SelfTestCase* test = s_test_cases + i;
|
||||
char *pass_fail;
|
||||
if (test->func != 0) { // Test is implemented
|
||||
pass_fail = bool_to_pass_fail(bitset32_get(&result, i));
|
||||
} else {
|
||||
pass_fail = "NYI";
|
||||
}
|
||||
prompt_send_response_fmt(buffer, 32, "%15s: %s", test->name, pass_fail);
|
||||
}
|
||||
}
|
253
src/fw/mfg/mfg_serials.c
Normal file
253
src/fw/mfg/mfg_serials.c
Normal file
|
@ -0,0 +1,253 @@
|
|||
/*
|
||||
* 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 "mfg_serials.h"
|
||||
|
||||
#include "console/prompt.h"
|
||||
#include "util/size.h"
|
||||
|
||||
static const uint8_t OTP_SERIAL_SLOT_INDICES[] = {
|
||||
OTP_SERIAL1, OTP_SERIAL2, OTP_SERIAL3, OTP_SERIAL4, OTP_SERIAL5
|
||||
};
|
||||
static const uint8_t OTP_PCBA_SLOT_INDICES[] = {
|
||||
OTP_PCBA_SERIAL1, OTP_PCBA_SERIAL2, OTP_PCBA_SERIAL3
|
||||
};
|
||||
#if PLATFORM_SILK || PLATFORM_CALCULUS || PLATFORM_ROBERT
|
||||
static const uint8_t OTP_HWVER_SLOT_INDICES[] = {
|
||||
OTP_HWVER1, OTP_HWVER2, OTP_HWVER3, OTP_HWVER4, OTP_HWVER5
|
||||
};
|
||||
#else
|
||||
static const uint8_t OTP_HWVER_SLOT_INDICES[] = {OTP_HWVER1};
|
||||
#endif
|
||||
|
||||
static const char DUMMY_SERIAL[MFG_SERIAL_NUMBER_SIZE + 1] = "XXXXXXXXXXXX";
|
||||
// FIXME: shouldn't the dummy HWVER be 9 X's?
|
||||
static const char DUMMY_HWVER[MFG_HW_VERSION_SIZE + 1] = "XXXXXXXX";
|
||||
static const char DUMMY_PCBA_SERIAL[MFG_PCBA_SERIAL_NUMBER_SIZE + 1] = "XXXXXXXXXXXX";
|
||||
|
||||
static void mfg_print_feedback(const MfgSerialsResult result, const uint8_t index, const char *value, const char *name);
|
||||
|
||||
const char* mfg_get_serial_number(void) {
|
||||
// Trying from "most recent" slot to "least recent":
|
||||
for (int i = ARRAY_LENGTH(OTP_SERIAL_SLOT_INDICES) - 1; i >= 0; --i) {
|
||||
const uint8_t index = OTP_SERIAL_SLOT_INDICES[i];
|
||||
if (otp_is_locked(index)) {
|
||||
return otp_get_slot(index);
|
||||
}
|
||||
}
|
||||
return DUMMY_SERIAL;
|
||||
}
|
||||
|
||||
const char* mfg_get_hw_version(void) {
|
||||
// Trying from "most recent" slot to "least recent":
|
||||
for (int i = ARRAY_LENGTH(OTP_HWVER_SLOT_INDICES) - 1; i >= 0; --i) {
|
||||
const uint8_t index = OTP_HWVER_SLOT_INDICES[i];
|
||||
if (otp_is_locked(index)) {
|
||||
return otp_get_slot(index);
|
||||
}
|
||||
}
|
||||
return DUMMY_HWVER;
|
||||
}
|
||||
|
||||
const char* mfg_get_pcba_serial_number(void) {
|
||||
// Trying from "most recent" slot to "least recent":
|
||||
for (int i = ARRAY_LENGTH(OTP_PCBA_SLOT_INDICES) - 1; i >= 0; --i) {
|
||||
const uint8_t index = OTP_PCBA_SLOT_INDICES[i];
|
||||
if (otp_is_locked(index)) {
|
||||
return otp_get_slot(index);
|
||||
}
|
||||
}
|
||||
return DUMMY_PCBA_SERIAL;
|
||||
}
|
||||
|
||||
static MfgSerialsResult prv_mfg_write_data_to_slot(const uint8_t *slot_indices, size_t num_slots,
|
||||
const char *data, size_t data_size,
|
||||
uint8_t *out_index) {
|
||||
for (unsigned int i = 0; i < num_slots; ++i) {
|
||||
const uint8_t index = slot_indices[i];
|
||||
const OtpWriteResult result = otp_write_slot(index, data);
|
||||
if (result == OtpWriteSuccess) {
|
||||
if (out_index) {
|
||||
*out_index = index;
|
||||
}
|
||||
return MfgSerialsResultSuccess;
|
||||
}
|
||||
// if OtpWriteFailCorrupt or OtpWriteFailAlreadyWritten, continue to next slot.
|
||||
}
|
||||
return MfgSerialsResultFailNoMoreSpace;
|
||||
}
|
||||
|
||||
MfgSerialsResult mfg_write_serial_number(const char* serial, size_t serial_size,
|
||||
uint8_t *out_index) {
|
||||
|
||||
if ((serial_size != (MFG_SERIAL_NUMBER_SIZE)) || (serial[serial_size] != '\0')) {
|
||||
return MfgSerialsResultFailIncorrectLength;
|
||||
}
|
||||
|
||||
return prv_mfg_write_data_to_slot(OTP_SERIAL_SLOT_INDICES, ARRAY_LENGTH(OTP_SERIAL_SLOT_INDICES),
|
||||
serial, serial_size, out_index);
|
||||
}
|
||||
|
||||
MfgSerialsResult mfg_write_pcba_serial_number(const char* serial, size_t serial_size,
|
||||
uint8_t *out_index) {
|
||||
|
||||
if ((serial_size > MFG_PCBA_SERIAL_NUMBER_SIZE) || (serial[serial_size] != '\0')) {
|
||||
return MfgSerialsResultFailIncorrectLength;
|
||||
}
|
||||
|
||||
return prv_mfg_write_data_to_slot(OTP_PCBA_SLOT_INDICES, ARRAY_LENGTH(OTP_PCBA_SLOT_INDICES),
|
||||
serial, serial_size, out_index);
|
||||
}
|
||||
|
||||
static MfgSerialsResult prv_mfg_write_hw_version(const char* hwver, size_t hwver_size,
|
||||
uint8_t *out_index) {
|
||||
if ((hwver_size > MFG_HW_VERSION_SIZE) || hwver[hwver_size] != '\0') {
|
||||
return MfgSerialsResultFailIncorrectLength;
|
||||
}
|
||||
return prv_mfg_write_data_to_slot(OTP_HWVER_SLOT_INDICES, ARRAY_LENGTH(OTP_HWVER_SLOT_INDICES),
|
||||
hwver, hwver_size, out_index);
|
||||
}
|
||||
|
||||
void command_serial_read(void) {
|
||||
prompt_send_response(mfg_get_serial_number());
|
||||
}
|
||||
|
||||
void command_hwver_read(void) {
|
||||
prompt_send_response(mfg_get_hw_version());
|
||||
}
|
||||
|
||||
void command_pcba_serial_read(void) {
|
||||
prompt_send_response(mfg_get_pcba_serial_number());
|
||||
}
|
||||
|
||||
void command_serial_write(const char *serial) {
|
||||
MfgSerialsResult result;
|
||||
uint8_t index = 0;
|
||||
|
||||
size_t serial_len = strlen(serial);
|
||||
if ((serial_len >= 11) && (serial_len <= MFG_SERIAL_NUMBER_SIZE)) {
|
||||
result = mfg_write_serial_number(serial, serial_len, &index);
|
||||
} else {
|
||||
result = MfgSerialsResultFailIncorrectLength;
|
||||
}
|
||||
|
||||
mfg_print_feedback(result, index, serial, "Serial");
|
||||
}
|
||||
|
||||
void command_hwver_write(const char *hwver) {
|
||||
MfgSerialsResult result;
|
||||
uint8_t index = 0;
|
||||
|
||||
size_t hwver_len = strlen(hwver);
|
||||
if (hwver_len > 0) {
|
||||
result = prv_mfg_write_hw_version(hwver, hwver_len, &index);
|
||||
} else {
|
||||
result = MfgSerialsResultFailIncorrectLength;
|
||||
}
|
||||
|
||||
mfg_print_feedback(result, index, hwver, "HW version");
|
||||
}
|
||||
|
||||
void command_pcba_serial_write(const char *pcba_serial) {
|
||||
MfgSerialsResult result;
|
||||
uint8_t index = 0;
|
||||
|
||||
size_t pcba_serial_len = strlen(pcba_serial);
|
||||
if ((pcba_serial_len > 0) && (pcba_serial_len <= MFG_PCBA_SERIAL_NUMBER_SIZE)) {
|
||||
result = mfg_write_pcba_serial_number(pcba_serial, pcba_serial_len, &index);
|
||||
} else {
|
||||
result = MfgSerialsResultFailIncorrectLength;
|
||||
}
|
||||
|
||||
mfg_print_feedback(result, index, pcba_serial, "PCBA Serial");
|
||||
}
|
||||
|
||||
static void mfg_print_feedback(const MfgSerialsResult result, const uint8_t index,
|
||||
const char *value, const char *name) {
|
||||
switch (result) {
|
||||
case MfgSerialsResultAlreadyWritten: {
|
||||
char buffer[48];
|
||||
const char * const field = otp_get_slot(index);
|
||||
prompt_send_response_fmt(buffer, sizeof(buffer), "%s already present! %s", name, field);
|
||||
break;
|
||||
}
|
||||
case MfgSerialsResultCorrupt: {
|
||||
char buffer[48];
|
||||
prompt_send_response_fmt(buffer, sizeof(buffer), "Writing failed; %s may be corrupt!", name);
|
||||
break;
|
||||
}
|
||||
case MfgSerialsResultFailIncorrectLength: {
|
||||
prompt_send_response("Incorrect length");
|
||||
break;
|
||||
}
|
||||
case MfgSerialsResultFailNoMoreSpace: {
|
||||
prompt_send_response("No more space!");
|
||||
break;
|
||||
}
|
||||
case MfgSerialsResultSuccess:
|
||||
prompt_send_response("OK");
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
#if defined(IS_BIGBOARD)
|
||||
|
||||
#include <stdio.h>
|
||||
#include "drivers/rtc.h"
|
||||
#include "system/logging.h"
|
||||
|
||||
static void prv_get_not_so_unique_serial(char *serial_number) {
|
||||
// Contains 96 bits (12 bytes) that uniquely identify the STM32F2/F4 MCUs:
|
||||
const uint8_t *DEVICE_ID_REGISTER = (const uint8_t *) 0x1FFF7A10;
|
||||
// BBs used the first bytes of the ID registers, which happened to be not very unique...
|
||||
for (int i = 2, r = 7; i < MFG_SERIAL_NUMBER_SIZE; i += 2, ++r) {
|
||||
sniprintf(&serial_number[i], 3 /* 2 hex digits + zero terminator */, "%02X",
|
||||
DEVICE_ID_REGISTER[r]);
|
||||
}
|
||||
serial_number[MFG_SERIAL_NUMBER_SIZE] = 0;
|
||||
}
|
||||
|
||||
static bool prv_get_more_unique_serial(char *serial_number) {
|
||||
for (int i = 2; i < MFG_SERIAL_NUMBER_SIZE; i += 2) {
|
||||
sniprintf(&serial_number[i], 3 /* 2 hex digits + zero terminator */, "%02X", rand());
|
||||
}
|
||||
serial_number[MFG_SERIAL_NUMBER_SIZE] = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
void mfg_write_bigboard_serial_number(void) {
|
||||
char serial_number[MFG_SERIAL_NUMBER_SIZE + 1];
|
||||
// Start with underscore, so it's easy to filter out from analytics:
|
||||
serial_number[0] = '_';
|
||||
serial_number[1] = 'B';
|
||||
|
||||
// Check whether the previous not-so-unique SN or the no SN ("XXXXXXXXXXXX") has been written:
|
||||
prv_get_not_so_unique_serial(serial_number);
|
||||
const char *current_serial_number = mfg_get_serial_number();
|
||||
if (strcmp(current_serial_number, serial_number) &&
|
||||
strcmp(current_serial_number, DUMMY_SERIAL)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Create a "more unique" serial number using rand():
|
||||
if (prv_get_more_unique_serial(serial_number)) {
|
||||
mfg_write_serial_number(serial_number, MFG_SERIAL_NUMBER_SIZE, NULL);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
61
src/fw/mfg/mfg_serials.h
Normal file
61
src/fw/mfg/mfg_serials.h
Normal file
|
@ -0,0 +1,61 @@
|
|||
/*
|
||||
* 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 <string.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "drivers/otp.h"
|
||||
|
||||
// These sizes represent the number of characters in the version
|
||||
// strings and serial numbers. To store these, one additional byte is
|
||||
// needed to store the null-terminator.
|
||||
#define MFG_HW_VERSION_SIZE 9
|
||||
#define MFG_SERIAL_NUMBER_SIZE 12
|
||||
#define MFG_PCBA_SERIAL_NUMBER_SIZE 12
|
||||
|
||||
//! @return The last written final assembly serial number or "XXXXXXXXXXXX" if
|
||||
//! no serial number has been written.
|
||||
const char* mfg_get_serial_number(void);
|
||||
const char* mfg_get_hw_version(void);
|
||||
const char* mfg_get_pcba_serial_number(void);
|
||||
|
||||
typedef enum MfgSerialsResult {
|
||||
MfgSerialsResultSuccess = OtpWriteSuccess,
|
||||
MfgSerialsResultAlreadyWritten = OtpWriteFailAlreadyWritten,
|
||||
MfgSerialsResultCorrupt = OtpWriteFailCorrupt,
|
||||
MfgSerialsResultFailIncorrectLength = 3,
|
||||
MfgSerialsResultFailNoMoreSpace = 4,
|
||||
} MfgSerialsResult;
|
||||
|
||||
//! Writes a new final assembly serial number to OTP.
|
||||
//! There are 3 slots for serial numbers. The last written one is returned
|
||||
//! from the \ref mfg_get_serial_number() function.
|
||||
//! @param serial The serial number (zero-terminated string) to be written
|
||||
//! @param serial_size The length of the buffer in bytes including terminating
|
||||
//! zero. Must be 13.
|
||||
//! @param[out] out_index Will contain the OTP index that was used to write the
|
||||
//! serial number, if the return value was OtpWriteSuccess.
|
||||
//! @return OtpWriteSuccess if the write was successfull or
|
||||
//! MfgSerialsResultFailNoMoreSpace if all 3 slots were taken already, or
|
||||
//! MfgSerialsResultFailIncorrectLength if the serial_size was not 13.
|
||||
MfgSerialsResult mfg_write_serial_number(const char* serial, size_t serial_size, uint8_t *out_index);
|
||||
|
||||
#if defined(IS_BIGBOARD)
|
||||
//! Writes a fake serial number based on the unique identifier of the MCU
|
||||
void mfg_write_bigboard_serial_number(void);
|
||||
#endif
|
72
src/fw/mfg/results_ui.c
Normal file
72
src/fw/mfg/results_ui.c
Normal file
|
@ -0,0 +1,72 @@
|
|||
/*
|
||||
* 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 "results_ui.h"
|
||||
|
||||
#include "applib/ui/app_window_stack.h"
|
||||
|
||||
static void prv_record_and_exit(MfgResultsUI *results_ui, bool result) {
|
||||
mfg_info_write_test_result(results_ui->test, result);
|
||||
|
||||
if (results_ui->results_cb) {
|
||||
results_ui->results_cb();
|
||||
}
|
||||
|
||||
app_window_stack_pop(true);
|
||||
}
|
||||
|
||||
static void prv_up_click_handler(ClickRecognizerRef recognizer, void *data) {
|
||||
prv_record_and_exit(data, true);
|
||||
}
|
||||
|
||||
static void prv_down_click_handler(ClickRecognizerRef recognizer, void *data) {
|
||||
prv_record_and_exit(data, false);
|
||||
}
|
||||
|
||||
static void prv_click_config_provider(void *data) {
|
||||
window_single_click_subscribe(BUTTON_ID_UP, prv_up_click_handler);
|
||||
window_single_click_subscribe(BUTTON_ID_DOWN, prv_down_click_handler);
|
||||
}
|
||||
|
||||
void mfg_results_ui_init(MfgResultsUI *results_ui, MfgTest test, Window *window) {
|
||||
GRect bounds = window->layer.bounds;
|
||||
bounds.size.w -= 5;
|
||||
bounds.size.h = 40;
|
||||
bounds.origin.y += 5;
|
||||
|
||||
TextLayer *pass = &results_ui->pass_text_layer;
|
||||
text_layer_init(pass, &bounds);
|
||||
text_layer_set_font(pass, fonts_get_system_font(FONT_KEY_GOTHIC_24_BOLD));
|
||||
text_layer_set_text_alignment(pass, GTextAlignmentRight);
|
||||
text_layer_set_text(pass, "Pass");
|
||||
layer_add_child(&window->layer, &pass->layer);
|
||||
|
||||
bounds.origin.y = 120;
|
||||
TextLayer *fail = &results_ui->fail_text_layer;
|
||||
text_layer_init(fail, &bounds);
|
||||
text_layer_set_font(fail, fonts_get_system_font(FONT_KEY_GOTHIC_24_BOLD));
|
||||
text_layer_set_text_alignment(fail, GTextAlignmentRight);
|
||||
text_layer_set_text(fail, "Fail");
|
||||
layer_add_child(&window->layer, &fail->layer);
|
||||
|
||||
results_ui->test = test;
|
||||
|
||||
window_set_click_config_provider_with_context(window, prv_click_config_provider, results_ui);
|
||||
}
|
||||
|
||||
void mfg_results_ui_set_callback(MfgResultsUI *ui, MfgResultsCallback cb) {
|
||||
ui->results_cb = cb;
|
||||
}
|
35
src/fw/mfg/results_ui.h
Normal file
35
src/fw/mfg/results_ui.h
Normal file
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
* 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 "applib/ui/window.h"
|
||||
#include "applib/ui/text_layer.h"
|
||||
#include "mfg/mfg_info.h"
|
||||
|
||||
typedef void (*MfgResultsCallback)(void);
|
||||
|
||||
typedef struct {
|
||||
MfgTest test;
|
||||
|
||||
TextLayer pass_text_layer;
|
||||
TextLayer fail_text_layer;
|
||||
|
||||
MfgResultsCallback results_cb;
|
||||
} MfgResultsUI;
|
||||
|
||||
void mfg_results_ui_init(MfgResultsUI *results_ui, MfgTest test, Window *window);
|
||||
void mfg_results_ui_set_callback(MfgResultsUI *ui, MfgResultsCallback cb);
|
112
src/fw/mfg/robert/mfg_info.c
Normal file
112
src/fw/mfg/robert/mfg_info.c
Normal file
|
@ -0,0 +1,112 @@
|
|||
/*
|
||||
* 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 "mfg/mfg_info.h"
|
||||
|
||||
#include "drivers/flash.h"
|
||||
#include "flash_region/flash_region.h"
|
||||
#include "mfg/mfg_serials.h"
|
||||
#include "system/logging.h"
|
||||
|
||||
//! Used to version this struct if we have to add additional fields in the future.
|
||||
#define CURRENT_DATA_VERSION 2
|
||||
|
||||
typedef struct {
|
||||
uint32_t data_version;
|
||||
|
||||
uint32_t color;
|
||||
uint32_t rtc_freq;
|
||||
char model[MFG_INFO_MODEL_STRING_LENGTH]; //!< Null terminated model string
|
||||
} MfgData;
|
||||
|
||||
static void prv_update_struct(const MfgData *data) {
|
||||
flash_erase_subsector_blocking(FLASH_REGION_MFG_INFO_BEGIN);
|
||||
flash_write_bytes((const uint8_t*) data, FLASH_REGION_MFG_INFO_BEGIN, sizeof(*data));
|
||||
}
|
||||
|
||||
static MfgData prv_fetch_struct(void) {
|
||||
MfgData result;
|
||||
|
||||
flash_read_bytes((uint8_t*) &result, FLASH_REGION_MFG_INFO_BEGIN, sizeof(result));
|
||||
|
||||
switch (result.data_version) {
|
||||
case CURRENT_DATA_VERSION:
|
||||
// Our data is valid. Fall through.
|
||||
break;
|
||||
case 1:
|
||||
// Our data is out of date. We need to do a conversion to populate the new model field.
|
||||
result.data_version = CURRENT_DATA_VERSION;
|
||||
result.model[0] = '\0';
|
||||
break;
|
||||
default:
|
||||
// No data present, just return an initialized struct with default values.
|
||||
return (MfgData) { .data_version = CURRENT_DATA_VERSION };
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
WatchInfoColor mfg_info_get_watch_color(void) {
|
||||
return prv_fetch_struct().color;
|
||||
}
|
||||
|
||||
void mfg_info_set_watch_color(WatchInfoColor color) {
|
||||
MfgData data = prv_fetch_struct();
|
||||
data.color = color;
|
||||
prv_update_struct(&data);
|
||||
}
|
||||
|
||||
uint32_t mfg_info_get_rtc_freq(void) {
|
||||
return prv_fetch_struct().rtc_freq;
|
||||
}
|
||||
|
||||
#if MANUFACTURING_FW
|
||||
void mfg_info_set_rtc_freq(uint32_t rtc_freq) {
|
||||
MfgData data = prv_fetch_struct();
|
||||
data.rtc_freq = rtc_freq;
|
||||
prv_update_struct(&data);
|
||||
}
|
||||
#endif
|
||||
|
||||
void mfg_info_get_model(char* buffer) {
|
||||
MfgData data = prv_fetch_struct();
|
||||
strncpy(buffer, data.model, sizeof(data.model));
|
||||
data.model[MFG_INFO_MODEL_STRING_LENGTH - 1] = '\0'; // Just in case
|
||||
}
|
||||
|
||||
void mfg_info_set_model(const char* model) {
|
||||
MfgData data = prv_fetch_struct();
|
||||
strncpy(data.model, model, sizeof(data.model));
|
||||
data.model[MFG_INFO_MODEL_STRING_LENGTH - 1] = '\0';
|
||||
prv_update_struct(&data);
|
||||
}
|
||||
|
||||
GPoint mfg_info_get_disp_offsets(void) {
|
||||
// Not implemented. Can just assume no offset
|
||||
return (GPoint) {};
|
||||
}
|
||||
|
||||
void mfg_info_set_disp_offsets(GPoint p) {
|
||||
// Not implemented.
|
||||
}
|
||||
|
||||
void mfg_info_update_constant_data(void) {
|
||||
// No constant data required for Robert.
|
||||
}
|
||||
|
||||
bool mfg_info_is_hrm_present(void) {
|
||||
return true;
|
||||
}
|
167
src/fw/mfg/silk/mfg_info.c
Normal file
167
src/fw/mfg/silk/mfg_info.c
Normal file
|
@ -0,0 +1,167 @@
|
|||
/*
|
||||
* 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 "mfg/mfg_info.h"
|
||||
|
||||
#include "drivers/flash.h"
|
||||
#include "flash_region/flash_region.h"
|
||||
#include "mfg/mfg_serials.h"
|
||||
#include "mfg/snowy/mfg_private.h"
|
||||
#include "system/logging.h"
|
||||
|
||||
//! Used to version this struct if we have to add additional fields in the future.
|
||||
#define CURRENT_DATA_VERSION 3
|
||||
|
||||
typedef struct {
|
||||
uint32_t data_version;
|
||||
|
||||
uint32_t color;
|
||||
uint32_t rtc_freq;
|
||||
char model[MFG_INFO_MODEL_STRING_LENGTH]; //!< Null terminated model string
|
||||
|
||||
bool test_results[MfgTestCount]; //!< UI Test Results
|
||||
uint32_t als_result; //!< Result for ALS reading
|
||||
} MfgData;
|
||||
|
||||
static void prv_update_struct(const MfgData *data) {
|
||||
flash_erase_subsector_blocking(FLASH_REGION_MFG_INFO_BEGIN);
|
||||
flash_write_bytes((const uint8_t*) data, FLASH_REGION_MFG_INFO_BEGIN, sizeof(*data));
|
||||
}
|
||||
|
||||
static MfgData prv_fetch_struct(void) {
|
||||
MfgData result;
|
||||
|
||||
flash_read_bytes((uint8_t*) &result, FLASH_REGION_MFG_INFO_BEGIN, sizeof(result));
|
||||
|
||||
switch (result.data_version) {
|
||||
case CURRENT_DATA_VERSION:
|
||||
// Our data is valid. Fall through.
|
||||
break;
|
||||
case 1:
|
||||
// Our data is out of date. We need to do a conversion to populate the new model field.
|
||||
result.data_version = CURRENT_DATA_VERSION;
|
||||
result.model[0] = '\0';
|
||||
break;
|
||||
case 2:
|
||||
result.data_version = CURRENT_DATA_VERSION;
|
||||
memset(result.test_results, 0, sizeof(result.test_results));
|
||||
result.als_result = 0;
|
||||
break;
|
||||
default:
|
||||
// No data present, just return an initialized struct with default values.
|
||||
return (MfgData) { .data_version = CURRENT_DATA_VERSION };
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
WatchInfoColor mfg_info_get_watch_color(void) {
|
||||
return prv_fetch_struct().color;
|
||||
}
|
||||
|
||||
void mfg_info_set_watch_color(WatchInfoColor color) {
|
||||
MfgData data = prv_fetch_struct();
|
||||
data.color = color;
|
||||
prv_update_struct(&data);
|
||||
}
|
||||
|
||||
uint32_t mfg_info_get_rtc_freq(void) {
|
||||
return prv_fetch_struct().rtc_freq;
|
||||
}
|
||||
|
||||
void mfg_info_set_rtc_freq(uint32_t rtc_freq) {
|
||||
MfgData data = prv_fetch_struct();
|
||||
data.rtc_freq = rtc_freq;
|
||||
prv_update_struct(&data);
|
||||
}
|
||||
|
||||
void mfg_info_get_model(char* buffer) {
|
||||
MfgData data = prv_fetch_struct();
|
||||
strncpy(buffer, data.model, sizeof(data.model));
|
||||
data.model[MFG_INFO_MODEL_STRING_LENGTH - 1] = '\0'; // Just in case
|
||||
}
|
||||
|
||||
void mfg_info_set_model(const char* model) {
|
||||
MfgData data = prv_fetch_struct();
|
||||
strncpy(data.model, model, sizeof(data.model));
|
||||
data.model[MFG_INFO_MODEL_STRING_LENGTH - 1] = '\0';
|
||||
prv_update_struct(&data);
|
||||
}
|
||||
|
||||
GPoint mfg_info_get_disp_offsets(void) {
|
||||
// Not implemented. Can just assume no offset
|
||||
return (GPoint) {};
|
||||
}
|
||||
|
||||
void mfg_info_set_disp_offsets(GPoint p) {
|
||||
// Not implemented.
|
||||
}
|
||||
|
||||
void mfg_info_update_constant_data(void) {
|
||||
// Not implemented
|
||||
}
|
||||
|
||||
#if CAPABILITY_HAS_BUILTIN_HRM
|
||||
bool mfg_info_is_hrm_present(void) {
|
||||
#if defined(TARGET_QEMU) || defined(IS_BIGBOARD)
|
||||
return true;
|
||||
#else
|
||||
char model[MFG_INFO_MODEL_STRING_LENGTH];
|
||||
mfg_info_get_model(model);
|
||||
if (!strcmp(model, "1002")) { // SilkHR
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
#if MFG_INFO_RECORDS_TEST_RESULTS
|
||||
void mfg_info_write_test_result(MfgTest test, bool pass) {
|
||||
MfgData data = prv_fetch_struct();
|
||||
data.test_results[test] = pass;
|
||||
prv_update_struct(&data);
|
||||
}
|
||||
|
||||
bool mfg_info_get_test_result(MfgTest test) {
|
||||
MfgData data = prv_fetch_struct();
|
||||
return data.test_results[test];
|
||||
}
|
||||
|
||||
#include "console/prompt.h"
|
||||
#define TEST_RESULT_TO_STR(test) ((data.test_results[(test)]) ? "PASS" : "FAIL")
|
||||
void command_mfg_info_test_results(void) {
|
||||
MfgData data = prv_fetch_struct();
|
||||
char buf[32];
|
||||
prompt_send_response_fmt(buf, 32, "Vibe: %s", TEST_RESULT_TO_STR(MfgTest_Vibe));
|
||||
prompt_send_response_fmt(buf, 32, "LCM: %s", TEST_RESULT_TO_STR(MfgTest_Display));
|
||||
prompt_send_response_fmt(buf, 32, "ALS: %s", TEST_RESULT_TO_STR(MfgTest_ALS));
|
||||
prompt_send_response_fmt(buf, 32, "Buttons: %s", TEST_RESULT_TO_STR(MfgTest_Buttons));
|
||||
|
||||
prompt_send_response_fmt(buf, 32, "ALS Reading: %"PRIu32, data.als_result);
|
||||
}
|
||||
|
||||
void mfg_info_write_als_result(uint32_t reading) {
|
||||
MfgData data = prv_fetch_struct();
|
||||
data.als_result = reading;
|
||||
prv_update_struct(&data);
|
||||
}
|
||||
|
||||
uint32_t mfg_info_get_als_result(void) {
|
||||
MfgData data = prv_fetch_struct();
|
||||
return data.als_result;
|
||||
}
|
||||
#endif
|
54
src/fw/mfg/snowy/boot_fpga.c
Normal file
54
src/fw/mfg/snowy/boot_fpga.c
Normal file
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
* 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 "mfg/snowy/mfg_private.h"
|
||||
|
||||
#include "drivers/flash.h"
|
||||
#include "flash_region/flash_region.h"
|
||||
#include "mfg/snowy/snowy_boot.fpga.auto.h"
|
||||
#include "util/attributes.h"
|
||||
|
||||
#define BOOT_FPGA_FLASH_ADDR (FLASH_REGION_MFG_INFO_BEGIN + 0x10000)
|
||||
|
||||
typedef struct PACKED {
|
||||
uint16_t fpga_len;
|
||||
uint16_t fpga_len_complemented;
|
||||
} BootFPGAHeader;
|
||||
|
||||
bool mfg_info_is_boot_fpga_bitstream_written(void) {
|
||||
BootFPGAHeader header;
|
||||
flash_read_bytes((void *)&header, BOOT_FPGA_FLASH_ADDR, sizeof header);
|
||||
return (header.fpga_len != 0xffff &&
|
||||
header.fpga_len_complemented != 0xffff);
|
||||
}
|
||||
|
||||
void mfg_info_write_boot_fpga_bitstream(void) {
|
||||
// Store the bootloader FPGA in the MFG info flash region so that the
|
||||
// bootloader can find it.
|
||||
_Static_assert(sizeof s_boot_fpga < 1<<16, "FPGA bitstream too big");
|
||||
BootFPGAHeader fpga_header = { (uint16_t)sizeof s_boot_fpga,
|
||||
(uint16_t)~sizeof s_boot_fpga };
|
||||
|
||||
_Static_assert(
|
||||
(BOOT_FPGA_FLASH_ADDR + sizeof fpga_header + sizeof s_boot_fpga)
|
||||
< FLASH_REGION_MFG_INFO_END,
|
||||
"FPGA bitstream will overflow FLASH_REGION_MFG_INFO!");
|
||||
flash_write_bytes((const uint8_t *)&fpga_header,
|
||||
BOOT_FPGA_FLASH_ADDR, sizeof fpga_header);
|
||||
flash_write_bytes((const uint8_t *)s_boot_fpga,
|
||||
BOOT_FPGA_FLASH_ADDR + sizeof fpga_header,
|
||||
sizeof s_boot_fpga);
|
||||
}
|
113
src/fw/mfg/snowy/mfg_info.c
Normal file
113
src/fw/mfg/snowy/mfg_info.c
Normal file
|
@ -0,0 +1,113 @@
|
|||
/*
|
||||
* 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 "mfg/mfg_info.h"
|
||||
|
||||
#include "drivers/flash.h"
|
||||
#include "flash_region/flash_region.h"
|
||||
#include "mfg/mfg_serials.h"
|
||||
#include "mfg/snowy/mfg_private.h"
|
||||
#include "system/logging.h"
|
||||
|
||||
//! Used to version this struct if we have to add additional fields in the future.
|
||||
#define CURRENT_DATA_VERSION 2
|
||||
|
||||
typedef struct {
|
||||
uint32_t data_version;
|
||||
|
||||
uint32_t color;
|
||||
uint32_t rtc_freq;
|
||||
char model[MFG_INFO_MODEL_STRING_LENGTH]; //!< Null terminated model string
|
||||
} MfgData;
|
||||
|
||||
static void prv_update_struct(const MfgData *data) {
|
||||
flash_erase_subsector_blocking(FLASH_REGION_MFG_INFO_BEGIN);
|
||||
flash_write_bytes((const uint8_t*) data, FLASH_REGION_MFG_INFO_BEGIN, sizeof(*data));
|
||||
mfg_info_write_boot_fpga_bitstream();
|
||||
}
|
||||
|
||||
static MfgData prv_fetch_struct(void) {
|
||||
MfgData result;
|
||||
|
||||
flash_read_bytes((uint8_t*) &result, FLASH_REGION_MFG_INFO_BEGIN, sizeof(result));
|
||||
|
||||
switch (result.data_version) {
|
||||
case CURRENT_DATA_VERSION:
|
||||
// Our data is valid. Fall through.
|
||||
break;
|
||||
case 1:
|
||||
// Our data is out of date. We need to do a conversion to populate the new model field.
|
||||
result.data_version = CURRENT_DATA_VERSION;
|
||||
result.model[0] = '\0';
|
||||
break;
|
||||
default:
|
||||
// No data present, just return an initialized struct with default values.
|
||||
return (MfgData) { .data_version = CURRENT_DATA_VERSION };
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
WatchInfoColor mfg_info_get_watch_color(void) {
|
||||
return prv_fetch_struct().color;
|
||||
}
|
||||
|
||||
void mfg_info_set_watch_color(WatchInfoColor color) {
|
||||
MfgData data = prv_fetch_struct();
|
||||
data.color = color;
|
||||
prv_update_struct(&data);
|
||||
}
|
||||
|
||||
uint32_t mfg_info_get_rtc_freq(void) {
|
||||
return prv_fetch_struct().rtc_freq;
|
||||
}
|
||||
|
||||
void mfg_info_set_rtc_freq(uint32_t rtc_freq) {
|
||||
MfgData data = prv_fetch_struct();
|
||||
data.rtc_freq = rtc_freq;
|
||||
prv_update_struct(&data);
|
||||
}
|
||||
|
||||
void mfg_info_get_model(char* buffer) {
|
||||
MfgData data = prv_fetch_struct();
|
||||
strncpy(buffer, data.model, sizeof(data.model));
|
||||
data.model[MFG_INFO_MODEL_STRING_LENGTH - 1] = '\0'; // Just in case
|
||||
}
|
||||
|
||||
void mfg_info_set_model(const char* model) {
|
||||
MfgData data = prv_fetch_struct();
|
||||
strncpy(data.model, model, sizeof(data.model));
|
||||
data.model[MFG_INFO_MODEL_STRING_LENGTH - 1] = '\0';
|
||||
prv_update_struct(&data);
|
||||
}
|
||||
|
||||
GPoint mfg_info_get_disp_offsets(void) {
|
||||
// Not implemented. Can just assume no offset
|
||||
return (GPoint) {};
|
||||
}
|
||||
|
||||
void mfg_info_set_disp_offsets(GPoint p) {
|
||||
// Not implemented.
|
||||
}
|
||||
|
||||
void mfg_info_update_constant_data(void) {
|
||||
if (mfg_info_is_boot_fpga_bitstream_written()) {
|
||||
PBL_LOG(LOG_LEVEL_INFO, "Boot FPGA bitstream already in flash.");
|
||||
} else {
|
||||
PBL_LOG(LOG_LEVEL_INFO, "Writing boot FPGA bitstream to flash...");
|
||||
mfg_info_write_boot_fpga_bitstream();
|
||||
}
|
||||
}
|
22
src/fw/mfg/snowy/mfg_private.h
Normal file
22
src/fw/mfg/snowy/mfg_private.h
Normal file
|
@ -0,0 +1,22 @@
|
|||
/*
|
||||
* 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>
|
||||
|
||||
void mfg_info_write_boot_fpga_bitstream(void);
|
||||
bool mfg_info_is_boot_fpga_bitstream_written(void);
|
87
src/fw/mfg/spalding/boot_fpga.c
Normal file
87
src/fw/mfg/spalding/boot_fpga.c
Normal file
|
@ -0,0 +1,87 @@
|
|||
/*
|
||||
* 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 "mfg/spalding/mfg_private.h"
|
||||
|
||||
#include "drivers/flash.h"
|
||||
#include "flash_region/flash_region.h"
|
||||
#include "mfg/spalding/spalding_boot.fpga.auto.h"
|
||||
#include "system/logging.h"
|
||||
#include "util/attributes.h"
|
||||
#include "util/crc32.h"
|
||||
|
||||
#include <inttypes.h>
|
||||
|
||||
const uintptr_t BOOT_FPGA_FLASH_ADDR = FLASH_REGION_MFG_INFO_BEGIN + 0x10000;
|
||||
|
||||
typedef struct PACKED {
|
||||
uint16_t fpga_len;
|
||||
uint16_t fpga_len_complemented;
|
||||
} BootFPGAHeader;
|
||||
|
||||
bool mfg_info_is_boot_fpga_bitstream_written(void) {
|
||||
BootFPGAHeader expected_fpga_header = { (uint16_t)sizeof s_boot_fpga,
|
||||
(uint16_t)~sizeof s_boot_fpga };
|
||||
|
||||
BootFPGAHeader header;
|
||||
flash_read_bytes((void *)&header, BOOT_FPGA_FLASH_ADDR, sizeof header);
|
||||
if (header.fpga_len != expected_fpga_header.fpga_len ||
|
||||
header.fpga_len_complemented != expected_fpga_header.fpga_len_complemented) {
|
||||
|
||||
PBL_LOG(LOG_LEVEL_DEBUG, "Boot FPGA length invalid, needs a rewrite");
|
||||
|
||||
// The length doesn't even match, we definitely need to update.
|
||||
return false;
|
||||
}
|
||||
|
||||
// Just because the length is the same that doesn't mean we don't need to update the FPGA
|
||||
// image. Compare CRCs to see if the new FPGA image is different.
|
||||
uint32_t expected_crc = crc32(CRC32_INIT, s_boot_fpga, sizeof(s_boot_fpga));
|
||||
uint32_t stored_crc = flash_crc32(
|
||||
BOOT_FPGA_FLASH_ADDR + sizeof(BootFPGAHeader), sizeof(s_boot_fpga));
|
||||
|
||||
PBL_LOG(LOG_LEVEL_DEBUG, "Comparing boot FPGA CRCs, expected 0x%"PRIx32" found 0x%"PRIx32,
|
||||
expected_crc, stored_crc);
|
||||
|
||||
return expected_crc == stored_crc;
|
||||
}
|
||||
|
||||
void mfg_info_write_boot_fpga_bitstream(void) {
|
||||
// Store the bootloader FPGA in the MFG info flash region so that the
|
||||
// bootloader can find it.
|
||||
_Static_assert(sizeof s_boot_fpga < 1<<16, "FPGA bitstream too big");
|
||||
BootFPGAHeader fpga_header = { (uint16_t)sizeof s_boot_fpga,
|
||||
(uint16_t)~sizeof s_boot_fpga };
|
||||
|
||||
// I have no idea why but clang really hates this assert
|
||||
//
|
||||
// ../../src/fw/mfg/spalding/boot_fpga.c:50:7: error: static_assert expression is not an
|
||||
// integral constant expression
|
||||
// (BOOT_FPGA_FLASH_ADDR + sizeof(BootFPGAHeader) + sizeof(s_boot_fpga))
|
||||
// ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
#if !__clang__
|
||||
_Static_assert(
|
||||
(BOOT_FPGA_FLASH_ADDR + sizeof(BootFPGAHeader) + sizeof(s_boot_fpga))
|
||||
< FLASH_REGION_MFG_INFO_END,
|
||||
"FPGA bitstream will overflow FLASH_REGION_MFG_INFO!");
|
||||
#endif
|
||||
|
||||
flash_write_bytes((const uint8_t *)&fpga_header,
|
||||
BOOT_FPGA_FLASH_ADDR, sizeof fpga_header);
|
||||
flash_write_bytes((const uint8_t *)s_boot_fpga,
|
||||
BOOT_FPGA_FLASH_ADDR + sizeof fpga_header,
|
||||
sizeof s_boot_fpga);
|
||||
}
|
131
src/fw/mfg/spalding/mfg_info.c
Normal file
131
src/fw/mfg/spalding/mfg_info.c
Normal file
|
@ -0,0 +1,131 @@
|
|||
/*
|
||||
* 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 "mfg/mfg_info.h"
|
||||
|
||||
#include "drivers/flash.h"
|
||||
#include "flash_region/flash_region.h"
|
||||
#include "mfg/mfg_serials.h"
|
||||
#include "mfg/spalding/mfg_private.h"
|
||||
#include "system/logging.h"
|
||||
|
||||
//! Used to version this struct if we have to add additional fields in the future.
|
||||
#define CURRENT_DATA_VERSION 3
|
||||
|
||||
typedef struct {
|
||||
uint32_t data_version;
|
||||
|
||||
uint32_t color;
|
||||
uint32_t rtc_freq;
|
||||
char model[MFG_INFO_MODEL_STRING_LENGTH]; //!< Null terminated model string
|
||||
int8_t disp_offset_x;
|
||||
int8_t disp_offset_y;
|
||||
} MfgData;
|
||||
|
||||
static void prv_update_struct(const MfgData *data) {
|
||||
flash_erase_subsector_blocking(FLASH_REGION_MFG_INFO_BEGIN);
|
||||
flash_write_bytes((const uint8_t*) data, FLASH_REGION_MFG_INFO_BEGIN, sizeof(*data));
|
||||
mfg_info_write_boot_fpga_bitstream();
|
||||
}
|
||||
|
||||
static MfgData prv_fetch_struct(void) {
|
||||
MfgData result;
|
||||
|
||||
flash_read_bytes((uint8_t*) &result, FLASH_REGION_MFG_INFO_BEGIN, sizeof(result));
|
||||
|
||||
switch (result.data_version) {
|
||||
case CURRENT_DATA_VERSION:
|
||||
// Our data is valid. Fall through.
|
||||
break;
|
||||
case 2:
|
||||
result.data_version = CURRENT_DATA_VERSION;
|
||||
result.disp_offset_x = 0;
|
||||
result.disp_offset_y = 0;
|
||||
break;
|
||||
case 1:
|
||||
// Our data is out of date. We need to do a conversion to populate the new model field.
|
||||
result.data_version = CURRENT_DATA_VERSION;
|
||||
result.model[0] = '\0';
|
||||
result.disp_offset_x = 0;
|
||||
result.disp_offset_y = 0;
|
||||
break;
|
||||
default:
|
||||
// No data present, just return an initialized struct with default values.
|
||||
return (MfgData) { .data_version = CURRENT_DATA_VERSION };
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
WatchInfoColor mfg_info_get_watch_color(void) {
|
||||
return prv_fetch_struct().color;
|
||||
}
|
||||
|
||||
void mfg_info_set_watch_color(WatchInfoColor color) {
|
||||
MfgData data = prv_fetch_struct();
|
||||
data.color = color;
|
||||
prv_update_struct(&data);
|
||||
}
|
||||
|
||||
GPoint mfg_info_get_disp_offsets(void) {
|
||||
return (GPoint) {
|
||||
.x = prv_fetch_struct().disp_offset_x,
|
||||
.y = prv_fetch_struct().disp_offset_y
|
||||
};
|
||||
}
|
||||
|
||||
void mfg_info_set_disp_offsets(GPoint p) {
|
||||
MfgData data = prv_fetch_struct();
|
||||
data.disp_offset_x = p.x;
|
||||
data.disp_offset_y = p.y;
|
||||
prv_update_struct(&data);
|
||||
}
|
||||
|
||||
uint32_t mfg_info_get_rtc_freq(void) {
|
||||
return prv_fetch_struct().rtc_freq;
|
||||
}
|
||||
|
||||
void mfg_info_set_rtc_freq(uint32_t rtc_freq) {
|
||||
MfgData data = prv_fetch_struct();
|
||||
data.rtc_freq = rtc_freq;
|
||||
prv_update_struct(&data);
|
||||
}
|
||||
|
||||
void mfg_info_get_model(char* buffer) {
|
||||
MfgData data = prv_fetch_struct();
|
||||
strncpy(buffer, data.model, sizeof(data.model));
|
||||
data.model[MFG_INFO_MODEL_STRING_LENGTH - 1] = '\0'; // Just in case
|
||||
}
|
||||
|
||||
void mfg_info_set_model(const char* model) {
|
||||
MfgData data = prv_fetch_struct();
|
||||
strncpy(data.model, model, sizeof(data.model));
|
||||
data.model[MFG_INFO_MODEL_STRING_LENGTH - 1] = '\0';
|
||||
prv_update_struct(&data);
|
||||
}
|
||||
|
||||
void mfg_info_update_constant_data(void) {
|
||||
if (mfg_info_is_boot_fpga_bitstream_written()) {
|
||||
PBL_LOG(LOG_LEVEL_INFO, "Boot FPGA bitstream already in flash.");
|
||||
} else {
|
||||
PBL_LOG(LOG_LEVEL_INFO, "Writing boot FPGA bitstream to flash...");
|
||||
|
||||
// Read the mfg data and write it back again. The prv_update_struct function write in a fresh
|
||||
// copy of the FPGA image as a side effect.
|
||||
MfgData data = prv_fetch_struct();
|
||||
prv_update_struct(&data);
|
||||
}
|
||||
}
|
22
src/fw/mfg/spalding/mfg_private.h
Normal file
22
src/fw/mfg/spalding/mfg_private.h
Normal file
|
@ -0,0 +1,22 @@
|
|||
/*
|
||||
* 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>
|
||||
|
||||
void mfg_info_write_boot_fpga_bitstream(void);
|
||||
bool mfg_info_is_boot_fpga_bitstream_written(void);
|
88
src/fw/mfg/tintin/mfg_info.c
Normal file
88
src/fw/mfg/tintin/mfg_info.c
Normal file
|
@ -0,0 +1,88 @@
|
|||
/*
|
||||
* 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 "mfg/mfg_info.h"
|
||||
|
||||
#include "console/dbgserial.h"
|
||||
|
||||
#include "services/common/legacy/factory_registry.h"
|
||||
#include "util/net.h"
|
||||
|
||||
#define COLOR_KEY "mfg_color"
|
||||
#define RTC_FREQ_KEY "mfg_rtcfreq"
|
||||
|
||||
static uint32_t prv_get_uint32(const char* key) {
|
||||
Record *rec = factory_registry_get(key, strlen(key), REGISTRY_SYSTEM_UUID);
|
||||
if (rec && (rec->value_length == sizeof(uint32_t))) {
|
||||
uint32_t *value = (uint32_t*)&rec->value;
|
||||
return ntohl(*value);
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static void prv_set_uint32(const char* key, uint32_t value) {
|
||||
// We store everything in the factory registry in network order, I'm not sure why.
|
||||
value = htonl(value);
|
||||
|
||||
int error = factory_registry_add(key, strlen(key), REGISTRY_SYSTEM_UUID, 0,
|
||||
(uint8_t*)&value, sizeof(value));
|
||||
|
||||
if (error) {
|
||||
dbgserial_putstr("Error");
|
||||
return;
|
||||
}
|
||||
|
||||
factory_registry_write_to_flash();
|
||||
}
|
||||
|
||||
WatchInfoColor mfg_info_get_watch_color(void) {
|
||||
return prv_get_uint32(COLOR_KEY);
|
||||
}
|
||||
|
||||
void mfg_info_set_watch_color(WatchInfoColor color) {
|
||||
prv_set_uint32(COLOR_KEY, color);
|
||||
}
|
||||
|
||||
uint32_t mfg_info_get_rtc_freq(void) {
|
||||
return prv_get_uint32(RTC_FREQ_KEY);
|
||||
}
|
||||
|
||||
void mfg_info_set_rtc_freq(uint32_t rtc_freq) {
|
||||
prv_set_uint32(RTC_FREQ_KEY, rtc_freq);
|
||||
}
|
||||
|
||||
void mfg_info_get_model(char* buffer) {
|
||||
// Not implemented, tintin/bianca's won't have this programmed.
|
||||
// FIXME: We could approximate this based on the watch color value.
|
||||
}
|
||||
|
||||
void mfg_info_set_model(const char* model) {
|
||||
// Not implemented, we won't be using this firmware for manufacturing tintin/biancas.
|
||||
}
|
||||
|
||||
GPoint mfg_info_get_disp_offsets(void) {
|
||||
// Not implemented. Can just assume no offset
|
||||
return (GPoint) {};
|
||||
}
|
||||
|
||||
void mfg_info_set_disp_offsets(GPoint p) {
|
||||
// Not implemented.
|
||||
}
|
||||
|
||||
void mfg_info_update_constant_data(void) {
|
||||
// No constant data required for tintin/bianca.
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue