Skip to content

feat(ostool-server): add automatic axloader serial binding - #206

Merged
ZR233 merged 14 commits into
mainfrom
codex/axloader-serial-binding
Oct 9, 2026
Merged

ZR233 merged 14 commits into
mainfrom
codex/axloader-serial-binding

Conversation

@ZR233

@ZR233 ZR233 commented Oct 8, 2026 •

Copy link
Copy Markdown
Member

问题

axloader 启动前需要确认本次上电对应的实际串口。服务端此前依赖手工串口配置,且固件上报信息不足、设备重复广播或一次扫描失败时,串口租约和会话恢复边界不够清晰。

实现

  • 将 HTTP Boot 串口协商固定为 v6 流程:axloader 上报本次 serial_id、boot_epoch、就绪状态和实际 UART 参数;服务端先确认串口身份,再通过网络 binding token 放行启动。
  • 增加共享 ostool-serial 管理器,负责候选串口发现、继电器串口排除、定位复用、不可克隆租约移交、读取线程回收和失败后的重新绑定。
  • axloader 模式的 serial 不再要求手工填写;U-Boot 手工串口流程保持不变。
  • Web UI 为 axloader 增加可选的持久化 boot.serial_parameters 覆盖,默认保存为 null。有效优先级为:Web UI 覆盖值 > 本次 axloader 上报值 > 固件无法读取 UART 参数时的 115200 8N1 无流控 默认值。UI 覆盖必须与实际串口输出一致。
  • 物理宿主配置保存时拒绝后端不支持的 Mark/Space 校验和 1.5 stop bits,Web UI 不再提供这些选项;QEMU 继续使用虚拟串口 provider。
  • 设备侧 ready=false、参数缺失、参数非法、宿主不支持,以及管理器 Timeout/DiscoveryFailed/Busy/IO 错误均作为可恢复绑定失败:回复具体错误、发布 Recovering、刷新 60 秒设备等待窗口,等待下一次广播;空 boot_epoch 不写入运行态有效代次,也不进入退休代次,重启后仍会重复返回明确的非法代次错误;只有管理器停止或非法请求等不可恢复错误终止 session。
  • CLI ostool axloader run 在 60 秒总窗口内重新读取设备状态,按每次扫描窗口重新申请绑定,支持设备就绪、身份帧迟到和 boot epoch 变化;设备缺少 UART 参数时使用协议默认值 115200 8N1、无流控;continue 仍只执行 direct 放行。
  • 同步 API 类型、运行态 warning、文档和中英文 README;v5 保留识别与 OTA,v2/v3/v4 保留识别与升级用途,普通启动要求 v6。

验证

  • cargo fmt --all
  • cargo test -p ostool-server --lib --target x86_64-unknown-linux-gnu -- --nocapture:167 passed
  • cargo test -p ostool-server --test axloader_serial --target x86_64-unknown-linux-gnu -- --nocapture:1 passed;覆盖身份帧扫描超时后的 session 恢复、同 epoch 重试、畸形 serial_id/boot_epoch 拒绝(含重启后空代次重复上报)、租约状态保留和 TFTP 文件保留
  • cargo test -p ostool-serial --target x86_64-unknown-linux-gnu -- --nocapture:6 passed
  • cargo test -p ostool --target x86_64-unknown-linux-gnu -- --nocapture:356 passed
  • cargo clippy -p ostool-server -p ostool --target x86_64-unknown-linux-gnu --all-features:通过;本次改动未新增 warning,仅有既存 async_trait 展开的 double_must_use 警告
  • Web UI pnpm test -- --run:8 files / 30 tests passed
  • Web UI pnpm build:通过
  • 之前的 QEMU/PTY、OTA 和 ASUS 实机验证继续有效;本次更新只改变失败恢复和 CLI 等待逻辑。

PR head 95af5ad 的 GitHub Actions 已完成,两个 Quality Check 均通过:

@mai-team-app mai-team-app Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

结论:REQUEST_CHANGES(1 个阻塞项 + 1 个建议项)

变更内容

把 loader 协议升级到 v6(httpboot-protocol 0.5.0),新增 ostool-serial crate(单例 SerialManager actor:枚举并独占打开候选串口、读取 AXLOADER-SERIAL/1 <serial_id> 身份帧、把不可克隆租约整段移交给会话),会话侧新增 SessionSerialRuntime(绑定/确认/撤销/运行态),server 增加 serial_manager SSE 主题、串口运行态 API、继电器排除与保留、device::reconcile() 的串口门禁,CLI 新增 ostool axloader run|continue,Web UI 在 axloader 模式隐藏手动串口并展示实际参数/运行态/管理器计数;OVMF+QEMU/PTY 集成测试与文档同步更新。

影响面

  • 公共契约:DEVICE_PROTOCOL_VERSION 5→6,新增 PREVIOUS_DEVICE_PROTOCOL_VERSION;LoaderAnnouncement/LoaderDeviceStatus 的新增字段都带 serde(default),旧设备 JSON 仍可反序列化。但 POST /api/v1/loaders/poll 现在对 v2/v3/v4 poll 装载器一律返回 serial_protocol_upgrade_required(LoaderPollResponse::Boot 已无任何调用点),旧装载器不再能启动。这是有意且有文档的破坏性变更(docs/api.md、docs/axloader-network-control.md §4.1),但必须与 TGOSKits 固件同批落地,否则 httpboot 板卡无法启动。
  • axloader 会话现在必须先建立串口 WebSocket 才会推送启动(device.rs 的 is_serial_connected() 门禁);CLI(ostool/src/run/httpboot_board.rs 会连接 WS)与 WebUI 已适配,serial: null 的板卡在 create_session/get_serial_status 仍返回可用串口。
  • U-Boot 手动串口流程保留,但现在先经 serial_manager.reserve() 再打开,并在会话结束前用 wait_owner_released() 等待真实归还;继电器端口在发现阶段通过排除集合保护。
  • 变更不是完全隔离的:state.rs/session.rs/ws.rs/transport.rs/virtual_qemu.rs 的串口与释放路径都被改写,SerialLease::Drop 通过 spawn_blocking 关闭 fd 并归还 token 的语义需要保持。

验证

  • 本审查环境缺少 cargo/rustc(PATH 与 ~/.cargo 均无工具链),因此未能本地复现 cargo fmt --all -- --check、cargo clippy --target x86_64-unknown-linux-gnu --all-features、cargo build、cargo test 及 pnpm 单测/构建/E2E。这是环境限制,不作为缺陷记录。
  • CI(head 4a3a66bf):check (stable, x86_64-unknown-linux-gnu) 两个 run(113174481120、113174757739)均 completed/success,mergeable_state: clean;未发现由本 PR 引起的 CI 失败。
  • 已用 git diff --unified=0 origin/main...HEAD 核对行号,两条 inline 评论都落在新增(RIGHT)行上。

历史评论

/pulls/206/reviews、/pulls/206/comments、/issues/206/comments 均为空,没有需要处理或延续的历史意见。

待解决问题

  1. 阻塞 ostool-server/src/device.rs:247:operation 的所有错误(包括可重试的设备侧推送失败)都被交给 serial_runtime.fail(),会终止串口运行时并 request_stop(SerialClosed),会话被释放、已上传的启动文件被清理;这与本 PR 文档“第二次仍冲突则停止本次推送,等待后续设备广播”以及改动前只记 log::warn! 后重试的行为不一致。
  2. 建议 ostool-server/src/serial/runtime.rs:279:用 ?/ensure! 处理单次绑定请求的设备侧条件(ready == false 等),reply 被丢弃、调用方只看到 “serial binding cancelled”,且会话被结束;CLI 对同样条件只是清晰报错。
  3. 残留风险(未阻塞):/api/v1/loaders/poll 无手动/旧协议回退路径;实体板卡、真实 UDP 广播路由与正式 registry 发布的验证只在 PR 文档 §4.3 中声明,本环境未复核。

复核过的正面点

身份帧解码有界(64 字节行 + 溢出丢弃)、SerialLease 不可克隆且 drop 后再归还 token、wait_owner_released 等待真实归还而非 WS 布尔值、SerialHub::attach 在生产者锁内订阅+快照以消除历史/实时边界丢字节、loader.rs 发现循环按 MAC+epoch 记录并 abort 旧 reconcile,这些设计与文档一致。

Powered by deepseek-flash

Comment thread ostool-server/src/device.rs Outdated
Comment thread ostool-server/src/serial/runtime.rs Outdated
@ZR233

ZR233 commented Oct 8, 2026

Copy link
Copy Markdown
Member Author

已按 review 修复并推送 f70b5a1:

  • device::reconcile 对串口绑定/continue/boot 推送的可重试错误只记录 warning 并返回,保留 session 与 SessionSerialRuntime,等待下一次设备广播;不再调用 serial_runtime.fail() 终止会话。
  • SessionSerialRuntime 对 ready=false、缺少串口参数、非法参数和宿主不支持参数通过本次 bind reply 返回具体错误,同时发布 recovering 状态并继续运行 owner task。
  • PTY 集成测试新增未就绪后继续绑定,以及设备拒绝启动后下一次广播重试;回归测试在旧实现下失败(serial binding cancelled),当前实现通过。

