Clusterboard A64 Indious Reset 问题:已解决

2021-08-01 16:56:37

裸机计算节点可能会出现软锁、自旋锁、死锁、过热、资源匮乏、Docker 守护进程消失、systemd 变得不稳定等等。在这些情况下,看门狗定时器就像一个死人的开关没有更新(按下),定时器达到零,并且承诺是看门狗电路像上电复位(POR)一样重新启动所有硬件。目标:解决Pine64集群板A64 SoC复位问题,允许软件和WDT复位。我的经验是,我无法在 U-Boot 主线中为集群板中的 Pine64/SOPINE 模块上的 Allwinner A64 SoC (sun50i) 开箱即用。这个问题比我想象的要复杂,所以如果它对其他人有帮助,我会记录我的思考过程并调查我是如何完成这项工作的。连接到集群板 D1 和 +RTC 的势垒二极管允许 WDT 复位(参见解决方案)。这是我在调查此非重启问题时尝试过的所有内容以及我的思考过程。请不要试图阅读所有内容。这是一个有很多失败的侦探故事,但有一个令人满意的解决方案。这是我对解决多年来未解决的问题的调查。以下是我问过自己的问题。

不可以。CPU 内核可能被锁定,或者时钟信号错误,并且无法跳转到例如位置 0 以让 CPU 像刚打开一样工作。我们需要一个硬件解决方案来重置计算模块——看门狗定时器。是和否。 SOPINE (Pine64) 模块中使用的全志 A64 SoC 在处理器中具有硬件看门狗定时器(A64 PDF 原理图,p161),但处理器外部没有专用看门狗电路(SOPINE PDF 原理图)。从上图中可以看出,WDT 可以发送中断 (IRQ) 或复位信号(但“复位信号”是什么意思?)。 WDOG_CFG_REG (0xB4) – WDOG_CONFIG 默认为 1,并向整个系统发送复位信号。 WDOG_CTRL_REG (0xB0) – 将 WDOG_KEY_FIELD 设置为 0xA57 并将 WDOG_RSTART 设置为 1 以触发复位。 WDOG_MODE_REG (0xB8) – 将 WDOG_INTV_VALUE 设置为 2 持续 2 秒,并将 WDOG_EN 设置为 1 以启用 WDT。写(0x1,WDOG_CFG_REG); // 通知整个系统writel(0x10, WDOG_MODE_REG); //看门狗间隔为1swritel(readl(WDOG_MODE_REG)|(1<<0), WDOG_MODE_REG); // 启用 Watchdogdelay_ms(500);writel(readl(WDOG_CTRL_REG)|(0xA57<<1)|(1<<0), WDOG_CTRL_REG); // 写入 0xA57 和 0x1// 系统将重置

A64 中的 WDT 有一个最大 16 秒看门狗周期的倒计时寄存器。当达到零时,它会生成系统范围的“通用重置”。来自 Allwinner A64 文档,USB 控制器接受复位信号作为寄存器标志以进入 USB 暂停。音频控制器 (OWA) 接受一个寄存器标志来重置该控制器。即使未使用的智能卡阅读器 (SCR) 也接受复位寄存器标志。 CPU 也有一些复位寄存器。因此,在 SoC 周围的各种寄存器中设置位会导致这些相关控制器中的复位。更多关于 CPU 复位,它包括内核复位、上电复位和 H_Reset。最后一种模式将重置核心集群,即所有核心。那么这些不同的复位寄存器是如何设置的呢?毕竟,CPU 出了问题,因此无法设置这些寄存器。我们来看看A64总线图。有一种叫做高级外设总线 (APB) 的东西连接到 WDT(定时器),后者连接到类似的总线。 APB 专为低带宽控制访问而设计,例如,在系统外围设备上注册接口。该总线具有类似于高级高性能总线 (AHB) 的地址和数据阶段,但大大减少了复杂度低的信号列表(例如,没有突发)。 APB 是可从 ARM Limited 获得许可的高级微控制器总线架构 (AMBA) 产品的一部分。从上面的系统图中,我们看到 WDT(定时器)将地址和数据放在 APB 上,APB 跨接至 AHB 的桥并设置各种控制器中的寄存器——复位寄存器设置在 SoC 周围。现在我明白了 WDT 如何使用专用总线重置所有外设。

