From b2e043dfc9cb8a7ed70d98eef74f3cefbd6cf4d4 Mon Sep 17 00:00:00 2001 From: szy Date: Fri, 22 May 2026 11:14:45 +0800 Subject: [PATCH] Add board-specific U-Boot load address configuration to ostool-server --- ostool-server/src/api/router.rs | 37 ++++++++- ostool-server/src/config.rs | 33 ++++++++ ostool-server/webui/src/types/api.ts | 3 + .../webui/src/views/BoardEditorView.test.ts | 24 ++++++ .../webui/src/views/BoardEditorView.vue | 34 +++++++- .../webui/src/views/SessionsView.test.ts | 3 + ostool/src/board/client.rs | 14 +++- ostool/src/run/uboot.rs | 81 +++++++++++++------ 8 files changed, 201 insertions(+), 28 deletions(-) diff --git a/ostool-server/src/api/router.rs b/ostool-server/src/api/router.rs index 49fcdaa..e22a937 100644 --- a/ostool-server/src/api/router.rs +++ b/ostool-server/src/api/router.rs @@ -535,6 +535,9 @@ fn normalize_boot_config(boot: &mut BootConfig) -> Result<(), ApiError> { match boot { BootConfig::Uboot(profile) => { normalize_optional_string(&mut profile.dtb_name); + normalize_optional_string(&mut profile.kernel_load_addr); + normalize_optional_string(&mut profile.fit_load_addr); + normalize_optional_string(&mut profile.bootm_addr); normalize_optional_string(&mut profile.board_ip); normalize_optional_string(&mut profile.server_ip); normalize_optional_string(&mut profile.netmask); @@ -1647,7 +1650,8 @@ mod tests { }; use crate::{ api::models::{ - BoardPowerStatusResponse, BoardRuntimeStatusResponse, SessionDetailResponse, + AdminBoardUpsertRequest, BoardPowerStatusResponse, BoardRuntimeStatusResponse, + SessionDetailResponse, }, build_app_state, config::{ @@ -3301,6 +3305,37 @@ mod tests { assert_eq!(profile.netmask.as_deref(), Some("255.255.255.0")); } + #[test] + fn normalize_board_upsert_request_trims_uboot_load_addresses() { + let request = AdminBoardUpsertRequest { + id: Some("demo".into()), + board_type: "demo".into(), + tags: vec![], + notes: None, + disabled: false, + serial: None, + power_management: PowerManagementConfig::Custom(CustomPowerManagement { + power_on_cmd: "echo on".into(), + power_off_cmd: "echo off".into(), + }), + boot: BootConfig::Uboot(crate::config::UbootProfile { + kernel_load_addr: Some(" 0x80200000 ".into()), + fit_load_addr: Some(" ".into()), + bootm_addr: Some(" 0x82200000 ".into()), + ..Default::default() + }), + }; + + let request = super::normalize_board_upsert_request(request).unwrap(); + let BootConfig::Uboot(profile) = request.boot else { + panic!("expected uboot profile"); + }; + + assert_eq!(profile.kernel_load_addr.as_deref(), Some("0x80200000")); + assert_eq!(profile.fit_load_addr, None); + assert_eq!(profile.bootm_addr.as_deref(), Some("0x82200000")); + } + #[tokio::test] async fn session_file_list_supports_multiple_paths_and_overwrite() { let app = test_router().await; diff --git a/ostool-server/src/config.rs b/ostool-server/src/config.rs index aa93b5f..e852af6 100644 --- a/ostool-server/src/config.rs +++ b/ostool-server/src/config.rs @@ -430,6 +430,12 @@ pub struct UbootProfile { pub use_tftp: bool, pub dtb_name: Option, #[serde(default)] + pub kernel_load_addr: Option, + #[serde(default)] + pub fit_load_addr: Option, + #[serde(default)] + pub bootm_addr: Option, + #[serde(default)] pub network_mode: UbootNetworkMode, #[serde(default)] pub board_ip: Option, @@ -722,6 +728,33 @@ network_mode = "static_ip" assert_eq!(value["boot"]["dtb_name"], json!("board.dtb")); } + #[test] + fn board_config_uboot_profile_supports_load_addresses() { + let config = r#" +id = "demo-1" +board_type = "demo" + +[power_management] +kind = "custom" +power_on_cmd = "echo on" +power_off_cmd = "echo off" + +[boot] +kind = "uboot" +kernel_load_addr = "0x80200000" +fit_load_addr = "0x82200000" +bootm_addr = "0x82200000" +"#; + + let decoded: BoardConfig = toml::from_str(config).unwrap(); + let BootConfig::Uboot(profile) = decoded.boot else { + panic!("expected uboot profile"); + }; + assert_eq!(profile.kernel_load_addr.as_deref(), Some("0x80200000")); + assert_eq!(profile.fit_load_addr.as_deref(), Some("0x82200000")); + assert_eq!(profile.bootm_addr.as_deref(), Some("0x82200000")); + } + #[test] fn board_config_round_trip_supports_virtual_power_management() { let board = BoardConfig { diff --git a/ostool-server/webui/src/types/api.ts b/ostool-server/webui/src/types/api.ts index 58d8dd3..ed9e8bd 100644 --- a/ostool-server/webui/src/types/api.ts +++ b/ostool-server/webui/src/types/api.ts @@ -103,6 +103,9 @@ export interface UbootProfile { kind: "uboot"; use_tftp: boolean; dtb_name: string | null; + kernel_load_addr: string | null; + fit_load_addr: string | null; + bootm_addr: string | null; network_mode: UbootNetworkMode; board_ip: string | null; server_ip: string | null; diff --git a/ostool-server/webui/src/views/BoardEditorView.test.ts b/ostool-server/webui/src/views/BoardEditorView.test.ts index a8347a7..9440351 100644 --- a/ostool-server/webui/src/views/BoardEditorView.test.ts +++ b/ostool-server/webui/src/views/BoardEditorView.test.ts @@ -86,6 +86,9 @@ function makeBoard(id = "demo-board"): BoardConfig { kind: "uboot", use_tftp: true, dtb_name: null, + kernel_load_addr: null, + fit_load_addr: null, + bootm_addr: null, network_mode: "dhcp", board_ip: null, server_ip: null, @@ -104,6 +107,9 @@ function makeStaticIpBoard(id = "static-board"): BoardConfig { kind: "uboot", use_tftp: true, dtb_name: null, + kernel_load_addr: "0x80200000", + fit_load_addr: "0x82200000", + bootm_addr: "0x82200000", network_mode: "static_ip", board_ip: "192.168.10.20", server_ip: "192.168.10.2", @@ -225,6 +231,9 @@ describe("BoardEditorView", () => { kind: "uboot", use_tftp: false, dtb_name: null, + kernel_load_addr: null, + fit_load_addr: null, + bootm_addr: null, network_mode: "dhcp", board_ip: null, server_ip: null, @@ -275,6 +284,9 @@ describe("BoardEditorView", () => { await wrapper.get('input[placeholder="当前 serverip"]').setValue("192.168.10.2"); await wrapper.get('input[placeholder="当前 netmask"]').setValue("255.255.255.0"); await wrapper.get('input[placeholder="未配置"]').setValue("192.168.10.1"); + await wrapper.get('input[placeholder="例如 0x80200000"]').setValue("0x80200000"); + await wrapper.get('input[placeholder="例如 0x82200000"]').setValue("0x82200000"); + await wrapper.get('input[placeholder="默认跟随 FIT load addr"]').setValue("0x82200000"); const saveButton = wrapper.findAll("button").find((button) => button.text() === "保存配置"); await saveButton!.trigger("click"); @@ -286,6 +298,9 @@ describe("BoardEditorView", () => { kind: "uboot", use_tftp: true, dtb_name: null, + kernel_load_addr: "0x80200000", + fit_load_addr: "0x82200000", + bootm_addr: "0x82200000", network_mode: "static_ip", board_ip: "192.168.10.20", server_ip: "192.168.10.2", @@ -317,6 +332,15 @@ describe("BoardEditorView", () => { expect((wrapper.get('input[placeholder="未配置"]').element as HTMLInputElement).value).toBe( "192.168.10.1", ); + expect((wrapper.get('input[placeholder="例如 0x80200000"]').element as HTMLInputElement).value).toBe( + "0x80200000", + ); + expect((wrapper.get('input[placeholder="例如 0x82200000"]').element as HTMLInputElement).value).toBe( + "0x82200000", + ); + expect( + (wrapper.get('input[placeholder="默认跟随 FIT load addr"]').element as HTMLInputElement).value, + ).toBe("0x82200000"); }); it("updates a board and keeps blank id as null in the payload", async () => { diff --git a/ostool-server/webui/src/views/BoardEditorView.vue b/ostool-server/webui/src/views/BoardEditorView.vue index 5c0f77d..7004e3e 100644 --- a/ostool-server/webui/src/views/BoardEditorView.vue +++ b/ostool-server/webui/src/views/BoardEditorView.vue @@ -36,6 +36,9 @@ interface BoardEditorFormState { boot_kind: BootKind; use_tftp: boolean; dtb_name: string; + kernel_load_addr: string; + fit_load_addr: string; + bootm_addr: string; network_mode: UbootNetworkMode; board_ip: string; server_ip: string; @@ -96,6 +99,9 @@ function defaultFormState(): BoardEditorFormState { boot_kind: "uboot", use_tftp: false, dtb_name: "", + kernel_load_addr: "", + fit_load_addr: "", + bootm_addr: "", network_mode: "dhcp", board_ip: "", server_ip: "", @@ -134,6 +140,9 @@ function boardToFormState(board: BoardConfig): BoardEditorFormState { next.boot_kind = "uboot"; next.use_tftp = board.boot.use_tftp; next.dtb_name = board.boot.dtb_name ?? ""; + next.kernel_load_addr = board.boot.kernel_load_addr ?? ""; + next.fit_load_addr = board.boot.fit_load_addr ?? ""; + next.bootm_addr = board.boot.bootm_addr ?? ""; next.network_mode = board.boot.network_mode ?? "dhcp"; next.board_ip = board.boot.board_ip ?? ""; next.server_ip = board.boot.server_ip ?? ""; @@ -166,6 +175,9 @@ function buildBootConfig(): BootConfig { kind: "uboot", use_tftp: form.value.use_tftp, dtb_name: trimToNull(form.value.dtb_name), + kernel_load_addr: trimToNull(form.value.kernel_load_addr), + fit_load_addr: trimToNull(form.value.fit_load_addr), + bootm_addr: trimToNull(form.value.bootm_addr), network_mode: useStaticIp ? "static_ip" : "dhcp", board_ip: useStaticIp ? trimToNull(form.value.board_ip) : null, server_ip: useStaticIp ? trimToNull(form.value.server_ip) : null, @@ -733,7 +745,7 @@ onMounted(() => { -