本地验证:

  • cargo fmt --all
  • cargo clippy --target x86_64-unknown-linux-gnu --all-features
  • cargo test -p ostool-server --test axloader_serial --target x86_64-unknown-linux-gnu -- --nocapture

@mai-team-app mai-team-app Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

结论:COMMENT(0 个阻塞项 + 1 个建议项)

变更内容

把 loader 协议升级到 v6(httpboot-protocol 0.5.0,DEVICE_PROTOCOL_VERSION = 6,并新增 PREVIOUS_DEVICE_PROTOCOL_VERSION),新增 ostool-serial crate(单例 SerialManager actor:枚举并独占打开候选串口、按 AXLOADER-SERIAL/1 <serial_id> 身份帧匹配、把不可克隆的 SerialLease 整段移交会话),server 侧新增 SessionSerialRuntime、serial_manager SSE 主题、SerialRuntimeStatus API、继电器排除与保留、device::reconcile() 的串口门禁,CLI 新增 ostool axloader run|continue,Web UI 在 axloader 模式隐藏手动串口并展示实际参数/运行态/管理器计数;OVMF+QEMU/PTY 集成测试与三份文档同步更新。

影响面与契约

  • 破坏性协议变更:POST /api/v1/loaders/poll 对 v2/v3/v4 一律返回 serial_protocol_upgrade_required(LoaderPollResponse::Boot 已无调用点),v5 设备只保留识别与 OTA。这是有意且有文档的(docs/api.md、docs/axloader-network-control.md §4.1),但必须与 TGOSKits 的 v6 固件、以及 httpboot-protocol 0.5.0/ostool-serial 0.1.0 的发布同批落地,否则现有 httpboot 板卡无法启动。这是合并顺序要求,不是代码缺陷。
  • 新增字段(LoaderAnnouncement.serial_id/serial_ready、LoaderDeviceStatus.serial、Session.serial_runtime)都带 serde(default),旧 JSON 仍可反序列化。
  • axloader(UefiHttp)会话现在必须先建立串口 WebSocket 才会推送启动(device.rs 的 is_serial_connected() 门禁,serial_ws 在升级前就 try_set_serial_connected());CLI 与 Web UI 已适配,serial: null 仍返回 available: true 与 ws_url。
  • U-Boot 手动串口流程保留(经 serial_manager.reserve() 后打开,并在释放时 wait_owner_released() 等真实归还);继电器端口在发现阶段被排除集合保护,QEMU 串口 provider 改为从电源配置派生(BoardConfig::serial_available()、去掉 QEMU 串口校验)。
  • 变更不是完全隔离的:state.rs/session.rs/ws.rs/transport.rs/virtual_qemu.rs 的串口与释放路径均被改写;SerialHub::attach 在生产者锁内订阅+快照、transport.rs 把 power-on 门禁从 WS 读取移到串口写入,这两处抵消了历史/实时边界丢字节与等待期无法处理 Ping/Close 的问题。

验证

  • 本地环境没有 cargo/rustc/pnpm(仅 node),因此未能复现 cargo fmt --all -- --check、cargo clippy/build/test 与 pnpm 单测/构建/E2E;已用 git diff --unified=0 origin/main...HEAD 核对行号,新文件 runtime.rs 的 hunk 为 @@ -0,0 +1,514 @@,inline 行落在新增(RIGHT)侧。这是环境限制,不作为缺陷记录。
  • CI(head f70b5a1):check run 113195629281 completed/success,113205866973 仍在 in_progress;未发现由本 PR 引起的 CI 失败。

历史评论

存在 1 条 CHANGES_REQUESTED(review 5452466994 @ 4a3a66b,Job 9c469b3e):阻塞项 device.rs:247(把可重试的设备侧推送失败交给 serial_runtime.fail(),导致会话释放、启动文件被清理)和建议项 serial/runtime.rs:279(Command::Bind 用 ? 丢弃 pending reply 并把可上报失败升级为致命错误)。两项技术判断合理,且已在 head f70b5a1 修复,实测证据如下:

  • device.rs:operation 的错误改为 log::warn!(..."waiting for the next device broadcast") 并返回 Err 给 UDP 发现循环;ostool-server/tests/axloader_serial.rs 新增用例断言设备 409 后 serial_runtime 仍可用于下一 epoch 的重试。
  • serial/runtime.rs:新增 reject_bind(),四个拒绝分支向 pending reply 发送真实错误、把 phase 置为 Recovering、记录 error 后 continue,ensure! 已移除。

剩余问题

  1. 建议 ostool-server/src/serial/runtime.rs:285:被拒绝的绑定不会推进等待期限,60 秒后 actor 仍会 bail! 并终止整个会话(细节见 inline 评论)。这是上一轮建议项未闭环的另一半,不阻塞合并,但会让「设备上报 ready = false」这一可恢复状态升级为会话丢失。

残余风险(未阻塞)

  • 期限到期终止会话(60 秒等待、已绑定后的 5 秒恢复)本身是 docs/axloader-serial-ownership.md §2.1/§3.1 记录的行为,本环境无法验证真实板卡上的实际体验。
  • 设备侧 DELETE /api/v1/serial/bindings/{id} 在本仓库只按 200 校验(device.rs 的撤销/清理路径),该状态码契约属于 TGOSKits 固件,未在本 PR 内核对。
  • 未逐行审阅的面:新增集成测试(ostool-server/tests/axloader_serial.rs、qemu_serial.rs、tests/common/serial.rs、ostool-serial/tests/manager.rs)、scripts/test-axloader-local.py 重写与 web UI 测试改动。
  • 已复核并排除的怀疑点:ostool-serial/src/native.rs 用 O_NONBLOCK 打开 tty 后并不会让物理读线程忙等——upstream serialport 的 posix TTYPort::read 先 poll::wait_read_fd(fd, timeout) 再读,20 ms 超时成为轮询周期;SerialLease/候选/定位缓存与租约归还的生命周期各阶段也按文档实现了「取消/超时先关闭 IO 再解除保留」。

同类 PR

repo:drivercraft/ostool is:pr axloader serial 仅命中本 PR 与已合并的历史工作 #121(feat: Add UEFI HTTP boot support),没有重复或竞争的未合并 PR。

Powered by deepseek-flash

Comment thread ostool-server/src/serial/runtime.rs
@ZR233

ZR233 commented Oct 8, 2026

Copy link
Copy Markdown
Member Author

补充修复已推送 50b51bf:将 ostool-server 中两个 Atomic::fetch_update 弃用调用(网络测试计数器、TFTP 清理重试计数器)更新为 try_update,release 构建不再产生对应弃用警告。

验证结果:

  • cargo fmt --all
  • cargo clippy --target x86_64-unknown-linux-gnu --all-features
  • cargo test -p ostool-server --test axloader_serial --target x86_64-unknown-linux-gnu -- --nocapture
  • cargo test -p ostool-server --lib --target x86_64-unknown-linux-gnu
  • cargo build --release --locked -p ostool-server --target x86_64-unknown-linux-gnu
  • 提交 50b51bf 的两组 GitHub Actions Quality Check 均成功:37747672404、37747677703。

@mai-team-app mai-team-app Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

结论:COMMENT(0 个阻塞项 + 2 个建议项)

变更内容

把 loader 设备协议升级到 v6(DEVICE_PROTOCOL_VERSION = 6、新增 PREVIOUS_DEVICE_PROTOCOL_VERSION = 5、httpboot-protocol 0.5.0),axloader 每次上电生成 32 位 serial_id 并上报实际 UART 参数与 boot_epoch;新增 ostool-serial crate(单例 SerialManager actor:枚举并独占打开候选串口、按 AXLOADER-SERIAL/1 <serial_id> 身份帧匹配、把不可克隆的 SerialLease 整段移交会话);server 侧新增 SessionSerialRuntime(绑定/确认/撤销/运行态)、serial_manager SSE 主题、SerialRuntimeStatus API、继电器排除与保留、device::reconcile() 的串口门禁,并把 serial: null 的 axloader 板卡从「无串口」改为「自动绑定」;CLI 新增 ostool axloader run|continue;Web UI 在 axloader 模式隐藏手动串口字段并展示实际参数/运行态/管理器计数。

