From 0778836bf9615399f5f7569f7977cf5fe7aa7cb1 Mon Sep 17 00:00:00 2001 From: Evgeny Boger Date: Sun, 5 Jul 2026 22:34:55 +0300 Subject: [PATCH 01/16] wb7, wb8: enable pstore/ramoops support in kernel config Enable CONFIG_PSTORE_RAM and CONFIG_PSTORE_CONSOLE for the wb8 and wb7 flavours. The backing reserved-memory node (compatible = "ramoops", last 1 MiB of DRAM, record-size 128K, console-size 256K, ecc 32) is injected at boot by the Wiren Board U-Boot DT fixup, so kernels running under an older U-Boot simply see no node and nothing changes. With the node present, panic and console logs survive warm resets and are harvested by the already-enabled systemd-pstore.service. Part of the Wiren Board panic-log persistence (pstore shuttle) work: U-Boot saves/restores this region across full power cycles via the swap partition. Targets feature/v6.18 (most recent kernel line) per project branch policy. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01EVPkc2g2yir6SGyrED6uNT --- arch/arm/configs/wirenboard7_defconfig | 8 ++++++++ arch/arm64/configs/wb8.config | 8 ++++++++ 2 files changed, 16 insertions(+) diff --git a/arch/arm/configs/wirenboard7_defconfig b/arch/arm/configs/wirenboard7_defconfig index 8da0c99bfeed..333f07913c2d 100644 --- a/arch/arm/configs/wirenboard7_defconfig +++ b/arch/arm/configs/wirenboard7_defconfig @@ -591,3 +591,11 @@ CONFIG_PREEMPTIRQ_DELAY_TEST=m CONFIG_STRICT_DEVMEM=y CONFIG_DEBUG_USER=y CONFIG_CONFIGFS_FS=y + +# Pstore/ramoops: persist kernel panic + console logs in a RAM region +# across warm resets. The reserved-memory "ramoops" node is injected by +# the Wiren Board U-Boot DT fixup (WIRENBOARD_RAMOOPS_FIXUP); without it +# these options are inert. PSTORE_RAM auto-selects REED_SOLOMON*. +CONFIG_PSTORE=y +CONFIG_PSTORE_RAM=y +CONFIG_PSTORE_CONSOLE=y diff --git a/arch/arm64/configs/wb8.config b/arch/arm64/configs/wb8.config index a5c55fc686aa..cfdb31de11b7 100644 --- a/arch/arm64/configs/wb8.config +++ b/arch/arm64/configs/wb8.config @@ -382,3 +382,11 @@ CONFIG_SUN50I_H6_PRCM_PPU=y #SOUND CONFIG_SND_HDA_PREALLOC_SIZE=2048 + +# Pstore/ramoops: persist kernel panic + console logs in a RAM region +# across warm resets. The reserved-memory "ramoops" node is injected by +# the Wiren Board U-Boot DT fixup (WIRENBOARD_RAMOOPS_FIXUP); without it +# these options are inert. PSTORE_RAM auto-selects REED_SOLOMON*. +CONFIG_PSTORE=y +CONFIG_PSTORE_RAM=y +CONFIG_PSTORE_CONSOLE=y From 9331a0f8b341057905f65fa4cc4cb639a5661deb Mon Sep 17 00:00:00 2001 From: Evgeny Boger Date: Sun, 5 Jul 2026 22:36:50 +0300 Subject: [PATCH 02/16] soc: sunxi: wb: warm-reset via SoC watchdog on panic when ramoops present A panicked Wiren Board 7 or 8 otherwise parks until the EC watchdog acts: on pre-2.4.0 EC firmware that is a full power cycle, destroying DRAM and the just-written ramoops records; 2.4.0+ warm-resets first. This panic notifier arms the idle SoC watchdog (~6 s, a few MMIO writes, safe in panic context) so the board takes a DRAM-preserving warm reset immediately and firmware-independently; U-Boot then parks the records on eMMC and performs the full recovery power cycle (pstore shuttle). The watchdog is resolved from the device tree by compatible and armed by mirroring the sunxi_wdt.c restart handler for both register layouts (sun6i-a31-wdt on H616/T507, sun4i-a10-wdt on R40/A40i), but with a 6 s interval (exact hardware encoding) instead of 0.5 s so kmsg_dump and the serial console can finish before the reset; a configured panic_timeout below 6 s intentionally wins. Gated at runtime on an actual ramoops node (injected by the Wiren Board U-Boot DT fixup): with an older U-Boot there is no node, the notifier never registers, and panic behaviour is unchanged - a kernel-only update is a strict no-op. Targets feature/v6.18 (most recent kernel line) per project branch policy. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01EVPkc2g2yir6SGyrED6uNT --- arch/arm/configs/wirenboard7_defconfig | 1 + arch/arm64/configs/wb8.config | 1 + drivers/soc/sunxi/Kconfig | 11 ++ drivers/soc/sunxi/Makefile | 1 + drivers/soc/sunxi/wb-ramoops-panic-reset.c | 167 +++++++++++++++++++++ 5 files changed, 181 insertions(+) create mode 100644 drivers/soc/sunxi/wb-ramoops-panic-reset.c diff --git a/arch/arm/configs/wirenboard7_defconfig b/arch/arm/configs/wirenboard7_defconfig index 333f07913c2d..ad64ce845282 100644 --- a/arch/arm/configs/wirenboard7_defconfig +++ b/arch/arm/configs/wirenboard7_defconfig @@ -599,3 +599,4 @@ CONFIG_CONFIGFS_FS=y CONFIG_PSTORE=y CONFIG_PSTORE_RAM=y CONFIG_PSTORE_CONSOLE=y +CONFIG_WB_RAMOOPS_PANIC_RESET=y diff --git a/arch/arm64/configs/wb8.config b/arch/arm64/configs/wb8.config index cfdb31de11b7..3fe80d65461e 100644 --- a/arch/arm64/configs/wb8.config +++ b/arch/arm64/configs/wb8.config @@ -390,3 +390,4 @@ CONFIG_SND_HDA_PREALLOC_SIZE=2048 CONFIG_PSTORE=y CONFIG_PSTORE_RAM=y CONFIG_PSTORE_CONSOLE=y +CONFIG_WB_RAMOOPS_PANIC_RESET=y diff --git a/drivers/soc/sunxi/Kconfig b/drivers/soc/sunxi/Kconfig index 8aecbc9b1976..047a75943d80 100644 --- a/drivers/soc/sunxi/Kconfig +++ b/drivers/soc/sunxi/Kconfig @@ -19,3 +19,14 @@ config SUNXI_SRAM Say y here to enable the SRAM controller support. This device is responsible on mapping the SRAM in the sunXi SoCs whether to the CPU/DMA, or to the devices. + +config WB_RAMOOPS_PANIC_RESET + bool "Wiren Board: SoC watchdog warm reset on panic when ramoops is present" + depends on ARCH_SUNXI && PSTORE_RAM + help + On panic, arm the (otherwise idle) SoC watchdog so a Wiren + Board 7 (Allwinner R40/A40i) or Wiren Board 8 (Allwinner + T507/H616) takes a DRAM-preserving warm reset and the panic + log survives in the ramoops region for the bootloader to + persist. Arms itself only when a ramoops reserved-memory + node is present. diff --git a/drivers/soc/sunxi/Makefile b/drivers/soc/sunxi/Makefile index 549159571d4f..428cd117bebb 100644 --- a/drivers/soc/sunxi/Makefile +++ b/drivers/soc/sunxi/Makefile @@ -1,3 +1,4 @@ # SPDX-License-Identifier: GPL-2.0-only obj-$(CONFIG_SUNXI_MBUS) += sunxi_mbus.o obj-$(CONFIG_SUNXI_SRAM) += sunxi_sram.o +obj-$(CONFIG_WB_RAMOOPS_PANIC_RESET) += wb-ramoops-panic-reset.o diff --git a/drivers/soc/sunxi/wb-ramoops-panic-reset.c b/drivers/soc/sunxi/wb-ramoops-panic-reset.c new file mode 100644 index 000000000000..5f5c4cf989d7 --- /dev/null +++ b/drivers/soc/sunxi/wb-ramoops-panic-reset.c @@ -0,0 +1,167 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Wiren Board 7 (Allwinner R40/A40i) and Wiren Board 8 (Allwinner + * T507/H616): warm-reset the SoC shortly after a panic so the panic + * log persists in the ramoops region. + * + * The ramoops region at the top of DRAM survives a SoC watchdog reset + * (DRAM stays powered), after which U-Boot parks the records on eMMC + * and performs a full recovery power cycle (the "pstore shuttle"). + * Without this hook a panicked kernel just parks until the EC watchdog + * acts: on pre-2.4.0 EC firmware the EC watchdog action is a full + * power cycle, destroying DRAM and the records; 2.4.0+ warm-resets + * first - this hook makes the reset immediate and firmware-independent. + * + * The panic notifier arms the SoC watchdog with a ~6 s interval using + * a few raw MMIO writes - nothing here can sleep, lock or allocate, so + * it is safe in panic context. The sunxi watchdog device (watchdog0) + * is idle on Wiren Board systems: userspace feeds the EC watchdog + * (watchdog1) instead. + * + * Why ~6 s instead of the 0.5 s hardware minimum: the panic notifiers + * run before kmsg_dump writes the ramoops records and before the + * serial console drains the backtrace, so a 0.5 s fuse could fire + * before the dump completes. An operator-configured panic_timeout + * shorter than 6 s intentionally wins over this hook. + * + * The hook arms itself only if a ramoops region actually registered: + * on systems booted by an older U-Boot (no injected ramoops node) + * panic behaviour is intentionally left unchanged, so updating the + * kernel alone changes nothing (no-lockstep updates). + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include + +/* + * Register layouts and arming sequence mirror the restart handler in + * drivers/watchdog/sunxi_wdt.c (sun4i_wdt_reg / sun6i_wdt_reg), except + * for the interval: the restart handler uses the minimal 0.5 s, we set + * 6 s, which has an exact hardware encoding (wdt_timeout_map[6] = 0x6). + * Neither WB variant uses a key value (that is sun20i/sun55i only). + */ +#define WDT_CTRL_RELOAD ((1 << 0) | (0x0a57 << 1)) +#define WDT_MODE_EN (1 << 0) +#define WDT_TIMEOUT_MASK 0x0F +#define WDT_INTV_6S 0x6 /* wdt_timeout_map[6] in sunxi_wdt.c */ + +struct wb_wdt_variant { + u8 ctrl; /* counter restart register */ + u8 cfg; /* reset configuration register */ + u8 mode; /* enable + interval register */ + u8 timeout_shift; + u8 reset_mask; + u8 reset_val; /* whole-system reset */ +}; + +/* H616/T507: watchdog in the timer block, dedicated CFG register */ +static const struct wb_wdt_variant sun6i_wdt_variant = { + .ctrl = 0x10, + .cfg = 0x14, + .mode = 0x18, + .timeout_shift = 4, + .reset_mask = 0x03, + .reset_val = 0x01, +}; + +/* R40/A40i: CFG and MODE share one register (offset 0x04) */ +static const struct wb_wdt_variant sun4i_wdt_variant = { + .ctrl = 0x00, + .cfg = 0x04, + .mode = 0x04, + .timeout_shift = 3, + .reset_mask = 0x02, + .reset_val = 0x02, +}; + +static const struct of_device_id wb_wdt_matches[] = { + { .compatible = "allwinner,sun6i-a31-wdt", .data = &sun6i_wdt_variant }, + { .compatible = "allwinner,sun4i-a10-wdt", .data = &sun4i_wdt_variant }, + { /* sentinel */ } +}; + +static void __iomem *wb_wdt_regs; +static const struct wb_wdt_variant *wb_wdt_variant; + +static int wb_ramoops_panic_reset(struct notifier_block *nb, + unsigned long action, void *data) +{ + const struct wb_wdt_variant *v = wb_wdt_variant; + u32 val; + + /* Set whole-system reset function */ + val = readl(wb_wdt_regs + v->cfg); + val &= ~(u32)v->reset_mask; + val |= v->reset_val; + writel(val, wb_wdt_regs + v->cfg); + + /* + * Set the ~6 s interval and enable. On sun4i CFG and MODE are + * the same register, so this read-modify-write must (and does) + * preserve the reset bit written above. + */ + val = readl(wb_wdt_regs + v->mode); + val &= ~((u32)WDT_TIMEOUT_MASK << v->timeout_shift); + val |= WDT_INTV_6S << v->timeout_shift; + val |= WDT_MODE_EN; + writel(val, wb_wdt_regs + v->mode); + + /* Restart the counter so the full interval runs from now */ + writel(WDT_CTRL_RELOAD, wb_wdt_regs + v->ctrl); + + pr_emerg("SoC watchdog armed, warm reset in ~6 s\n"); + + return NOTIFY_DONE; +} + +static struct notifier_block wb_ramoops_panic_nb = { + .notifier_call = wb_ramoops_panic_reset, + /* run late: let other notifiers log first, the dump is on panic anyway */ + .priority = INT_MIN + 1, +}; + +static int __init wb_ramoops_panic_reset_init(void) +{ + const struct of_device_id *match; + struct device_node *np; + + if (!of_machine_is_compatible("allwinner,sun50i-h616") && + !of_machine_is_compatible("allwinner,sun8i-r40")) + return 0; + + /* + * Arm only when a ramoops region exists (injected into + * /reserved-memory by the Wiren Board U-Boot DT fixup). + */ + np = of_find_compatible_node(NULL, NULL, "ramoops"); + if (!np) + return 0; + of_node_put(np); + + np = of_find_matching_node_and_match(NULL, wb_wdt_matches, &match); + if (!np) { + pr_warn("no SoC watchdog node found, not armed\n"); + return 0; + } + + wb_wdt_regs = of_iomap(np, 0); + of_node_put(np); + if (!wb_wdt_regs) + return -ENOMEM; + + wb_wdt_variant = match->data; + + atomic_notifier_chain_register(&panic_notifier_list, + &wb_ramoops_panic_nb); + pr_info("armed (ramoops present)\n"); + + return 0; +} +late_initcall(wb_ramoops_panic_reset_init); From f41160ed7151510c1be94a9a5247a0f7a4e82767 Mon Sep 17 00:00:00 2001 From: Evgeny Boger Date: Sun, 5 Jul 2026 22:37:39 +0300 Subject: [PATCH 03/16] mfd: wbec: know the newer EC poweron reasons EC firmware 2.4.x reports two reasons the driver's string table predates: 8 = watchdog warm reset (warm-watchdog-reset EC series) and 9 = full power cycle request (used by the U-Boot pstore shuttle after parking panic logs). Add them, plus the firmware's own placeholder 7 (Unknown) so the indices line up, and convert the table to designated initializers with a source-of-truth note (mirrors LINUX_POWERON_REASON in wb-embedded-controller src/wbec.c; append-only ABI). Out-of-range values still print Unknown, so older kernels against even newer firmware stay harmless. Targets feature/v6.18 (most recent kernel line) per project branch policy. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01EVPkc2g2yir6SGyrED6uNT --- drivers/mfd/wbec.c | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/drivers/mfd/wbec.c b/drivers/mfd/wbec.c index 269add5fd438..f873795c6cae 100644 --- a/drivers/mfd/wbec.c +++ b/drivers/mfd/wbec.c @@ -25,14 +25,21 @@ /* For power off WBEC activates PWON pin on PMIC for 6s */ #define WBEC_POWER_RESET_DELAY_MS 10000 +/* + * Mirrors LINUX_POWERON_REASON in wb-embedded-controller src/wbec.c; + * append-only ABI. + */ static const char * const wbec_poweron_reason[] = { - "Power supply on", - "Power button", - "RTC alarm", - "Reboot", - "Reboot instead of poweroff", - "Watchdog", - "PMIC is unexpectedly off", + [0] = "Power supply on", + [1] = "Power button", + [2] = "RTC alarm", + [3] = "Reboot", + [4] = "Reboot instead of poweroff", + [5] = "Watchdog", + [6] = "PMIC is unexpectedly off", + [7] = "Unknown", + [8] = "Watchdog (warm reset)", + [9] = "Full power cycle request", }; static const struct regmap_config wbec_regmap_config_v1 = { From cc0b8be0bcbfff9c814fd5c7c3240f1372cbf411 Mon Sep 17 00:00:00 2001 From: Evgeny Boger Date: Mon, 6 Jul 2026 01:34:59 +0300 Subject: [PATCH 04/16] wb7, wb8: ship fixed pstore/ramoops reserved-memory node in DTBs Add a reserved-memory ramoops node at the fixed address 0x46000000 (1 MiB, record 128K, console 256K, ecc 32, no-map) to the Wiren Board 7 (sun8i-r40) and Wiren Board 8 (sun50i-h616) family device trees. The address matches U-Boot WB_RAMOOPS_BASE exactly. Carrying the node in the kernel DTB (rather than relying only on the U-Boot DT fixup) is what makes a kernel-only update useful: under an old U-Boot with no shuttle, the node is still present, the region survives a warm reset, and the same kernel harvests the panic log on the next boot. A new U-Boot's fixup yields to this node (it already takes precedence), and its shuttle adds full-power-cycle survival on top. Placed in the WB family base dtsis (84x/85x for WB8, 72x/74x for WB7; 73x inherits via 72x) so every shipped board gets it exactly once. The fixed low address is bench-verified to retain across a warm reset on all DRAM densities. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01EVPkc2g2yir6SGyrED6uNT --- .../allwinner/sun8i-r40-wirenboard72x.dtsi | 19 +++++++++++++++++++ .../allwinner/sun8i-r40-wirenboard74x.dtsi | 19 +++++++++++++++++++ .../allwinner/sun50i-h616-wirenboard84x.dtsi | 19 +++++++++++++++++++ .../allwinner/sun50i-h616-wirenboard85x.dtsi | 15 +++++++++++++++ 4 files changed, 72 insertions(+) diff --git a/arch/arm/boot/dts/allwinner/sun8i-r40-wirenboard72x.dtsi b/arch/arm/boot/dts/allwinner/sun8i-r40-wirenboard72x.dtsi index 05531e205128..60b788355af0 100644 --- a/arch/arm/boot/dts/allwinner/sun8i-r40-wirenboard72x.dtsi +++ b/arch/arm/boot/dts/allwinner/sun8i-r40-wirenboard72x.dtsi @@ -38,6 +38,25 @@ stdout-path = "serial0:115200n8"; }; + /* Fixed-address pstore/ramoops region; survives a warm reset so the + * same kernel can harvest it even under an old U-Boot with no shuttle. + * The base MUST match U-Boot WB_RAMOOPS_BASE (0x46000000). + */ + reserved-memory { + #address-cells = <1>; + #size-cells = <1>; + ranges; + + ramoops@46000000 { + compatible = "ramoops"; + reg = <0x46000000 0x100000>; + record-size = <0x20000>; + console-size = <0x40000>; + ecc-size = <32>; + no-map; + }; + }; + regulators { compatible = "simple-bus"; #address-cells = <1>; diff --git a/arch/arm/boot/dts/allwinner/sun8i-r40-wirenboard74x.dtsi b/arch/arm/boot/dts/allwinner/sun8i-r40-wirenboard74x.dtsi index 508fc00c10a4..b95047ef1576 100644 --- a/arch/arm/boot/dts/allwinner/sun8i-r40-wirenboard74x.dtsi +++ b/arch/arm/boot/dts/allwinner/sun8i-r40-wirenboard74x.dtsi @@ -40,6 +40,25 @@ stdout-path = "serial0:115200n8"; }; + /* Fixed-address pstore/ramoops region; survives a warm reset so the + * same kernel can harvest it even under an old U-Boot with no shuttle. + * The base MUST match U-Boot WB_RAMOOPS_BASE (0x46000000). + */ + reserved-memory { + #address-cells = <1>; + #size-cells = <1>; + ranges; + + ramoops@46000000 { + compatible = "ramoops"; + reg = <0x46000000 0x100000>; + record-size = <0x20000>; + console-size = <0x40000>; + ecc-size = <32>; + no-map; + }; + }; + regulators { compatible = "simple-bus"; #address-cells = <1>; diff --git a/arch/arm64/boot/dts/allwinner/sun50i-h616-wirenboard84x.dtsi b/arch/arm64/boot/dts/allwinner/sun50i-h616-wirenboard84x.dtsi index 32bae534ec59..c8bcee814eee 100644 --- a/arch/arm64/boot/dts/allwinner/sun50i-h616-wirenboard84x.dtsi +++ b/arch/arm64/boot/dts/allwinner/sun50i-h616-wirenboard84x.dtsi @@ -48,6 +48,25 @@ rtc_onboard = &rtc_onboard; }; + /* Fixed-address pstore/ramoops region; survives a warm reset so the + * same kernel can harvest it even under an old U-Boot with no shuttle. + * The base MUST match U-Boot WB_RAMOOPS_BASE (0x46000000). + */ + reserved-memory { + #address-cells = <2>; + #size-cells = <2>; + ranges; + + ramoops@46000000 { + compatible = "ramoops"; + reg = <0x0 0x46000000 0x0 0x100000>; + record-size = <0x20000>; + console-size = <0x40000>; + ecc-size = <32>; + no-map; + }; + }; + wirenboard { adc-type = <3>; pwm-buzzer = <1>; diff --git a/arch/arm64/boot/dts/allwinner/sun50i-h616-wirenboard85x.dtsi b/arch/arm64/boot/dts/allwinner/sun50i-h616-wirenboard85x.dtsi index c9d81df23e05..97752b8ddbbe 100644 --- a/arch/arm64/boot/dts/allwinner/sun50i-h616-wirenboard85x.dtsi +++ b/arch/arm64/boot/dts/allwinner/sun50i-h616-wirenboard85x.dtsi @@ -404,6 +404,21 @@ alloc-ranges = <0x0 0x00000000 0x0 0x80000000>; linux,cma-default; }; + + /* Fixed-address pstore/ramoops region; survives a warm reset so + * the same kernel can harvest it even under an old U-Boot with no + * shuttle. The base MUST match U-Boot WB_RAMOOPS_BASE (0x46000000). + * #address-cells/#size-cells/ranges (2/2) are inherited from the + * /reserved-memory node in sun50i-h616.dtsi that this merges into. + */ + ramoops@46000000 { + compatible = "ramoops"; + reg = <0x0 0x46000000 0x0 0x100000>; + record-size = <0x20000>; + console-size = <0x40000>; + ecc-size = <32>; + no-map; + }; }; soc { From 2c7ae138e8c395d8a8c88c72a1ff5a5bba4a1f66 Mon Sep 17 00:00:00 2001 From: Evgeny Boger Date: Mon, 6 Jul 2026 01:37:51 +0300 Subject: [PATCH 05/16] soc: sunxi: wb: brake panic warm-reset loops via an RTC reset-reason stamp A kernel that panics every boot would warm-reset forever. There is no hardware reset-cause latch on these SoCs (the watchdog status register is interrupt-mode only and clears on reset), so use the RTC general- purpose register documented "for storing power-off information": it survives a warm reset and is wiped by the EC's hard 5V cycle - exactly the reset-reason signal needed. The panic handler stamps RTC GP reg 3 (0x0700010c) before arming the watchdog. A boot that comes up still stamped knows it just warm-reset from a panic and declines to re-arm, letting the EC watchdog escalate to a hard power cycle (which wipes the stamp and breaks the loop). A 60 s healthy-uptime delayed work clears the stamp so ordinary panics always warm-reset. H616 only for now (the R40 RTC's wipe-on-hard-cycle behaviour is not yet verified; R40 keeps arming unconditionally). Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01EVPkc2g2yir6SGyrED6uNT --- drivers/soc/sunxi/wb-ramoops-panic-reset.c | 66 ++++++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/drivers/soc/sunxi/wb-ramoops-panic-reset.c b/drivers/soc/sunxi/wb-ramoops-panic-reset.c index 5f5c4cf989d7..adda57506051 100644 --- a/drivers/soc/sunxi/wb-ramoops-panic-reset.c +++ b/drivers/soc/sunxi/wb-ramoops-panic-reset.c @@ -39,6 +39,7 @@ #include #include #include +#include /* * Register layouts and arming sequence mirror the restart handler in @@ -90,12 +91,56 @@ static const struct of_device_id wb_wdt_matches[] = { static void __iomem *wb_wdt_regs; static const struct wb_wdt_variant *wb_wdt_variant; +/* + * Loop brake. A kernel that panics on every boot would otherwise + * warm-reset forever. There is no hardware "reset cause" latch on these + * SoCs (the watchdog status register is interrupt-mode only and clears + * on reset), but the RTC block has general-purpose registers documented + * "for storing power-off information": one of them survives a warm reset + * and is wiped by the EC's hard 5 V cycle, which is exactly the reset- + * reason signal we need. The panic handler stamps it before arming; + * if a boot comes up still stamped and we have not yet cleared it after + * a healthy uptime, we decline to arm again and let the EC watchdog + * escalate to a hard power cycle (which wipes the stamp and breaks the + * loop). H616 only for now; the R40 RTC's wipe-on-hard-cycle behaviour + * is not yet verified, so R40 keeps arming unconditionally. + */ +#define WB_RTC_BREADCRUMB_PHYS 0x0700010c /* RTC GP data reg 3 */ +#define WB_RTC_BREADCRUMB_MAGIC 0x50414e31 /* "PAN1" */ + +static void __iomem *wb_rtc_breadcrumb; +static bool wb_reset_from_panic; + +static void wb_breadcrumb_clear_fn(struct work_struct *work) +{ + /* Healthy uptime reached: allow the next panic to warm-reset again. */ + if (wb_rtc_breadcrumb) + writel(0, wb_rtc_breadcrumb); + wb_reset_from_panic = false; + pr_info("healthy, panic warm-reset re-armed\n"); +} +static DECLARE_DELAYED_WORK(wb_breadcrumb_clear, wb_breadcrumb_clear_fn); + static int wb_ramoops_panic_reset(struct notifier_block *nb, unsigned long action, void *data) { const struct wb_wdt_variant *v = wb_wdt_variant; u32 val; + /* + * If this boot itself came back from a panic-armed warm reset and + * has not yet proven healthy, do not arm again: let the EC watchdog + * hard-cycle the board so a crash loop cannot spin on warm resets. + */ + if (wb_reset_from_panic) { + pr_emerg("panic again after a warm reset; not re-arming, leaving it to the EC watchdog\n"); + return NOTIFY_DONE; + } + + /* Stamp the reset-reason breadcrumb before triggering the reset. */ + if (wb_rtc_breadcrumb) + writel(WB_RTC_BREADCRUMB_MAGIC, wb_rtc_breadcrumb); + /* Set whole-system reset function */ val = readl(wb_wdt_regs + v->cfg); val &= ~(u32)v->reset_mask; @@ -158,6 +203,27 @@ static int __init wb_ramoops_panic_reset_init(void) wb_wdt_variant = match->data; + /* + * Reset-reason breadcrumb (H616 only for now). If this boot came up + * with the panic stamp still set, we just warm-reset from a panic: + * hold off arming until a healthy uptime clears it. + */ + if (of_machine_is_compatible("allwinner,sun50i-h616")) { + wb_rtc_breadcrumb = ioremap(WB_RTC_BREADCRUMB_PHYS, 4); + if (wb_rtc_breadcrumb && + readl(wb_rtc_breadcrumb) == WB_RTC_BREADCRUMB_MAGIC) { + wb_reset_from_panic = true; + pr_info("came back from a panic warm reset; holding off re-arm\n"); + } + } + /* + * Clear the stamp once we have stayed up long enough to count as a + * healthy session (runs whether or not the stamp was set, so a plain + * boot re-arms immediately and a post-panic boot re-arms after the + * delay). 60 s comfortably exceeds pstore harvest. + */ + schedule_delayed_work(&wb_breadcrumb_clear, msecs_to_jiffies(60000)); + atomic_notifier_chain_register(&panic_notifier_list, &wb_ramoops_panic_nb); pr_info("armed (ramoops present)\n"); From aa805ee317ade0ee74776e5ecdfb03b856edfc33 Mon Sep 17 00:00:00 2001 From: Evgeny Boger Date: Mon, 6 Jul 2026 02:20:39 +0300 Subject: [PATCH 06/16] soc: sunxi: wb: fix stale top-of-DRAM header; only log re-arm when held off Review follow-up: the module header still said the ramoops region is at the top of DRAM and omitted the loop brake; update it for the fixed 0x46000000 placement and mention the decline-to-re-arm behaviour. Also gate the 'healthy, panic warm-reset re-armed' message on wb_reset_from_panic so it only prints when a hold-off was actually lifted, not on every boot. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01EVPkc2g2yir6SGyrED6uNT --- drivers/soc/sunxi/wb-ramoops-panic-reset.c | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/drivers/soc/sunxi/wb-ramoops-panic-reset.c b/drivers/soc/sunxi/wb-ramoops-panic-reset.c index adda57506051..c79394e8127a 100644 --- a/drivers/soc/sunxi/wb-ramoops-panic-reset.c +++ b/drivers/soc/sunxi/wb-ramoops-panic-reset.c @@ -4,9 +4,13 @@ * T507/H616): warm-reset the SoC shortly after a panic so the panic * log persists in the ramoops region. * - * The ramoops region at the top of DRAM survives a SoC watchdog reset - * (DRAM stays powered), after which U-Boot parks the records on eMMC - * and performs a full recovery power cycle (the "pstore shuttle"). + * The ramoops region (a fixed low DRAM address, 0x46000000, carried by + * the reserved-memory node in the board DT) survives a SoC watchdog + * reset (DRAM stays powered), after which U-Boot parks the records on + * eMMC and performs a full recovery power cycle (the "pstore shuttle"). + * A fixed address also lets a kernel-only update capture logs: the + * region survives the warm reset and this same kernel harvests it on + * the next boot even under an old U-Boot with no shuttle. * Without this hook a panicked kernel just parks until the EC watchdog * acts: on pre-2.4.0 EC firmware the EC watchdog action is a full * power cycle, destroying DRAM and the records; 2.4.0+ warm-resets @@ -27,7 +31,9 @@ * The hook arms itself only if a ramoops region actually registered: * on systems booted by an older U-Boot (no injected ramoops node) * panic behaviour is intentionally left unchanged, so updating the - * kernel alone changes nothing (no-lockstep updates). + * kernel alone changes nothing (no-lockstep updates). It also declines + * to re-arm when it detects it just warm-reset from a panic (see the + * loop brake below), leaving a repeat panic to the EC watchdog. */ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt @@ -114,10 +120,13 @@ static bool wb_reset_from_panic; static void wb_breadcrumb_clear_fn(struct work_struct *work) { /* Healthy uptime reached: allow the next panic to warm-reset again. */ + bool was_held = wb_reset_from_panic; + if (wb_rtc_breadcrumb) writel(0, wb_rtc_breadcrumb); wb_reset_from_panic = false; - pr_info("healthy, panic warm-reset re-armed\n"); + if (was_held) + pr_info("healthy, panic warm-reset re-armed\n"); } static DECLARE_DELAYED_WORK(wb_breadcrumb_clear, wb_breadcrumb_clear_fn); From f2228f1f273d002e2865b3eb013702ffde81af25 Mon Sep 17 00:00:00 2001 From: Evgeny Boger Date: Mon, 6 Jul 2026 02:57:32 +0300 Subject: [PATCH 07/16] wb7, wb8: move fixed ramoops region to 0x43800000 (below decompression buffer) Match the U-Boot change: move the reserved-memory ramoops node from 0x46000000 to 0x43800000 across the WB7/WB8 family DTs, and update the address in the panic-reset module header. 0x46000000 sat only 32 MiB above the arm64 kernel decompression buffer at 0x44000000; a kernel larger than 32 MiB uncompressed would clobber it. 0x43800000 sits in the gap below the decompression buffer, immune to kernel growth, and is bench-verified to retain across a warm reset on all densities. Must stay in sync with U-Boot WB_RAMOOPS_BASE. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01EVPkc2g2yir6SGyrED6uNT --- arch/arm/boot/dts/allwinner/sun8i-r40-wirenboard72x.dtsi | 6 +++--- arch/arm/boot/dts/allwinner/sun8i-r40-wirenboard74x.dtsi | 6 +++--- .../arm64/boot/dts/allwinner/sun50i-h616-wirenboard84x.dtsi | 6 +++--- .../arm64/boot/dts/allwinner/sun50i-h616-wirenboard85x.dtsi | 6 +++--- drivers/soc/sunxi/wb-ramoops-panic-reset.c | 2 +- 5 files changed, 13 insertions(+), 13 deletions(-) diff --git a/arch/arm/boot/dts/allwinner/sun8i-r40-wirenboard72x.dtsi b/arch/arm/boot/dts/allwinner/sun8i-r40-wirenboard72x.dtsi index 60b788355af0..e8d35c33af70 100644 --- a/arch/arm/boot/dts/allwinner/sun8i-r40-wirenboard72x.dtsi +++ b/arch/arm/boot/dts/allwinner/sun8i-r40-wirenboard72x.dtsi @@ -40,16 +40,16 @@ /* Fixed-address pstore/ramoops region; survives a warm reset so the * same kernel can harvest it even under an old U-Boot with no shuttle. - * The base MUST match U-Boot WB_RAMOOPS_BASE (0x46000000). + * The base MUST match U-Boot WB_RAMOOPS_BASE (0x43800000). */ reserved-memory { #address-cells = <1>; #size-cells = <1>; ranges; - ramoops@46000000 { + ramoops@43800000 { compatible = "ramoops"; - reg = <0x46000000 0x100000>; + reg = <0x43800000 0x100000>; record-size = <0x20000>; console-size = <0x40000>; ecc-size = <32>; diff --git a/arch/arm/boot/dts/allwinner/sun8i-r40-wirenboard74x.dtsi b/arch/arm/boot/dts/allwinner/sun8i-r40-wirenboard74x.dtsi index b95047ef1576..1d4f52a6c122 100644 --- a/arch/arm/boot/dts/allwinner/sun8i-r40-wirenboard74x.dtsi +++ b/arch/arm/boot/dts/allwinner/sun8i-r40-wirenboard74x.dtsi @@ -42,16 +42,16 @@ /* Fixed-address pstore/ramoops region; survives a warm reset so the * same kernel can harvest it even under an old U-Boot with no shuttle. - * The base MUST match U-Boot WB_RAMOOPS_BASE (0x46000000). + * The base MUST match U-Boot WB_RAMOOPS_BASE (0x43800000). */ reserved-memory { #address-cells = <1>; #size-cells = <1>; ranges; - ramoops@46000000 { + ramoops@43800000 { compatible = "ramoops"; - reg = <0x46000000 0x100000>; + reg = <0x43800000 0x100000>; record-size = <0x20000>; console-size = <0x40000>; ecc-size = <32>; diff --git a/arch/arm64/boot/dts/allwinner/sun50i-h616-wirenboard84x.dtsi b/arch/arm64/boot/dts/allwinner/sun50i-h616-wirenboard84x.dtsi index c8bcee814eee..13e8156cb153 100644 --- a/arch/arm64/boot/dts/allwinner/sun50i-h616-wirenboard84x.dtsi +++ b/arch/arm64/boot/dts/allwinner/sun50i-h616-wirenboard84x.dtsi @@ -50,16 +50,16 @@ /* Fixed-address pstore/ramoops region; survives a warm reset so the * same kernel can harvest it even under an old U-Boot with no shuttle. - * The base MUST match U-Boot WB_RAMOOPS_BASE (0x46000000). + * The base MUST match U-Boot WB_RAMOOPS_BASE (0x43800000). */ reserved-memory { #address-cells = <2>; #size-cells = <2>; ranges; - ramoops@46000000 { + ramoops@43800000 { compatible = "ramoops"; - reg = <0x0 0x46000000 0x0 0x100000>; + reg = <0x0 0x43800000 0x0 0x100000>; record-size = <0x20000>; console-size = <0x40000>; ecc-size = <32>; diff --git a/arch/arm64/boot/dts/allwinner/sun50i-h616-wirenboard85x.dtsi b/arch/arm64/boot/dts/allwinner/sun50i-h616-wirenboard85x.dtsi index 97752b8ddbbe..38c56bc06b42 100644 --- a/arch/arm64/boot/dts/allwinner/sun50i-h616-wirenboard85x.dtsi +++ b/arch/arm64/boot/dts/allwinner/sun50i-h616-wirenboard85x.dtsi @@ -407,13 +407,13 @@ /* Fixed-address pstore/ramoops region; survives a warm reset so * the same kernel can harvest it even under an old U-Boot with no - * shuttle. The base MUST match U-Boot WB_RAMOOPS_BASE (0x46000000). + * shuttle. The base MUST match U-Boot WB_RAMOOPS_BASE (0x43800000). * #address-cells/#size-cells/ranges (2/2) are inherited from the * /reserved-memory node in sun50i-h616.dtsi that this merges into. */ - ramoops@46000000 { + ramoops@43800000 { compatible = "ramoops"; - reg = <0x0 0x46000000 0x0 0x100000>; + reg = <0x0 0x43800000 0x0 0x100000>; record-size = <0x20000>; console-size = <0x40000>; ecc-size = <32>; diff --git a/drivers/soc/sunxi/wb-ramoops-panic-reset.c b/drivers/soc/sunxi/wb-ramoops-panic-reset.c index c79394e8127a..99c184beda65 100644 --- a/drivers/soc/sunxi/wb-ramoops-panic-reset.c +++ b/drivers/soc/sunxi/wb-ramoops-panic-reset.c @@ -4,7 +4,7 @@ * T507/H616): warm-reset the SoC shortly after a panic so the panic * log persists in the ramoops region. * - * The ramoops region (a fixed low DRAM address, 0x46000000, carried by + * The ramoops region (a fixed low DRAM address, 0x43800000, carried by * the reserved-memory node in the board DT) survives a SoC watchdog * reset (DRAM stays powered), after which U-Boot parks the records on * eMMC and performs a full recovery power cycle (the "pstore shuttle"). From 4b4abb2f23086bedfe08e7ba35b2f1982ba712b7 Mon Sep 17 00:00:00 2001 From: Evgeny Boger Date: Mon, 6 Jul 2026 03:14:24 +0300 Subject: [PATCH 08/16] wb7: document ramoops 0x43800000 clearance on R40 (no build guard, WB7 untested) The fixed ramoops address was chosen for the arm64/H616 boot map and reused on 32-bit R40. Document in the R40 dtsi that it clears the used loads and sits ~4 MiB above the stock (unused) RAMDISK_ADDR_R, that there is no build-time guard on the R40 side, and that retention on real WB7 hardware is still pending validation. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01EVPkc2g2yir6SGyrED6uNT --- arch/arm/boot/dts/allwinner/sun8i-r40-wirenboard72x.dtsi | 9 +++++++++ arch/arm/boot/dts/allwinner/sun8i-r40-wirenboard74x.dtsi | 9 +++++++++ 2 files changed, 18 insertions(+) diff --git a/arch/arm/boot/dts/allwinner/sun8i-r40-wirenboard72x.dtsi b/arch/arm/boot/dts/allwinner/sun8i-r40-wirenboard72x.dtsi index e8d35c33af70..a3d23e7a8d86 100644 --- a/arch/arm/boot/dts/allwinner/sun8i-r40-wirenboard72x.dtsi +++ b/arch/arm/boot/dts/allwinner/sun8i-r40-wirenboard72x.dtsi @@ -41,6 +41,15 @@ /* Fixed-address pstore/ramoops region; survives a warm reset so the * same kernel can harvest it even under an old U-Boot with no shuttle. * The base MUST match U-Boot WB_RAMOOPS_BASE (0x43800000). + * + * The address was chosen for the arm64/H616 boot map but is reused + * here for R40. On R40 it clears the used load addresses (fdt + * 0x43000000, overlay 0x43300000) and sits ~4 MiB above the stock + * RAMDISK_ADDR_R (0x43400000). WB firmware boots without an initrd + * (bootz "... - ..."), so ramdisk_addr_r is unused; if that ever + * changes, an initrd >4 MiB would overlap this region. There is no + * build-time guard on the R40 side (the U-Boot static_asserts are + * WB8-only). Pending: retention validation on real WB7 hardware. */ reserved-memory { #address-cells = <1>; diff --git a/arch/arm/boot/dts/allwinner/sun8i-r40-wirenboard74x.dtsi b/arch/arm/boot/dts/allwinner/sun8i-r40-wirenboard74x.dtsi index 1d4f52a6c122..b4e85d3c4915 100644 --- a/arch/arm/boot/dts/allwinner/sun8i-r40-wirenboard74x.dtsi +++ b/arch/arm/boot/dts/allwinner/sun8i-r40-wirenboard74x.dtsi @@ -43,6 +43,15 @@ /* Fixed-address pstore/ramoops region; survives a warm reset so the * same kernel can harvest it even under an old U-Boot with no shuttle. * The base MUST match U-Boot WB_RAMOOPS_BASE (0x43800000). + * + * The address was chosen for the arm64/H616 boot map but is reused + * here for R40. On R40 it clears the used load addresses (fdt + * 0x43000000, overlay 0x43300000) and sits ~4 MiB above the stock + * RAMDISK_ADDR_R (0x43400000). WB firmware boots without an initrd + * (bootz "... - ..."), so ramdisk_addr_r is unused; if that ever + * changes, an initrd >4 MiB would overlap this region. There is no + * build-time guard on the R40 side (the U-Boot static_asserts are + * WB8-only). Pending: retention validation on real WB7 hardware. */ reserved-memory { #address-cells = <1>; From 51f5ee45da11ea79b2b9b7ee3e32b5128e83a100 Mon Sep 17 00:00:00 2001 From: Evgeny Boger Date: Mon, 6 Jul 2026 18:56:17 +0300 Subject: [PATCH 09/16] mfd: wbec: quiet the expected v1-protocol fallback The v2 pad-word SPI protocol is a newer-EC feature; boards with older EC firmware (e.g. WB74) only ever speak v1, so probe always tries v2 first and falls back. That fallback logged at info level as "WBEC not found with v2 protocol, trying v1", which reads like a fault and has repeatedly triggered false bug reports on perfectly healthy v1 boards. Demote the fallback notice to dev_dbg (still reachable via dynamic debug) and reword it as an ordinary negotiation step. The eventual success line ("WBEC found with v1/v2 protocol") already records which protocol won, so nothing is lost from the default log. Targets feature/v6.18 (most recent kernel line) per project branch policy. Co-Authored-By: Claude Fable 5 --- drivers/mfd/wbec.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/drivers/mfd/wbec.c b/drivers/mfd/wbec.c index f873795c6cae..1df31b6ccfe9 100644 --- a/drivers/mfd/wbec.c +++ b/drivers/mfd/wbec.c @@ -361,7 +361,13 @@ static int wbec_probe(struct spi_device *spi) ret = wbec_check_present(wbec); if (ret == -ENODEV) { - dev_info(wbec->dev, "WBEC not found with v2 protocol, trying v1\n"); + /* + * Expected on every board that speaks the v1 protocol (the v2 + * pad-word protocol is a newer-EC feature): probe v2 first, then + * fall back to v1. Keep this at debug level so a normal v1 board + * does not log a scary "not found" line during ordinary probing. + */ + dev_dbg(wbec->dev, "no WBEC on v2 protocol, falling back to v1\n"); /* don't worry about memory leak, previous regmap will be freed by devm */ wbec->regmap = devm_regmap_init_spi(spi, &wbec_regmap_config_v1); From c946339d42f8e288bef1ea9e7c8311b8718d7704 Mon Sep 17 00:00:00 2001 From: Evgeny Boger Date: Mon, 6 Jul 2026 20:57:19 +0300 Subject: [PATCH 10/16] soc: sunxi: wb: extend the panic-loop breadcrumb to R40 The RTC reset-reason breadcrumb that brakes panic warm-reset loops was H616-only: it needs an RTC general-purpose register that survives the SoC warm reset but is wiped by the EC's hard 5 V cycle, and that behaviour had not been verified on R40, so R40 armed unconditionally. Make the breadcrumb register per-SoC by moving it into wb_wdt_variant (rtc_breadcrumb_phys; 0 = no breadcrumb, arm unconditionally). H616 keeps its RTC GP data reg 3 (0x0700010c). R40 gets GP data reg 0 (0x01c20500, RTC base 0x01c20400 + the sun6i-rtc GP_DATA block at +0x100), which WB7.4.2 bench testing confirmed survives a sun4i-WDT warm reset (0xCAFE55AA persisted through a panic reset) and is cleared by the EC full power cycle (reads 0, poweron_reason 9) - the same semantics as the H616 breadcrumb. GP reg 0 is free: no nvmem-cell in the R40 DT consumes the RTC nvmem provider. This drops the of_machine_is_compatible("...h616") special-case; the per-SoC field now drives it, so both WB7 and WB8 get the crash-loop brake. Targets feature/v6.18 (most recent kernel line) per project branch policy. Co-Authored-By: Claude Fable 5 --- drivers/soc/sunxi/wb-ramoops-panic-reset.c | 36 +++++++++++++++++----- 1 file changed, 28 insertions(+), 8 deletions(-) diff --git a/drivers/soc/sunxi/wb-ramoops-panic-reset.c b/drivers/soc/sunxi/wb-ramoops-panic-reset.c index 99c184beda65..8db9501786ea 100644 --- a/drivers/soc/sunxi/wb-ramoops-panic-reset.c +++ b/drivers/soc/sunxi/wb-ramoops-panic-reset.c @@ -66,6 +66,14 @@ struct wb_wdt_variant { u8 timeout_shift; u8 reset_mask; u8 reset_val; /* whole-system reset */ + /* + * Physical address of the RTC general-purpose register used as the + * panic-loop-breaker breadcrumb (see the loop brake below). Must be a + * register that survives a warm reset but is wiped by the EC's hard 5 V + * cycle. 0 = no verified register on this SoC: arm unconditionally, no + * loop brake. + */ + u32 rtc_breadcrumb_phys; }; /* H616/T507: watchdog in the timer block, dedicated CFG register */ @@ -76,6 +84,7 @@ static const struct wb_wdt_variant sun6i_wdt_variant = { .timeout_shift = 4, .reset_mask = 0x03, .reset_val = 0x01, + .rtc_breadcrumb_phys = 0x0700010c, /* RTC base 0x07000000 + GP data reg 3 */ }; /* R40/A40i: CFG and MODE share one register (offset 0x04) */ @@ -86,6 +95,14 @@ static const struct wb_wdt_variant sun4i_wdt_variant = { .timeout_shift = 3, .reset_mask = 0x02, .reset_val = 0x02, + /* + * R40 shares the sun6i-rtc battery-backed GP data registers (8 x 32-bit + * at RTC base + 0x100). GP data reg 0 is bench-confirmed on WB7.4.2 to + * survive a sun4i-WDT warm reset and to be wiped by the EC full 5 V + * cycle - the same semantics as the H616 breadcrumb - and is free (no + * nvmem-cell consumer references it in the R40 DT). + */ + .rtc_breadcrumb_phys = 0x01c20500, /* RTC base 0x01c20400 + GP data reg 0 */ }; static const struct of_device_id wb_wdt_matches[] = { @@ -108,10 +125,12 @@ static const struct wb_wdt_variant *wb_wdt_variant; * if a boot comes up still stamped and we have not yet cleared it after * a healthy uptime, we decline to arm again and let the EC watchdog * escalate to a hard power cycle (which wipes the stamp and breaks the - * loop). H616 only for now; the R40 RTC's wipe-on-hard-cycle behaviour - * is not yet verified, so R40 keeps arming unconditionally. + * loop). The register is per-SoC (wb_wdt_variant.rtc_breadcrumb_phys): + * H616 uses RTC GP data reg 3, R40 uses RTC GP data reg 0 - both + * bench-confirmed to survive a warm reset and be wiped by the EC hard + * cycle. A SoC with the field left 0 has no breadcrumb and arms + * unconditionally. */ -#define WB_RTC_BREADCRUMB_PHYS 0x0700010c /* RTC GP data reg 3 */ #define WB_RTC_BREADCRUMB_MAGIC 0x50414e31 /* "PAN1" */ static void __iomem *wb_rtc_breadcrumb; @@ -213,12 +232,13 @@ static int __init wb_ramoops_panic_reset_init(void) wb_wdt_variant = match->data; /* - * Reset-reason breadcrumb (H616 only for now). If this boot came up - * with the panic stamp still set, we just warm-reset from a panic: - * hold off arming until a healthy uptime clears it. + * Reset-reason breadcrumb, per SoC (wb_wdt_variant.rtc_breadcrumb_phys; + * 0 = none, arm unconditionally). If this boot came up with the panic + * stamp still set, we just warm-reset from a panic: hold off arming + * until a healthy uptime clears it. */ - if (of_machine_is_compatible("allwinner,sun50i-h616")) { - wb_rtc_breadcrumb = ioremap(WB_RTC_BREADCRUMB_PHYS, 4); + if (wb_wdt_variant->rtc_breadcrumb_phys) { + wb_rtc_breadcrumb = ioremap(wb_wdt_variant->rtc_breadcrumb_phys, 4); if (wb_rtc_breadcrumb && readl(wb_rtc_breadcrumb) == WB_RTC_BREADCRUMB_MAGIC) { wb_reset_from_panic = true; From 99b3567da4b00164244e896aff8d778c4517fb24 Mon Sep 17 00:00:00 2001 From: Evgeny Boger Date: Tue, 7 Jul 2026 22:15:45 +0300 Subject: [PATCH 11/16] soc: sunxi: wb: R40 has no usable loop-brake breadcrumb (panic path wipes RTC GP bank) The R40 sun6i-rtc GP data registers survive a *bare* sun4i-WDT warm reset, but E2E bench testing on WB7.4.2 showed the *panic-path* warm reset wipes the entire GP data bank - all 8 registers, including ones this driver never writes. A post-panic boot therefore never sees the "PAN1" stamp, so the panic-loop brake could never engage on R40. Set rtc_breadcrumb_phys = 0 (arm unconditionally) instead of pointing at a register that is proven to be cleared, and make the comments honest about the consequence: a persistent panic on R40 warm-reset loops rather than escalating to an EC hard cycle. H616/WB8 is unaffected - its GP reg 3 breadcrumb is E2E-confirmed to survive the warm reset and be wiped by the EC hard 5 V cycle. Also warn (not silently skip) if the breadcrumb ioremap fails. Co-Authored-By: Claude Opus 4.8 (1M context) --- drivers/soc/sunxi/wb-ramoops-panic-reset.c | 33 ++++++++++++++-------- 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/drivers/soc/sunxi/wb-ramoops-panic-reset.c b/drivers/soc/sunxi/wb-ramoops-panic-reset.c index 8db9501786ea..014425fec4be 100644 --- a/drivers/soc/sunxi/wb-ramoops-panic-reset.c +++ b/drivers/soc/sunxi/wb-ramoops-panic-reset.c @@ -96,13 +96,19 @@ static const struct wb_wdt_variant sun4i_wdt_variant = { .reset_mask = 0x02, .reset_val = 0x02, /* - * R40 shares the sun6i-rtc battery-backed GP data registers (8 x 32-bit - * at RTC base + 0x100). GP data reg 0 is bench-confirmed on WB7.4.2 to - * survive a sun4i-WDT warm reset and to be wiped by the EC full 5 V - * cycle - the same semantics as the H616 breadcrumb - and is free (no - * nvmem-cell consumer references it in the R40 DT). + * R40: no usable breadcrumb, so no panic-loop brake (0 = arm + * unconditionally). The sun6i-rtc GP data registers survive a *bare* + * sun4i-WDT warm reset, but E2E bench testing on WB7.4.2 showed the + * *panic-path* warm reset wipes the entire GP data bank - all 8 regs, + * including ones this driver never writes - so a post-panic boot never + * sees the stamp and the brake could never engage. Leaving it 0 is honest + * about that, rather than depending on a register proven to be cleared. + * Consequence: a persistent panic on R40 warm-reset loops instead of + * escalating to an EC hard cycle. TODO: give R40 a store that survives its + * panic path (U-Boot bootcount, or a shuttle-side counter in the eMMC park + * header) before relying on a loop brake here. */ - .rtc_breadcrumb_phys = 0x01c20500, /* RTC base 0x01c20400 + GP data reg 0 */ + .rtc_breadcrumb_phys = 0, /* R40 panic path wipes RTC GP regs; see above */ }; static const struct of_device_id wb_wdt_matches[] = { @@ -126,10 +132,12 @@ static const struct wb_wdt_variant *wb_wdt_variant; * a healthy uptime, we decline to arm again and let the EC watchdog * escalate to a hard power cycle (which wipes the stamp and breaks the * loop). The register is per-SoC (wb_wdt_variant.rtc_breadcrumb_phys): - * H616 uses RTC GP data reg 3, R40 uses RTC GP data reg 0 - both - * bench-confirmed to survive a warm reset and be wiped by the EC hard - * cycle. A SoC with the field left 0 has no breadcrumb and arms - * unconditionally. + * H616 uses RTC GP data reg 3, bench-confirmed to survive a warm reset and + * be wiped by the EC hard 5 V cycle. R40 has NO usable breadcrumb: its GP + * regs survive a bare warm reset but the panic-path warm reset wipes the + * whole bank (E2E-confirmed on WB7.4.2), so its field is left 0. A SoC with + * the field 0 has no breadcrumb and arms unconditionally (no loop brake) - + * see the R40 variant note above. */ #define WB_RTC_BREADCRUMB_MAGIC 0x50414e31 /* "PAN1" */ @@ -239,8 +247,9 @@ static int __init wb_ramoops_panic_reset_init(void) */ if (wb_wdt_variant->rtc_breadcrumb_phys) { wb_rtc_breadcrumb = ioremap(wb_wdt_variant->rtc_breadcrumb_phys, 4); - if (wb_rtc_breadcrumb && - readl(wb_rtc_breadcrumb) == WB_RTC_BREADCRUMB_MAGIC) { + if (!wb_rtc_breadcrumb) + pr_warn("breadcrumb ioremap failed; panic-loop brake disabled (arms every boot)\n"); + else if (readl(wb_rtc_breadcrumb) == WB_RTC_BREADCRUMB_MAGIC) { wb_reset_from_panic = true; pr_info("came back from a panic warm reset; holding off re-arm\n"); } From c05acf3ee0e073e86d48c1baf6fa1c3c04a07add Mon Sep 17 00:00:00 2001 From: Evgeny Boger Date: Tue, 7 Jul 2026 22:15:58 +0300 Subject: [PATCH 12/16] wb7: correct R40 ramoops-node comments (retention validated, U-Boot guard exists) The ramoops reserved-memory comment in the R40 board dtsi still said retention was "pending validation on real WB7 hardware" and that there was "no build-time guard on the R40 side". Both are now stale: retention is validated on real WB7.4.2 hardware (panic -> sun4i WDT warm reset -> kernel harvests dmesg + console from 0x43800000), and the WB7 U-Boot shuttle carries R40 static_asserts bounding this address against RAMDISK_ADDR_R, so an env change that shrank the gap trips a build error there. Comment-only; no functional change. Co-Authored-By: Claude Opus 4.8 (1M context) --- arch/arm/boot/dts/allwinner/sun8i-r40-wirenboard72x.dtsi | 8 +++++--- arch/arm/boot/dts/allwinner/sun8i-r40-wirenboard74x.dtsi | 8 +++++--- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/arch/arm/boot/dts/allwinner/sun8i-r40-wirenboard72x.dtsi b/arch/arm/boot/dts/allwinner/sun8i-r40-wirenboard72x.dtsi index a3d23e7a8d86..2d6f3de89ed3 100644 --- a/arch/arm/boot/dts/allwinner/sun8i-r40-wirenboard72x.dtsi +++ b/arch/arm/boot/dts/allwinner/sun8i-r40-wirenboard72x.dtsi @@ -47,9 +47,11 @@ * 0x43000000, overlay 0x43300000) and sits ~4 MiB above the stock * RAMDISK_ADDR_R (0x43400000). WB firmware boots without an initrd * (bootz "... - ..."), so ramdisk_addr_r is unused; if that ever - * changes, an initrd >4 MiB would overlap this region. There is no - * build-time guard on the R40 side (the U-Boot static_asserts are - * WB8-only). Pending: retention validation on real WB7 hardware. + * changes, an initrd >4 MiB would overlap this region. The WB7 U-Boot + * shuttle carries R40 static_asserts bounding this address against + * RAMDISK_ADDR_R, so such an env change trips a build error there. + * Retention validated on real WB7.4.2 hardware (panic -> sun4i WDT + * warm reset -> kernel harvests dmesg + console from this region). */ reserved-memory { #address-cells = <1>; diff --git a/arch/arm/boot/dts/allwinner/sun8i-r40-wirenboard74x.dtsi b/arch/arm/boot/dts/allwinner/sun8i-r40-wirenboard74x.dtsi index b4e85d3c4915..58b4e37d8dd4 100644 --- a/arch/arm/boot/dts/allwinner/sun8i-r40-wirenboard74x.dtsi +++ b/arch/arm/boot/dts/allwinner/sun8i-r40-wirenboard74x.dtsi @@ -49,9 +49,11 @@ * 0x43000000, overlay 0x43300000) and sits ~4 MiB above the stock * RAMDISK_ADDR_R (0x43400000). WB firmware boots without an initrd * (bootz "... - ..."), so ramdisk_addr_r is unused; if that ever - * changes, an initrd >4 MiB would overlap this region. There is no - * build-time guard on the R40 side (the U-Boot static_asserts are - * WB8-only). Pending: retention validation on real WB7 hardware. + * changes, an initrd >4 MiB would overlap this region. The WB7 U-Boot + * shuttle carries R40 static_asserts bounding this address against + * RAMDISK_ADDR_R, so such an env change trips a build error there. + * Retention validated on real WB7.4.2 hardware (panic -> sun4i WDT + * warm reset -> kernel harvests dmesg + console from this region). */ reserved-memory { #address-cells = <1>; From 55ace431ba5608a60a3c2707f61e161f12e156f8 Mon Sep 17 00:00:00 2001 From: Evgeny Boger Date: Tue, 7 Jul 2026 23:43:53 +0300 Subject: [PATCH 13/16] soc: sunxi: wb: give R40 a DRAM breadcrumb so the panic-loop brake works The preceding commit documented that R40 has no usable loop-brake breadcrumb: its sun6i-rtc GP data bank is wiped by the panic-path warm reset (E2E-confirmed on WB7.4.2 - a bare sun4i-WDT reset preserves the bank, but the panic path wipes all 8 regs), so no GP register can carry the "PAN1" reset-reason stamp across a panic. Give R40 a breadcrumb that does survive its panic path: a 4-byte word in a dedicated no-map DRAM reservation at 0x43900000, immediately above the ramoops region. Low DRAM survives the panic-path warm reset (the whole ramoops/pstore premise) and is lost on a genuine 5 V cycle - exactly the survives-warm-reset / wiped-by-power-cycle semantics the brake needs, the same as the H616 RTC register. Being no-map, ioremap() maps it uncached, so the panic-time stamp reaches DRAM without a cache flush (safe in panic context) and the address does not trip the ARM ioremap-on-RAM check. Rename the per-SoC field rtc_breadcrumb_phys -> breadcrumb_phys (it is no longer RTC-only) and log the raw breadcrumb value read at boot. H616 is unchanged (still its RTC GP data reg 3, 0x0700010c). Validated on WB7.4.2 (A40i, ALEPBSP3): - SURVIVE: a sentinel word is overwritten by "PAN1" on panic and read back intact after the sun4i-WDT warm reset - the exact reset that wipes the RTC bank. - BRAKE: a second panic while the stamp is held prints "panic again after a warm reset; not re-arming" and does not arm the SoC watchdog; a healthy 60 s uptime clears the stamp and re-arms. - WIPE: a genuine EC 5 V cycle (RTC-alarm power-off, poweron_reason "RTC alarm") clears the word, so a real power cycle resets the brake. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../allwinner/sun8i-r40-wirenboard72x.dtsi | 18 ++++ .../allwinner/sun8i-r40-wirenboard74x.dtsi | 18 ++++ drivers/soc/sunxi/wb-ramoops-panic-reset.c | 92 +++++++++++-------- 3 files changed, 89 insertions(+), 39 deletions(-) diff --git a/arch/arm/boot/dts/allwinner/sun8i-r40-wirenboard72x.dtsi b/arch/arm/boot/dts/allwinner/sun8i-r40-wirenboard72x.dtsi index 2d6f3de89ed3..8b643bef6619 100644 --- a/arch/arm/boot/dts/allwinner/sun8i-r40-wirenboard72x.dtsi +++ b/arch/arm/boot/dts/allwinner/sun8i-r40-wirenboard72x.dtsi @@ -66,6 +66,24 @@ ecc-size = <32>; no-map; }; + + /* + * Panic-loop-brake breadcrumb (R40 only). One word here is + * stamped "PAN1" by wb-ramoops-panic-reset before it warm- + * resets the SoC on panic; a boot that comes up still stamped + * declines to re-arm and lets the EC hard-cycle, breaking a + * crash loop. Immediately above the ramoops region, so it sits + * in the same warm-reset-surviving / power-cycle-wiped low-DRAM + * window (base + 57 MiB, below KERNEL_COMP_ADDR_R). R40 needs a + * DRAM breadcrumb because its RTC GP bank is wiped by the panic- + * path warm reset (H616 uses an RTC register instead). Address + * must match breadcrumb_phys in the driver. no-map so the + * driver's ioremap gives an uncached, reset-surviving mapping. + */ + wb_panic_breadcrumb: wb-panic-breadcrumb@43900000 { + reg = <0x43900000 0x1000>; + no-map; + }; }; regulators { diff --git a/arch/arm/boot/dts/allwinner/sun8i-r40-wirenboard74x.dtsi b/arch/arm/boot/dts/allwinner/sun8i-r40-wirenboard74x.dtsi index 58b4e37d8dd4..3c946b4bb84e 100644 --- a/arch/arm/boot/dts/allwinner/sun8i-r40-wirenboard74x.dtsi +++ b/arch/arm/boot/dts/allwinner/sun8i-r40-wirenboard74x.dtsi @@ -68,6 +68,24 @@ ecc-size = <32>; no-map; }; + + /* + * Panic-loop-brake breadcrumb (R40 only). One word here is + * stamped "PAN1" by wb-ramoops-panic-reset before it warm- + * resets the SoC on panic; a boot that comes up still stamped + * declines to re-arm and lets the EC hard-cycle, breaking a + * crash loop. Immediately above the ramoops region, so it sits + * in the same warm-reset-surviving / power-cycle-wiped low-DRAM + * window (base + 57 MiB, below KERNEL_COMP_ADDR_R). R40 needs a + * DRAM breadcrumb because its RTC GP bank is wiped by the panic- + * path warm reset (H616 uses an RTC register instead). Address + * must match breadcrumb_phys in the driver. no-map so the + * driver's ioremap gives an uncached, reset-surviving mapping. + */ + wb_panic_breadcrumb: wb-panic-breadcrumb@43900000 { + reg = <0x43900000 0x1000>; + no-map; + }; }; regulators { diff --git a/drivers/soc/sunxi/wb-ramoops-panic-reset.c b/drivers/soc/sunxi/wb-ramoops-panic-reset.c index 014425fec4be..8718ca33db66 100644 --- a/drivers/soc/sunxi/wb-ramoops-panic-reset.c +++ b/drivers/soc/sunxi/wb-ramoops-panic-reset.c @@ -67,13 +67,21 @@ struct wb_wdt_variant { u8 reset_mask; u8 reset_val; /* whole-system reset */ /* - * Physical address of the RTC general-purpose register used as the - * panic-loop-breaker breadcrumb (see the loop brake below). Must be a - * register that survives a warm reset but is wiped by the EC's hard 5 V - * cycle. 0 = no verified register on this SoC: arm unconditionally, no - * loop brake. + * Physical address of the panic-loop-breaker breadcrumb word (see the + * loop brake below). Must be storage that survives a warm reset but is + * wiped by the EC's hard 5 V cycle. Two kinds are used: + * - H616: an RTC general-purpose MMIO register in the always-on + * domain, left intact by the warm reset. + * - R40: a word in a dedicated no-map DRAM reservation - R40's RTC GP + * bank is wiped by the panic-path warm reset, but low DRAM (same + * physics as the ramoops region) survives it and is lost on a real + * power cycle. + * The driver ioremap()s this address and reads/writes one word; ioremap + * gives an uncached mapping either way, so the panic-time stamp reaches + * the register/DRAM without a cache flush (safe in panic context). + * 0 = no breadcrumb on this SoC: arm unconditionally, no loop brake. */ - u32 rtc_breadcrumb_phys; + u32 breadcrumb_phys; }; /* H616/T507: watchdog in the timer block, dedicated CFG register */ @@ -84,7 +92,7 @@ static const struct wb_wdt_variant sun6i_wdt_variant = { .timeout_shift = 4, .reset_mask = 0x03, .reset_val = 0x01, - .rtc_breadcrumb_phys = 0x0700010c, /* RTC base 0x07000000 + GP data reg 3 */ + .breadcrumb_phys = 0x0700010c, /* RTC base 0x07000000 + GP data reg 3 */ }; /* R40/A40i: CFG and MODE share one register (offset 0x04) */ @@ -96,19 +104,19 @@ static const struct wb_wdt_variant sun4i_wdt_variant = { .reset_mask = 0x02, .reset_val = 0x02, /* - * R40: no usable breadcrumb, so no panic-loop brake (0 = arm - * unconditionally). The sun6i-rtc GP data registers survive a *bare* - * sun4i-WDT warm reset, but E2E bench testing on WB7.4.2 showed the - * *panic-path* warm reset wipes the entire GP data bank - all 8 regs, - * including ones this driver never writes - so a post-panic boot never - * sees the stamp and the brake could never engage. Leaving it 0 is honest - * about that, rather than depending on a register proven to be cleared. - * Consequence: a persistent panic on R40 warm-reset loops instead of - * escalating to an EC hard cycle. TODO: give R40 a store that survives its - * panic path (U-Boot bootcount, or a shuttle-side counter in the eMMC park - * header) before relying on a loop brake here. + * R40 breadcrumb: a word in the dedicated no-map DRAM reservation at + * 0x43900000 (immediately above the ramoops region; see the + * wb-panic-breadcrumb node in the board DT). R40's sun6i-rtc GP data + * bank cannot carry the breadcrumb - E2E bench testing on WB7.4.2 showed + * the *panic-path* warm reset wipes the entire bank (all 8 regs, a + * *bare* sun4i-WDT reset preserves them but the panic path does not). + * Low DRAM instead survives that warm reset (the whole ramoops premise) + * and is lost on a genuine EC 5 V cycle - exactly the reset-reason + * signal the loop brake needs. Being no-map, ioremap() maps it uncached, + * so the panic-time stamp reaches DRAM without a cache flush and the + * address does not trip the ARM ioremap-on-RAM check. */ - .rtc_breadcrumb_phys = 0, /* R40 panic path wipes RTC GP regs; see above */ + .breadcrumb_phys = 0x43900000, /* must match wb-panic-breadcrumb@43900000 in the board DT */ }; static const struct of_device_id wb_wdt_matches[] = { @@ -131,17 +139,17 @@ static const struct wb_wdt_variant *wb_wdt_variant; * if a boot comes up still stamped and we have not yet cleared it after * a healthy uptime, we decline to arm again and let the EC watchdog * escalate to a hard power cycle (which wipes the stamp and breaks the - * loop). The register is per-SoC (wb_wdt_variant.rtc_breadcrumb_phys): - * H616 uses RTC GP data reg 3, bench-confirmed to survive a warm reset and - * be wiped by the EC hard 5 V cycle. R40 has NO usable breadcrumb: its GP - * regs survive a bare warm reset but the panic-path warm reset wipes the - * whole bank (E2E-confirmed on WB7.4.2), so its field is left 0. A SoC with - * the field 0 has no breadcrumb and arms unconditionally (no loop brake) - - * see the R40 variant note above. + * loop). The breadcrumb storage is per-SoC (wb_wdt_variant.breadcrumb_phys), + * both kinds bench-confirmed to survive a warm reset and be wiped by the EC + * hard 5 V cycle: H616 uses RTC GP data reg 3 (an always-on MMIO register); + * R40 uses a word in a no-map DRAM reservation, because its RTC GP bank is + * wiped by the panic-path warm reset (see the sun4i variant note above). + * A SoC with the field 0 has no breadcrumb and arms unconditionally (no + * loop brake). */ -#define WB_RTC_BREADCRUMB_MAGIC 0x50414e31 /* "PAN1" */ +#define WB_BREADCRUMB_MAGIC 0x50414e31 /* "PAN1" */ -static void __iomem *wb_rtc_breadcrumb; +static void __iomem *wb_breadcrumb; static bool wb_reset_from_panic; static void wb_breadcrumb_clear_fn(struct work_struct *work) @@ -149,8 +157,8 @@ static void wb_breadcrumb_clear_fn(struct work_struct *work) /* Healthy uptime reached: allow the next panic to warm-reset again. */ bool was_held = wb_reset_from_panic; - if (wb_rtc_breadcrumb) - writel(0, wb_rtc_breadcrumb); + if (wb_breadcrumb) + writel(0, wb_breadcrumb); wb_reset_from_panic = false; if (was_held) pr_info("healthy, panic warm-reset re-armed\n"); @@ -174,8 +182,8 @@ static int wb_ramoops_panic_reset(struct notifier_block *nb, } /* Stamp the reset-reason breadcrumb before triggering the reset. */ - if (wb_rtc_breadcrumb) - writel(WB_RTC_BREADCRUMB_MAGIC, wb_rtc_breadcrumb); + if (wb_breadcrumb) + writel(WB_BREADCRUMB_MAGIC, wb_breadcrumb); /* Set whole-system reset function */ val = readl(wb_wdt_regs + v->cfg); @@ -240,18 +248,24 @@ static int __init wb_ramoops_panic_reset_init(void) wb_wdt_variant = match->data; /* - * Reset-reason breadcrumb, per SoC (wb_wdt_variant.rtc_breadcrumb_phys; + * Reset-reason breadcrumb, per SoC (wb_wdt_variant.breadcrumb_phys; * 0 = none, arm unconditionally). If this boot came up with the panic * stamp still set, we just warm-reset from a panic: hold off arming * until a healthy uptime clears it. */ - if (wb_wdt_variant->rtc_breadcrumb_phys) { - wb_rtc_breadcrumb = ioremap(wb_wdt_variant->rtc_breadcrumb_phys, 4); - if (!wb_rtc_breadcrumb) + if (wb_wdt_variant->breadcrumb_phys) { + wb_breadcrumb = ioremap(wb_wdt_variant->breadcrumb_phys, 4); + if (!wb_breadcrumb) { pr_warn("breadcrumb ioremap failed; panic-loop brake disabled (arms every boot)\n"); - else if (readl(wb_rtc_breadcrumb) == WB_RTC_BREADCRUMB_MAGIC) { - wb_reset_from_panic = true; - pr_info("came back from a panic warm reset; holding off re-arm\n"); + } else { + u32 stamp = readl(wb_breadcrumb); + + pr_info("breadcrumb @0x%08x reads 0x%08x\n", + wb_wdt_variant->breadcrumb_phys, stamp); + if (stamp == WB_BREADCRUMB_MAGIC) { + wb_reset_from_panic = true; + pr_info("came back from a panic warm reset; holding off re-arm\n"); + } } } /* From 68b52d30f38ea362554208209e7fe05572c7d43f Mon Sep 17 00:00:00 2001 From: Evgeny Boger Date: Wed, 8 Jul 2026 01:27:01 +0300 Subject: [PATCH 14/16] soc: sunxi: wb: resolve the R40 breadcrumb address from DT, not a hardcode The previous commit baked R40's breadcrumb DRAM address (0x43900000) into the driver. A kernel Image is then only safe on a DTB that reserves exactly that address: on a DTB without the reservation the driver would ioremap and stamp 0x43900000 as if it were the breadcrumb, but there it is live System RAM. Resolve the address from the DT instead. The panic-breadcrumb reserved- memory node now carries compatible = "wirenboard,panic-breadcrumb"; the driver looks it up with of_find_compatible_node() + of_address_to_resource(). If the node is absent - e.g. a kernel Image booted on an older DTB that predates it - the driver leaves the breadcrumb address 0, skips the ioremap entirely, and arms unconditionally (no loop brake, no ioremap-on-RAM). H616 keeps its fixed RTC breadcrumb_phys; only R40 uses the DT lookup (breadcrumb_compatible). The reserved-memory node is renamed wb-panic-breadcrumb@43900000 -> panic-breadcrumb@43900000 (generic node name; the driver matches on the compatible now, so the name is cosmetic) and keeps its label, reg and no-map. A compatible with no matching reserved-mem driver is a harmless static no-map reservation (of_reserved_mem returns -ENOENT, the region stays reserved). Re-validated on WB7.4.2 (ALEPBSP3): - node present: driver resolves "breadcrumb @0x43900000" from DT; SURVIVE (PAN1 read back after the sun4i-WDT warm reset), BRAKE ("panic again after a warm reset; not re-arming"), and WIPE (a genuine RTC-alarm 5 V cycle clears the word) all pass. - node absent (booted on a DTB without the node): "no wirenboard,panic-breadcrumb node in DT; panic-loop brake disabled (arms every boot)", armed, and no ioremap-on-RAM warning. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../allwinner/sun8i-r40-wirenboard72x.dtsi | 11 +- .../allwinner/sun8i-r40-wirenboard74x.dtsi | 11 +- drivers/soc/sunxi/wb-ramoops-panic-reset.c | 132 +++++++++++------- 3 files changed, 98 insertions(+), 56 deletions(-) diff --git a/arch/arm/boot/dts/allwinner/sun8i-r40-wirenboard72x.dtsi b/arch/arm/boot/dts/allwinner/sun8i-r40-wirenboard72x.dtsi index 8b643bef6619..64b189084622 100644 --- a/arch/arm/boot/dts/allwinner/sun8i-r40-wirenboard72x.dtsi +++ b/arch/arm/boot/dts/allwinner/sun8i-r40-wirenboard72x.dtsi @@ -76,11 +76,14 @@ * in the same warm-reset-surviving / power-cycle-wiped low-DRAM * window (base + 57 MiB, below KERNEL_COMP_ADDR_R). R40 needs a * DRAM breadcrumb because its RTC GP bank is wiped by the panic- - * path warm reset (H616 uses an RTC register instead). Address - * must match breadcrumb_phys in the driver. no-map so the - * driver's ioremap gives an uncached, reset-surviving mapping. + * path warm reset (H616 uses an RTC register instead). The driver + * matches this node by its compatible and reads the address from + * reg, so a kernel booted on a DTB without this node just skips + * the brake (it never ioremaps a hardcoded address). no-map so + * the driver's ioremap gives an uncached, reset-surviving mapping. */ - wb_panic_breadcrumb: wb-panic-breadcrumb@43900000 { + wb_panic_breadcrumb: panic-breadcrumb@43900000 { + compatible = "wirenboard,panic-breadcrumb"; reg = <0x43900000 0x1000>; no-map; }; diff --git a/arch/arm/boot/dts/allwinner/sun8i-r40-wirenboard74x.dtsi b/arch/arm/boot/dts/allwinner/sun8i-r40-wirenboard74x.dtsi index 3c946b4bb84e..efd9d5fa35e7 100644 --- a/arch/arm/boot/dts/allwinner/sun8i-r40-wirenboard74x.dtsi +++ b/arch/arm/boot/dts/allwinner/sun8i-r40-wirenboard74x.dtsi @@ -78,11 +78,14 @@ * in the same warm-reset-surviving / power-cycle-wiped low-DRAM * window (base + 57 MiB, below KERNEL_COMP_ADDR_R). R40 needs a * DRAM breadcrumb because its RTC GP bank is wiped by the panic- - * path warm reset (H616 uses an RTC register instead). Address - * must match breadcrumb_phys in the driver. no-map so the - * driver's ioremap gives an uncached, reset-surviving mapping. + * path warm reset (H616 uses an RTC register instead). The driver + * matches this node by its compatible and reads the address from + * reg, so a kernel booted on a DTB without this node just skips + * the brake (it never ioremaps a hardcoded address). no-map so + * the driver's ioremap gives an uncached, reset-surviving mapping. */ - wb_panic_breadcrumb: wb-panic-breadcrumb@43900000 { + wb_panic_breadcrumb: panic-breadcrumb@43900000 { + compatible = "wirenboard,panic-breadcrumb"; reg = <0x43900000 0x1000>; no-map; }; diff --git a/drivers/soc/sunxi/wb-ramoops-panic-reset.c b/drivers/soc/sunxi/wb-ramoops-panic-reset.c index 8718ca33db66..51732cda9eee 100644 --- a/drivers/soc/sunxi/wb-ramoops-panic-reset.c +++ b/drivers/soc/sunxi/wb-ramoops-panic-reset.c @@ -40,6 +40,7 @@ #include #include +#include #include #include #include @@ -67,21 +68,28 @@ struct wb_wdt_variant { u8 reset_mask; u8 reset_val; /* whole-system reset */ /* - * Physical address of the panic-loop-breaker breadcrumb word (see the - * loop brake below). Must be storage that survives a warm reset but is - * wiped by the EC's hard 5 V cycle. Two kinds are used: - * - H616: an RTC general-purpose MMIO register in the always-on - * domain, left intact by the warm reset. - * - R40: a word in a dedicated no-map DRAM reservation - R40's RTC GP - * bank is wiped by the panic-path warm reset, but low DRAM (same - * physics as the ramoops region) survives it and is lost on a real - * power cycle. - * The driver ioremap()s this address and reads/writes one word; ioremap - * gives an uncached mapping either way, so the panic-time stamp reaches - * the register/DRAM without a cache flush (safe in panic context). - * 0 = no breadcrumb on this SoC: arm unconditionally, no loop brake. + * Panic-loop-breaker breadcrumb storage (see the loop brake below): a + * word that survives a warm reset but is wiped by the EC's hard 5 V + * cycle. The driver ioremap()s its physical address and reads/writes one + * word; ioremap gives an uncached mapping, so the panic-time stamp + * reaches the register/DRAM without a cache flush (safe in panic + * context). The address is supplied one of two ways: + * - breadcrumb_phys != 0: a fixed physical address baked into the + * variant. H616 uses an RTC general-purpose MMIO register in the + * always-on domain, left intact by the warm reset. + * - breadcrumb_compatible != NULL: look the address up from a DT + * reserved-memory node with this compatible. R40 uses a no-map DRAM + * reservation this way, because its RTC GP bank is wiped by the + * panic-path warm reset (low DRAM, same physics as ramoops, survives + * it and is lost on a real power cycle). Resolving from DT means a + * kernel Image booted on a DTB that lacks the node simply gets no + * breadcrumb - it never ioremaps a hardcoded address that might be + * live RAM under that DTB. + * Neither set (both 0/NULL) = no breadcrumb on this SoC: arm + * unconditionally, no loop brake. */ u32 breadcrumb_phys; + const char *breadcrumb_compatible; }; /* H616/T507: watchdog in the timer block, dedicated CFG register */ @@ -104,19 +112,21 @@ static const struct wb_wdt_variant sun4i_wdt_variant = { .reset_mask = 0x02, .reset_val = 0x02, /* - * R40 breadcrumb: a word in the dedicated no-map DRAM reservation at - * 0x43900000 (immediately above the ramoops region; see the - * wb-panic-breadcrumb node in the board DT). R40's sun6i-rtc GP data - * bank cannot carry the breadcrumb - E2E bench testing on WB7.4.2 showed - * the *panic-path* warm reset wipes the entire bank (all 8 regs, a - * *bare* sun4i-WDT reset preserves them but the panic path does not). - * Low DRAM instead survives that warm reset (the whole ramoops premise) - * and is lost on a genuine EC 5 V cycle - exactly the reset-reason - * signal the loop brake needs. Being no-map, ioremap() maps it uncached, - * so the panic-time stamp reaches DRAM without a cache flush and the - * address does not trip the ARM ioremap-on-RAM check. + * R40 breadcrumb: a word in a no-map DRAM reservation, whose address is + * taken from the board DT (the panic-breadcrumb reserved-memory node, + * currently 0x43900000, immediately above the ramoops region). R40's + * sun6i-rtc GP data bank cannot carry the breadcrumb - E2E bench testing + * on WB7.4.2 showed the *panic-path* warm reset wipes the entire bank + * (all 8 regs; a *bare* sun4i-WDT reset preserves them but the panic path + * does not). Low DRAM instead survives that warm reset (the whole ramoops + * premise) and is lost on a genuine EC 5 V cycle - exactly the reset- + * reason signal the loop brake needs. Being no-map, ioremap() maps it + * uncached, so the panic-time stamp reaches DRAM without a cache flush + * and the address does not trip the ARM ioremap-on-RAM check. Looking it + * up by compatible (rather than a fixed address) keeps a kernel Image + * safe on a DTB that has no such node: no node -> no breadcrumb. */ - .breadcrumb_phys = 0x43900000, /* must match wb-panic-breadcrumb@43900000 in the board DT */ + .breadcrumb_compatible = "wirenboard,panic-breadcrumb", }; static const struct of_device_id wb_wdt_matches[] = { @@ -131,21 +141,21 @@ static const struct wb_wdt_variant *wb_wdt_variant; /* * Loop brake. A kernel that panics on every boot would otherwise * warm-reset forever. There is no hardware "reset cause" latch on these - * SoCs (the watchdog status register is interrupt-mode only and clears - * on reset), but the RTC block has general-purpose registers documented - * "for storing power-off information": one of them survives a warm reset - * and is wiped by the EC's hard 5 V cycle, which is exactly the reset- - * reason signal we need. The panic handler stamps it before arming; - * if a boot comes up still stamped and we have not yet cleared it after - * a healthy uptime, we decline to arm again and let the EC watchdog - * escalate to a hard power cycle (which wipes the stamp and breaks the - * loop). The breadcrumb storage is per-SoC (wb_wdt_variant.breadcrumb_phys), - * both kinds bench-confirmed to survive a warm reset and be wiped by the EC - * hard 5 V cycle: H616 uses RTC GP data reg 3 (an always-on MMIO register); - * R40 uses a word in a no-map DRAM reservation, because its RTC GP bank is - * wiped by the panic-path warm reset (see the sun4i variant note above). - * A SoC with the field 0 has no breadcrumb and arms unconditionally (no - * loop brake). + * SoCs (the watchdog status register is interrupt-mode only and clears on + * reset), so we keep a one-word "breadcrumb" in storage that survives a + * warm reset but is wiped by the EC's hard 5 V cycle - exactly the reset- + * reason signal we need. The panic handler stamps it before arming; if a + * boot comes up still stamped and we have not yet cleared it after a + * healthy uptime, we decline to arm again and let the EC watchdog escalate + * to a hard power cycle (which wipes the stamp and breaks the loop). + * + * The breadcrumb storage is per-SoC (see the variant fields above), both + * kinds bench-confirmed to survive a warm reset and be wiped by the EC hard + * 5 V cycle: H616 uses a fixed RTC GP data register (an always-on MMIO + * register); R40 uses a word in a no-map DRAM reservation whose address is + * resolved from the DT (its RTC GP bank is wiped by the panic-path warm + * reset). A SoC with neither an address nor a resolvable DT node has no + * breadcrumb and arms unconditionally (no loop brake). */ #define WB_BREADCRUMB_MAGIC 0x50414e31 /* "PAN1" */ @@ -220,6 +230,7 @@ static int __init wb_ramoops_panic_reset_init(void) { const struct of_device_id *match; struct device_node *np; + phys_addr_t breadcrumb_phys; if (!of_machine_is_compatible("allwinner,sun50i-h616") && !of_machine_is_compatible("allwinner,sun8i-r40")) @@ -248,20 +259,45 @@ static int __init wb_ramoops_panic_reset_init(void) wb_wdt_variant = match->data; /* - * Reset-reason breadcrumb, per SoC (wb_wdt_variant.breadcrumb_phys; - * 0 = none, arm unconditionally). If this boot came up with the panic - * stamp still set, we just warm-reset from a panic: hold off arming - * until a healthy uptime clears it. + * Resolve the reset-reason breadcrumb address (see the loop brake + * above): either a fixed physical address baked into the variant + * (H616's RTC register) or one read from a DT reserved-memory node with + * the variant's breadcrumb_compatible (R40's no-map DRAM word). If that + * node is absent - e.g. this kernel Image booted on a DTB that does not + * ship it - we leave the address 0 and arm unconditionally, rather than + * ioremapping a hardcoded address that might be live RAM under that DTB. + */ + breadcrumb_phys = wb_wdt_variant->breadcrumb_phys; + if (wb_wdt_variant->breadcrumb_compatible) { + struct device_node *bc = + of_find_compatible_node(NULL, NULL, + wb_wdt_variant->breadcrumb_compatible); + struct resource res; + + if (!bc) + pr_info("no %s node in DT; panic-loop brake disabled (arms every boot)\n", + wb_wdt_variant->breadcrumb_compatible); + else if (of_address_to_resource(bc, 0, &res)) + pr_warn("%s node has no usable reg; panic-loop brake disabled\n", + wb_wdt_variant->breadcrumb_compatible); + else + breadcrumb_phys = res.start; + of_node_put(bc); + } + + /* + * If this boot came up with the panic stamp still set, we just warm-reset + * from a panic: hold off arming until a healthy uptime clears it. */ - if (wb_wdt_variant->breadcrumb_phys) { - wb_breadcrumb = ioremap(wb_wdt_variant->breadcrumb_phys, 4); + if (breadcrumb_phys) { + wb_breadcrumb = ioremap(breadcrumb_phys, 4); if (!wb_breadcrumb) { pr_warn("breadcrumb ioremap failed; panic-loop brake disabled (arms every boot)\n"); } else { u32 stamp = readl(wb_breadcrumb); - pr_info("breadcrumb @0x%08x reads 0x%08x\n", - wb_wdt_variant->breadcrumb_phys, stamp); + pr_info("breadcrumb @%pa reads 0x%08x\n", + &breadcrumb_phys, stamp); if (stamp == WB_BREADCRUMB_MAGIC) { wb_reset_from_panic = true; pr_info("came back from a panic warm reset; holding off re-arm\n"); From 6a8f3ae9d5bdc95a292dfc384fbb144cecc197be Mon Sep 17 00:00:00 2001 From: Evgeny Boger Date: Wed, 8 Jul 2026 01:28:51 +0300 Subject: [PATCH 15/16] soc: sunxi: wb: note the DRAM breadcrumb read is not guaranteed bit-perfect Document, at the magic compare, why a plain equality check is used: the R40 breadcrumb is a DRAM word whose contents survive a warm reset but can occasionally bit-flip across it (observed on the panic_timeout/declined- panic reset path in bench testing on WB7.4.2). A non-magic read is the fail-safe direction - the boot is treated as normal and the notifier arms, costing at most one extra warm reset before the brake re-engages on a clean read. A bit-tolerant compare is intentionally avoided: it would risk a false positive (a random DRAM word within a few bits of MAGIC wrongly read as post-panic, declining to warm-reset a legitimate panic). Comment-only; no functional change. Co-Authored-By: Claude Opus 4.8 (1M context) --- drivers/soc/sunxi/wb-ramoops-panic-reset.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/drivers/soc/sunxi/wb-ramoops-panic-reset.c b/drivers/soc/sunxi/wb-ramoops-panic-reset.c index 51732cda9eee..5b7f3df8219c 100644 --- a/drivers/soc/sunxi/wb-ramoops-panic-reset.c +++ b/drivers/soc/sunxi/wb-ramoops-panic-reset.c @@ -298,6 +298,20 @@ static int __init wb_ramoops_panic_reset_init(void) pr_info("breadcrumb @%pa reads 0x%08x\n", &breadcrumb_phys, stamp); + /* + * Plain equality is deliberate. On R40 the store is a + * DRAM word whose contents survive a warm reset but are + * not guaranteed bit-perfect - a rare flipped bit makes + * this read != MAGIC (observed on the panic_timeout/ + * declined-panic reset path in bench testing). That is + * the fail-safe direction: we just treat the boot as + * normal and arm, costing at worst one extra warm reset + * before the brake re-engages on a clean read. A + * bit-tolerant compare would instead risk a false + * positive - a random DRAM word within a few bits of + * MAGIC wrongly read as "came back from panic", declining + * to warm-reset a legitimate panic - so it is avoided. + */ if (stamp == WB_BREADCRUMB_MAGIC) { wb_reset_from_panic = true; pr_info("came back from a panic warm reset; holding off re-arm\n"); From d736a376afcb752de4c95c41f867d87d03de59f9 Mon Sep 17 00:00:00 2001 From: Evgeny Boger Date: Wed, 8 Jul 2026 03:10:32 +0300 Subject: [PATCH 16/16] soc: sunxi: wb: use the RTC breadcrumb for R40 like H616 (revert the DRAM detour) A controlled re-investigation on WB7.4.2 refuted the premise behind the R40 DRAM-breadcrumb detour: the R40 sun6i-rtc GP data bank DOES survive a pure panic-path sun4i-WDT warm reset (GP sentinels intact through a genuine sysrq-c panic; early-boot read, single warm reset, no EC cycle) - exactly like H616's RTC. The earlier "the R40 panic path wipes the RTC" finding was an EC-power-cut confound: on the escalation the EC does a "reset power" - a *brief* 5 V cut that zeroes the RTC but leaves DRAM intact by remanence, masquerading as a warm reset. That confound also makes a DRAM word the *wrong* store: it survives the brief EC power cut byte-perfect (bench-observed), so the "PAN1" stamp never clears and the loop brake would get stuck holding off. The RTC register clears on that same cut - the wipe the brake needs. So revert the whole DRAM detour and give R40 the same simple RTC breadcrumb as H616: - R40 breadcrumb_phys = 0x01c20500 (RTC GP data reg 0), a fixed RTC MMIO address ioremap()d like H616's 0x0700010c. Drop the per-variant breadcrumb_compatible / DT-node lookup (of_find_compatible_node + of_address_to_resource), the phys_addr_t local and the extra include. - Remove the panic-breadcrumb@43900000 no-map reserved-memory node from both R40 board dtsi. - Correct the comments to the re-investigated understanding, and fix the loop-brake escalation description: a declined panic reboots via panic_timeout -> emergency_restart -> wbec_restart (sys-off priority 192 > sunxi-wdt 128) -> EC "reset power" -> RTC cleared (or, with panic_timeout = 0, the EC watchdog does the cut) - not the previous inaccurate "EC watchdog ~60 s" wording. The Phase-1 logging (warn on ioremap failure, log the raw breadcrumb value) is kept. H616 is unchanged; ramoops/pstore is untouched. Re-validated on WB7.4.2 (ALEPBSP3), production panic_timeout=10: - SURVIVE: panic -> 6 s sun4i-WDT warm reset -> boot reads RTC 0x01c20500 == 0x50414e31 ("PAN1"), "came back from a panic warm reset". - BRAKE: 2nd panic while held -> "not re-arming, escalating to an EC power cut", no SoC watchdog armed. - WIPE: the declined panic escalates via wbec_restart; the EC power cut clears the RTC (next boot reads 0x00000000) -> arms normally, loop broken (the exact case the DRAM word survived byte-perfect). Co-Authored-By: Claude Opus 4.8 (1M context) --- .../allwinner/sun8i-r40-wirenboard72x.dtsi | 21 --- .../allwinner/sun8i-r40-wirenboard74x.dtsi | 21 --- drivers/soc/sunxi/wb-ramoops-panic-reset.c | 160 +++++++----------- 3 files changed, 59 insertions(+), 143 deletions(-) diff --git a/arch/arm/boot/dts/allwinner/sun8i-r40-wirenboard72x.dtsi b/arch/arm/boot/dts/allwinner/sun8i-r40-wirenboard72x.dtsi index 64b189084622..2d6f3de89ed3 100644 --- a/arch/arm/boot/dts/allwinner/sun8i-r40-wirenboard72x.dtsi +++ b/arch/arm/boot/dts/allwinner/sun8i-r40-wirenboard72x.dtsi @@ -66,27 +66,6 @@ ecc-size = <32>; no-map; }; - - /* - * Panic-loop-brake breadcrumb (R40 only). One word here is - * stamped "PAN1" by wb-ramoops-panic-reset before it warm- - * resets the SoC on panic; a boot that comes up still stamped - * declines to re-arm and lets the EC hard-cycle, breaking a - * crash loop. Immediately above the ramoops region, so it sits - * in the same warm-reset-surviving / power-cycle-wiped low-DRAM - * window (base + 57 MiB, below KERNEL_COMP_ADDR_R). R40 needs a - * DRAM breadcrumb because its RTC GP bank is wiped by the panic- - * path warm reset (H616 uses an RTC register instead). The driver - * matches this node by its compatible and reads the address from - * reg, so a kernel booted on a DTB without this node just skips - * the brake (it never ioremaps a hardcoded address). no-map so - * the driver's ioremap gives an uncached, reset-surviving mapping. - */ - wb_panic_breadcrumb: panic-breadcrumb@43900000 { - compatible = "wirenboard,panic-breadcrumb"; - reg = <0x43900000 0x1000>; - no-map; - }; }; regulators { diff --git a/arch/arm/boot/dts/allwinner/sun8i-r40-wirenboard74x.dtsi b/arch/arm/boot/dts/allwinner/sun8i-r40-wirenboard74x.dtsi index efd9d5fa35e7..58b4e37d8dd4 100644 --- a/arch/arm/boot/dts/allwinner/sun8i-r40-wirenboard74x.dtsi +++ b/arch/arm/boot/dts/allwinner/sun8i-r40-wirenboard74x.dtsi @@ -68,27 +68,6 @@ ecc-size = <32>; no-map; }; - - /* - * Panic-loop-brake breadcrumb (R40 only). One word here is - * stamped "PAN1" by wb-ramoops-panic-reset before it warm- - * resets the SoC on panic; a boot that comes up still stamped - * declines to re-arm and lets the EC hard-cycle, breaking a - * crash loop. Immediately above the ramoops region, so it sits - * in the same warm-reset-surviving / power-cycle-wiped low-DRAM - * window (base + 57 MiB, below KERNEL_COMP_ADDR_R). R40 needs a - * DRAM breadcrumb because its RTC GP bank is wiped by the panic- - * path warm reset (H616 uses an RTC register instead). The driver - * matches this node by its compatible and reads the address from - * reg, so a kernel booted on a DTB without this node just skips - * the brake (it never ioremaps a hardcoded address). no-map so - * the driver's ioremap gives an uncached, reset-surviving mapping. - */ - wb_panic_breadcrumb: panic-breadcrumb@43900000 { - compatible = "wirenboard,panic-breadcrumb"; - reg = <0x43900000 0x1000>; - no-map; - }; }; regulators { diff --git a/drivers/soc/sunxi/wb-ramoops-panic-reset.c b/drivers/soc/sunxi/wb-ramoops-panic-reset.c index 5b7f3df8219c..7a2f549bd4d9 100644 --- a/drivers/soc/sunxi/wb-ramoops-panic-reset.c +++ b/drivers/soc/sunxi/wb-ramoops-panic-reset.c @@ -33,14 +33,13 @@ * panic behaviour is intentionally left unchanged, so updating the * kernel alone changes nothing (no-lockstep updates). It also declines * to re-arm when it detects it just warm-reset from a panic (see the - * loop brake below), leaving a repeat panic to the EC watchdog. + * loop brake below), letting a repeat panic escalate to an EC power cut. */ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt #include #include -#include #include #include #include @@ -68,28 +67,15 @@ struct wb_wdt_variant { u8 reset_mask; u8 reset_val; /* whole-system reset */ /* - * Panic-loop-breaker breadcrumb storage (see the loop brake below): a - * word that survives a warm reset but is wiped by the EC's hard 5 V - * cycle. The driver ioremap()s its physical address and reads/writes one - * word; ioremap gives an uncached mapping, so the panic-time stamp - * reaches the register/DRAM without a cache flush (safe in panic - * context). The address is supplied one of two ways: - * - breadcrumb_phys != 0: a fixed physical address baked into the - * variant. H616 uses an RTC general-purpose MMIO register in the - * always-on domain, left intact by the warm reset. - * - breadcrumb_compatible != NULL: look the address up from a DT - * reserved-memory node with this compatible. R40 uses a no-map DRAM - * reservation this way, because its RTC GP bank is wiped by the - * panic-path warm reset (low DRAM, same physics as ramoops, survives - * it and is lost on a real power cycle). Resolving from DT means a - * kernel Image booted on a DTB that lacks the node simply gets no - * breadcrumb - it never ioremaps a hardcoded address that might be - * live RAM under that DTB. - * Neither set (both 0/NULL) = no breadcrumb on this SoC: arm - * unconditionally, no loop brake. + * Physical address of the RTC general-purpose register used as the + * panic-loop-breaker breadcrumb (see the loop brake below). Must be an + * always-on register that survives a warm reset but is cleared by the + * EC's power cut. The driver ioremap()s it and reads/writes one word; + * ioremap gives an uncached mapping, so the panic-time stamp reaches the + * register without a cache flush (safe in panic context). 0 = no + * breadcrumb on this SoC: arm unconditionally, no loop brake. */ u32 breadcrumb_phys; - const char *breadcrumb_compatible; }; /* H616/T507: watchdog in the timer block, dedicated CFG register */ @@ -112,21 +98,21 @@ static const struct wb_wdt_variant sun4i_wdt_variant = { .reset_mask = 0x02, .reset_val = 0x02, /* - * R40 breadcrumb: a word in a no-map DRAM reservation, whose address is - * taken from the board DT (the panic-breadcrumb reserved-memory node, - * currently 0x43900000, immediately above the ramoops region). R40's - * sun6i-rtc GP data bank cannot carry the breadcrumb - E2E bench testing - * on WB7.4.2 showed the *panic-path* warm reset wipes the entire bank - * (all 8 regs; a *bare* sun4i-WDT reset preserves them but the panic path - * does not). Low DRAM instead survives that warm reset (the whole ramoops - * premise) and is lost on a genuine EC 5 V cycle - exactly the reset- - * reason signal the loop brake needs. Being no-map, ioremap() maps it - * uncached, so the panic-time stamp reaches DRAM without a cache flush - * and the address does not trip the ARM ioremap-on-RAM check. Looking it - * up by compatible (rather than a fixed address) keeps a kernel Image - * safe on a DTB that has no such node: no node -> no breadcrumb. + * R40 uses sun6i-rtc GP data reg 0 (RTC base 0x01c20400 + 0x100), an + * always-on register - the same mechanism as H616. Controlled + * re-investigation on WB7.4.2 confirmed it survives a sun4i-WDT warm + * reset and is cleared by the EC's power cut, exactly the reset-reason + * signal the loop brake needs. + * + * (An earlier revision wrongly concluded the R40 panic path "wipes the + * RTC" and detoured through a no-map DRAM word. That was an EC-power-cut + * confound: the EC "reset power" is a *brief* 5 V cut that zeroes the RTC + * but leaves DRAM intact by remanence, so it masquerades as a warm reset. + * A DRAM word therefore *fails* the wipe requirement - it survives that + * brief cut byte-perfect and the stamp never clears - whereas the RTC + * register clears correctly. The RTC is the right store for both SoCs.) */ - .breadcrumb_compatible = "wirenboard,panic-breadcrumb", + .breadcrumb_phys = 0x01c20500, /* RTC base 0x01c20400 + GP data reg 0 */ }; static const struct of_device_id wb_wdt_matches[] = { @@ -142,20 +128,29 @@ static const struct wb_wdt_variant *wb_wdt_variant; * Loop brake. A kernel that panics on every boot would otherwise * warm-reset forever. There is no hardware "reset cause" latch on these * SoCs (the watchdog status register is interrupt-mode only and clears on - * reset), so we keep a one-word "breadcrumb" in storage that survives a - * warm reset but is wiped by the EC's hard 5 V cycle - exactly the reset- - * reason signal we need. The panic handler stamps it before arming; if a - * boot comes up still stamped and we have not yet cleared it after a - * healthy uptime, we decline to arm again and let the EC watchdog escalate - * to a hard power cycle (which wipes the stamp and breaks the loop). + * reset), but the RTC block has general-purpose registers documented "for + * storing power-off information": one survives a warm reset and is cleared + * by the EC's power cut - exactly the reset-reason signal we need. The panic + * handler stamps it before arming; if a boot comes up still stamped and we + * have not yet cleared it after a healthy uptime, we decline to arm again + * and let the reboot escalate to an EC power cut (which clears the stamp and + * breaks the fast warm-reset loop). * - * The breadcrumb storage is per-SoC (see the variant fields above), both - * kinds bench-confirmed to survive a warm reset and be wiped by the EC hard - * 5 V cycle: H616 uses a fixed RTC GP data register (an always-on MMIO - * register); R40 uses a word in a no-map DRAM reservation whose address is - * resolved from the DT (its RTC GP bank is wiped by the panic-path warm - * reset). A SoC with neither an address nor a resolvable DT node has no - * breadcrumb and arms unconditionally (no loop brake). + * The register is per-SoC (wb_wdt_variant.breadcrumb_phys), both bench- and + * E2E-confirmed to survive a warm reset and be cleared by the EC power cut: + * H616 uses RTC GP data reg 3 (0x0700010c), R40 uses RTC GP data reg 0 + * (0x01c20500). A SoC with the field 0 has no breadcrumb and arms + * unconditionally (no loop brake). + * + * How the escalation actually fires: on a declined panic the notifier does + * not arm the SoC watchdog, so the reboot takes the kernel's own restart + * path - panic_timeout -> emergency_restart -> the highest-priority restart + * handler, which on Wiren Board is wbec_restart (sys-off priority 192, above + * sunxi-wdt's 128). That asks the EC to "reset power": a brief 5 V cut that + * clears the RTC stamp (but, by DRAM remanence, would have left a DRAM word + * intact - which is why the breadcrumb must live in the RTC, not DRAM). If + * panic_timeout is 0 the board instead parks until the EC watchdog cuts + * power - same effect on the stamp. */ #define WB_BREADCRUMB_MAGIC 0x50414e31 /* "PAN1" */ @@ -182,12 +177,15 @@ static int wb_ramoops_panic_reset(struct notifier_block *nb, u32 val; /* - * If this boot itself came back from a panic-armed warm reset and - * has not yet proven healthy, do not arm again: let the EC watchdog - * hard-cycle the board so a crash loop cannot spin on warm resets. + * If this boot itself came back from a panic-armed warm reset and has + * not yet proven healthy, do not arm again. The reboot then takes the + * kernel restart path (panic_timeout -> emergency_restart -> wbec_restart) + * and the EC cuts power, which clears the RTC stamp and escalates the + * crash loop off the fast warm-reset path. If panic_timeout is 0 the EC + * watchdog does the power cut instead. */ if (wb_reset_from_panic) { - pr_emerg("panic again after a warm reset; not re-arming, leaving it to the EC watchdog\n"); + pr_emerg("panic again after a warm reset; not re-arming, escalating to an EC power cut\n"); return NOTIFY_DONE; } @@ -230,7 +228,6 @@ static int __init wb_ramoops_panic_reset_init(void) { const struct of_device_id *match; struct device_node *np; - phys_addr_t breadcrumb_phys; if (!of_machine_is_compatible("allwinner,sun50i-h616") && !of_machine_is_compatible("allwinner,sun8i-r40")) @@ -259,59 +256,20 @@ static int __init wb_ramoops_panic_reset_init(void) wb_wdt_variant = match->data; /* - * Resolve the reset-reason breadcrumb address (see the loop brake - * above): either a fixed physical address baked into the variant - * (H616's RTC register) or one read from a DT reserved-memory node with - * the variant's breadcrumb_compatible (R40's no-map DRAM word). If that - * node is absent - e.g. this kernel Image booted on a DTB that does not - * ship it - we leave the address 0 and arm unconditionally, rather than - * ioremapping a hardcoded address that might be live RAM under that DTB. - */ - breadcrumb_phys = wb_wdt_variant->breadcrumb_phys; - if (wb_wdt_variant->breadcrumb_compatible) { - struct device_node *bc = - of_find_compatible_node(NULL, NULL, - wb_wdt_variant->breadcrumb_compatible); - struct resource res; - - if (!bc) - pr_info("no %s node in DT; panic-loop brake disabled (arms every boot)\n", - wb_wdt_variant->breadcrumb_compatible); - else if (of_address_to_resource(bc, 0, &res)) - pr_warn("%s node has no usable reg; panic-loop brake disabled\n", - wb_wdt_variant->breadcrumb_compatible); - else - breadcrumb_phys = res.start; - of_node_put(bc); - } - - /* - * If this boot came up with the panic stamp still set, we just warm-reset - * from a panic: hold off arming until a healthy uptime clears it. + * Reset-reason breadcrumb, per SoC (wb_wdt_variant.breadcrumb_phys; + * 0 = none, arm unconditionally). If this boot came up with the panic + * stamp still set, we just warm-reset from a panic: hold off arming + * until a healthy uptime clears it. */ - if (breadcrumb_phys) { - wb_breadcrumb = ioremap(breadcrumb_phys, 4); + if (wb_wdt_variant->breadcrumb_phys) { + wb_breadcrumb = ioremap(wb_wdt_variant->breadcrumb_phys, 4); if (!wb_breadcrumb) { pr_warn("breadcrumb ioremap failed; panic-loop brake disabled (arms every boot)\n"); } else { u32 stamp = readl(wb_breadcrumb); - pr_info("breadcrumb @%pa reads 0x%08x\n", - &breadcrumb_phys, stamp); - /* - * Plain equality is deliberate. On R40 the store is a - * DRAM word whose contents survive a warm reset but are - * not guaranteed bit-perfect - a rare flipped bit makes - * this read != MAGIC (observed on the panic_timeout/ - * declined-panic reset path in bench testing). That is - * the fail-safe direction: we just treat the boot as - * normal and arm, costing at worst one extra warm reset - * before the brake re-engages on a clean read. A - * bit-tolerant compare would instead risk a false - * positive - a random DRAM word within a few bits of - * MAGIC wrongly read as "came back from panic", declining - * to warm-reset a legitimate panic - so it is avoided. - */ + pr_info("breadcrumb @0x%08x reads 0x%08x\n", + wb_wdt_variant->breadcrumb_phys, stamp); if (stamp == WB_BREADCRUMB_MAGIC) { wb_reset_from_panic = true; pr_info("came back from a panic warm reset; holding off re-arm\n");