nmhw21: board: add user reset option via rs232

allow invoking factory reset and recovery boot via:
- uart break, followed by command ('r', 'f')
- using uart rxd as reset line

Co-authored-by: Rene Straub <rene.straub@netmodule.com>
Reviewed-by: Patrick Zysset <patrick.zysset@netmodule.com>
This commit is contained in:
René Straub 2020-06-08 16:15:27 +02:00
parent a3fd0500ec
commit 7b1000b59d
1 changed files with 412 additions and 24 deletions

View File

@ -104,6 +104,7 @@ DECLARE_GLOBAL_DATA_PTR;
#define GPIO_SIM_SEL GPIO_TO_PIN(1, 12)
#define GPIO_SIM_PRES_N GPIO_TO_PIN(2, 16)
#define GPIO_UART2_RX GPIO_TO_PIN(0, 2) /* UART Rx Pin as GPIO */
/*
@ -133,6 +134,15 @@ DECLARE_GLOBAL_DATA_PTR;
#define DDR3_CLOCK_FREQUENCY (400)
#define DURATION_FACTORY_RESET (2000) /* Reset button time to initiate factory reset */
#define DURATION_RECOVERY_BOOT (12000) /* Reset button time to initiate recovery boot */
#if (DURATION_RECOVERY_BOOT < (DURATION_FACTORY_RESET+1000))
#error Recovery boot time must be larger than factory reset + 1 second
#endif
#if !defined(CONFIG_SPL_BUILD)
/* Hardware version information of mainboard, loaded by get_hw_version() */
static int hw_ver = -1;
@ -1109,53 +1119,431 @@ static void blink_led(int pulses)
#endif
}
static void check_reset_button(void)
{
int counter = 0;
/* Check how long button is pressed */
do {
if (!get_button_state())
#define RESET_CHECK_PERIOD (100) /* 100 ms */
typedef enum _action_t
{
NONE = 0,
WAIT,
FACTORY_RESET,
RECOVERY_BOOT
} action_t;
static void uart2_rx_gpio_pin_mux(void)
{
/* Configure UART2 rxd pin as GPIO */
static struct module_pin_mux uart2_gpio_pin_mux[] = {
{OFFSET(spi0_sclk), (MODE(7) | PULLUDEN | PULLUP_EN | RXACTIVE)}, /* (A17) uart2_rxd = gpio0_2 */
{-1}
};
configure_module_pin_mux(uart2_gpio_pin_mux);
}
static void uart2_rx_rx_pin_mux(void)
{
/* Configure UART2 rxd pin as rxd function */
static struct module_pin_mux uart2_pin_mux[] = {
{OFFSET(spi0_sclk), (MODE(1) | PULLUDEN | PULLUP_EN | RXACTIVE)}, /* (A17) uart2_rxd */
{-1}
};
configure_module_pin_mux(uart2_pin_mux);
}
static bool get_rxd_state(void)
{
uart2_rx_gpio_pin_mux();
(void)gpio_request(GPIO_UART2_RX, "");
int val = gpio_get_value(GPIO_UART2_RX);
gpio_free(GPIO_UART2_RX);
uart2_rx_rx_pin_mux();
return (val == 0);
}
static int check_button(int time, bool button)
{
/*
* Hardware button logic
*
* input:
* button: current button press state (true = pressed)
* logic:
* - if button is not pressed initially, return action NONE for all
* invocations (state NO_ACTION)
* - if button pressed initially, return action WAIT and measure duration
* of press (state PRESSED).
* - while button is pressed, measure time, indicate actions with LED,
* - when button is released, check time and decide on factory reset
* or recovery boot.
*/
typedef enum _state_t { INIT = 0, PRESSED, DONE } state_t;
static state_t state = INIT; /* Current state */
static action_t act = NONE; /* Action to report to user, returned at function end */
static int duration = 0;
if (time == 0) {
state = INIT;
}
/* TODO: Remove printf, puts */
/* printf("button: state %d button %d -> ", state, button); */
switch (state) {
case INIT:
if (button) {
/* Pressed, start to measure time until released */
duration = 0;
act = WAIT;
state = PRESSED;
}
else {
/* If not pressed, no further action is possible */
act = NONE;
state = DONE;
}
break;
udelay(100*1000); /* 100ms */
counter += 100;
if (counter == 2000) {
case PRESSED:
if (button) {
duration += RESET_CHECK_PERIOD;
if (duration == DURATION_FACTORY_RESET) {
/* Indicate factory reset threshold */
blink_led(1);
}
else if (counter == 12000) {
/* Indicate recovery boot threshold */
else if (duration == DURATION_RECOVERY_BOOT) {
/* Indicate recovery boot threshold and leave state */
blink_led(2);
act = RECOVERY_BOOT;
state = DONE;
}
} while (counter < 12000);
}
else {
/* Check how long button was pressed */
if (duration >= DURATION_RECOVERY_BOOT) {
act = RECOVERY_BOOT;
state = DONE;
}
else if (duration >= DURATION_FACTORY_RESET) {
act = FACTORY_RESET;
state = DONE;
}
else {
/* too short, nothing to do */
act = NONE;
state = DONE;
}
}
break;
if (counter < 2000) {
/* Don't do anything for duration < 2s */
case DONE:
/* Final state when an action has been chosen */
break;
default:
break;
}
else if (counter < 12000)
/* printf("act %d\n", act); */
return act;
}
static int check_break_length(int time, bool rx_line)
{
/*
* RS232 RxD Reset input logic
*
* Treats RxD line as reset input. Line is considered active when
* break condition applies (RS232 line > 3.0 V)
*
* input:
* rx_line: true if rxd input is '0' (break condition)
* logic:
* see explanation in check_button()
* additional logic is added to debounce input
*/
#define BREAK_DEB_TIME 500
typedef enum _state_t { WAIT_BREAK = 0, HAVE_BREAK, DONE } state_t;
static state_t state = WAIT_BREAK; /* Current state */
static action_t act = NONE; /* Action to report to user, returned at function end */
static int duration = 0;
static int wait_time = 0;
if (time == 0) {
state = WAIT_BREAK;
wait_time = BREAK_DEB_TIME;
}
/* TODO: Remove */
/* printf("break length: state %d have_break_cond %d duration %d -> ", state, have_break_cond, duration); */
switch (state) {
case WAIT_BREAK:
if (rx_line) {
/* Break detected, start to measure time */
duration = 0;
wait_time = BREAK_DEB_TIME;
act = WAIT;
state = HAVE_BREAK;
}
if (wait_time > 0) {
wait_time -= RESET_CHECK_PERIOD;
act = WAIT;
}
else {
/* If no break seen, stop here */
act = NONE;
state = DONE;
}
break;
case HAVE_BREAK:
if (rx_line) {
wait_time = BREAK_DEB_TIME;
duration += RESET_CHECK_PERIOD;
if (duration == DURATION_FACTORY_RESET) {
/* Indicate factory reset threshold */
blink_led(1);
}
else if (duration == DURATION_RECOVERY_BOOT) {
/* Indicate recovery boot threshold and leave state */
blink_led(2);
act = RECOVERY_BOOT; /* TODO: maybe remain in state until break removed */
state = DONE;
}
}
if (wait_time > 0) {
wait_time -= RESET_CHECK_PERIOD;
}
else {
/* puts("break condition released\n"); */
/* Check how long button was pressed */
if (duration >= DURATION_RECOVERY_BOOT) {
act = RECOVERY_BOOT;
state = DONE;
}
else if (duration >= DURATION_FACTORY_RESET) {
act = FACTORY_RESET;
state = DONE;
}
else {
/* too short, nothing to do */
act = NONE;
state = DONE;
}
}
break;
case DONE:
/* Final state when an action has been chosen */
break;
default:
break;
}
/* printf("act %d\n", act); */
return act;
}
static int check_break_command(int time, int has_input, int c)
{
/*
* Break command logic
*
* Tries to receive the break button (system magic key) and a command to execute.
*
* input:
* has_input: true if a character is available
* c: character code (can be 0x00 for break condition, system magic key)
* logic:
* - waits up to 500 ms to receive a break character. if character is not seen,
* stops here, action = NONE. if character is detected goes to next state
* - waits up to 500 ms to receive command character. if character is not seen,
* stops here, action = NONE. if character is detected define action accordingly
* 'f': factory reset
* 'r': recovery boot
*/
#define BREAK_WAIT_TIME 1000 /* Initial wait time for break character */
#define CHARACTER_WAIT_TIME 1500 /* Subsequent wait time for command */
typedef enum _state_t { WAIT_BREAK = 1, WAIT_COMMAND, DONE } state_t;
static state_t state = WAIT_BREAK; /* Current state */
static action_t act = NONE; /* Action to report to user, returned at function end */
static int wait_time;
/* TODO: remove */
/* printf("break command: state %d, input %d %02x -> ", state, has_input, c); */
if (time == 0) {
state = WAIT_BREAK;
wait_time = BREAK_WAIT_TIME;
}
switch (state) {
case WAIT_BREAK: /* Wait for break indication */
act = WAIT;
if (has_input) {
if (c == 0x00) {
/* puts("break detected\n"); */
wait_time = CHARACTER_WAIT_TIME;
state = WAIT_COMMAND;
}
}
if (wait_time > 0) {
wait_time -= RESET_CHECK_PERIOD;
}
else {
/* No break received, nothing to do from our side */
act = NONE;
state = DONE;
}
break;
case WAIT_COMMAND:
if (has_input) {
wait_time = CHARACTER_WAIT_TIME;
if (c == 0x00) {
/* puts("break detected\n"); */
act = WAIT;
}
else if (c == 'f') {
blink_led(1);
act = FACTORY_RESET;
state = DONE;
}
else if (c == 'r') {
blink_led(2);
act = RECOVERY_BOOT;
state = DONE;
}
else {
/* Unknown command */
act = NONE;
state = DONE;
}
}
if (wait_time > 0) {
wait_time -= RESET_CHECK_PERIOD;
}
else {
/* puts("character timeout\n"); */
act = NONE;
state = DONE;
}
break;
case DONE:
/* Final state when an action has been chosen */
break;
default:
break;
}
/* printf("act %d\n", act); */
return act;
}
static void check_reset_button(void)
{
/* Do factory reset for duration between 2s and 12s */
/*
* Runs state machines of all reset sources to find out if one detects
* an action.
*
* SMs return
* - WAIT if they are still trying to get a result.
* - NONE if they know there is no action required.
* - FACTORY_RESET, RECOVERY_BOOT if detected.
*/
action_t action = NONE;
int counter = 0;
do {
action_t a_[3];
bool button;
bool rx_line;
int has_input;
int c = -1;
/* Get state machine inputs */
button = get_button_state();
rx_line = get_rxd_state();
has_input = tstc();
if (has_input) {
c = getc();
}
/* run processing state machines */
a_[0] = NONE;
a_[1] = NONE;
a_[2] = NONE;
a_[0] = check_button(counter, button);
a_[1] = check_break_length(counter, rx_line);
a_[2] = check_break_command(counter, has_input, c);
/* if all SMs are sure there is not action, stop here */
if ((a_[0] == NONE) && (a_[1] == NONE) && (a_[2] == NONE)) {
action = NONE;
break;
}
/* if one of the SMs has an action, stop here */
for (int i=0; i<3; i++) {
if ((a_[i] == FACTORY_RESET) || (a_[i] == RECOVERY_BOOT)) {
action = a_[i];
break;
}
}
mdelay(RESET_CHECK_PERIOD);
counter += RESET_CHECK_PERIOD;
} while ((action != FACTORY_RESET) && (action != RECOVERY_BOOT));
switch (action) {
case FACTORY_RESET: {
char new_bootargs[512];
char *bootargs = getenv("bootargs");
if (bootargs == 0) bootargs = "";
puts("Do factory reset during boot...\n");
if (bootargs == 0) bootargs = "";
strncpy(new_bootargs, bootargs, sizeof(new_bootargs));
strncat(new_bootargs, " factory-reset", sizeof(new_bootargs));
setenv("bootargs", new_bootargs);
break;
}
else
{
/* Boot into recovery for duration > 12s */
case RECOVERY_BOOT:
puts("Booting recovery image...\n");
/* Set bootcmd to run recovery */
setenv("bootcmd", "run recovery");
break;
default:
set_indicator_led(0, 0);
break;
}
}