Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions debian/changelog
Original file line number Diff line number Diff line change
@@ -1,3 +1,36 @@
wb-ec-firmware (2.4.0) stable; urgency=medium

* WB85: warm SoC reset via short PWROK/RESET pulse — PMIC rails and DRAM
stay powered, so pstore/ramoops panic logs survive the reboot
* WB85: two-stage watchdog — first timeout does a warm reset
(new poweron reason 8 "Watchdog (warm reset)"); if Linux does not feed
the watchdog before the next timeout, a hard power cycle follows
(reason 5 "Watchdog"). Consumers detecting watchdog resets must treat
both 5 and 8 as watchdog
* WB85: reset_pmic request (POWER_CTRL bit 2) now performs the short warm
pulse instead of the 2 s PMIC RESET hold
* WB74: behavior unchanged (no warm reset support)
* Fix state-machine wedge: a watchdog timeout racing a 3.3V loss or a
reset_pmic request in the same main loop pass started a second power
sequence on top of the warm reset (or PMIC reset) already in flight,
aborting the pulse and leaving the PMIC RESET (PWROK) line latched -
the SoC stayed held in reset forever (5V on, no SPL/U-Boot output,
watchdog resets no longer able to recover the board). Now only one
power action is taken per pass, hard_reset/hard_off/power-on always
release the reset line, and a completed warm pulse guarantees 5V is on
* Watchdog escalation restarts after each hard power cycle: with no
feeds the resets alternate warm/hard instead of latching to hard-only,
so the first hang of every fresh boot still gets a DRAM-preserving
warm reset (ramoops stays readable)
* Poweroff request racing a watchdog timeout no longer resurrects the
board: the power-off wins, the deferred timeout is absorbed
* Add wbec + linux-power-control + wdt integration test suite with a
board model (PMIC/SoC/5V rail simulation): reproduces the wedge,
sweeps PMIC failure timing across all reset sequence phases and
asserts the watchdog always recovers the SoC in bounded time

-- Wiren Board Robot <info@wirenboard.com> Fri, 04 Jul 2026 14:00:00 +0300

wb-ec-firmware (2.3.0) stable; urgency=medium

* Port for Debian 13
Expand Down
5 changes: 5 additions & 0 deletions include/config.h
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,11 @@

// Время, на которое выключается питание при перезагрузке
#define WBEC_POWER_RESET_TIME_MS 1000
// Длительность импульса на линии PMIC RESET (PWROK) при тёплом сбросе.
// Тёплый сброс перезапускает SoC через его линию RESET, не отключая
// питание PMIC — содержимое DRAM (в т.ч. ramoops) сохраняется.
// Работает только если в PMIC не включен рестарт по PWROK (AXP REG32[4]=0)
#define WBEC_WARM_RESET_PULSE_MS 100
// Время загрузки Linux и драйверов WBEC
// До этого времени питание выключается сразу при коротком нажатии
// После - отправляется запрос в Linux
Expand Down
7 changes: 7 additions & 0 deletions include/config_wb85.h
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,13 @@
// Поддержка spi-uart
#define EC_UART_REGMAP_SUPPORT

// Тёплый сброс SoC импульсом на линии PWROK/RESET (см. linux_cpu_pwr_seq_warm_reset).
// Проверено на WB 8.5: линия заведена на RESET процессора T507, PMIC (AXP15060)
// игнорирует импульс при REG32[4]=0 и не отключает питание DRAM.
// На WB74 не включаем: поведение импульса на этой платформе не проверялось,
// там watchdog по-прежнему сразу делает жёсткий сброс по питанию
#define WBEC_HAS_WARM_RESET

// name freq sda scl
#define SOFTWARE_I2C_DESC(macro) macro(WBMZ6, 100000, GPIOF, 1, GPIOF, 0)