影响面与契约

  • 破坏性协议变更:POST /api/v1/loaders/poll 对 v2/v3/v4 一律返回 serial_protocol_upgrade_required(LoaderPollResponse::Boot 在 server 中已无任何构造点,v2 的 boot_payload_unsupported 分支被删除),v5 设备只保留识别与 OTA。这是有意且有文档的(docs/api.md、docs/axloader-network-control.md §4.1/§4.2),但必须与 TGOSKits 的 v6 固件、以及 httpboot-protocol 0.5.0/ostool-serial 0.1.0 的发布同批落地,否则现有 httpboot 板卡无法启动。这是合并顺序要求,不作为代码缺陷记录。
  • 新增字段(LoaderAnnouncement.serial_id/serial_ready、LoaderDeviceStatus.serial、Session.serial_runtime、SerialStatusResponse.runtime/manager)都带 serde(default),旧 JSON 仍可反序列化;ostool board run 的 httpboot 路径会先建立串口 WebSocket,与新的 is_serial_connected() 门禁相容。
  • 变更不是完全隔离的:state.rs/session.rs/serial/ws.rs/serial/transport.rs/virtual_qemu.rs 的串口与释放路径都被改写(wait_for_session_tasks_to_stop 改为等待管理器确认租约真实归还、SerialHub::attach 在生产锁内订阅+快照、power-on 门禁从 WS 读取移到串口写入)。已逐条核对这些路径没有引入新的死锁或生命周期漏洞。

验证

  • 本审查环境没有 Rust 工具链与 pnpm(command -v cargo/pnpm 均不存在,~/.cargo 不存在),因此未能本地复现 helper 建议的 cargo fmt/clippy/test 与 pnpm 单测/构建/E2E;这是环境限制,不作为缺陷记录。
  • CI(head 50b51bfe,check run 的 head_sha 与本 Job 固定版本完全一致):113212940947、113212957988 两次 check (stable, x86_64-unknown-linux-gnu) 均 completed/success(run 37747672404、37747677703),覆盖 cargo fmt --all -- --check、clippy、build、单测、webui 单测/构建、Playwright E2E 与 cargo publish --workspace --dry-run --locked。未发现由本 PR 引起的 CI 失败;mergeable_state: blocked 来自既有 review 状态而非 CI。
  • 已用 git diff --unified=0 origin/main...HEAD 核对行号:ostool-server/src/serial/runtime.rs 为新增文件(唯一 hunk @@ -0,0 +1,514 @@),inline 评论落在新增(RIGHT)侧。

历史评论

  • review 5452466994(CHANGES_REQUESTED @ 4a3a66b)阻塞项 device.rs:把可重试的设备侧推送失败交给 serial_runtime.fail()。技术判断成立,已在 f70b5a1 修复:错误改为 log::warn!(... "waiting for the next device broadcast") 并返回给 UDP 循环,ostool-server/tests/axloader_serial.rs 新增「设备 409 后下一次广播重试」用例断言 starts == 3。
  • 同轮建议项 serial/runtime.rs(Command::Bind 用 ? 丢弃 pending reply):已修复,四个拒绝分支改为 reject_bind(),向 pending reply 返回真实错误、置 Recovering 并继续运行。
  • review 5453479862(COMMENTED @ f70b5a1)剩余建议项(被拒绝的绑定不推进 60 秒等待期限):在 head 上仍然成立,见 F-002。
  • 作者在 issue comment 6054699269 / 6055786253 中的说明与代码一致;50b51bf 把两处 Atomic::fetch_update 改为 try_update,经核对语义等价(network_test.rs 只做 map(|_| ())/map_err(|_| ...),state.rs 测试只做 is_err(),都不读取 Err 载荷),不影响行为,且 CI 在 stable 上通过。

待解决问题

  1. 建议(body-only,README 在本次改动范围外,无可用 RIGHT 行) README.md:18/336、README.en.md:18/360 未随本 PR 同步:仍写「v2/v3/v4 旧协议兼容及 v5 设备 HTTP 接口」「v2/v3/v4 poll 仍收到原 httpboot_entry」「服务端仍允许 v2 loader 启动两个新字段均未配置的旧会话,遇到任一新字段则在 poll 阶段明确拒绝」。但 head 上 poll_loader 已删除该分支,任何旧 poll 都返回 serial_protocol_upgrade_required,且 grep -rn "LoaderPollResponse::Boot" --include=*.rs . 只剩 httpboot-protocol 的一个单测。按 /project/repo/AGENTS.md「用户可见的服务器 API/工作流变化需同步 README,且中英文根 README 保持一致」,建议在同一变更内更新这两份 README,明确「旧 poll 仅保留识别与升级、普通启动要求 v6」。
  2. 建议(inline) ostool-server/src/serial/runtime.rs:448-455:reject_bind 不推进 60 秒 deadline,设备持续上报 serial.ready == false 会在 60 秒后把可恢复状态升级为「会话丢失 + 已上传文件被清理」。详细说明见 inline 评论。

残余风险(未阻塞,仅记录)

  • v5 设备(或 v2/v3/v4 poll)在存在活跃会话时会触发 serial_runtime.fail()(device.rs、api/router.rs)并终止会话,SessionStopReason 呈现为 SerialClosed,与「必须升级装载器」的原因不完全对应;运行态 error 里有正确原因,属有意且有文档的升级策略,但界面措辞可读性一般。
  • 实体板卡、生产 UDP/netns 路由、非 Linux 宿主、以及正式 registry 发布后的构建不在本 PR 内且本环境无法复核(PR 文档 §4.3 也声明为本地验收,非远程结论)。
  • 未逐行审阅的面:新增集成测试(ostool-server/tests/axloader_serial.rs、qemu_serial.rs、tests/common/serial.rs、ostool-serial/tests/manager.rs)、scripts/test-axloader-local.py 重写与 webui 测试/e2e 增补;已抽查断言有效(真实 PTY 打开/关闭计数、租约移交、别名排除、定位复用、两次上电参数恢复)。

复核过的正面点

身份帧解码有界(64 字节行 + 溢出丢弃)、SerialLease 不可克隆且 drop 后由 wait_owner_released 等真实归还、Command::Confirm/Release 带 token 防止旧租约影响新主人、SerialHub::attach 在生产者锁内订阅+快照消除历史/实时边界丢字节、loader.rs 发现循环按 MAC+epoch 去重并 abort 旧 reconcile、discovery.rs 的同型号 USB 匹配改为要求唯一命中——这些设计与 docs/axloader-serial-ownership.md 一致,未发现新的正确性、并发或资源泄漏问题。

同类 PR

repo:drivercraft/ostool is:pr axloader serial 只命中本 PR 与已合并的 #121(UEFI HTTP Boot 支持),没有重复或竞争的未合并 PR。

Powered by deepseek-flash

Comment thread ostool-server/src/serial/runtime.rs
@ZR233

ZR233 commented Oct 8, 2026

Copy link
Copy Markdown
Member Author

补充实机验证(不含敏感连接信息):

  • ostool-server 0.8.2 已部署,运行 SHA-256 为 351249c642bc21c232d3e1a64c6310f1856393779f47eb563aa06e5d93fea5d2,重启后 API 健康。
  • ASUS NUC 的 axloader OTA job 902b5390-8584-447a-8795-eff5a5c2ceae 已 succeeded;设备持久状态的 active_sha256、running_sha256 都是 01c35e458c32a7954108088e7cd23dcfe2b8a08adabbc43ea54f684e9e47edfd,trial=false、last_outcome=confirmed。
  • 将 ASUS 板卡配置切为 serial: null 后进行真实 board connect,server 在多个并发板卡 session 下持续保留串口 session,并按下一次广播重试;设备最终上报 ready=false、parameters=null、UART selection failed: NOT_FOUND,因此该硬件的自动串口绑定未通过。
  • 该错误来自 axloader 的 UEFI ConOut/SerialIo 唯一选择链路,server 只按协议报告具体失败;当前实体 BIOS 未提供可选的 UEFI SerialIo/ConOut 串口,需要开启 Serial Console 或补充固件专属 UART 路径后再验收自动绑定。

@ZR233

ZR233 commented Oct 8, 2026

Copy link
Copy Markdown
Member Author

更正实机诊断表述:NOT_FOUND 是 axloader select_console() 的原始 UEFI 状态,当前实现尚未区分 ConOut 变量缺失、没有 SerialIo handle 或设备路径未匹配三种情况;因此不能仅凭该状态断言 BIOS 缺少 SerialIo。QEMU 的 ConOut 串口路径可通过;ASUS 需要先增加固件侧诊断上下文(或开启/选择 Serial Console)后再判断具体路径,server 端不能把宿主 /dev/ttyUSB6 猜测映射为 UEFI handle。

@mai-team-app mai-team-app Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

结论:COMMENT(0 个阻塞项 + 3 个建议项)

本 PR 在 head 84e7848 上仍以单例 ostool-serial 管理器 + v6 设备协议实现 axloader 串口自动绑定;本轮新增的最后一个提交 84e7848 feat(ostool-server): persist axloader serial overrides 把宿主串口参数做成可选持久化覆盖并加入 115200/8N1 回退,其余内容与上一轮一致。未发现阻塞问题。

