From 76ce56d06007c0a0e79d10678adf405b26bc79b2 Mon Sep 17 00:00:00 2001 From: nyin Date: Fri, 28 Aug 2026 14:54:14 +0800 Subject: [PATCH] samples/ec_boot: add EC boot from file --- samples/ec_boot/CMakeLists.txt | 40 ++ samples/ec_boot/README.md | 327 ++++++++++++ samples/ec_boot/boards/lsqsh_evb_cpu1.overlay | 28 + samples/ec_boot/prj.conf | 23 + samples/ec_boot/sample.yaml | 12 + samples/ec_boot/src/ec_fw/ec_ram_test.bin | Bin 0 -> 379 bytes samples/ec_boot/src/ecboot_port.c | 491 ++++++++++++++++++ samples/ec_boot/src/ecboot_port.h | 110 ++++ samples/ec_boot/src/main.c | 36 ++ 9 files changed, 1067 insertions(+) create mode 100755 samples/ec_boot/CMakeLists.txt create mode 100755 samples/ec_boot/README.md create mode 100755 samples/ec_boot/boards/lsqsh_evb_cpu1.overlay create mode 100755 samples/ec_boot/prj.conf create mode 100755 samples/ec_boot/sample.yaml create mode 100755 samples/ec_boot/src/ec_fw/ec_ram_test.bin create mode 100755 samples/ec_boot/src/ecboot_port.c create mode 100755 samples/ec_boot/src/ecboot_port.h create mode 100755 samples/ec_boot/src/main.c diff --git a/samples/ec_boot/CMakeLists.txt b/samples/ec_boot/CMakeLists.txt new file mode 100755 index 0000000..6c0690a --- /dev/null +++ b/samples/ec_boot/CMakeLists.txt @@ -0,0 +1,40 @@ +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.20.0) + +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(ec_boot) + +# Firmware source switch (mirrors openbmc_zephyr_app). 0 = embedded ec_fw[] array +# (no filesystem); 1 = read EC_FW_FILE_PATH (/ro/ec_fw/ec_ram_test.bin) at runtime +# via POSIX open()/read(). This single variable drives BOTH the CMake staging +# below AND the C macro in ecboot_port.h (via add_definitions). +set(EC_FW_FROM_FILE 0 CACHE STRING "Load EC firmware from a file (1) or embedded array (0)") +add_definitions(-DEC_FW_FROM_FILE=${EC_FW_FROM_FILE}) + +# Embed the EC firmware binary as a C byte array so we don't depend on a +# filesystem (the original openbmc build packaged it into rofs at /ro/ec_fw/). +# generate_inc_file_for_target produces ec_fw.inc defining: +# const unsigned char ec_fw[] = { ... }; +# const size_t ec_fw_len = ; +generate_inc_file_for_target(app + src/ec_fw/ec_ram_test.bin + ${ZEPHYR_BINARY_DIR}/include/generated/ec_fw.inc) + +# When EC_FW_FROM_FILE=1, also stage the raw bin into the configured rofs tree +# (mirrors openbmc_zephyr_app/CMakeLists.txt install_config_file), so it ends up +# at /ro/ec_fw/ec_ram_test.bin after the filesystem image is flashed. +# NOTE: openbmc's install_config_file() is its own CMake helper and is not +# available here, so we use the standard file(COPY) to achieve the same staging. +if(EC_FW_FROM_FILE) + set(EC_RAM_TEST_BIN ${CMAKE_CURRENT_SOURCE_DIR}/src/ec_fw/ec_ram_test.bin) + if(EXISTS ${EC_RAM_TEST_BIN}) + file(COPY ${EC_RAM_TEST_BIN} + DESTINATION ${CMAKE_BINARY_DIR}/configure_file/ec_fw/) + endif() +endif() + +target_sources(app PRIVATE + src/main.c + src/ecboot_port.c +) diff --git a/samples/ec_boot/README.md b/samples/ec_boot/README.md new file mode 100755 index 0000000..b1c68ac --- /dev/null +++ b/samples/ec_boot/README.md @@ -0,0 +1,327 @@ +# EC Boot Sample 说明文档 + +BMC(Zephyr,cpu1)通过 UART5 与 EC 通信,将内嵌的 EC 固件(`ec_ram_test.bin`) +经 IAP 协议下载到 EC RAM(0x10001000)并跳转执行,随后可监听 EC UART 输出。 + +--- + +## 1. 目录结构 + +``` +samples/ec_boot/ +├── CMakeLists.txt # 构建脚本,负责把 ec_fw 生成 inc 并编入固件 +├── prj.conf # Kconfig 配置(shell / gpio / log / 关闭 XIP) +├── sample.yaml +├── boards/ +│ └── lsqsh_evb_cpu1.overlay # 板级 overlay:使能 uart5 + pinctrl +├── src/ +│ ├── main.c # 注册 shell 命令 ec_boot / ec_listen +│ ├── ecboot_port.h # 引脚/协议宏定义与函数声明 +│ ├── ecboot_port.c # IAP 握手、下载、跳转、EC UART 监听实现 +│ └── ec_fw/ +│ └── ec_ram_test.bin # 内嵌的 EC 固件镜像(原始 bin,无文件系统) +└── testlog/ # 验证日志 +``` + +--- + +## 2. 硬件与引脚配置 + +EC boot 依赖以下三类外设,必须在 board overlay / dts / 源码中正确配置: + +### 2.1 UART5(EC boot/ISP 串口) + +| 项目 | 配置 | +|---------------|-------------------------------------------------| +| 设备 | `&uart5`(源码宏 `EC_UART_SPEC = DEVICE_DT_GET(DT_NODELABEL(uart5))`) | +| 引脚 | PN14 = TX(FUNC4),PN13 = RX(FUNC4, input-enable) | +| 波特率 | 115200 8N1(`current-speed = <115200>`) | +| 物理连接 | BMC UART5 ↔ EC PH05(TXD)/PH04(RXD) 同一对线 | + +`boards/lsqsh_evb_cpu1.overlay` 中已定义 pinctrl 节点并使能 `&uart5`: + +```dts +&uart5 { + status = "okay"; + current-speed = <115200>; + pinctrl-0 = <&ec_uart5_txd_pn14 &ec_uart5_rxd_pn13>; + pinctrl-names = "default"; +}; +``` + +> 注意:1os 板型变体的 `lsqsh_evb_pinctrl.dtsi` 未定义 uart5 的 pinctrl 节点, +> 因此必须在 overlay 里**内联定义** `ec_uart5_txd_pn14` / `ec_uart5_rxd_pn13`, +> 否则 `&uart5` 引用会被丢弃、设备无法就绪。 + +### 2.2 GPIO_I:BOOT 脚与 RST 脚 + +| 信号 | GPIO 设备 | 引脚 | 宏定义 | +|-------------|----------------------|--------|-----------------------------------------| +| EC_BOOT | `gpioi`(GPIO_I) | pin 12 | `EC_BOOT_GPIO_SPEC` / `EC_BOOT_GPIO_PIN` | +| EC_RST | `gpioi`(GPIO_I) | pin 10 | `EC_RST_GPIO_SPEC` / `EC_RST_GPIO_PIN` | + +源码定义(`ecboot_port.h`): + +```c +#define EC_BOOT_GPIO_SPEC DEVICE_DT_GET(DT_NODELABEL(gpioi)) +#define EC_BOOT_GPIO_PIN 12 +#define EC_RST_GPIO_SPEC DEVICE_DT_GET(DT_NODELABEL(gpioi)) +#define EC_RST_GPIO_PIN 10 +``` + +复位时序(`ec_boot_try_once` 中实现): + +``` +RST=0 (强制 EC 复位) + -> 延时 10ms +BOOT=1 (请求 EC 进入 boot/ISP 模式) + -> 延时 10ms +RST=1 (释放复位,EC 从 boot 模式启动) + -> 延时 200ms +``` + +> 硬件注意:EC_BOOT(I12)经 R98(0R) 直连 EC,EC 侧可能把该线拉低。 +> BMC 推挽输出已验证能驱动 I12 为高(DOC_DOS bit12=1),但引脚实际电平 +> 读取(OE_DIN 0x4006f3a0 bit12)可能为 0。只要 0x55→0xAA 握手成功, +> 说明 EC 已进入 boot 模式,可继续。 + +### 2.3 关键协议宏(`ecboot_port.h`) + +```c +#define EC_RAM_LOAD_ADDR 0x10001000U /* EC RAM 加载/跳转地址 */ +#define EC_WRITE_CHUNK 1024U /* 单帧最大写入字节数 */ +#define EC_READ_TIMEOUT_MS 5000 /* ACK 超时 */ +#define CMD_MEM_BULK_WRITE 0x02 /* 写帧: [cmd][addr:4][len:4][data] */ +#define CMD_PROGRAM_GO 0x04 /* 跳转: [cmd][func:4][arg:4] */ +#define EC_BOOT_ECHO_MS 5000 /* ec_boot 成功后回显时长(ms),0=关 */ +#define EC_UART_ECHO_MS 60000 /* ec_listen 单次监听时长(ms) */ +#define EC_BOOT_MAX_TRIES 6 /* ec_boot 最大尝试次数 */ +``` + +--- + +## 3. EC 固件(内嵌 bin) + +由于该平台**没有文件系统**,原始 openbmc 把 EC 固件放在 `/ro/ec_fw/`,这里改为 +把 bin **直接编入 BMC 固件镜像**。 + +### 3.1 生成机制 + +`CMakeLists.txt` 使用 `generate_inc_file_for_target` 把 bin 转成 C 字节数组: + +```cmake +generate_inc_file_for_target(app + src/ec_fw/ec_ram_test.bin + ${ZEPHYR_BINARY_DIR}/include/generated/ec_fw.inc) +``` + +生成的 `ec_fw.inc` 仅含裸字节初始化列表(`0x37, 0x01, ...`),在 `ecboot_port.c` +末尾用它初始化数组: + +```c +const unsigned char ec_fw[] = { +#include "ec_fw.inc" +}; +const size_t ec_fw_len = sizeof(ec_fw); +``` + +### 3.2 更新 EC 固件 + +1. 编译你的 EC 程序(如 `/home/yinning/code/615/ec_ram_test/`), + 链接地址须为 `0x10001000`,产物为 `ec_ram_test.bin`。 +2. 复制到 `src/ec_fw/ec_ram_test.bin` 覆盖旧文件。 +3. 重新 `west build`,新 bin 会自动编入。 + +> EC 固件要求:链接到 `0x10001000`,运行后通过 EC UART1(PH05)打印, +> 且无需自行重配 UART(复用 bootloader 已初始化的 115200 8N1),否则可能 +> 与 BMC 波特率/时钟不匹配导致后续发送停滞。 + +### 3.2.1 固件来源宏开关(`EC_FW_FROM_FILE`) + +代码通过 `EC_FW_FROM_FILE` 选择固件来源,该开关是**单一来源**: +`CMakeLists.txt` 里定义为 cache 变量并 `add_definitions` 透传给 C,同时控制 CMake +构建期是否预置 bin 和 C 运行期是否读文件,无需两处分别改。 + +| `EC_FW_FROM_FILE` | 固件来源 | 是否需要文件系统 | +|---|---|---| +| `0`(默认) | 内嵌的 `ec_fw[]` 数组(编译进 BMC 镜像) | 否 | +| `1` | 从挂载的文件系统读取 `EC_FW_FILE_PATH`(默认 `/ro/ec_fw/ec_ram_test.bin`) | 是 | + +- 切换方式:改 `CMakeLists.txt` 顶部 `set(EC_FW_FROM_FILE 0 ...)` 为 `1`(或用 + `west build -DEC_FW_FROM_FILE=1`),重新构建即可。 +- 文件模式下 `ec_fw_get()` 会用 POSIX `open`/`lseek`/`read` 把整个文件读入 + `k_malloc` buffer(用后 `k_free`),支持任意大小固件,不再受编译期数组限制。 + 该实现与已验证可用的 openbmc `src/ecboot/ecboot.c` 完全一致。 +- 构建期:当 `EC_FW_FROM_FILE=1` 时,`CMakeLists.txt` 用 `file(COPY)` 把 + `src/ec_fw/ec_ram_test.bin` 预置到 `configure_file/ec_fw/`(对齐 openbmc 的 + `install_config_file`),烧录后位于 `/ro/ec_fw/ec_ram_test.bin`。 +- 文件模式需在 `prj.conf` 开启(参考文件末尾注释,默认注释不启): + ```kconfig + CONFIG_POSIX_API=y + CONFIG_FILE_SYSTEM=y + CONFIG_FILE_SYSTEM_LITTLEFS=y # 或你实际用的后端 + ``` + 并且要在运行 `ec_boot` 前把对应分区挂载到路径前缀(如 `/ro`)。 + +> 设计意图:当前平台无文件系统,用内嵌数组最省事;将来若 BMC 侧有了可读的 +> 文件系统(如 LittleFS/EXT2 挂载到 `/ro`),只需打开 `EC_FW_FROM_FILE=1` 并挂载 +> 分区,即可直接加载 `/ro/ec_fw/ec_ram_test.bin`,无需重新编译 BMC 固件。 + +### 3.3 当前测试固件说明(`ec_ram_test.bin`) + +当前的 `src/ec_fw/ec_ram_test.bin` **只是一个 IAP 下载链路的验证固件**,并不具备 +真实 EC 功能。源码位于 `/home/yinning/code/615/ec_ram_test/uart.c`,它的功能很简单: + +- 链接地址 `0x10001000`(SRAM0),通过 UART IAP 由 BMC 下载并执行; +- 复用 EC bootloader 已初始化好的 **UART1(PH05/TXD,基地址 `0x40085000`,115200 8N1)**, + 不再重配时钟/引脚; +- 上电后先打印 `EC RAM OK - UART IAP PROGRAM_GO works!\r\n`; +- 随后进入 `while(1)` 死循环,每 500ms 打印一次 `tick N\r\n`(N 为计数,到 10 回绕为个位数), + 用于验证「EC 确实在 RAM 中跑起来、且 UART1 输出能被 BMC 监听到」。 + +核心片段: + +```c +#define UART1 ((reg_uart_t *)0x40085000) /* EC UART1 寄存器基址(裸机硬编码,非 DTS) */ + +int main(void) +{ + uart1_init(); /* 空实现:复用 bootloader 的 UART 配置 */ + uart_puts("EC RAM OK - UART IAP PROGRAM_GO works!\r\n"); + unsigned int cnt = 0; + while (1) { + uart_puts("tick "); + uart_putc('0' + (cnt % 10)); + uart_puts("\r\n"); + cnt++; + mdelay(500); + } +} +``` + +> 注意:EC 侧是**裸机程序**,`UART1` 的 `0x40085000` 来自 LinkedSemi HAL 头 +> `reg_base_addr.h`(`UART1_BASE_ADDR`),并非来自某个 EC 侧 dts 文件。 +> BMC 侧 `dts/riscv/linkedsemi/lsqsh.dtsi` 里的 `uart1: uart@40085000` 是 BMC 自己的 +> 描述,与 EC 通信用的是 `uart5`(overlay 使能),二者不要混淆。 + +### 3.4 更换为真实 EC 固件 + +`ec_ram_test.bin` 仅用于打通下载链路。实际使用时,把它换成你自己的真实 EC 固件即可: + +1. **准备真实固件 bin** + - 链接地址必须仍是 `0x10001000`(与 `EC_RAM_LOAD_ADDR` 一致); + - 固件运行后若要通过 UART 与 BMC 交互,建议复用 EC bootloader 已初始化的 UART1 + (不要再重配波特率/时钟,避免与 BMC 侧 uart5 不匹配); + - 若真实固件需要 BMC 通过 IAP 协议主动下发更多段/数据,可扩展 `ec_write_firmware()` + 的分块逻辑(目前按 `EC_WRITE_CHUNK=1024` 一次或分多次写满整个 `ec_fw_len`)。 + +2. **替换 bin 并重新构建** + + ```bash + # 把真实固件放到 ec_fw 目录,覆盖测试固件 + cp <你的真实固件>.bin src/ec_fw/ec_ram_test.bin + # 注意:CMakeLists.txt 里写死的文件名是 ec_ram_test.bin,改名需同步改 CMakeLists.txt + west build -p always -b lsqsh_evb@1os/lsqsh/cpu1 linkedsemi_zephyr_project/samples/ec_boot + ``` + +3. **运行验证** + + ``` + uart:~$ ec_boot + ``` + + 若真实固件不再打印 `tick`,可用 `ec_listen` 监听其实际 UART 输出(或接 + web_serialport 直连 EC UART 验证),确认下载/跳转是否成功。 + +> 提示:若真实 EC 固件较大(> 1KB),`ec_write_firmware()` 会自动分多个写帧, +> 每帧 EC 回 `0x02 0x00` ACK,无需改动即可支持。如需校验和/重试,可在该函数内扩展。 + +--- + +## 4. Shell 命令使用 + +### 4.1 `ec_boot` —— 完整 IAP 烧录并启动 EC + +执行完整流程:配置 GPIO → 复位 EC 进 boot 模式 → 0x55/0xAA 自动波特率握手 +→ 同步字校验(3C3C A5A5 → C3C3 5A5A)→ 分块下载 `ec_fw[]` → `PROGRAM_GO` 跳转, +成功后保持 BOOT 脚为高并回显 EC 输出 `EC_BOOT_ECHO_MS` 毫秒。 + +``` +uart:~$ ec_boot +ec_boot: attempt 1/6 ... +ecboot: baud handshake ok (0x55->0xAA) +ecboot: sync word ok (3C 3C A5 A5 -> C3 C3 5A 5A) +ecboot: download fw 379 bytes -> 0x10001000 +ecboot: wrote pkt 1 (379 bytes), next addr 0x1000117b +ecboot: firmware download done, 1 packets +ecboot: PROGRAM_GO sent, EC jumps to 0x10001000 +ecboot: --- echo EC UART output for 5000 ms --- +EC RAM OK - UART IAP PROGRAM_GO works! +tick 0 +tick 1 +... +ecboot: --- echo done, 153 bytes from EC --- +ec_boot: SUCCESS on attempt 1 +``` + +失败时会自动拉低 BOOT 脚,最多重试 `EC_BOOT_MAX_TRIES` 次后放弃。 + +### 4.2 `ec_listen` —— 监听 EC UART 输出(独立命令) + +**不触发任何 boot/reset**,仅在调用后的 `EC_UART_ECHO_MS`(默认 60000 = 60 秒) +内持续 `uart_poll_in(UART5)`,把 EC 发出的字节用 `uart_poll_out` 原样转发到 +BMC console。适合 EC 已跑起来后,随时查看其 `tick` 输出。 + +``` +uart:~$ ec_listen +ec_uart_echo: listening on EC UART for 60000 ms ... +EC RAM OK - UART IAP PROGRAM_GO works! +tick 0 +tick 1 +tick 2 +... +ec_uart_echo: done, NNN bytes from EC +``` + +> 行为说明:EC 在 `while(1)` 持续发 tick,但 BMC 仅在 `ec_listen`/`ec_boot` +> 的回显循环**主动读 UART5 期间**才会收到并显示。60 秒窗口结束后循环退出, +> EC 仍在发,但 BMC 不再读取,UART5 接收 FIFO 填满后新字节被丢弃,console +> 上即停止。需要再看就再敲一次 `ec_listen`。 + +### 4.3 典型操作流程 + +``` +1) 首次启动 EC: + uart:~$ ec_boot # 下载并跳转,自动回显 5 秒 + +2) 之后想继续观察 EC 输出: + uart:~$ ec_listen # 再听 60 秒 + +3) 想换 EC 固件: + 替换 src/ec_fw/ec_ram_test.bin -> 重新 west build -> ec_boot +``` + +--- + +## 5. 构建与运行 + +```bash +# 在 venv 中(交叉编译工具链见工程配置) +export ZEPHYR_TOOLCHAIN_VARIANT=cross-compile +export CROSS_COMPILE=~/ali/Xuantie-900-gcc-elf-newlib-x86_64-V3.0.1/bin/riscv64-unknown-elf- +west build -p always -b lsqsh_evb@1os/lsqsh/cpu1 linkedsemi_zephyr_project/samples/ec_boot +``` + +烧录后上电,串口终端进入 shell 即可使用上述命令。 + +--- + +## 6. 常见问题 + +| 现象 | 原因 / 处理 | +|------|-------------| +| `ecboot: device not ready (boot/rst/uart)` | overlay 未使能 `&uart5` 或 gpioi 未就绪;检查 `boards/*.overlay` | +| 握手失败 `baud handshake failed` | EC 未进 boot 模式;确认 BOOT/RST 时序,或硬件 key-press 强驱 | +| EC 只打印 1 个 tick / 乱码 | 旧版用 `shell_print` 逐字符转发破坏了原始字节;已改为 `uart_poll_out` 直传 | +| `ec_listen` 结束后看不到 tick | 正常:监听窗口结束,BMC 停止读 UART5;再敲一次即可 | +| EC 重启重打 `EC RAM OK` | EC 固件自身行为(如计数复位),非 BMC 烧录问题;改 EC 源码去掉复位 | diff --git a/samples/ec_boot/boards/lsqsh_evb_cpu1.overlay b/samples/ec_boot/boards/lsqsh_evb_cpu1.overlay new file mode 100755 index 0000000..bcae9b3 --- /dev/null +++ b/samples/ec_boot/boards/lsqsh_evb_cpu1.overlay @@ -0,0 +1,28 @@ +/* EC boot sample board overlay (cpu1). + * Enable uart5 (EC boot/ISP UART, PN14=TX / PN13=RX, FUNC4) which is left + * without pinctrl in the 1os board variant (lsqsh_evb_pinctrl.dtsi does not + * define uart5_txd_pn14 / uart5_rxd_pn13, so the base cpu1.dts reference is + * dropped). Define the pinctrl nodes inline here and wire them to &uart5. + * gpioi (GPIO_I, carrying BOOT=I12 / RST=I10) is already okay. + */ +#include + +/ { +}; + +&pinctrl { + /omit-if-no-ref/ec_uart5_txd_pn14: ec_uart5_txd_pn14 { + pinmux = ; + }; + /omit-if-no-ref/ec_uart5_rxd_pn13: ec_uart5_rxd_pn13 { + pinmux = ; + input-enable; + }; +}; + +&uart5 { + status = "okay"; + current-speed = <115200>; + pinctrl-0 = <&ec_uart5_txd_pn14 &ec_uart5_rxd_pn13>; + pinctrl-names = "default"; +}; diff --git a/samples/ec_boot/prj.conf b/samples/ec_boot/prj.conf new file mode 100755 index 0000000..2cb91d8 --- /dev/null +++ b/samples/ec_boot/prj.conf @@ -0,0 +1,23 @@ +# EC boot control sample needs shell, UART (ttyS5) and GPIO (gpiochip8 / I12,I10) +# UART is auto-enabled by &uart5 status="okay" in the cpu1 dts, no CONFIG_UART symbol. +# Align with fs_sample_perf (known bootable on cpu1): XIP disabled so the +# image is loaded to SRAM @0x10080000 (matches FSBL exe_addr). +CONFIG_XIP=n +CONFIG_SHELL=y +CONFIG_SHELL_HISTORY=y +CONFIG_GPIO=y +CONFIG_LOG=y +CONFIG_LOG_DEFAULT_LEVEL=3 +CONFIG_MAIN_STACK_SIZE=10240 + +# === Optional: load EC firmware from a filesystem instead of the embedded bin === +# The lines below are EXAMPLES only (commented out). Enable them only when +# EC_FW_FROM_FILE=1 is set in CMakeLists.txt (set(EC_FW_FROM_FILE 1 ...) or +# west build -DEC_FW_FROM_FILE=1). That mirrors the verified openbmc_zephyr_app +# config: POSIX open()/read() + a mounted /ro volume. Remove the leading '# ' +# to enable, and mount the volume at /ro before running ec_boot. +#CONFIG_POSIX_API=y +#CONFIG_FILE_SYSTEM=y +#CONFIG_FILE_SYSTEM_LITTLEFS=y # or your actual backend +# (the bin is staged to /ro/ec_fw/ec_ram_test.bin at build time via +# file(COPY) in CMakeLists.txt) diff --git a/samples/ec_boot/sample.yaml b/samples/ec_boot/sample.yaml new file mode 100755 index 0000000..dbe3a9b --- /dev/null +++ b/samples/ec_boot/sample.yaml @@ -0,0 +1,12 @@ +sample: + name: EC boot control + description: > + BMC forces the EC into boot/ISP mode via GPIO_I12 (EC_PFO1_BOOT) and + GPIO_I10 (EC_RST), then performs the 0x55/0xAA auto-baud handshake over + /dev/ttyS5. Shell command "ec_boot" retries up to 6 times. +tests: + sample.ec_boot: + tags: ec_boot + harness: keyboard + integration_platforms: + - lsqsh_evb@1os/lsqsh/cpu1 diff --git a/samples/ec_boot/src/ec_fw/ec_ram_test.bin b/samples/ec_boot/src/ec_fw/ec_ram_test.bin new file mode 100755 index 0000000000000000000000000000000000000000..d341ace4f61c1ceee8012689ca5e7e1fe219c9e4 GIT binary patch literal 379 zcmXqKWD?NTU|i7b&dMG!4v zn268epZm98=Wv+eU0KL>P3el-C9ey9=Yr0Jof1xG7npH+hqq&yFxxkN5O2q6=H>33 zQ`085u`MX<5A9c8^_;_eDT9NB=XO>FhV4K*efup=&hQpyW8hut=G#Bx^bL1mR)$)j zUXB~bCmaXz%D;cV0ix5*89;RI>>J0mjx2H)ZvXz@>d4do+uHw%J^gJi${--j>MCL` z$sn*@gh9ak%9%5==E9tu;*(oi#D!U|oN4sh9?9V#%*9aa)oN(Id3zUAZ?o`pbq2fX zZ0yGEuN;Kg*_nZM@i#B$WCHp8>3!uzFB#NYkD9h}O=dHY4?4C9V&^fKovHg>ofU!{ xeHHw@6?7Fs9fLv?JRJiR0)qVAfuiy5{tD&!McKuQyj%=GP?DLPtpKEfJOCmxj{^Vz literal 0 HcmV?d00001 diff --git a/samples/ec_boot/src/ecboot_port.c b/samples/ec_boot/src/ecboot_port.c new file mode 100755 index 0000000..5336b7b --- /dev/null +++ b/samples/ec_boot/src/ecboot_port.c @@ -0,0 +1,491 @@ +/* + * ecboot_port.c - EC boot control ported from openbmc_zephyr_app ecboot.c + * See ecboot_port.h for pin/route background. + */ + +#include +#include +#include +#include +#include +#include +#include + +#if EC_FW_FROM_FILE +/* Read firmware from a mounted filesystem via POSIX open/read, matching the + * verified openbmc_zephyr_app implementation (ecboot.c ec_write_firmware). + * Requires CONFIG_POSIX_API=y in prj.conf; the standard headers are then + * provided by Zephyr's POSIX subsystem. supplies SEEK_SET/SEEK_END. */ +#include +#include +#include +#include +#endif + +#include "ecboot_port.h" + +LOG_MODULE_REGISTER(ecboot_port, LOG_LEVEL_INF); + +/* Generated from src/ec_fw/ec_ram_test.bin by generate_inc_file_for_target. + * The .inc file contains only the raw byte initializer list + * (0x37, 0x01, ...), so we use it to initialize our own array. */ +extern const unsigned char ec_fw[]; +extern const size_t ec_fw_len; + +/* Resolve the EC firmware image to {ptr, len, need_free}. + * - EC_FW_FROM_FILE=0: use the embedded ec_fw[] array (need_free=false). + * - EC_FW_FROM_FILE=1: open EC_FW_FILE_PATH (e.g. /ro/ec_fw/ec_ram_test.bin) + * via POSIX open/read into a k_malloc buffer (need_free=true; caller must + * ec_fw_put() it). Mirrors openbmc ecboot.c ec_write_firmware(). + * Returns 0 on success, negative errno on failure. */ +static int ec_fw_get(const uint8_t **out_ptr, size_t *out_len, bool *need_free) +{ +#if EC_FW_FROM_FILE + *need_free = false; + + int fw = open(EC_FW_FILE_PATH, O_RDONLY); + if (fw < 0) { + LOG_ERR("ecboot: open %s failed: %d (%s)", + EC_FW_FILE_PATH, errno, strerror(errno)); + return -errno; + } + + off_t sz = lseek(fw, 0, SEEK_END); + if (sz <= 0) { + LOG_ERR("ecboot: %s empty or bad size=%lld", + EC_FW_FILE_PATH, (long long)sz); + close(fw); + return -EINVAL; + } + lseek(fw, 0, SEEK_SET); + size_t fsize = (size_t)sz; + + uint8_t *buf = k_malloc(fsize); + if (!buf) { + LOG_ERR("ecboot: oom for %u bytes", (uint32_t)fsize); + close(fw); + return -ENOMEM; + } + + size_t rd = 0; + while (rd < fsize) { + ssize_t n = read(fw, buf + rd, fsize - rd); + if (n < 0) { + LOG_ERR("ecboot: read %s failed: %d (%s)", + EC_FW_FILE_PATH, errno, strerror(errno)); + k_free(buf); + close(fw); + return -errno; + } + if (n == 0) { + break; /* EOF */ + } + rd += (size_t)n; + } + close(fw); + + LOG_INF("ecboot: loaded %u bytes from %s", (uint32_t)rd, EC_FW_FILE_PATH); + *out_ptr = buf; + *out_len = rd; + *need_free = true; + return 0; +#else + *out_ptr = ec_fw; + *out_len = ec_fw_len; + *need_free = false; + return 0; +#endif +} + +static void ec_fw_put(const uint8_t *ptr, bool need_free) +{ +#if EC_FW_FROM_FILE + if (need_free) { + k_free((void *)ptr); + } +#else + ARG_UNUSED(ptr); + ARG_UNUSED(need_free); +#endif +} + +/* Compile-time device references (cpu1 dts: gpioi = GPIO_I, uart5 = EC UART). + * Declared before any function that uses them. */ +static const struct device *ec_boot_dev = EC_BOOT_GPIO_SPEC; +static const struct device *ec_rst_dev = EC_RST_GPIO_SPEC; +static const struct device *ec_uart_dev = EC_UART_SPEC; +static const struct device *ec_console_dev = EC_CONSOLE_DEV; + +/* Forward declaration (defined later, after ec_program_go) */ +void ec_echo_ec_output(const struct shell *sh); + +/* ns16550 poll_in returns -1 on "no data"; only real errors bail out */ +static bool ec_uart_real_error(int rc) +{ + return rc < 0 && rc != -EAGAIN && rc != -1; +} + +/* Blocking exact write (poll_out already waits for THRE internally). */ +static int ec_uart_writen(const uint8_t *buf, size_t len) +{ + for (size_t i = 0; i < len; i++) { + uart_poll_out(ec_uart_dev, buf[i]); + } + return (int)len; +} + +/* Blocking exact read with timeout. Returns bytes read or negative error. */ +static int ec_uart_readn(uint8_t *buf, size_t len, int timeout_ms) +{ + size_t got = 0; + int64_t end = k_uptime_get() + timeout_ms; + + while (got < len) { + uint8_t c; + int r = uart_poll_in(ec_uart_dev, &c); + if (r == 0) { + buf[got++] = c; + continue; + } + if (ec_uart_real_error(r)) { + LOG_ERR("ecboot: uart_poll_in err=%d", r); + return (got == 0) ? r : (int)got; + } + if (k_uptime_get() >= end) { + LOG_WRN("ecboot: read timeout, got %u/%u", got, len); + return (got == 0) ? -ETIMEDOUT : (int)got; + } + k_sleep(K_MSEC(2)); + } + return (int)got; +} + +/* Wait for the EC to answer the 0x55 auto-baud request with 0xAA. + * Returns 0 on success, -ETIMEDOUT on failure. + */ +static int ec_wait_handshake(k_timeout_t timeout) +{ + uint8_t c; + int64_t deadline = k_uptime_get() + k_ticks_to_ms_floor32(timeout.ticks); + int rc; + + /* drain any stale input */ + while (uart_poll_in(ec_uart_dev, &c) == 0) { + /* discard */ + } + + uart_poll_out(ec_uart_dev, EC_UART_SYNC_REQ); + + while (k_uptime_get() < deadline) { + rc = uart_poll_in(ec_uart_dev, &c); + if (rc == 0) { + if (c == EC_UART_SYNC_ACK) { + return 0; + } + /* unexpected byte, keep waiting */ + } else if (rc != -EAGAIN && rc != -1) { + /* ns16550 poll_in returns -1 on "no data"; only bail + * on real errors */ + return rc; + } + k_sleep(K_MSEC(2)); + } + return -ETIMEDOUT; +} + +/* Send the sync word and verify the EC echoes the expected reply. + * tx / rx are len bytes each. Returns 0 on match, -EIO on mismatch. + */ +static int ec_scan_seq(const uint8_t *tx, const uint8_t *rx_expect, + size_t len, k_timeout_t timeout) +{ + uint8_t rx[16]; + int rc; + int64_t deadline; + + if (len > sizeof(rx)) { + len = sizeof(rx); + } + + for (size_t i = 0; i < len; i++) { + uart_poll_out(ec_uart_dev, tx[i]); + } + + deadline = k_uptime_get() + k_ticks_to_ms_floor32(timeout.ticks); + for (size_t i = 0; i < len; i++) { + do { + rc = uart_poll_in(ec_uart_dev, &rx[i]); + if (rc == 0) { + break; + } else if (rc != -EAGAIN && rc != -1) { + /* ns16550 poll_in returns -1 on "no data" */ + return rc; + } + if (k_uptime_get() >= deadline) { + return -ETIMEDOUT; + } + k_sleep(K_MSEC(2)); + } while (1); + + if (rx[i] != rx_expect[i]) { + LOG_ERR("ec_scan_seq mismatch idx %zu want 0x%02x got 0x%02x", + i, rx_expect[i], rx[i]); + return -EIO; + } + } + return 0; +} + +int ec_boot_try_once(const struct shell *sh) +{ + int rc = 0; + /* EC IAP sync word: send 3C 3C A5 A5, EC echoes C3 C3 5A 5A + * (serialPort.html / ecboot.c protocol step 2) */ + static const uint8_t sync_tx[] = {0x3C, 0x3C, 0xA5, 0xA5}; + static const uint8_t sync_rx[] = {0xC3, 0xC3, 0x5A, 0x5A}; + + if (!device_is_ready(ec_boot_dev) || !device_is_ready(ec_rst_dev) || + !device_is_ready(ec_uart_dev)) { + LOG_ERR("ecboot: device not ready (boot/rst/uart)"); + return -ENODEV; + } + + /* Assert boot pin (output, initially low) */ + rc = gpio_pin_configure(ec_boot_dev, EC_BOOT_GPIO_PIN, + GPIO_OUTPUT | GPIO_OUTPUT_INIT_LOW); + if (rc < 0) { + LOG_ERR("ecboot: config BOOT failed %d", rc); + return rc; + } + rc = gpio_pin_configure(ec_rst_dev, EC_RST_GPIO_PIN, + GPIO_OUTPUT | GPIO_OUTPUT_INIT_HIGH); + if (rc < 0) { + LOG_ERR("ecboot: config RST failed %d", rc); + return rc; + } + + /* Reset sequence: RST=0 -> BOOT=1 -> release RST (simulate key press) */ + gpio_pin_set(ec_rst_dev, EC_RST_GPIO_PIN, 0); /* force EC reset */ + k_sleep(K_MSEC(10)); + gpio_pin_set(ec_boot_dev, EC_BOOT_GPIO_PIN, 1); /* request boot mode */ + k_sleep(K_MSEC(10)); + gpio_pin_set(ec_rst_dev, EC_RST_GPIO_PIN, 1); /* release reset */ + k_sleep(K_MSEC(200)); + + /* Auto-baud handshake */ + rc = ec_wait_handshake(K_MSEC(1000)); + if (rc == 0) { + LOG_INF("ecboot: baud handshake ok (0x55->0xAA)"); + rc = ec_scan_seq(sync_tx, sync_rx, sizeof(sync_tx), K_MSEC(500)); + if (rc == 0) { + LOG_INF("ecboot: sync word ok (3C 3C A5 A5 -> C3 C3 5A 5A)"); + /* Handshake complete: download the embedded firmware and + * jump EC to it. */ + rc = ec_write_firmware(); + if (rc == 0) { + rc = ec_program_go(EC_RAM_LOAD_ADDR); + } + if (rc == 0) { + /* SUCCESS: keep BOOT pin HIGH. Releasing it would + * pull EC's BOOT/RESET sampling line low (via R98) + * and disturb/reset the running firmware, killing + * the tick loop. Leave EC running and echo output. */ + LOG_INF("ecboot: firmware downloaded & started, " + "leaving BOOT pin asserted"); + ec_echo_ec_output(sh); + return 0; + } + } else { + LOG_ERR("ecboot: sync word failed (%d)", rc); + } + } else { + LOG_ERR("ecboot: baud handshake failed (%d)", rc); + } + + /* Failure path: release boot pin so a later attempt can retry */ + gpio_pin_set(ec_boot_dev, EC_BOOT_GPIO_PIN, 0); + + return rc; +} + +/* Bulk-write the EC firmware into EC RAM at EC_RAM_LOAD_ADDR. + * The image comes from ec_fw_get() (embedded array or a file, depending on + * EC_FW_FROM_FILE). Frame: [0x02][addr:4 LE][len:4 LE][data...]; + * EC ACKs with [0x02][0x00]. */ +int ec_write_firmware(void) +{ + const uint8_t *fw; + size_t fw_len; + bool need_free; + + int rc = ec_fw_get(&fw, &fw_len, &need_free); + if (rc < 0) { + return rc; + } + if (fw_len == 0) { + LOG_ERR("ecboot: firmware image is empty"); + ec_fw_put(fw, need_free); + return -ENOENT; + } + + LOG_INF("ecboot: download fw %u bytes -> 0x%08x", + (uint32_t)fw_len, EC_RAM_LOAD_ADDR); + + uint32_t addr = EC_RAM_LOAD_ADDR; + size_t remain = fw_len; + size_t off = 0; + int pkt = 0; + + while (remain > 0) { + size_t to_send = (remain > EC_WRITE_CHUNK) ? EC_WRITE_CHUNK : remain; + + uint8_t hdr[9]; + uint32_t wlen = (uint32_t)to_send; + hdr[0] = CMD_MEM_BULK_WRITE; + memcpy(&hdr[1], &addr, 4); + memcpy(&hdr[5], &wlen, 4); + + if (ec_uart_writen(hdr, sizeof(hdr)) < 0 || + ec_uart_writen(&fw[off], to_send) < 0) { + LOG_ERR("ecboot: write pkt %d tx failed", pkt); + ec_fw_put(fw, need_free); + return -EIO; + } + + uint8_t ack[2] = { 0 }; + int r = ec_uart_readn(ack, sizeof(ack), EC_READ_TIMEOUT_MS); + if (r < 0 || ack[0] != CMD_MEM_BULK_WRITE || ack[1] != 0x00) { + LOG_ERR("ecboot: write pkt %d ACK fail (r=%d got=%02x %02x)", + pkt, r, ack[0], ack[1]); + ec_fw_put(fw, need_free); + return -EIO; + } + + addr += (uint32_t)to_send; + off += to_send; + remain -= to_send; + pkt++; + LOG_INF("ecboot: wrote pkt %d (%u bytes), next addr 0x%08x", + pkt, (uint32_t)to_send, addr); + } + + LOG_INF("ecboot: firmware download done, %d packets", pkt); + ec_fw_put(fw, need_free); + return 0; +} + +/* Send PROGRAM_GO: [0x04][func_ptr:4 LE][arg:4 LE]. EC jumps to func_ptr. */ +int ec_program_go(uint32_t addr) +{ + uint8_t frame[9]; + + frame[0] = CMD_PROGRAM_GO; + memcpy(&frame[1], &addr, 4); + memset(&frame[5], 0, 4); + + if (ec_uart_writen(frame, sizeof(frame)) < 0) { + LOG_ERR("ecboot: PROGRAM_GO tx failed"); + return -EIO; + } + LOG_INF("ecboot: PROGRAM_GO sent, EC jumps to 0x%08x", addr); + return 0; +} + +/* Debug helper: drain whatever the EC keeps printing on its UART and echo it + * verbatim to the shell, for EC_BOOT_ECHO_MS milliseconds. This lets us tell + * apart "EC died after first tick" from "EC keeps ticking but BMC didn't + * show it". Does not disturb any GPIO. */ +void ec_echo_ec_output(const struct shell *sh) +{ +#if EC_BOOT_ECHO_MS > 0 + ec_uart_echo(sh, EC_BOOT_ECHO_MS); +#endif +} + +/* Standalone listen: continuously poll_in EC UART (uart5) and forward each + * byte verbatim to the BMC console device with uart_poll_out, for `ms` + * milliseconds. Raw forwarding (bypasses shell line-buffering/escaping) so + * the output matches what web_serialport shows when attached to EC UART. + * + * NOTE: UART5 receives EC bytes into its RX FIFO as long as the hardware is + * enabled, but they are only *shown* while this loop is actively reading. + * When `ms` elapses the loop returns and further EC output is no longer + * drained (FIFO fills and new bytes are dropped) until you call again. */ +int ec_uart_echo(const struct shell *sh, int ms) +{ + if (!device_is_ready(ec_uart_dev)) { + shell_error(sh, "ec_uart_echo: EC UART not ready"); + return -ENODEV; + } + if (ms <= 0) { + shell_print(sh, "ec_uart_echo: disabled (ms=%d)", ms); + return 0; + } + + int64_t end = k_uptime_get() + ms; + uint8_t c; + int got = 0; + const struct device *out = ec_console_dev; + + if (!out || !device_is_ready(out)) { + shell_print(sh, "ec_uart_echo: console dev not ready, " + "fallback to shell_print"); + out = NULL; + } + + shell_print(sh, "ec_uart_echo: listening on EC UART for %d ms ...", ms); + while (k_uptime_get() < end) { + int r = uart_poll_in(ec_uart_dev, &c); + if (r == 0) { + if (out) { + uart_poll_out(out, c); + } else { + shell_print(sh, "%c", c); + } + got++; + } else if (ec_uart_real_error(r)) { + shell_print(sh, "ec_uart_echo: uart_poll_in err=%d", r); + break; + } + k_sleep(K_MSEC(2)); + } + shell_print(sh, "ec_uart_echo: done, %d bytes from EC", got); + return 0; +} + +int cmd_ec_boot(const struct shell *sh, size_t argc, char **argv) +{ + int rc = -EIO; + + ARG_UNUSED(argc); + ARG_UNUSED(argv); + + if (!device_is_ready(ec_boot_dev) || !device_is_ready(ec_rst_dev) || + !device_is_ready(ec_uart_dev)) { + shell_error(sh, "ec_boot: device not ready (boot/rst/uart). " + "Is &uart5 enabled in the board overlay?"); + return -ENODEV; + } + + for (int i = 1; i <= EC_BOOT_MAX_TRIES; i++) { + shell_print(sh, "ec_boot: attempt %d/%d ...", i, EC_BOOT_MAX_TRIES); + rc = ec_boot_try_once(sh); + if (rc == 0) { + shell_print(sh, "ec_boot: SUCCESS on attempt %d", i); + return 0; + } + shell_print(sh, "ec_boot: attempt %d failed (%d), retrying", i, rc); + k_sleep(K_MSEC(300)); + } + + shell_error(sh, "ec_boot: all %d attempts failed, giving up", + EC_BOOT_MAX_TRIES); + return rc; +} + +/* The .inc holds only the raw byte initializer list (0x37, 0x01, ...); + * use it to initialize the embedded firmware image array. */ +const unsigned char ec_fw[] = { +#include "ec_fw.inc" +}; +const size_t ec_fw_len = sizeof(ec_fw); diff --git a/samples/ec_boot/src/ecboot_port.h b/samples/ec_boot/src/ecboot_port.h new file mode 100755 index 0000000..ef23294 --- /dev/null +++ b/samples/ec_boot/src/ecboot_port.h @@ -0,0 +1,110 @@ +/* + * ecboot_port.h - EC boot control ported from openbmc_zephyr_app ecboot.c + * + * BMC(Zephyr, cpu1) forces the EC into boot/ISP mode by: + * 1. Pulling EC_PFO1_BOOT (GPIO_I12, gpiochip8 pin12) HIGH + * 2. Toggling EC_RST (GPIO_I10, gpiochip8 pin10) LOW->HIGH (reset pulse) + * Then it talks to the EC over /dev/ttyS5 at 115200 8N1 and waits for the + * 0x55 -> 0xAA auto-baud handshake. + * + * NOTE: on this board EC_PFO1_BOOT (I12) is connected to the EC through + * R98 = 0R, so the EC side can hold the line LOW. The BMC push-pull output + * has been verified to drive I12 HIGH (DOC_DOS bit12=1) but R98 reads 0V + * because the EC pulls it down. If the command fails, use the hardware + * key-press method (strong 3V3 drive) instead. Current cpu1 firmware keeps + * espi1 disabled, so I12 is a clean GPIO (not muxed to eSPI). + */ + +#ifndef __ECBOOT_PORT_H__ +#define __ECBOOT_PORT_H__ + +#include +#include +#include + +/* EC_BOOT = EC_PFO1_BOOT, connected to EC via R98 (0R) -> GPIO_I pin12 */ +#define EC_BOOT_GPIO_SPEC DEVICE_DT_GET(DT_NODELABEL(gpioi)) +#define EC_BOOT_GPIO_PIN 12 + +/* EC_RST (reset EC) -> GPIO_I pin10 */ +#define EC_RST_GPIO_SPEC DEVICE_DT_GET(DT_NODELABEL(gpioi)) +#define EC_RST_GPIO_PIN 10 + +/* EC UART for boot/ISP handshake -> &uart5 (enabled via board overlay) */ +#define EC_UART_SPEC DEVICE_DT_GET(DT_NODELABEL(uart5)) +#define EC_UART_BAUD 115200 + +/* BMC console UART (the one the user talks to). Used to forward EC output + * verbatim with uart_poll_out, bypassing shell line-buffering/formatting. */ +#define EC_CONSOLE_DEV DEVICE_DT_GET(DT_CHOSEN(zephyr_console)) + +/* Auto-baud handshake chars */ +#define EC_UART_SYNC_REQ 0x55 /* BMC sends */ +#define EC_UART_SYNC_ACK 0xAA /* EC replies */ + +/* Number of full boot attempts before giving up */ +#define EC_BOOT_MAX_TRIES 6 + +/* ===== EC IAP download protocol (from openbmc ecboot.c) ===== + * After the two-step handshake (0x55->0xAA, 3C3CA5A5->C3C35A5A), the BMC + * sends firmware in bulk-write frames and finally a "program go" frame. + */ +#define EC_RAM_LOAD_ADDR 0x10001000U /* bram .text entry / PROGRAM_GO target */ +#define EC_WRITE_CHUNK 1024U /* max bytes per bulk-write frame */ +#define EC_READ_TIMEOUT_MS 5000 /* ACK/reply timeout */ + +/* frame command words */ +#define CMD_MEM_BULK_WRITE 0x02 /* [cmd][addr:4 LE][len:4 LE][data...] */ +#define CMD_PROGRAM_GO 0x04 /* [cmd][func_ptr:4 LE][arg:4 LE] */ + +/* Debug: after PROGRAM_GO, keep draining EC UART output and echo it to the + * shell for EC_BOOT_ECHO_MS milliseconds so we can see whether the EC keeps + * printing "tick N" or dies after the first one. Set to 0 to disable. */ +#define EC_BOOT_ECHO_MS 5000 + +/* Standalone "listen" command: continuously poll_in UART5 and forward to the + * BMC console for this many ms each time the command is invoked. */ +#define EC_UART_ECHO_MS 60000 + +/* Embedded EC firmware image (generated from src/ec_fw/ec_ram_test.bin by + * generate_inc_file_for_target; no filesystem required). */ +extern const unsigned char ec_fw[]; +extern const size_t ec_fw_len; + +/* Firmware source selection. + * - 0 (default): use the embedded ec_fw[] array (no filesystem needed). + * - 1: open EC_FW_FILE_PATH (default /ro/ec_fw/ec_ram_test.bin) at runtime via + * POSIX open()/read() into a k_malloc buffer, mirroring the verified + * openbmc_zephyr_app implementation (src/ecboot/ecboot.c ec_write_firmware). + * When 1 you must also enable in prj.conf: + * CONFIG_POSIX_API=y, CONFIG_FILE_SYSTEM=y, CONFIG_FILE_SYSTEM_=y + * (e.g. LITTLEFS/EXT2), and mount the volume at the path prefix (e.g. /ro) + * before running ec_boot. The bin is placed there at build time via + * install_config_file() in CMakeLists.txt (see openbmc for reference). */ +#ifndef EC_FW_FROM_FILE +#define EC_FW_FROM_FILE 0 +#endif +#define EC_FW_FILE_PATH "/ro/ec_fw/ec_ram_test.bin" + +/* Bulk-write the embedded ec_fw[] into EC RAM at EC_RAM_LOAD_ADDR. + * Returns 0 on success, negative on failure. */ +int ec_write_firmware(void); + +/* Send PROGRAM_GO to jump EC to the given address. */ +int ec_program_go(uint32_t addr); + +/* Try to put EC into boot mode and complete the handshake exactly once. + * sh is used only for debug echo of EC output after a successful jump. + * Returns 0 on success, negative errno / positive failure code otherwise. + */ +int ec_boot_try_once(const struct shell *sh); + +/* Shell command: attempt EC boot up to EC_BOOT_MAX_TRIES times then stop. */ +int cmd_ec_boot(const struct shell *sh, size_t argc, char **argv); + +/* Standalone listen: continuously poll_in EC UART and forward to the BMC + * console for `ms` milliseconds. Lets you watch EC output (e.g. the tick + * loop) for a fixed window without going through the full boot sequence. */ +int ec_uart_echo(const struct shell *sh, int ms); + +#endif /* __ECBOOT_PORT_H__ */ diff --git a/samples/ec_boot/src/main.c b/samples/ec_boot/src/main.c new file mode 100755 index 0000000..240c90d --- /dev/null +++ b/samples/ec_boot/src/main.c @@ -0,0 +1,36 @@ +/* + * EC boot control sample (cpu1). + * Registers a shell command "ec_boot" that forces the EC into boot/ISP mode + * and waits for the auto-baud handshake, retrying up to 6 times. + * + * Pin/route background: see ecboot_port.h + */ + +#include +#include +#include + +#include "ecboot_port.h" + +LOG_MODULE_REGISTER(ec_boot_sample, LOG_LEVEL_INF); + +SHELL_CMD_REGISTER(ec_boot, NULL, + "force EC into boot mode (retry up to 6 times)", cmd_ec_boot); + +/* Standalone listener: continuously forward EC UART output to the BMC console + * for EC_UART_ECHO_MS (default 60s). Useful to watch the EC tick loop after it + * has been started, without re-running the full boot sequence. */ +static int cmd_ec_listen(const struct shell *sh, size_t argc, char **argv) +{ + ARG_UNUSED(argc); + ARG_UNUSED(argv); + return ec_uart_echo(sh, EC_UART_ECHO_MS); +} +SHELL_CMD_REGISTER(ec_listen, NULL, + "listen EC UART and forward to console for 60s", cmd_ec_listen); + +int main(void) +{ + LOG_INF("EC boot sample ready. Type 'ec_boot' in the shell."); + return 0; +} -- Gitee