A64 SoC 外部有一个 AXP803 电源管理 IC (PMIC),能够以编程方式改变其电压。它是 SoC 和外围设备的电源轨。可以对此进行编程以对 SoC 进行电源循环吗?通过将 PWROK 线物理接地,PMIC 关闭。当接地被移除时,PMIC 恢复活力,就像设备刚刚插入一样。那么,如果 CPU 断电,什么会切换 PWROK 线?有一个串行接口。这能为我们做什么?简短的回答:没什么。这是一条死胡同。 AXP803 主要是一个锂离子充电 IC,没有任何“关闭然后承诺重新打开”的模式。使用 SOPINE 的 AXP803 PMIC 实现 PMIC 复位需要一些外部硬件或至少一个 RC(电阻-电容)电路。让我们坚持使用 WDT 解决方案。 AXP803 PMIC 监控低功耗、电池不良、PWRON 引脚信号、过热和 GPIO 输入边沿信号等情况。当事件发生时,相应的 IRQ 状态将设置为 1,并将驱动 IRQ 引脚为低电平。由主机使用/通知此 IRQ。当电压确实下降时,PMIC 将降低电流直到初级电压上升。 AXP803 通过精简串行总线 (RSB) 与 A64 通信,因此 A64 可以“注意到”传入的电源错误状态。这是一个我要离开的兔子洞,但是为了回答这个思路,是的,对掉电情况采取了行动。这就是事情出轨的地方。有几个社区补丁/黑客可以添加对 AXP803 PMIC 的支持,通过直接写入寄存器添加 sunxi WDT 复位,并启用驱动程序模型 (DM) 复位类。仅仅在 U-Boot 中尝试 PSCI、RESET、SYSRESET 和 DM_RESET 会导致诸如“错误:do_reset() 已定义”之类的编译器错误,或诸如“此平台不支持系统重置”之类的运行时错误,甚至只是板子挂起。主要选项有哪些?电源状态协调接口 (PSCI) 用于 CPU 和用于系统关闭和复位的整体系统电源管理。当 CONFIG_PSCI_RESET 被使能时,在复位时,当 CONFIG_ARM_PSCI_FW 也被使能时,一些指令通过 PSCI 0.2 接口写入 PSCI 子系统的某个地址。看似简单,又看似复杂。绝大多数 ARM 板都没有在它们的 defconfigs 中设置 #CONFIG_PSCI。

绝大多数 ARM 板在它们的 defconfigs 中有 CONFIG_SYSRESET=y。这似乎是一种以编程方式重置 SoC 上各种组件的模块化方式。它具有热复位和冷复位的规定,以及复位 PMIC(根据 sysreset.h 关闭然后打开)。为了使水变得模糊,在 SYSRESET 中,当 CONFIG_SYSRESET_PSCI 启用时,它可以与 PSCI 交互以执行与 PSCI 相同的操作,但是很少有 defconfigs 具有此功能。你可以看到这越来越令人困惑。这是我自己下火车并试验寄存器的地方。一次又一次地找不到正确的配置标志组合,我的新方法是通过超时触发 WDT,并最终通过 U-Boot shell 中的 reset 命令触发。我的目标是通过串行电缆监控 A64,让制造商的示例重置代码执行并观察正确的重置。在 defconfig 文件中 # CONFIG_PSCI_RESET is not set 禁用 PSCI 复位后,让我们检查 arch/arm/mach-sunxi/board.c。它有一个包含来自 0x01c20c00 的 writel 指令的部分。实际上,寻址和位处理看起来很好。当我明确地尝试调用一系列寄存器写入时,要么什么也没发生,要么在到达 mdelay() 语句时系统挂起,或者电路板只是停止。写(0x1,WDOG_CFG_REG); // 通知整个系统writel(0x10, WDOG_MODE_REG); //看门狗间隔为1swritel(readl(WDOG_MODE_REG)|(1<<0), WDOG_MODE_REG); // 启用 Watchdogdelay_ms(500);writel(readl(WDOG_CTRL_REG)|(0xA57<<1)|(1<<0), WDOG_CTRL_REG); // Writel 0xA57 和 0x1// 系统将重置对于我的十六进制值绝对明确,这是我在代码中尝试的内容。这将永远循环。