本轮新增内容(相对 50b51bf)

  • UefiHttpProfile.serial_parameters: Option<AxloaderSerialParameters>(config.rs:721-747)持久化宿主侧覆盖;BoardConfig::validate 与 normalize_boot_config 复用 validate();Web UI 在 axloader 区新增「指定串口参数」及波特率/数据位/校验/停止位/流控,未启用时提交 serial_parameters: null。
  • 会话侧 effective_parameters() 给出优先级「持久化覆盖 → 本次设备上报 → 115200/8N1」,SerialRuntimeStatus 新增 warning(取自设备 serial.error),docs/api.md、docs/admin-ui.md、docs/axloader-serial-ownership.md 同步更新;ostool-server/tests/axloader_serial.rs 把覆盖设为 38400 并断言宿主实际按 38400 配置(覆盖端到端路径),config.rs、runtime.rs、board-form.test.ts、BoardEditor.test.tsx 也有对应单测。

影响面与契约

  • 破坏性协议变更不变:POST /api/v1/loaders/poll 对任何带 boot command 的 poll 返回 serial_protocol_upgrade_required(api/router.rs:813),v2/v3/v4 已不能启动,必须与 TGOSKits v6 固件、httpboot-protocol 0.5.0/ostool-serial 0.1.0 同批落地;新增字段都带 serde(default),旧 JSON 仍可反序列化。
  • 新配置只影响 httpboot 板卡的串口发现参数,不改动 U-Boot/PXE 串口、TFTP、OTA 与会话释放路径(serial 对 httpboot 仍强制为 null),改动可视为对该子系统的局部扩展。

验证

  • CI(head 84e7848,check run 的 head_sha 与本 Job 固定版本完全一致):check (stable, x86_64-unknown-linux-gnu) 两个 run(113265664971、113265688580)均 completed/success;未发现由本 PR 引起的 CI 失败。mergeable_state: blocked 来自本 Job 家族此前的 CHANGES_REQUESTED review,不是 CI。
  • 本审查环境没有 cargo/rustc/pnpm(command -v 均不存在),因此未能本地复现 cargo fmt/clippy/build/test 与 pnpm 单测/构建/E2E,这是环境限制,不作为缺陷记录。
  • 行号核对:git diff --unified=0 origin/main...HEAD 显示 ostool-server/src/serial/runtime.rs 为新增文件(唯一 hunk @@ -0,0 +1,591 @@),ostool-server/src/config.rs 新增 hunk 为 @@ -719,0 +721,100 @@,两条 inline 评论都落在新增(RIGHT)行上。

历史评论

  • review 5452466994(CHANGES_REQUESTED @ 4a3a66b):阻塞项 device.rs 把可重试的设备侧推送失败交给 serial_runtime.fail(),已在 f70b5a1 修复,本轮复核有效(改为 log::warn! 后返回,等待下一次广播,且新增「设备 409 后下一 epoch 重试」用例)。
  • review 5453479862(@ f70b5a1)与 5453945232(@ 50b51bf)的建议项在 head 上仍然成立:reject_bind 不推进 60 秒期限(F-002)、README 未同步(F-001,且本轮新增的 serial_parameters 也未写入 README)。
  • 作者在 issue comments 6054699269 / 6055786253 / 6056454453 / 6056509601 中的说明与代码一致;其实机记录的 ready=false 场景正好落在 F-002 上。

待解决问题(全部非阻塞)

  1. F-001 建议(body-only) README.md:18/336、README.en.md:18/360 仍写「v2/v3/v4 旧协议兼容」「v2/v3/v4 poll 仍收到原 httpboot_entry」「服务端仍允许 v2 loader 启动两个新字段均未配置的旧会话」,但 head 上任何带 boot command 的 poll 都只返回 serial_protocol_upgrade_required,且本 PR 新增并持久化了 boot.serial_parameters 却未写进两份根 README。/project/repo/AGENTS.md 要求用户可见的服务器 API/配置变化同步 README,并保持中英文一致。README 不在本 PR 改动文件内,无法作为 inline 行评论,故记在此处。
  2. F-002 建议(inline,ostool-server/src/serial/runtime.rs:456) reject_bind() 走的可恢复路径不推进 deadline,设备持续上报 ready = false 时 60 秒后会 bail! 并由 attach() 触发 session.request_stop(SerialClosed),终止会话、清理已上传文件,并用 deadline 文案覆盖设备给出的真实原因。
  3. F-003 建议(inline,ostool-server/src/config.rs:739) 持久化覆盖接受 parity = mark/space 与 stop_bits = one_point_five,而非 QEMU 板卡的宿主后端固定拒绝这些组合,保存期无法发现,失败被推迟到运行期并与 F-002 叠加成会话终止;Web UI 下拉仍把这些组合列为可选项。

残余风险(未阻塞,仅记录)

  • 实体板卡自动绑定尚未通过(作者 comment 6056454453 / 6056509601 记录 ready=false、UART selection failed: NOT_FOUND,且无法据此断言 BIOS 缺少 SerialIo),因此实体路径目前只有失败结论;生产 UDP/netns 路由、非 Linux 宿主、registry 正式发布后的构建不在本 PR 内,本环境也无法复核。
  • 未逐行审阅的面:新增/改写的集成测试(ostool-server/tests/axloader_serial.rs、qemu_serial.rs、tests/common/serial.rs、ostool-serial/tests/manager.rs)、scripts/test-axloader-local.py 重写、webui 测试与 e2e 增补;已抽查关键断言(覆盖参数生效、租约移交、relay 别名排除、定位复用、双次上电参数恢复)有效。

同类 PR

repo:drivercraft/ostool is:pr axloader serial 只命中本 PR 与已合并的 #121(UEFI HTTP Boot 支持),没有重复或竞争的未合并 PR。

Powered by deepseek-flash

Comment thread ostool-server/src/serial/runtime.rs
Comment thread ostool-server/src/config.rs
@ZR233

ZR233 commented Oct 9, 2026

Copy link
Copy Markdown
Member Author

已处理当前 review 中记录的全部问题,并推送到 head 97af14f:

  • F-001:同步中英文根 README,明确 v6 普通启动、v5 识别/OTA 和 v2/v3/v4 识别/升级行为;补充 boot.serial_parameters 的实际覆盖规则。
  • F-002:把 deadline 刷新放入 reject_bind(),每次合法设备报告(包括 ready=false、参数缺失和宿主不支持)都会开启新的 60 秒发现窗口,避免可恢复状态终止 session。
  • F-003:物理板配置保存时调用宿主串口能力校验,拒绝 Mark/Space 和 1.5 stop bits;Web UI 已移除对应选项;QEMU 虚拟串口保持独立路径。

本地已通过格式化、配置回归测试、Axloader PTY 集成测试;新的 CI 正在运行。

@mai-team-app mai-team-app Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

结论:COMMENT(0 个阻塞项 + 2 个建议项)

本轮审查 head 97af14f(相对上一轮 84e7848 新增 e1d6862、97af14f)。此前轮次的阻塞项与建议项均已修复并复核,未发现新的阻塞问题;剩余两条建议都集中在「等待窗口语义」这一处。

本轮新增内容(相对 84e7848)

  • e1d6862 fix(ostool-server): close axloader serial review gaps:非 QEMU 板卡在 BoardConfig::validate 中调用 ostool_serial::validate_host_parameters,保存期即拒绝 mark/space/one_point_five(含回归测试 physical_httpboot_rejects_host_unsupported_serial_modes);Web UI 校验/停止位下拉移除这些组合;docs/api.md 同步说明;两份根 README 改写为「v6 串口自动绑定、v5 识别与 OTA、v2/v3/v4 仅保留识别与升级、普通启动返回 serial_protocol_upgrade_required」。
  • 97af14f fix(ostool-server): refresh serial retry deadline on rejection:把等待窗口刷新移入 reject_bind()(新增 deadline: &mut Instant 参数与 SERIAL_BIND_TIMEOUT 常量),四个设备侧拒绝分支(缺 serial、ready = false、参数非法、宿主不支持)都会顺延 60 秒窗口,不再因最初窗口到期而终止会话。

影响面与契约

  • 破坏性协议变更不变:DEVICE_PROTOCOL_VERSION = 6、PREVIOUS_DEVICE_PROTOCOL_VERSION = 5、httpboot-protocol 0.5.0;/api/v1/loaders/poll 对任何带 boot command 的旧 poll 返回 serial_protocol_upgrade_required,v5 只保留识别与 OTA。新增字段(LoaderAnnouncement.serial_id/serial_ready、LoaderDeviceStatus.serial、Session.serial_runtime、SerialStatusResponse.runtime/manager)都带 serde(default),旧 JSON 仍可反序列化;ostool 侧的 SerialStatusResponse 也带 serde(default)。
  • 合并顺序要求(非代码缺陷,但不满足会让现有 httpboot 板卡无法启动):需与 TGOSKits 的 v6 固件、httpboot-protocol 0.5.0 / ostool-serial 0.1.0 的 registry 发布同批落地。
  • 新配置只影响 httpboot 板卡的串口发现:normalize_board_upsert_request 对 httpboot 强制 serial: null,serial_available() 与 QEMU 串口 provider 从电源配置派生。U-Boot/PXE 手动串口、TFTP、OTA 与会话释放路径的改动集中在「经管理器 reserve / wait_owner_released」;state.rs/session.rs/ws.rs/transport.rs 的行为改写已复核,未发现新的死锁、丢字节或租约泄漏(SerialHub::attach 在生产者锁内订阅+快照、transport.rs 把上电门禁从 WS 读取移到串口写入、SerialLease::drop 经 spawn_blocking 关闭 IO 后再归还 token)。