Expand Down
1 change: 1 addition & 0 deletions include/linux-power-control.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,6 @@ void linux_cpu_pwr_seq_on(void);
void linux_cpu_pwr_seq_hard_off(void);
void linux_cpu_pwr_seq_hard_reset(void);
void linux_cpu_pwr_seq_reset_pmic(void);
void linux_cpu_pwr_seq_warm_reset(void);
bool linux_cpu_pwr_seq_is_busy(void);
void linux_cpu_pwr_seq_do_periodic_work(void);
1 change: 1 addition & 0 deletions include/wdt.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@ void wdt_set_timeout(uint16_t secs);
void wdt_start_reset(void);
void wdt_stop(void);
bool wdt_handle_timed_out(void);
bool wdt_handle_fed(void);
void wdt_do_periodic_work(void);
43 changes: 43 additions & 0 deletions src/linux-power-control.c
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ enum pwr_state {

PS_RESET_5V_WAIT, // Нужно при перезаргрузке - выключаем 5В и ждём разрядку линий
PS_RESET_PMIC_WAIT, // Сброс PMIC через PMIC_RESET_PWROK. Ждём, пока пропадёт 3.3В
PS_WARM_RESET_PULSE, // Тёплый сброс SoC: короткий импульс на PMIC_RESET_PWROK
};

struct pwr_ctx {
Expand Down Expand Up @@ -136,6 +137,10 @@ void linux_cpu_pwr_seq_on(void)
return;
}

// Линия PMIC RESET (PWROK) не должна достаться последовательности
// включения взведённой (например, от прерванного тёплого сброса) -
// иначе SoC останется заклиненным в сбросе
pmic_reset_gpio_off();
linux_cpu_pwr_5v_gpio_on();
new_state(PS_ON_STEP1_WAIT_3V3);
}
Expand All @@ -148,6 +153,9 @@ void linux_cpu_pwr_seq_hard_off(void)
{
linux_cpu_pwr_5v_gpio_off();
pmic_pwron_gpio_off();
// Отпускаем линию сброса: никакая последовательность не должна
// оставлять её взведённой после своего завершения или прерывания
pmic_reset_gpio_off();
new_state(PS_OFF_COMPLETE);
}

Expand All @@ -159,6 +167,10 @@ void linux_cpu_pwr_seq_hard_reset()
{
linux_cpu_pwr_5v_gpio_off();
pmic_pwron_gpio_off();
// Отпускаем линию сброса: если жёсткий сброс прервал тёплый сброс
// или сброс PMIC, линия не должна остаться взведённой - иначе после
// подачи 5В SoC навсегда останется в сбросе
pmic_reset_gpio_off();
new_state(PS_RESET_5V_WAIT);
}

Expand All @@ -173,6 +185,22 @@ void linux_cpu_pwr_seq_reset_pmic(void)
new_state(PS_RESET_PMIC_WAIT);
}

/**
* @brief Тёплый сброс SoC коротким импульсом на линии PMIC_RESET_PWROK.
* Линия одновременно заведена на PWROK PMIC и RESET процессора T507.
* Если в PMIC отключен рестарт по PWROK (AXP REG32[4]=0, значение по
* умолчанию), PMIC игнорирует импульс и все его выходы, включая питание
* DRAM, остаются включёнными — сбрасывается только SoC, содержимое DRAM
* сохраняется (это позволяет ramoops пережить сброс).
* Если же PMIC настроен на рестарт по PWROK, пропадёт 3.3В и штатная
* логика включения (PS_ON_STEP1_WAIT_3V3) выполнит полный цикл включения.
*/
void linux_cpu_pwr_seq_warm_reset(void)
{
pmic_reset_gpio_on();
new_state(PS_WARM_RESET_PULSE);
}