// arch/arm/lib/reset.c#define TIMER_REG 0x01C20C00// ... snip ...do { putc('A'); writel(0x1, TIMER_REG + 0xB4); // 将 1:0 设置为 01 putc('B'); // writel((1 << 5), TIMER_REG + 0xB8); // 将 7:4 设置为 0001 (...00010000) writel(0x10, TIMER_REG + 0xB8); // 0b10000 = 0x10,或 1s putc('C'); writel(readl(TIMER_REG + 0xB8) | (1 << 0), TIMER_REG + 0xB8); // 将 0 设置为 1 以启用 WDT putc('D');延迟(500); putc('E'); writel(readl(TIMER_REG + 0xB0) | (0xA57 << 1) | (1 << 0), TIMER_REG + 0xB0); // 将 12:1 设置为 0xA57,并将 0 设置为 1 // 也可以是 writel(0x14AF, TIMER_REG + 0xB0); putc('F');} while(1); writel ( readl ( TIMER_REG + 0xB8 ) | ( 1 < < 0 ) , TIMER_REG + 0xB8 ) ; // 设置 0 为 1 以启用 WDT writel ( readl ( TIMER_REG + 0xB0 ) | ( 0xA57 < < 1 ) | ( 1 < < 0 ) , TIMER_REG + 0xB0 ) ; // 将 12:1 设置为 0xA57,并将 0 设置为 1 我对“Pine64 reset”的初始搜索一无所获(太具体了)。论坛中有一些没有得到答复的求助请求,这就是我尝试自己调试 U-Boot 的原因。有一天,当我机智的尽头时,我反而搜索了“A64 看门狗重置”,这让我找到了一个与才华横溢的人在九页标题为“H6 著名的重启问题”的主题中合作的深层线索。 Allwinner 生产 A64 和 H6,后者与 A64 非常相似,但具有更好的视频支持(在集群计算机中不需要)。大奖。 “我尝试调试 arch/arm/mach-sunxi/board.c 中的 reset_cpu(),它设置了一些看门狗寄存器并无限循环,但看门狗似乎从未启动。” (参考)“也许 nowayout 参数应该设置为 1?我记得 H3 上的 nowayout=0 只是禁用看门狗硬件重置。”

下一个想法是查看 Arm Trusted Firmware(现在称为 Trusted Firmware A,或 TF-A)。 “主线 u-boot 有一个重置命令,它会触发基于看门狗的重启,当看门狗超时到期时,它只会锁定机器。同样的事情只会发生在内核中。内核告诉 ATF 重置,ATF 做与 u-boot(基于看门狗的重置)相同的事情,然后 SoC 锁定。” ( ref) “在 ATF 中更改为 R_WDOG 而不是 WDOG 解决了这个问题。 ……可以在 build/patch/atf/atf-sunxi64/ 中添加补丁。” ( ref) 一致认为问题出在 ATF(现在称为 TF-A)中,修复(针对 H6)非常简单:diff --git a/plat/allwinner/sun50i_h6/include/sunxi_mmap.hb /plat/allwinner/sun50i_h6/include/sunxi_mmap.hindex f36491a8..f01745a4 100644--- a/plat/allwinner/sun50i_h6/include/sunxi_mmap.h+++ b/plat/allwinner/@sun_include50i_hh++ ,4 +58,7 @@ #define SUNXI_R_UART_BASE 0x07080000 #define SUNXI_R_PIO_BASE 0x07022000 +#undef SUNXI_WDOG_BASE+#define SUNXI_WDOG_BASE SUNXI_R_WDOG_BASE+ #endif /*-'sundog_BASE+ #endif /*-'sundog-关于看门狗的现在*解决方案是否可以像将常规看门狗代码指向受信任的看门狗一样简单?我们再来看看系统总线。似乎在 2021 年 TF-A 已经在使用安全看门狗 (SUNXI_R_WDOG),如下所示。对于 A64,这里无事可做。