验证

  • CI(head 97af14f,check run 的 head_sha 与本 Job 固定版本完全一致):check (stable, x86_64-unknown-linux-gnu) 两次均 completed/success(run 37869050284、37869057327,job 113622600570、113622624408),覆盖 cargo fmt --all -- --check、clippy、build、单测、webui 单测/构建、Playwright E2E 与 cargo publish --workspace --dry-run --locked。未发现由本 PR 引起的 CI 失败;mergeable_state: blocked 来自此前 CHANGES_REQUESTED review,不是 CI。
  • 本审查环境没有 Rust 工具链与 pnpm(command -v cargo/rustc/pnpm 均不存在,~/.cargo 不存在),因此未能本地复现 cargo fmt/clippy/build/test 与 pnpm 单测/构建/E2E;这是环境限制,不作为缺陷记录。
  • 行号核对:git diff --unified=0 origin/main...HEAD 显示 ostool-server/src/serial/runtime.rs 为新增文件(唯一 hunk @@ -0,0 +1,606 @@),inline 评论落在新增(RIGHT)侧的 runtime.rs:387。

历史评论

  • review 5452466994(CHANGES_REQUESTED @ 4a3a66b)阻塞项 device.rs:把可重试的设备侧推送失败交给 serial_runtime.fail()。判断成立,已在 f70b5a1 修复;本轮复核 head 仍成立——operation 的 Err 只记 log::warn!(... waiting for the next device broadcast) 并返回给 UDP 发现循环。
  • 同轮建议项 serial/runtime.rs(Command::Bind 用 ? 丢弃 pending reply):已修复为 reject_bind(),向 pending reply 发送真实错误并置 Recovering。
  • review 5453479862 / 5453945232 / 5455307370 的建议项「reject_bind 不推进 60 秒期限」:已在 97af14f 修复(reject_bind 直接刷新 *deadline,四个分支都传入 &mut deadline),作者回复 4225772050 / 4225772248 与代码一致。
  • review 5455307370 的 README 未同步项:已在 e1d6862 修复(两份根 README 的中英文段落都改写为 v6/旧 poll 语义,保持一致)。
  • review 5455307370 的「持久化覆盖接受宿主不支持组合」项:已在 e1d6862 修复(保存期拒绝 + Web UI 移除选项 + 文档说明 + 回归测试)。
  • 作者在 issue comments 中的说明与代码一致;本环境无法复核其实机记录。

待解决问题(全部非阻塞)

  1. 建议(inline,ostool-server/src/serial/runtime.rs:387) RuntimeEvent::Opened 仍用 ? 把管理器错误升级为整个会话的终止,而管理器真正给身份帧的窗口是扫描期限 max(5 秒, 组合数 × 2 秒)(单组合约 6 秒,ostool-serial/src/manager.rs 的 scan_deadline),不是 docs §2.1 暗示的 60 秒。设备 ready = true 但宿主在数秒内没看到身份帧时会销毁会话并清理已上传文件,与 97af14f 刚把 reject_bind() 改为可恢复的做法不一致,文档也未描述该窗口。详见 inline 评论。
  2. 建议(body-only) 服务端现在按每个有效设备报告重启 60 秒窗口,但 ostool/src/axloader.rs:109 的 CLI 仍是一次性 deadline: Instant::now() + 60s;docs/axloader-serial-ownership.md §2.1 的「设备等待上限 60 秒」也没有说明窗口刷新规则。同一块持续上报 ready = false 的板子会呈现「服务端一直等 / CLI 60 秒失败」两种行为。建议在 §2.1 写清服务端按报告刷新、管理器按请求扫描窗口 max(5 秒, 组合数 × 2 秒),并让 CLI 在轮询到新状态时刷新期限(或在 CLI 帮助与 docs/axloader-network-control.md 中明确 run 的身份帧上限是 60 秒)。

残余风险(未阻塞,仅记录)

  • v5 设备(或旧 poll)在存在活跃会话时仍会触发 serial_runtime.fail() 并终止会话,SessionStopReason 呈现为 SerialClosed,与「必须升级装载器」的原因不完全对应;运行态 error 里有正确原因,属有意且有文档的升级策略。
  • 实体板卡的自动绑定目前只有失败结论(作者 comment 记录 ready = false、UART selection failed: NOT_FOUND),生产 UDP/netns 路由、非 Linux 宿主、正式 registry 发布后的构建不在本 PR 内,本环境也无法复核。
  • 未逐行审阅的面:新增/改写的集成测试(ostool-server/tests/axloader_serial.rs、qemu_serial.rs、tests/common/serial.rs、ostool-serial/tests/manager.rs)、scripts/test-axloader-local.py 重写、webui 测试与 e2e 增补;已抽查关键断言(覆盖参数 38400 实际生效、租约移交、relay 别名排除、定位复用、双次上电参数恢复、ready=false 后恢复)有效。

复核过的正面点

身份帧解码有界(64 字节行 + 溢出丢弃)、SerialLease 不可克隆且 drop 后再归还 token、Command::Confirm/Release 带 token 防止旧租约影响新主人、wait_owner_released 等真实归还而非 WS 布尔值、loader.rs 发现循环按 MAC+epoch 去重并 abort 旧 reconcile、discovery.rs 同型号 USB 匹配要求唯一命中、native.rs 的 unsafe 均有 SAFETY 说明且 RestoreSettings 在读取线程 join 后再恢复 termios——这些设计与 docs/axloader-serial-ownership.md 一致。

同类 PR

用 repo:drivercraft/ostool is:pr axloader serial 搜索只命中本 PR 与已合并的 #121(UEFI HTTP Boot 支持),没有重复或竞争的未合并 PR。

Powered by deepseek-flash

Comment thread ostool-server/src/serial/runtime.rs Outdated
@ZR233

ZR233 commented Oct 9, 2026

Copy link
Copy Markdown
Member Author

已处理新 review,并推送 532c030:

  • RuntimeEvent::Opened 对 Timeout、DiscoveryFailed、Busy 和 IO 绑定错误进入 Recovering,回复具体错误、释放失败定位并等待下一次广播;不可恢复 manager 错误仍终止。
  • CLI ostool axloader run 在 60 秒总窗口内轮询 /status,每次扫描失败后重新按当前 epoch、serial_id 和参数绑定。
  • docs/axloader-serial-ownership.md 已说明服务端按有效报告刷新 60 秒窗口,管理器每次扫描使用 max(5 秒, 参数组合数 × 2 秒),CLI 使用相同总窗口和重试规则。

本地 server、ostool、ostool-serial 测试和 Clippy 已通过,新的 CI 正在运行。

@mai-team-app mai-team-app Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

结论:COMMENT(0 个阻塞项 + 2 个建议项)

本轮审查 head 532c030(相对上一轮 97af14f 只改 3 个文件:docs/axloader-serial-ownership.md、ostool-server/src/serial/runtime.rs、ostool/src/axloader.rs)。上一轮的两条建议项都已闭环,未发现新的阻塞问题;剩余两条建议都属于「运行态信息 / 测试覆盖」范畴。

本轮新增内容

  • RuntimeEvent::Opened 不再用 ? 把管理器错误升级为会话终止:Timeout/DiscoveryFailed/Busy/Io 进入新的可恢复分支(current.take()、serial_manager.invalidate()、清空 verifying_until、reject_bind(Some(reply)) 回复具体错误并刷新 60 秒窗口),InvalidRequest/Stopped 仍终止会话。
  • reject_bind() 的 reply 改为 Option(供 Opened 分支在 reply 已被消费时复用),并新增 port/binding_id 清空与 warning(取自设备 serial.error)。
  • CLI ostool axloader run:改为先 fetch_status 再进循环;ready = false 时每 250 ms 重读状态;manager.bind 返回 Timeout|DiscoveryFailed|Busy|Io 时重读状态并重新申请绑定;单次请求期限 min(剩余, 5 秒)、总窗口 60 秒(CLI_BIND_TIMEOUT)。
  • 文档 §2.1/§3 补上「服务端按有效报告刷新窗口」「管理器单次扫描窗口 max(5 秒, 组合数 × 2 秒)」「CLI 60 秒总窗口与重试条件」。

影响面与契约

  • 只涉及 axloader 自动绑定与 CLI 等待逻辑;U-Boot/PXE 手动串口、TFTP、OTA、会话释放路径未改动;协议版本与 serde(default) 兼容结论与上一轮一致。
  • 合并顺序要求不变(非代码缺陷):DEVICE_PROTOCOL_VERSION = 6 需与 TGOSKits v6 固件、httpboot-protocol 0.5.0 / ostool-serial 0.1.0 的 registry 发布同批落地。