/**
* @brief Статус работы алгоритма управления питанием
*
Expand Down Expand Up @@ -294,6 +322,21 @@ void linux_cpu_pwr_seq_do_periodic_work(void)
}
break;

// Тёплый сброс SoC: отпускаем линию RESET после короткого импульса.
// Если PMIC проигнорировал импульс, 3.3В на месте и включение
// завершится сразу; если PMIC перезапустился - штатное включение
case PS_WARM_RESET_PULSE:
if (in_state_time_ms() > WBEC_WARM_RESET_PULSE_MS) {
pmic_reset_gpio_off();
pmic_pwron_gpio_off();
// Тёплый сброс всегда заканчивается последовательностью включения.
// Гарантируем ей 5В: если тёплый сброс был запрошен, когда 5В
// оказалось снятым, ожидание 3.3В без 5В бессмысленно
linux_cpu_pwr_5v_gpio_on();
new_state(PS_ON_STEP1_WAIT_3V3);
}
break;

// Сброс PMIC через RESET самого PMIC
case PS_RESET_PMIC_WAIT:
if ((!vmon_get_ch_status(VMON_CHANNEL_V33)) || (in_state_time_ms() > 2000)) {
Expand Down
70 changes: 70 additions & 0 deletions src/wbec.c
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@
m(REASON_WATCHDOG, "Watchdog" ) \
m(REASON_PMIC_OFF, "PMIC is unexpectedly off" ) \
m(REASON_UNKNOWN, "Unknown" ) \
/* Новые значения добавлять только в конец: значения - часть ABI regmap */ \
m(REASON_WATCHDOG_WARM, "Watchdog (warm reset)" ) \

#define __LINUX_POWERON_REASON_NAME(name, string) name,
#define __LINUX_POWERON_REASON_STRING(name, string) string,
Expand Down Expand Up @@ -77,6 +79,11 @@ struct wbec_ctx {
unsigned power_loss_cnt;
systime_t power_loss_timestamp;
enum linux_poweron_reason poweron_reason;
// Была ли уже попытка тёплого сброса по watchdog.
// Сбрасывается, когда Linux сбрасывает watchdog (система жива).
// Если после тёплого сброса Linux так и не начал сбрасывать watchdog,
// следующий таймаут приведёт к жёсткому сбросу по питанию
bool wd_warm_reset_attempted;
};

static struct wbec_ctx wbec_ctx;
Expand Down Expand Up @@ -546,13 +553,68 @@ void wbec_do_periodic_work(void)
linux_cpu_pwr_seq_hard_reset();
new_state(WBEC_STATE_POWER_ON_SEQUENCE_WAIT);
} else if (linux_powerctrl_req == LINUX_POWERCTRL_PMIC_RESET) {
#if defined(WBEC_HAS_WARM_RESET)
// Тёплый сброс SoC по запросу из Linux: короткий импульс на линии
// PWROK/RESET. Питание PMIC (и DRAM) не отключается, поэтому
// сохранённые в DRAM логи (ramoops) переживают перезагрузку
wbec_ctx.poweron_reason = REASON_REBOOT;
console_print("\r\n\n");
console_print_w_prefix("Warm reset request, pulse PMIC RESET (PWROK) line now\r\n\n");
linux_cpu_pwr_seq_warm_reset();
new_state(WBEC_STATE_POWER_ON_SEQUENCE_WAIT);
#else
// Историческое поведение: сброс PMIC через линию RESET (до 2 с)
wbec_ctx.poweron_reason = REASON_REBOOT;
console_print("\r\n\n");
console_print_w_prefix("PMIC reset request, activate PMIC RESET line now\r\n\n");
linux_cpu_pwr_seq_reset_pmic();
new_state(WBEC_STATE_POWER_ON_SEQUENCE_WAIT);
#endif
}

// За один проход разрешено только одно действие с питанием.
// Если запрос из Linux уже начал смену состояния, проверки ниже
// (watchdog, контроль 3.3В) выполнять нельзя: их обработчики
// запускают свои последовательности поверх уже начатой и могут
// прервать её (например, оставить линию PMIC RESET (PWROK)
// взведённой навсегда - SoC заклинит в сбросе).
// Взведённые флаги watchdog не теряются: они будут обработаны
// после завершения начатой последовательности
if (wbec_ctx.state != WBEC_STATE_WORKING) {
break;
}