// plat/allwinner/common/sunxi_native_pm.c#define SUNXI_WDOG0_CTRL_REG (SUNXI_R_WDOG_BASE + 0x0010)#define SUNXI_WDOG0_CFG_REG (SUNXI_R_WDOG_BASE + 0x0014)#define SUNXI_WDOG0_10_detribute(F_WDOG0_0_1) SUNXI_WDOG0_0_静态(F)_WDOG0_0_静态(F)_WDOG0_0_10_静态___de_reg__0_0_静态_detribute_WDOG0_0_静态_detribute_WDOG0_10_静态_detribute_WDOG_0_10_静态__dead2 sunxi_system_reset(void) { gicv2_cpuif_disable(); /* 当看门狗超时时复位整个系统 */ mmio_write_32(SUNXI_WDOG0_CFG_REG, 1); /* 以最短超时(0.5 秒)启用看门狗 */ mmio_write_32(SUNXI_WDOG0_MODE_REG, (0 << 4) | 1); /* 在恐慌之前等待两倍看门狗超时 */ mdelay(1000); ERROR("PSCI: 系统重置失败\n"); panic();} 由于 ARM 受信任的看门狗是受信任的(对吧?),因此需要通过 TF-A 从用户世界到安全世界进行通信。现在,TF-A 在 sunxi_native_pm.c 和 sunxi_scpi_pm.c 中都定义了 sunxi_system_reset()。 // plat/allwinner/common/sunxi_scpi_pm.cstatic void __dead2 sunxi_system_reset(void) { uint32_t ret; gicv2_cpuif_disable(); /* 向SCP发送系统复位请求。 */ ret = scpi_sys_power_state(scpi_system_reboot); if (ret != SCP_OK) { ERROR("PSCI: SCPI %s 失败: %d\n", "reboot", ret); } psci_power_down_wfi();} 我们现在必须更深入地了解 SCPI,它代表系统控制和电源接口。使用了两种实现中的哪一种?根据 allwinner-common.mk 中的逻辑,默认使用原生实现。那么,如何以编程方式调用它呢?让我们追查sunxi_native_pm.c中的“native PSCI ops”结构,看看谁执行了“system_reset”操作。这导致 psci_system_off.c 使用名为 psci_system_reset(void) 的方法。 // lib/psci/psci_system_off.cvoid __dead2 psci_system_reset(void) { psci_print_power_domain_map(); /* 通知安全负载调度器 */ if ((psci_spd_pm != NULL) && (psci_spd_pm->svc_system_reset != NULL)) { psci_spd_pm->svc_system_reset(); } console_flush(); /* 调用平台特定的钩子 */ psci_plat_pm_ops->system_reset(); /* 该函数不返回。我们永远不应该到达这里 */} // lib/psci/psci_main.c/************************************ ************************************************ * PSCI 顶部用于服务 SMC 的级别处理程序。 ****************************************************** ****************************/u_register_t psci_smc_handler(uint32_t smc_fid,// ... snip ... case PSCI_SYSTEM_OFF: psci_system_off() ; /* 我们不应该从 psci_system_off() 返回 */ break; case PSCI_SYSTEM_RESET: psci_system_reset(); /* 我们不应该从 psci_system_reset() 返回 */ break;

SMC 是一种符合高级微控制器总线架构 (AMBA) 的 SoC 外设。它是一个带有片上 AMBA 总线接口的地址空间控制器。用户指南变得冗长,但假设它是 TF-A 安全代码使用的受保护地址空间的看门人。回到 U-Boot,我们看到在 A64 的 DTS 中,PSCI 使用 SMC。我们现在已经完全回到了 U-Boot 中的 PSCI。让我们回到 U-Boot shell 并尝试手动发出一些 SMC 命令,看看它是否有效。首先添加 CONFIG_CMD_SMC=y。 U-Boot> smcsmc - 发出安全监控调用Usage:smc <fid> [arg1 ... arg6] [id] - fid 函数 ID - arg SMC 参数,传递给 X1-X6(默认为零) - id 安全操作系统 ID / 会话 ID,传递给 W7(默认为零)U-Boot> 没有明显的文档。没有 SMC 示例。只是在池的深处踩水。函数 ID 到底是什么?在 U-Boot 主线中,我找到了一个名为 durian.c 的孤独文件,看到一个提示: // psci.cvoid reset_cpu(ulong addr) { struct arm_smccc_res res; arm_smccc_smc(0x84000009, 0, 0, 0, 0, 0, 0, 0, &res); // <-- hint debug("reset cpu error, %lx\n", res.a0);} 查看smc的命令处理器,它也通过smccc中的do_call()方法到达arm_smccc_smc()- call.c,并再次在名为 invoke_psci_fn() 的方法中。

追查后者,我发现 invoke_psci_fn(PSCI_0_2_FN_SYSTEM_RESET, 0, 0, 0) 在代码深处。然后将 PSCI_0_2_FN_SYSTEM_RESET 定义为 PSCI_0_2_FN(9)。我们最终到达: // include/linux/psci.h#define PSCI_0_2_FN_BASE 0x84000000#define PSCI_0_2_FN(n) (PSCI_0_2_FN_BASE + (n))// 那么,PSCI_0_2_FN_SYSTEM_RESET = PSCI_0_2_FN00,0x800x800x000x00发现reset的Function ID也是0x84000009。让我们使用 U-Boot 中的 smc 命令通过 TF-A 触发系统重置。绝对没有发生任何事情,除了董事会仍然挂起。回到原点。无论如何,这似乎是一个黑客。继续。我再次深入研究了 Allwinner A64 用户手册,并查看了 C++ 结构中的 WDT 寄存器偏移量。结构偏移量是否正确?我看到 u32(4 字节)条目,那么 u32 ctl(WDOG_CTRL_REG)是否真的从 0x10 偏移量开始,或者错误地从 0x12(4 字节 * 3)开始? // arch/arm/include/asm/arch-sunxi/watchdog.hstruct sunxi_wdog { u32 irq_en; /* 0x00 */ u32 irq_sta; /* 0x04 */ u32 res1[2]; u32 ctl; /* 0x10 <-- 0x10 还是 0x12? */ u32 配置文件; /* 0x14 */ u32 模式; /* 0x18 */ u32 res2;};如果这是一个简单的指针错误,那不是很好吗?让我们快速测试一下。