验证

  • CI(head 532c030,check run 的 head_sha 与本 Job 固定版本一致):两次 check (stable, x86_64-unknown-linux-gnu) 均 completed/success(run 37871714991、37871708641,job 113631086723、113631067357),覆盖 fmt/clippy/build/单测、webui 单测与构建、Playwright E2E 与 cargo publish --workspace --dry-run --locked;未发现由本 PR 引起的 CI 失败。mergeable_state: blocked 来自此前 CHANGES_REQUESTED review,不是 CI。
  • 本审查环境没有 Rust 工具链与 pnpm(command -v cargo/rustc/pnpm 均缺失、~/.cargo 不存在),因此未能本地复现 cargo fmt/clippy/test 与 pnpm 测试;这是环境限制,不作为缺陷记录。
  • 行号核对:git diff --unified=0 origin/main...HEAD 显示 ostool-server/src/serial/runtime.rs(唯一 hunk @@ -0,0 +1,648 @@)与 ostool/src/axloader.rs(唯一 hunk @@ -0,0 +1,327 @@)在本 PR 内都是新增文件,两条 inline 评论都落在新增(RIGHT)行上。

历史评论

  • review 5452466994 / 5453479862 / 5453945232 / 5455307370 / 5464758765 的全部条目已在前几轮或 97af14f 闭环:device.rs 的可重试设备侧推送错误不再 fail()、reject_bind 刷新 60 秒窗口、物理板保存期拒绝 mark/space 与 1.5 stop bits、两份根 README 同步。
  • 上一轮 job 59e5fd25 的两条建议:① Opened 的 ? 会把约 5~6 秒扫描超时升级为会话终止 → 本轮已修复(见 F-001 的核对,代码确为可恢复分支);② CLI 一次性 60 秒期限 → 已改为轮询+重试且文档写明。作者 comment 6072619291 的说明与代码一致。
  • 作者 issue comments 6054699269 / 6055786253 / 6056454453 / 6056509601 / 6072281053 / 6072619291 的说明均与 head 代码一致;实机自动绑定仍未通过(ready=false、UART selection failed: NOT_FOUND)属固件侧结论,不在本 PR 判定范围。

待解决问题(均为建议,非阻塞)

  1. 建议(inline,ostool-server/src/serial/runtime.rs:395) 新增的可恢复分支没有回归测试:本轮提交没有任何测试变更,而该分支正是上一轮建议项与更早阻塞项的同一类语义(可恢复失败不得结束会话)。
  2. 建议(inline,ostool-server/src/serial/runtime.rs:516) reject_bind() 无条件清空 port/binding_id,会把仍被会话持有并正在使用的实时租约信息一起抹掉,并让 device.rs 的令牌一致性比较失去区分能力。

残余风险(未阻塞,仅记录)

  • 60 秒窗口现在只在「没有后续设备报告」时到期:设备持续广播但始终没有身份帧时,会话与板卡租约、候选串口扫描会一直重试(文档 §2.1 已写明刷新语义,停止条件只能由操作者取消会话)。
  • CLI 与服务端对「status.serial 缺失」「ready = true 但 parameters 缺失/宿主不支持」仍不对称:CLI 在第一条报告上即失败,服务端为可恢复;文档 §3 只列举了 CLI 重试的三类条件。
  • 实体板卡自动绑定目前只有失败结论;生产 UDP/netns 路由、非 Linux 宿主、正式 registry 发布后的构建不在本 PR 内,本环境无法复核。
  • 未逐行审阅的面:ostool-server/tests/axloader_serial.rs、qemu_serial.rs、tests/common/serial.rs、ostool-serial/tests/manager.rs、scripts/test-axloader-local.py 重写与 webui 测试/e2e 增补(已抽查关键断言:覆盖参数 38400 生效、租约移交、relay 别名排除、定位复用、双次上电参数恢复、ready=false 后恢复)。

同类 PR

repo:drivercraft/ostool is:pr axloader serial 只命中本 PR 与已合并的 #121(UEFI HTTP Boot 支持),没有重复或竞争的未合并 PR。

Powered by deepseek-flash

Comment thread ostool-server/src/serial/runtime.rs
Comment thread ostool-server/src/serial/runtime.rs Outdated

@mai-team-app mai-team-app Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

结论:COMMENT(0 个阻塞项 + 1 个建议项)

本轮审查 head 77061dd(相对上一轮 532c030 新增 654026b、700ab63、a854be4、77061dd)。上一轮的两条建议项都已在 head 上闭环,未发现新的阻塞问题;剩余一条建议属于「设备侧报告的校验归类」范畴。

本轮新增内容(相对 532c030)

  • 700ab63 fix(ostool-server): keep active binding state by epoch:reject_bind() 新增 active: ActiveBindingStatus 参数,引入 active_binding_status(lease, current, boot_epoch),在仍持有实时租约时继续发布 port 与同一 boot epoch 的 binding_id,只有没有实时租约时才清空;docs/api.md、docs/axloader-serial-ownership.md §3 同步说明 Recovering 可能保留待重新验证的租约。
  • 654026b/a854be4:ostool-server/tests/axloader_serial.rs 增加「身份帧扫描超时后不终止会话(Recovering + 仍可重试同一 epoch)」与「设备上报 ready=false 时保留实时租约端口/令牌、且已上传 kernel.elf 仍在」的断言,正好覆盖上一轮的回归测试缺口。

影响面与契约

  • 破坏性协议变更不变:DEVICE_PROTOCOL_VERSION = 6、PREVIOUS_DEVICE_PROTOCOL_VERSION = 5、httpboot-protocol 0.5.0;/api/v1/loaders/poll 对任何带 boot command 的旧 poll 返回 serial_protocol_upgrade_required,v5 只保留识别与 OTA。新增字段(LoaderAnnouncement.serial_id/serial_ready、LoaderDeviceStatus.serial、Session.serial_runtime、SerialStatusResponse.runtime/manager)都带 serde(default),旧 JSON 仍可反序列化。
  • 本 PR 不是完全隔离的改动:state.rs/session.rs/serial/ws.rs/serial/transport.rs/virtual_qemu.rs 的串口与释放路径都被改写(wait_for_session_tasks_to_stop 改为等管理器确认租约真实归还、SerialHub::attach 在生产锁内订阅+快照、power-on 门禁从 WS 读取移到串口写入、PhysicalSerial 迁入 ostool-serial);U-Boot/PXE 手动串口、TFTP、OTA 的语义保持,仅改为经 serial_manager.reserve()/wait_owner_released() 协调。
  • 合并顺序要求(非代码缺陷):需与 TGOSKits 的 v6 固件、httpboot-protocol 0.5.0 / ostool-serial 0.1.0 的 registry 发布同批落地,否则现有 httpboot 板卡无法启动。