#if defined(WBEC_HAS_WARM_RESET)
// Если Linux сбрасывает watchdog - система жива,
// разрешаем следующую попытку тёплого сброса
if (wdt_handle_fed()) {
wbec_ctx.wd_warm_reset_attempted = false;
}

// Если сработал WDT - сначала пробуем тёплый сброс (DRAM сохраняется,
// логи ramoops можно будет прочитать после перезагрузки).
// Если после тёплого сброса система не ожила (Linux не сбросил
// watchdog до следующего таймаута) - жёсткий сброс по питанию
if (wdt_handle_timed_out()) {
console_print("\r\n\n");
if (!wbec_ctx.wd_warm_reset_attempted) {
wbec_ctx.wd_warm_reset_attempted = true;
wbec_ctx.poweron_reason = REASON_WATCHDOG_WARM;
console_print_w_prefix("Watchdog is timed out, try warm reset first (DRAM is preserved).\r\n");
linux_cpu_pwr_seq_warm_reset();
} else {
wbec_ctx.poweron_reason = REASON_WATCHDOG;
console_print_w_prefix("Watchdog is timed out again, reset power.\r\n");
linux_cpu_pwr_seq_hard_reset();
// Жёсткий сброс - последняя ступень эскалации. После него
// начинается новый цикл загрузки, и первое зависание в нём
// снова заслуживает тёплого сброса (с сохранением DRAM/ramoops),
// поэтому эскалация начинается заново
wbec_ctx.wd_warm_reset_attempted = false;
}
new_state(WBEC_STATE_POWER_ON_SEQUENCE_WAIT);
}
#else
// Если сработал WDT - перезагружаемся по питанию
if (wdt_handle_timed_out()) {
wbec_ctx.poweron_reason = REASON_WATCHDOG;
Expand All @@ -561,6 +623,14 @@ void wbec_do_periodic_work(void)
linux_cpu_pwr_seq_hard_reset();
new_state(WBEC_STATE_POWER_ON_SEQUENCE_WAIT);
}
#endif

// Сработал watchdog - сброс уже начат. Контроль 3.3В в этом проходе
// пропускаем, чтобы не запустить вторую последовательность поверх
// начатой (см. комментарий выше)
if (wbec_ctx.state != WBEC_STATE_WORKING) {
break;
}

// Если пропало 3.3В - пробуем перезапустить питание, но не более N раз за M минут
// Если питание пропадает слишком часто - выключаемся
Expand Down
23 changes: 23 additions & 0 deletions src/wdt.c
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ struct wdt_ctx {
systime_t timestamp;
bool run;
bool timed_out;
bool fed;
};

static struct wdt_ctx wdt_ctx = {
Expand Down Expand Up @@ -56,6 +57,27 @@ bool wdt_handle_timed_out(void)
return ret;
}

// Возвращает true, если Linux сбрасывал watchdog через regmap
// с момента предыдущего вызова. Признак того, что система жива.
bool wdt_handle_fed(void)
{
bool ret = wdt_ctx.fed;
if (ret) {
wdt_ctx.fed = 0;
}
return ret;
}

#ifdef __unittest_env__
#include <string.h>

void utest_wdt_module_reset_state(void)
{
memset(&wdt_ctx, 0, sizeof(wdt_ctx));
wdt_ctx.timeout_s = WBEC_WATCHDOG_INITIAL_TIMEOUT_S;
}
#endif

void wdt_do_periodic_work(void)
{
if (wdt_ctx.run) {
Expand All @@ -78,6 +100,7 @@ void wdt_do_periodic_work(void)
}
if (w.reset) {
wdt_start_reset();
wdt_ctx.fed = 1;
}
}

Expand Down
Loading