#define u32 unsigned int#define SUNXI_TIMER_BASE 0x01c20c00int main() { struct sunxi_wdog { u32 irq_en; /* 0x00 */ u32 irq_sta; /* 0x04 */ u32 res1[2]; u32 ctl; /* 0x10 <-- 是的,正确的偏移量 */ u32 cfg; /* 0x14 */ u32 模式; /* 0x18 */ u32 res2; }; static const struct sunxi_wdog *wdog = (struct sunxi_wdog *)SUNXI_TIMER_BASE; std::cout << &wdog->ctl << std::endl; // 0x1c20c10 - 错误的地址,应该是 0x1c20cb0 std::cout << &wdog->cfg << std::endl; // 0x1c20c14 - 错误的地址,应该是 0x1c20cb4 std::cout << &wdog->mode << std::endl; // 0x1c20c18 - 错误的地址,应该是 0x1c20cb8 return 0;} // #define SUNXI_TIMER_BASE 0x01c20c00#define WDOG0_IRQ_EN_REG 0x01c20ca0 // Ends in A0int main() { q struct sunxi_en; } /* 0x00 */ u32 irq_sta; /* 0x04 */ u32 res1[2]; u32 ctl; /* 0x10 */ u32 cfg; /* 0x14 */ u32 模式; /* 0x18 */ u32 res2; }; static const struct sunxi_wdog *wdog = (struct sunxi_wdog *)WDOG0_IRQ_EN_REG; std::cout << &wdog->ctl << std::endl; // 0x1c20cb0 - 更正 std::cout << &wdog->cfg << std::endl; // 0x1c20cb4 - 更正 std::cout << &wdog->mode << std::endl; // 0x1c20cb8 - 正确返回 0;} 除了 Pine H64 和 Rongpin RP-H6B 似乎没有受到影响外,这个问题确实存在。 OrangePi 板(Lite2 / One Plus 和 3)上的许多用户都在抱怨这个问题。松树 H64 = H6 V200-AWIN H6448BA 7782 => OK OrangePi Lite 2 = H6 V200-AWIN H8068BA 61C2 => KO PineH64 = H8069BA ​​6892 => OK 橙色 Pi 3 = HA047BA = 690420Lite>H6KOOLite 2 => H6KOO2Lite H6448BA 6662 => KO Beelink GS1 = H6 V200-AWIN H7309BA 6842 => KO Sunxi硬件再次社区共识是使用可信看门狗(R_WDOG)。但是,怎么样?在 705 页的 Allwinner A64 用户指南中,它仅对第 74 页的 0x01F01000 处的 R_WDOG 寄存器进行了单独引用。并且提到 R_WDOG 是“CPUS”域下的安全模块。没有关于如何使用模块或其使用的寄存器偏移量的参考。让我们看看是否可以通过从 H6 设备树中嫁接将可信看门狗添加到 A64 设备树中。

// 来自 Allwinner H6 手册页。 72// r_watchdog: watchdog@7020400 {// compatible = "allwinner,sun50i-h6-wdt",// "allwinner,sun6i-a31-wdt";// reg = <0x07020400 0x20>;// 中断 = <GIC_SPI 103 IRQ_TYPE_LEVEL_HIGH>;//clocks = <&osc24M>;// };// 更新寄存器以使用 A64r_watchdog 的 R_WDOG:watchdog@1f01000 { compatible = "allwinner,sun50i-h6-wdt", "allwinner,sun450i-a66 -wdt", "allwinner,sun6i-a31-wdt"; reg = <0x01f01000 0x20>;中断 = <GIC_SPI 103 IRQ_TYPE_LEVEL_HIGH>;时钟 = <&osc24M>;};没有效果。说实话,我什至不确定嫁接到 A64 的 GIC_SPI 103 是否有任何作用。我将不理会这个并尝试其他方法。让我们使用通常无法访问的汇编函数来查找 A64 修订号。 // TF-A: bl31_main.cextern int cpu_get_rev_var(void);// 获取版本号INFO("CPU revision: %d\n", cpu_get_rev_var());// Output: "INFO: CPU revision: 4" 让我们做确保在 TF-A 中启用所有处理器勘误表以确保安全(在 Dockerfile 中)。跑步 ......