验证

  • 本审查环境没有 Rust 工具链与 pnpm(command -v cargo/rustc/pnpm 均不存在,target/ 仅有占位目录),因此未能本地复现 helper 建议的 cargo fmt --check、cargo clippy、cargo test 与 pnpm 单测/构建/E2E;这是环境限制,不作为缺陷记录。
  • CI(head 77061dd,check run 的 head_sha 与本 Job 固定版本完全一致):两次 check (stable, x86_64-unknown-linux-gnu) 均 completed/success(job 113648864770/run 37877348607、job 113648852836/run 37877344990),覆盖 fmt、clippy、build、cargo test(含本 PR 新增的 ostool-server/tests/axloader_serial.rs、被 #[ignore] 的 qemu_serial.rs 除外)、webui 单测/构建、Playwright E2E 与 cargo publish --workspace --dry-run --locked。未发现由本 PR 引起的 CI 失败;mergeable_state: blocked 来自此前 CHANGES_REQUESTED review,不是 CI。
  • 行号核对:git diff --unified=0 origin/main...HEAD 显示 ostool-server/src/serial/runtime.rs 为新增文件(@@ -0,0 +1,702 @@),inline 评论落在新增(RIGHT)侧的 374 行。

历史评论

  • review 5452466994(CHANGES_REQUESTED @ 4a3a66b)阻塞项 device.rs:可重试的设备侧推送失败被交给 serial_runtime.fail() 终止会话 → 已在 f70b5a1 修复,head 上 operation 的 Err 只 log::warn!(... waiting for the next device broadcast) 并返回给 UDP 发现循环,本轮复核成立。
  • 同轮建议项(Command::Bind 用 ? 丢弃 pending reply)→ 已由 reject_bind() 修复;review 5453479862 / 5453945232 / 5455307370 的「reject_bind 不推进 60 秒期限」→ 已在 97af14f 修复(*deadline = Instant::now() + SERIAL_BIND_TIMEOUT);README 未同步与「持久化覆盖接受 mark/space/1.5 stop bits」→ 已在 e1d6862 修复;review 5464758765 的「Opened 的 ? 把扫描超时升级为会话终止」「CLI 一次性期限」→ 已在 532c030 修复。上述各项均已用 head 代码复核。
  • review 5464944711(job 5b09d667 @ 532c030)的两条建议:①可恢复 Opened 分支缺少回归测试 → 本轮测试补齐;②reject_bind() 无条件清空 port/binding_id 会抹掉仍在使用的实时租约信息 → 本轮 active_binding_status() 按 epoch 保留并配文档。作者 issue comments(6072281053 / 6072619291 等)与 head 代码一致;其实机 ready=false、UART selection failed: NOT_FOUND 的记录属固件侧诊断,不在本 PR 判定范围。

待解决问题(非阻塞)

  1. 建议(inline,ostool-server/src/serial/runtime.rs:374) 设备上报的 serial_id/boot_epoch 没有在服务端校验就进入绑定,管理器返回的 BindError::InvalidRequest 被当作不可恢复错误,会让会话进入 releasing 并清理已上传文件;这与本 PR 对其它设备侧报告(ready=false、缺参数、非法参数、宿主不支持)一律走 reject_bind() 保留会话的做法不一致。详见 inline 评论。

残余风险(未阻塞,仅记录)

  • v5 设备(或 v2/v3/v4 poll)在存在活跃会话时会经 serial_runtime.fail()(device.rs、api/router.rs)终止会话,SessionStopReason 呈现为 SerialClosed;运行态 error 里有正确原因,属有意且有文档的升级策略(docs/api.md、docs/axloader-network-control.md §4.1),但界面措辞与「必须升级装载器」的原因不完全对应。
  • 60 秒等待窗口只在「没有后续有效设备报告」时到期:设备持续广播但始终没有身份帧时,会话、板卡租约与候选串口扫描会持续重试,停止条件只能由操作者取消会话(docs/axloader-serial-ownership.md §2.1 已写明刷新语义)。服务端按报告刷新窗口,而 CLI 是固定 60 秒总窗口,两者对持续 ready=false 的设备会呈现不同行为。
  • 实体板卡自动绑定目前只有失败结论;生产 UDP/netns 路由、非 Linux 宿主、正式 registry 发布后的构建不在本 PR 内,本环境无法复核。
  • 未逐行审阅的面:ostool-server/scripts/test-axloader-local.py 重写、ostool-server/webui 的 e2e/单测增补;已抽查关键断言(覆盖参数 38400 实际生效、租约移交、relay 别名排除、定位复用、双次上电参数恢复、ready=false 后恢复并保留会话文件)有效。

复核过的正面点

身份帧解码有界(64 字节行 + 溢出丢弃)、SerialLease 不可克隆且 Drop 经 spawn_blocking 关闭 IO 后再归还 token、Command::Confirm/Release 带 token 防止旧租约影响新主人、wait_owner_released 等真实归还而非 WS 布尔值、loader.rs 发现循环按 MAC+epoch 去重并 abort 旧 reconcile、SerialHub::attach 在生产者锁内订阅+快照消除历史/实时边界丢字节、discovery.rs 同型号 USB 匹配要求唯一命中、ostool-serial/src/native.rs 的 unsafe 均有 SAFETY 说明且 RestoreSettings 在接收线程 join 后才恢复 termios——这些设计与 docs/axloader-serial-ownership.md 一致。

同类 PR

repo:drivercraft/ostool is:pr axloader serial 只命中本 PR 与已合并的 #121(UEFI HTTP Boot 支持),没有重复或竞争的未合并 PR。

Powered by deepseek-flash

Comment thread ostool-server/src/serial/runtime.rs

@mai-team-app mai-team-app Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

结论:COMMENT(0 个阻塞项 + 2 个建议项)

本轮审查 head c66f8a7,相对上一轮 77061dd 只新增一个提交 c66f8a7 fix(ostool-server): recover from invalid loader identity(ostool-server/src/serial/runtime.rs +32、ostool-server/tests/axloader_serial.rs +60)。上一轮的建议项已闭环,未发现阻塞问题;剩余两条建议属「运行态诊断」与「CLI/服务端行为一致性」范畴。

上一轮建议项复核(review 5465310063 @ 77061dd)

唯一建议项「设备上报的 serial_id/boot_epoch 未校验就进入绑定,管理器返回 BindError::InvalidRequest 被当作不可恢复错误并终止会话」已在 c66f8a7 修复且复核有效:

  • runtime.rs:280-295 在 power_wait = true 之后、读取 device.serial 之前先拒绝空 boot_epoch;runtime.rs:312-327 拒绝非 32 位小写十六进制的 serial_id,两者都复用 reject_bind()(发布 Recovering、回真实错误、刷新 60 秒窗口)。
  • 我逐条核对了 ostool-serial/src/lib.rs:110-118 的 InvalidRequest 三个前置条件(空 boot_epoch、!valid_serial_id、parameters.validate() 失败):前两项现在被服务端提前覆盖,第三项由 runtime.rs:349-354 的 parameters.validate() 覆盖(BindRequest.parameters 即同一组已校验参数)。因此 RuntimeEvent::Opened 的 Err(error) => return Err(error.into()) 现在只剩管理器 Stopped(以及理论上不可达的 InvalidRequest)会终止会话,与 PR 的失败恢复策略一致。
  • httpboot_protocol::valid_serial_id 与 ostool-serial 内部校验是同一实现(httpboot-protocol/src/serial.rs:118),无规则漂移。
  • 新增测试用真实 PTY/HTTP/WebSocket 断言两个畸形报告都返回具体错误、phase 为 Recovering、!session.is_stop_requested(),随后同一 epoch 仍能完成绑定;随后并入原有两次上电循环,未破坏既有断言,覆盖是有效的。

验证

  • CI(head c66f8a760458a9cee11ec7033449eaf065fb3e7c):check (stable, x86_64-unknown-linux-gnu) 两次均 completed/success(check run 113656975486、113656962130),覆盖 cargo fmt --all -- --check、clippy、build、单测(含 ostool-server/tests/axloader_serial.rs;qemu_serial.rs 为 #[ignore])、webui 单测/构建、Playwright E2E 与 cargo publish --workspace --dry-run --locked。未发现由本 PR 引起的 CI 失败;mergeable_state: blocked 来自此前 CHANGES_REQUESTED review,不是 CI。
  • 本地未复现 Rust/前端检查:本环境没有 cargo/rustc(~/.cargo 不存在),也没有 pnpm(只有 node v24.14.1),因此 helper 建议的 cargo fmt --check、cargo clippy --all-features -- -D warnings、cargo test(httpboot-protocol / ostool / ostool-serial / ostool-server)与 pnpm 单测/构建/E2E 均未能运行;按 /project/repo/AGENTS.md 的要求在此明确说明未运行的检查,这是环境限制,不作为缺陷记录。
  • 行号核对:git diff --unified=0 origin/main...HEAD 显示 ostool-server/src/serial/runtime.rs(@@ -0,0 +1,734 @@)与 ostool/src/axloader.rs(@@ -0,0 +1,327 @@)在本 PR 内均为新增文件,两条 inline 评论都落在新增(RIGHT)侧的真实行上。

影响面与契约

  • 破坏性协议变更不变:DEVICE_PROTOCOL_VERSION = 6、PREVIOUS_DEVICE_PROTOCOL_VERSION = 5、httpboot-protocol 0.5.0;/api/v1/loaders/poll 对任何带 boot command 的旧 poll 返回 serial_protocol_upgrade_required,v5 只保留识别与 OTA。LoaderAnnouncement.serial_id/serial_ready、LoaderDeviceStatus.serial、Session.serial_runtime、SerialStatusResponse.runtime/manager 都带 serde(default),旧 JSON 仍可反序列化。
  • 合并顺序要求(非代码缺陷):需与 TGOSKits 的 v6 固件、httpboot-protocol 0.5.0 / ostool-serial 0.1.0 的 registry 发布同批落地,否则现有 httpboot 板卡无法启动。
  • 本 PR 不是隔离改动:state.rs/session.rs/serial/ws.rs/serial/transport.rs/virtual_qemu.rs 的串口与释放路径都被改写。本轮复核未发现新的死锁、丢字节或租约泄漏;顺带确认 transport.rs 的「输出队列 try_send 满即结束会话」是 base 已有且有注释说明的既有语义(origin/main:ostool-server/src/serial/transport.rs:16),不是本 PR 引入的回归。

待解决问题(均为建议,非阻塞)

  1. F-001(inline,ostool-server/src/serial/runtime.rs:280) 新增的空 boot_epoch 可恢复分支会把空串写进公开运行态并被 restart() 退休,导致后续同类报告被静默忽略、界面保留过期错误。
  2. F-002(inline,ostool/src/axloader.rs:107) CLI 对「ready = true 但 parameters 缺失」直接失败退出,而服务端按「覆盖值 > 上报值 > 115200 8N1」回退并继续绑定;CLI 既无覆盖入口也无默认回退,PR 文档的优先级链在 CLI 上未落地。

残余风险(未阻塞,仅记录)

  • 实体板卡的自动绑定目前只有失败结论(作者 comment 记录 ready = false、UART selection failed: NOT_FOUND),属固件侧诊断,不在本 PR 判定范围。
  • v5 设备或旧 poll 在存在活跃会话时会经 serial_runtime.fail()(device.rs、api/router.rs)终止会话,SessionStopReason 呈现为 SerialClosed,与「必须升级装载器」的原因不完全对应(运行态 error 有正确原因),属有意且有文档的升级策略。
  • 60 秒窗口只在「没有后续有效设备报告」时到期;设备持续广播但始终没有身份帧时会持续重试,停止条件只能由操作者取消会话(docs/axloader-serial-ownership.md §2.1 已写明刷新语义)。
  • 未逐行审阅的面:ostool-server/tests/common/serial.rs、ostool-serial/tests/manager.rs、ostool-server/tests/qemu_serial.rs(#[ignore],仅由重写后的 scripts/test-axloader-local.py 手动触发)与 webui e2e 增补;已抽查关键断言(覆盖参数 38400 实际生效、租约移交、relay 别名排除、定位复用、双次上电参数恢复、畸形身份后会话保留且文件仍在)有效。生产 UDP/netns 路由、非 Linux 宿主与正式 registry 发布后的构建不在本 PR 内,本环境无法复核。

同类 PR

用 repo:drivercraft/ostool is:pr axloader serial 搜索只命中本 PR 与已合并的 #121(UEFI HTTP Boot 支持),没有重复或竞争的未合并 PR。

Powered by deepseek-flash

Comment thread ostool-server/src/serial/runtime.rs
Comment thread ostool/src/axloader.rs Outdated

@mai-team-app mai-team-app Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

结论:APPROVE(0 个阻塞项)

变更内容

把 loader 设备协议升级到 v6(DEVICE_PROTOCOL_VERSION = 6、新增 PREVIOUS_DEVICE_PROTOCOL_VERSION = 5、httpboot-protocol 0.5.0),axloader 每次上电上报 serial_id/boot_epoch/就绪状态/实际 UART 参数;新增 ostool-serial crate(单例 SerialManager actor:枚举并独占打开候选串口、按 AXLOADER-SERIAL/1 <serial_id> 身份帧匹配、把不可克隆的 SerialLease 整段移交会话);server 侧新增 SessionSerialRuntime(绑定/确认/撤销/运行态)、serial_manager SSE 主题、SerialRuntimeStatus API、继电器排除与保留、device::reconcile() 的串口门禁;CLI 新增 ostool axloader run|continue;Web UI 在 axloader 模式隐藏手动串口并展示实际参数/运行态/管理器计数。

影响面与契约

  • 破坏性协议变更:/api/v1/loaders/poll 对任何带 boot command 的旧 poll 返回 serial_protocol_upgrade_required,v5 设备只保留识别与 OTA。已确认 v4 的 OTA 决策分支(api/router.rs 的 PROTOCOL_VERSION == 4 分支)位于新 Reject 之前,因此文档 §4.2 的「旧设备仍可识别和升级」与代码一致。
  • 合并顺序要求(非代码缺陷):需与 TGOSKits 的 v6 固件、httpboot-protocol 0.5.0 / ostool-serial 0.1.0 的 registry 发布同批落地;否则现有 v5 板卡不能自动启动,且其活跃会话会经 serial_runtime.fail() 以 SerialClosed 结束(运行态 error 保留正确原因,属有文档的升级策略)。
  • 新增字段(LoaderAnnouncement.serial_id/serial_ready、LoaderDeviceStatus.serial、Session.serial_runtime、SerialStatusResponse.runtime/manager)都带 serde(default),旧 JSON 仍可反序列化。U-Boot/PXE 手动串口保留,但改为先 serial_manager.reserve() 再打开,并在释放时用 wait_owner_released() 等待真实归还。
  • 非隔离改动:state.rs/session.rs/serial/ws.rs/serial/transport.rs/virtual_qemu.rs 的串口与释放路径被改写。本轮复核未发现新的死锁、丢字节或租约泄漏:SerialHub::attach 在生产锁内订阅+快照,SerialLease::drop 经 spawn_blocking 关闭 IO 并收到 reader 退出后再归还 token,wait_for_session_tasks_to_stop 在 WS 断开后等管理器确认真实归还且有 2 秒兜底与整体重试。

验证

  • 本审查环境没有 Rust 工具链与 pnpm(command -v cargo/rustc/pnpm 均缺失、~/.cargo 不存在),因此 helper 建议的 cargo fmt --check、cargo clippy --all-features -- -D warnings、cargo test(httpboot-protocol / ostool / ostool-serial / ostool-server)与 pnpm 单测/构建/E2E 均未能在本地复现,按 /project/repo/AGENTS.md 的要求在此明确说明。
  • CI(head 95af5ada,与固定版本一致):check (stable, x86_64-unknown-linux-gnu) 两次均 completed/success(job 113666862726 / run 37883052411、job 113666851978 / run 37883049048),覆盖 fmt、clippy、build、cargo test(含新增的 ostool-server/tests/axloader_serial.rs)、webui 单测/构建、Playwright E2E 与 cargo publish --workspace --dry-run --locked。未发现由本 PR 引起的 CI 失败;mergeable_state: blocked 来自此前 review 状态,不是 CI。
  • 行号核对:本 PR 内 ostool-server/src/serial/runtime.rs 与 ostool/src/axloader.rs 均为新增文件(唯一 hunk @@ -0,0 +1,736 @@ / @@ -0,0 +1,345 @@);本轮无 inline 评论。

历史评论

  • review 5465500614(job 7ace838f @ c66f8a7)的两条建议项均已在 head 95af5ada 修复并复核有效:
    1. 空 boot_epoch 不再写入运行态有效代次、也不进入退休代次(runtime.rs:86-88 只退休非空 epoch,583-585 用 !device.boot_epoch.is_empty() 保护 status.boot_epoch);tests/axloader_serial.rs 新增「restart(true) 后再次上报空 epoch 仍返回 invalid device boot epoch 且 !session.is_stop_requested()」断言,旧实现下该断言会得到 waiting for a new boot epoch,确实能拦住回归。
    2. CLI 改为 effective_parameters(reported) = reported.unwrap_or_default()(协议默认 115200 8N1、无流控),新增单测 missing_firmware_parameters_use_protocol_defaults;docs/axloader-serial-ownership.md §3.2 同步说明「CLI 使用本次上报参数、缺失时用默认值,Web UI 覆盖只属于 server session」。
  • 更早各轮条目(device.rs 可重试推送错误不再 fail()、reject_bind 刷新 60 秒窗口、Opened 的 Timeout/DiscoveryFailed/Busy/IO 走可恢复路径、Recovering 保留同 epoch 端口与令牌、保存期拒绝 mark/space/1.5 stop bits、两份根 README 同步、BindError::InvalidRequest 可恢复)在 head 上逐条复核仍为已修复。

待解决问题

无阻塞项,也没有需要修改的建议项;本轮不产生 inline 评论。残余风险(不阻塞,仅记录):

  • 实体板卡自动绑定目前只有失败结论(作者记录 ready=false、UART selection failed: NOT_FOUND,属固件侧 ConOut/SerialIo 选择诊断);生产 UDP/netns 路由、非 Linux 宿主与正式 registry 发布后的构建不在本 PR 内,本环境也无法复核(docs/axloader-serial-ownership.md §4.3 亦声明为本地验收)。
  • 60 秒窗口只在「没有后续有效设备报告」时到期:设备持续广播但始终没有身份帧时会持续重试,停止条件只能由操作者取消会话(§2.1 已写明刷新语义)。
  • CLI 与服务端对「宿主不支持或非法的上报参数」仍不对称(CLI 立即报错退出,服务端按可恢复处理);§3.2 只列举了 CLI 的三类重试条件,属可接受且有文档的语义差异。
  • 未逐行审阅的面:ostool-server/scripts/test-axloader-local.py 重写、webui 单测/e2e 增补与部分新增集成测试(已抽查关键断言有效:覆盖参数 38400 实际生效、租约移交、relay 别名排除、定位复用、双次上电参数恢复、畸形身份/空 epoch 后会话保留且 TFTP 文件仍在)。

同类 PR

repo:drivercraft/ostool is:pr axloader 只命中本 PR 与历史已合并工作(#121 UEFI HTTP Boot、#179/#194/#198 axloader 协议演进),没有重复或竞争的未合并 PR。

Powered by deepseek-flash

@ZR233
ZR233 merged commit 0331dfa into main Oct 9, 2026
2 checks passed
@ZR233
ZR233 deleted the codex/axloader-serial-binding branch October 9, 2026 04:36
This was referenced Oct 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant