diff --git a/vendor/ebaina/README.md b/vendor/ebaina/README.md index 4cd01b1cd1ce56f5858f9a44ee375ace5565a93c..44e1791ee403ead9256b69b1ca11a8f6c1d8ec70 100644 --- a/vendor/ebaina/README.md +++ b/vendor/ebaina/README.md @@ -6,6 +6,7 @@ - 如有除openEuler系统外的其他系统需求,可下载[【易百纳】Euler Pi 2.0](https://pan.baidu.com/s/1GwvuEjbLGsMLyX8kkG8dlQ?pwd=s7hs)资料,需注意,其他系统需求不基于[Hi3403V100 HiSpark社区版本](https://gitee.com/HiSpark/pegasus/tree/master)。 - 海鸥派相关咨询或者疑问请前往[易百纳主导的海鸥派开源开发者社区](https://gitee.com/hieulerpi)提问。 - [海鸥派淘宝购买链接](https://item.taobao.com/item.htm?abbucket=13&id=755989596567&skuId=5948917988054)。 +- 提供了命令行烧录方式说明,详情请查看[《histart 自动更新系统设计与使用手册》](./docs/histart/README_zh.md)。 ## 一、补丁说明 diff --git a/vendor/ebaina/docs/histart/README_zh.md b/vendor/ebaina/docs/histart/README_zh.md new file mode 100644 index 0000000000000000000000000000000000000000..a4bddb6eb0f232a91e354cc680d5b91c3f18b9af --- /dev/null +++ b/vendor/ebaina/docs/histart/README_zh.md @@ -0,0 +1,125 @@ +# histart 自动更新系统设计与使用手册 + +## 1. 系统概述 + +`histart` 是一套为海思 (HiSilicon) Hi3403V100平台设计的自动固件烧录方案,实现了基于网络 (TFTP) 的一键式全自动更新;该方案解决了传统手动烧录过程繁琐、容易出错且难以维护多个分区表的问题。**目前只支持eMMC烧录。** + +## 2. 系统架构 + +系统由两部分组成: +1. **离线预处理器 (Host 端)**:`gen_hstart.py`。负责将 XML 分区表换算为 U-Boot 可执行的脚本。 +2. **在线执行引擎 (Target 端)**:`cmd_histart.c`。集成在 U-Boot 中的新命令 `histart`。 + +## 3. 集成步骤 + +### 3.1 预处理器配置 + +在[`gen_hstart.py`](../../file/histart/gen_hstart.py)脚本顶部的配置区,可以根据实际硬件修改参数: + +- `LOAD_ADDR_HEX`: 镜像文件在 DDR 中的加载基地址(默认 `0x41000000`)。 +- `MAX_CHUNK_SIZE`: 单次下载的最大分段大小(默认 `256MB`),建议设为 DDR 容量的 1/4 到 1/2。 +- `BLOCK_SIZE_BYTES`: eMMC 扇区大小(标准为 `512`)。 + +### 3.2 U-Boot 命令集成 + +**【注意】:这里以u-boot-2020.01为例。** + +1. 将 [`cmd_histart.c`](../../file/histart/u-boot-2020.01/cmd/cmd_histart.c) 放置于 U-Boot 源码的 `cmd/` 目录下。 + +2. 修改 `cmd/Makefile`,添加: + + ```makefile + obj-$(CONFIG_CMD_HISTART) += cmd_histart.o + ``` + + ![image-20260508203250622](./pic/image-20260508203250622.png) + +3. 修改 `cmd/Kconfig`:在合适位置(如 config SYS_XTRACE命令后)添加配置定义: + + ``` + config CMD_HISTART + bool "histart - Hisilicon auto-start burn system" + default y + help + This command enables the HiSilicon automatic firmware update system. + It allows U-Boot to download and execute burn scripts from a TFTP + server, supporting one-click multi-partition updates with CRC32 + verification and automatic reboot. + ``` + +4. 配置并编译:执行 `make menuconfig` 并勾选 `histart` 命令,随后执行 `make`4. 测试与操作人员指南 (使用步骤) + + ![image-20260508203314336](./pic/image-20260508203314336.png) + +## 4. 使用步骤 + +### 前提条件 + +- 开发板相关源码已编译完成,已形成烧录文件。 + +- 客户端(操作平台,例如Linux系统)部署了TFTP服务。 + + 1. windows安装Tftpd工具即可。 + + 2. Ubuntu安装Tftpd: + + ```bash + apt-get update + apt-get -y install tftpd-hpa + mkdir /tftpboot + chmod 777 /tftpboot + ``` + + `vim /etc/default/tftpd-hpa`修改以下内容: `TFTP_USERNAME="tftp" TFTP_DIRECTORY="/tftpboot" TFTP_ADDRESS="0.0.0.0:69" TFTP_OPTIONS="-l -c -s"` ,其中TFTP_DIRECTORY 指定文件存储路径。 -c 允许创建新文件。 + + ![image-20260508204836125](./pic/image-20260508204836125.png) + + 重启服务以应用配置: + + ```bash + systemctl restart tftpd-hpa.service + ``` + +- 客户端拥有python3环境。 + +- 使用串口线缆和网线连接客户端与开发板。 + +- 已经烧录了uboot镜像。 + +### 步骤 1:准备烧录相关文件 + +在客户端创建TFTP文件夹,将镜像文件和[`gen_hstart.py`](../../file/histart/gen_hstart.py)拷贝至TFTP文件夹内,生成histart脚本,进入TFTP文件夹目录执行下面命令。。 + +```bash +python3 gen_hstart.py path/to/emmc_burn_table.xml +``` + +**输出结果**:在 XML 所在目录下生成 `histart.txt` 和 `scripts/` 文件夹。 + +### 步骤 2:使用histart烧录 + +1. 重启板端,`ctrl+c`进入uboot。 + +2. 配置网络环境,(需根据情况修改serverip、ipaddr和gatewayip,确保能 ping 通服务器): + + ``` + setenv serverip + setenv ethaddr 00:cf:55:38:4f:0a + setenv ipaddr + setenv netmask 255.255.255.0 + setenv gatewayip + ``` + + ![image-20260508203713664](./pic/image-20260508203713664.png) + +3. 执行histart烧录。 + + ```bash + histart + ``` + + ![image-20260508203728787](./pic/image-20260508203728787.png) + + **自动完成**:系统将自动依次下载并烧录所有分区。完成后,系统将自动执行 `reset` 重启设备。 + + ![image-20260508203636216](./pic/image-20260508203636216.png) diff --git a/vendor/ebaina/docs/histart/pic/image-20260508203250622.png b/vendor/ebaina/docs/histart/pic/image-20260508203250622.png new file mode 100644 index 0000000000000000000000000000000000000000..a19b54de616dfb27bbffaf9c1a6d11870b43c5a2 Binary files /dev/null and b/vendor/ebaina/docs/histart/pic/image-20260508203250622.png differ diff --git a/vendor/ebaina/docs/histart/pic/image-20260508203314336.png b/vendor/ebaina/docs/histart/pic/image-20260508203314336.png new file mode 100644 index 0000000000000000000000000000000000000000..716a5c13aa31d52cd5280d0f984143e9847db8e5 Binary files /dev/null and b/vendor/ebaina/docs/histart/pic/image-20260508203314336.png differ diff --git a/vendor/ebaina/docs/histart/pic/image-20260508203636216.png b/vendor/ebaina/docs/histart/pic/image-20260508203636216.png new file mode 100644 index 0000000000000000000000000000000000000000..ad17b1044392d043aa416407b91352bd5cbbff2d Binary files /dev/null and b/vendor/ebaina/docs/histart/pic/image-20260508203636216.png differ diff --git a/vendor/ebaina/docs/histart/pic/image-20260508203713664.png b/vendor/ebaina/docs/histart/pic/image-20260508203713664.png new file mode 100644 index 0000000000000000000000000000000000000000..df4ba33f0ff633042379442b7d172eb592eddd4e Binary files /dev/null and b/vendor/ebaina/docs/histart/pic/image-20260508203713664.png differ diff --git a/vendor/ebaina/docs/histart/pic/image-20260508203728787.png b/vendor/ebaina/docs/histart/pic/image-20260508203728787.png new file mode 100644 index 0000000000000000000000000000000000000000..26c5eb3e2e5724230fda231ffd829198631c8a4b Binary files /dev/null and b/vendor/ebaina/docs/histart/pic/image-20260508203728787.png differ diff --git a/vendor/ebaina/docs/histart/pic/image-20260508204836125.png b/vendor/ebaina/docs/histart/pic/image-20260508204836125.png new file mode 100644 index 0000000000000000000000000000000000000000..c3a39047e93961acae02bba6f72318bf8101e614 Binary files /dev/null and b/vendor/ebaina/docs/histart/pic/image-20260508204836125.png differ diff --git a/vendor/ebaina/file/histart/gen_hstart.py b/vendor/ebaina/file/histart/gen_hstart.py new file mode 100644 index 0000000000000000000000000000000000000000..973f287430ec6301a55d29b97d3519657e578b7a --- /dev/null +++ b/vendor/ebaina/file/histart/gen_hstart.py @@ -0,0 +1,197 @@ +# Copyright (c) 2026 Nanjing Qinuo Information Technology Co., Ltd. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import xml.etree.ElementTree as ET +import os +import re +import sys +import argparse + +# --- Configuration Constants --- +SCRIPTS_SUBDIR = 'scripts' +LOAD_ADDR_HEX = '0x41000000' +MMC_DEV_ID = 0 +BLOCK_SIZE_BYTES = 512 +MAX_CHUNK_SIZE = 256 * 1024 * 1024 # 256MB per chunk to fit in most DDR configs + +def parse_size_to_bytes(size_str): + """Converts size strings like '512K', '20M' or hex '0x1000' to integer byte counts.""" + if not size_str: + return 0 + size_str = size_str.upper().strip() + + # Handle hex strings + if size_str.startswith('0X'): + try: + return int(size_str, 16) + except ValueError: + return 0 + + units = {'K': 1024, 'M': 1024**2, 'G': 1024**3} + match = re.match(r'(\d+)([KMG]?)', size_str) + if match: + number = int(match.group(1)) + unit = match.group(2) + return number * units.get(unit, 1) + return 0 + +def split_file(source_path, chunk_size, output_dir): + """Splits a file into multiple chunks and returns a list of filenames.""" + file_size = os.path.getsize(source_path) + if file_size <= chunk_size: + return [os.path.basename(source_path)] + + base_name = os.path.basename(source_path) + chunk_names = [] + print(f"[*] Large file detected ({file_size / (1024**2):.1f} MB). Splitting into {chunk_size / (1024**2):.0f}MB chunks...") + + with open(source_path, 'rb') as f: + part_num = 0 + while True: + chunk_data = f.read(chunk_size) + if not chunk_data: + break + chunk_name = f"{base_name}.part{part_num}" + chunk_path = os.path.join(output_dir, chunk_name) + with open(chunk_path, 'wb') as out_f: + out_f.write(chunk_data) + chunk_names.append(chunk_name) + part_num += 1 + + return chunk_names + +def generate_update_scripts(xml_file): + # Determine the output base directory based on the XML file location + output_root = os.path.dirname(os.path.abspath(xml_file)) + scripts_path = os.path.join(output_root, SCRIPTS_SUBDIR) + + print(f"[*] Processing: {xml_file}") + print(f"[*] Output root: {output_root}") + + if not os.path.exists(scripts_path): + os.makedirs(scripts_path) + + try: + with open(xml_file, 'rb') as f: + raw_content = f.read().decode('gb2312', errors='ignore') + + if not raw_content.strip(): + print(f"[!] Error: XML file is empty.") + sys.exit(1) + + root = ET.fromstring(raw_content) + if root.tag != 'Partition_Info': + print(f"[!] Error: Invalid XML format.") + sys.exit(1) + + except Exception as e: + print(f"[!] XML Error: {e}") + sys.exit(1) + + index_script_lines = [] + partitions = root.findall('Part') + + for part in partitions: + name = part.get('PartitionName') + start_str = part.get('Start') + length_str = part.get('Length') + source_path_xml = part.get('SelectFile') + + if not all([name, start_str, length_str, source_path_xml]): + continue + + # Resolve actual file path relative to XML location + xml_dir = os.path.dirname(os.path.abspath(xml_file)) + actual_source_path = os.path.join(xml_dir, source_path_xml.replace('\\', os.sep)) + + if not os.path.exists(actual_source_path): + print(f"[!] Warning: Source file not found: {actual_source_path}. Skipping.") + continue + + # 1. Split file if it exceeds MAX_CHUNK_SIZE + chunk_filenames = split_file(actual_source_path, MAX_CHUNK_SIZE, output_root) + + # 2. Calculate block addresses + start_bytes = parse_size_to_bytes(start_str) + start_blk = start_bytes // BLOCK_SIZE_BYTES + + is_dynamic = (length_str.strip() == '-') + if is_dynamic: + calc_cmds = [ + f"setexpr count_blk ${{emmc_total_blk}} - {hex(start_blk)}", + ] + max_count_var = "${count_blk}" + else: + length_bytes = parse_size_to_bytes(length_str) + if length_bytes == 0: continue + calc_cmds = [] + max_count_var = hex(length_bytes // BLOCK_SIZE_BYTES) + + # Construct sub-script + sub_script_name = f"[[{name}" + sub_script_rel_path = f"{SCRIPTS_SUBDIR}/{sub_script_name}" + + content = [ + f"# Auto-generated burn script for: {name}", + f"echo '>>> Partition: {name} ({length_str})'" + ] + content.extend(calc_cmds) + + # 3. Generate multi-stage burning logic for chunks + current_offset_blk = start_blk + for i, chunk_file in enumerate(chunk_filenames): + chunk_size = os.path.getsize(os.path.join(output_root, chunk_file)) + + content.extend([ + f"echo '>>> Chunk {i}: {chunk_file}'", + f"mw.b {LOAD_ADDR_HEX} 0xFF {hex(MAX_CHUNK_SIZE)}", + f"tftp {LOAD_ADDR_HEX} {chunk_file}", + f"crc32 {LOAD_ADDR_HEX} ${{filesize}}", + f"setexpr actual_blkcnt ${{filesize}} + 0x1ff", + f"setexpr actual_blkcnt ${{actual_blkcnt}} / {hex(BLOCK_SIZE_BYTES)}", + # Per-chunk boundary check (Assertion style for compatibility) + f"setexpr current_limit {max_count_var} - {hex(current_offset_blk - start_blk)}", + f"echo '>>> Checking boundary...'", + f"itest.l ${{actual_blkcnt}} <= ${{current_limit}}", + f"mmc write {hex(MMC_DEV_ID)} {LOAD_ADDR_HEX} {hex(current_offset_blk)} ${{actual_blkcnt}}", + ]) + current_offset_blk += (chunk_size + BLOCK_SIZE_BYTES - 1) // BLOCK_SIZE_BYTES + + content.append(f"echo '>>> {name} write complete.'") + + with open(os.path.join(scripts_path, sub_script_name), 'w') as f: + f.write('\n'.join(content) + '\n') + + index_script_lines.append(f"histart {sub_script_rel_path}") + + # Finalize main index script + index_script_lines.extend([ + "echo '====================================='", + "echo ' FIRMWARE UPDATE COMPLETED SUCCESS '", + "echo ' REBOOTING IN 3 SECONDS... '", + "echo '====================================='", + "sleep 3", + "reset", + ]) + + with open(os.path.join(output_root, 'histart.txt'), 'w') as f: + f.write('\n'.join(index_script_lines) + '\n') + + print(f"[+] Success: Scripts and chunks generated in '{output_root}'") + +if __name__ == '__main__': + parser = argparse.ArgumentParser(description='HiSilicon hstart Script Generator') + parser.add_argument('xml', help='Path to the emmc_burn_table.xml file') + args = parser.parse_args() + generate_update_scripts(args.xml) diff --git a/vendor/ebaina/file/histart/u-boot-2020.01/cmd/cmd_histart.c b/vendor/ebaina/file/histart/u-boot-2020.01/cmd/cmd_histart.c new file mode 100644 index 0000000000000000000000000000000000000000..df56b1c07da8b22e4e33070caf81b8e3db88b11f --- /dev/null +++ b/vendor/ebaina/file/histart/u-boot-2020.01/cmd/cmd_histart.c @@ -0,0 +1,177 @@ +/* + * Copyright (c) 2026 Nanjing Qinuo Information Technology Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include +#include + +/* Configuration Constants */ +#define HISTART_SCRIPT_MAX_SIZE (32 * 1024) /* 32KB Limit for script files */ +#define HISTART_DEFAULT_FILE "histart.txt" +#define HISTART_LOAD_ADDR 0x41000000 +#define HISTART_MMC_DEV_ID 0 /* Default eMMC device ID */ +#define RADIX_HEX 16 /* Hexadecimal base for string conversion */ + +/** + * detect_emmc_capacity - Detect total blocks of eMMC and set environment variable + */ +static void detect_emmc_capacity(void) +{ + struct mmc *mmc = find_mmc_device(HISTART_MMC_DEV_ID); + if (mmc) { + if (mmc_init(mmc) == 0) { + char val[32]; + /* capacity is in bytes, convert to 512B blocks */ + snprintf(val, sizeof(val), "0x%llx", (unsigned long long)mmc->capacity_user / 512); + env_set("emmc_total_blk", val); + } + } +} + +/** + * get_script_next_line - Extract the next valid command line from the buffer + * @line_buf_ptr: Pointer to the current position in the script buffer + * Handles \r, \n, skip comments (#), and terminates at EOF or '%' + */ +static char *get_script_next_line(char **line_buf_ptr) +{ + char *line_buf = *line_buf_ptr; + char *next_line = NULL; + + if (!line_buf || *line_buf == '\0') { + return NULL; + } + + /* Skip leading whitespace and line breaks */ + while (1) { + while (*line_buf && (isspace(*line_buf))) { + line_buf++; + } + + /* Check for EOF or end-of-script marker */ + if (*line_buf == '\0' || *line_buf == '%') { + return NULL; + } + + /* Handle comments: skip until end of line and continue searching */ + if (*line_buf == '#') { + while (*line_buf && *line_buf != '\r' && *line_buf != '\n') { + line_buf++; + } + continue; + } + + break; + } + + next_line = line_buf; + + /* Find the end of the current line */ + while (*line_buf && *line_buf != '\r' && *line_buf != '\n') { + line_buf++; + } + + /* Null-terminate the current line and advance the buffer pointer */ + if (*line_buf != '\0') { + *line_buf = '\0'; + *line_buf_ptr = line_buf + 1; + } else { + *line_buf_ptr = line_buf; + } + + /* Trim trailing spaces from the extracted line */ + char *end = next_line + strlen(next_line) - 1; + while (end > next_line && isspace(*end)) { + *end = '\0'; + end--; + } + + return next_line; +} + +/** + * do_histart - The main histart command handler + */ +static int do_histart(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[]) +{ + const char *script_name = (argc > 1) ? argv[1] : HISTART_DEFAULT_FILE; + char cmd[128]; + char *script_heap_buf = NULL; + char *script_ptr = NULL; + char *next_line = NULL; + const char *filesize_str; + unsigned long filesize = 0; + int ret = CMD_RET_SUCCESS; + + /* Automatically detect eMMC capacity for dynamic partitions */ + detect_emmc_capacity(); + + /* 1. Fetch script from TFTP to temporary staging area */ + printf("histart: Fetching script [%s] to 0x%08x...\n", script_name, HISTART_LOAD_ADDR); + snprintf(cmd, sizeof(cmd), "tftp 0x%08x %s", HISTART_LOAD_ADDR, script_name); + if (run_command(cmd, 0) != 0) { + printf("histart: Error - TFTP failed for %s\n", script_name); + return CMD_RET_FAILURE; + } + + /* 2. Validate script size */ + filesize_str = env_get("filesize"); + if (!filesize_str) { + printf("histart: Error - 'filesize' env not found after TFTP\n"); + return CMD_RET_FAILURE; + } + filesize = simple_strtoul(filesize_str, NULL, RADIX_HEX); + if (filesize == 0 || filesize > HISTART_SCRIPT_MAX_SIZE) { + printf("histart: Error - Script too large (%lu bytes, max %d)\n", + filesize, HISTART_SCRIPT_MAX_SIZE); + return CMD_RET_FAILURE; + } + + /* 3. Clone script to heap to prevent overwrite by subsequent image downloads */ + script_heap_buf = malloc(filesize + 1); + if (!script_heap_buf) { + printf("histart: Error - Memory allocation failed\n"); + return CMD_RET_FAILURE; + } + memcpy(script_heap_buf, (void *)HISTART_LOAD_ADDR, filesize); + script_heap_buf[filesize] = '\0'; + + /* 4. Parse and Execute Line-by-Line */ + script_ptr = script_heap_buf; + while ((next_line = get_script_next_line(&script_ptr)) != NULL) { + if (strlen(next_line) == 0) { + continue; + } + + printf("\n[histart] %s\n", next_line); + if (run_command(next_line, 0) != 0) { + printf("histart: Error - Command failed. Aborting script execution.\n"); + ret = CMD_RET_FAILURE; + break; + } + } + + free(script_heap_buf); + return ret; +} + +U_BOOT_CMD( + histart, 2, 1, do_histart, + "HiSilicon automatic firmware update system", + "[script_name] - Load and execute a flash script from TFTP" +);