From 9f49250c75737a133aeb1112a655f409bb50ab13 Mon Sep 17 00:00:00 2001 From: GuDongBin Date: Thu, 27 Aug 2026 10:03:46 +0800 Subject: [PATCH 1/2] cv6xx: parse composite image headers dynamically --- src/defib/protocol/hisilicon_cv6xx.py | 250 ++++++++++++++++++++------ tests/test_protocol_cv6xx.py | 174 +++++++++++++----- 2 files changed, 322 insertions(+), 102 deletions(-) diff --git a/src/defib/protocol/hisilicon_cv6xx.py b/src/defib/protocol/hisilicon_cv6xx.py index e0b34e8..9bfb10c 100644 --- a/src/defib/protocol/hisilicon_cv6xx.py +++ b/src/defib/protocol/hisilicon_cv6xx.py @@ -4,7 +4,7 @@ 1. Handshake: Send DEADBEEF magic with baud rate, loop until "uart ddr"/"uart flash" 2. Board ID query: Send CE frame with timestamps, get CPU/Board ID 3. Parse composite boot file: GSL + DDR params (multiple tables) + U-Boot -4. Transfer: GSL → 0x04020000, DDR table → 0x41000000, wait DDR training, U-Boot → 0x41000000 +4. Transfer: GSL → 0x04021A00, DDR table → 0x41000000, wait DDR training, U-Boot → 0x41000000 """ from __future__ import annotations @@ -13,6 +13,7 @@ import logging import struct import time +from collections.abc import Iterator from dataclasses import dataclass from typing import Callable @@ -41,7 +42,9 @@ "hi3516dv500", "hi3519dv500", ]) -GSL_LOAD_ADDR = 0x04020000 +# The CV6xx GSL protocol downloads the composite image at CP_STEP1_ADDR, +# after the BootROM/GSL stack and BSS reserved at the start of SRAM. +GSL_LOAD_ADDR = 0x04021A00 DDR_LOAD_ADDR = 0x41000000 UBOOT_LOAD_ADDR = 0x41000000 DDR_TRAINING_WAIT = 1.5 # seconds @@ -60,6 +63,14 @@ SPL_DDR_TRAINED = "DDR size" SPL_TABLE_REJECTED = "The table is incorrect" +IMAGE_ALIGNMENT = 0x100 +CODE_ALIGNMENT = 0x200 +IMAGE_HEADER_FIELDS_SIZE = 40 +DDR_PARAMS_FIELDS_SIZE = 308 +MAX_GSL_HEADER_OFFSET = 0x10000 +MAX_SECTION_GAP = 0x1000 +MAX_DDR_TABLES = 8 + def _emit(callback: Callable[[ProgressEvent], None] | None, event: ProgressEvent) -> None: if callback is not None: @@ -80,60 +91,183 @@ class CV6xxBootParts: file_data: bytes +@dataclass(frozen=True) +class _CodeImage: + """Validated code-image header and payload bounds.""" + + offset: int + structure_length: int + payload_length: int + + @property + def end(self) -> int: + return self.offset + self.structure_length + self.payload_length + + +@dataclass(frozen=True) +class _DdrParamsImage: + """Validated DDR parameter-image header and table bounds.""" + + offset: int + structure_length: int + table_offset: int + table_size: int + table_count: int + board_mapping: bytes + + @property + def tables_end(self) -> int: + return self.table_offset + self.table_size * self.table_count + + +def _magic_offsets( + file_data: bytes, + magic: int, + start: int = 0, + end: int | None = None, +) -> Iterator[int]: + """Yield aligned occurrences of an image magic within the requested range.""" + magic_bytes = struct.pack("= 0: + if offset % IMAGE_ALIGNMENT == 0: + yield offset + offset = file_data.find(magic_bytes, offset + 1, limit) + + +def _parse_code_image(file_data: bytes, offset: int, magic: int, name: str) -> _CodeImage: + """Validate a GSL/U-Boot image header and return its payload bounds.""" + if offset + IMAGE_HEADER_FIELDS_SIZE > len(file_data): + raise ProtocolError(f"Truncated {name} header at 0x{offset:X}") + + image_magic, version, structure_length, signature_length = struct.unpack_from( + "<4I", file_data, offset, + ) + payload_length = struct.unpack_from(" structure_length + ): + raise ProtocolError(f"Invalid {name} structure bounds at 0x{offset:X}") + if payload_length == 0 or payload_length % CODE_ALIGNMENT != 0: + raise ProtocolError(f"Invalid {name} payload length at 0x{offset:X}") + + image = _CodeImage(offset, structure_length, payload_length) + if image.end > len(file_data): + raise ProtocolError(f"Truncated {name} payload at 0x{offset:X}") + return image + + +def _parse_ddr_params(file_data: bytes, offset: int) -> _DdrParamsImage: + """Validate a DDR parameter header, board map, and all declared tables.""" + if offset + DDR_PARAMS_FIELDS_SIZE > len(file_data): + raise ProtocolError(f"Truncated DDR params header at 0x{offset:X}") + + magic, version, structure_length, signature_length = struct.unpack_from( + "<4I", file_data, offset, + ) + params_area_offset, table_size, table_count = struct.unpack_from( + "<3I", file_data, offset + 32, + ) + board_mapping = file_data[offset + 300:offset + 308] + + if magic != CV6XX_DDR_PARAMS_MAGIC: + raise ProtocolError(f"Invalid DDR params magic at 0x{offset:X}") + if version == 0: + raise ProtocolError(f"Invalid DDR params structure version at 0x{offset:X}") + if ( + structure_length < DDR_PARAMS_FIELDS_SIZE + or structure_length % IMAGE_ALIGNMENT != 0 + or signature_length == 0 + or signature_length > structure_length + or params_area_offset % IMAGE_ALIGNMENT != 0 + ): + raise ProtocolError(f"Invalid DDR params structure bounds at 0x{offset:X}") + if ( + table_size == 0 + or table_size % IMAGE_ALIGNMENT != 0 + or not 1 <= table_count <= MAX_DDR_TABLES + ): + raise ProtocolError(f"Invalid DDR table dimensions at 0x{offset:X}") + if not any(index < table_count for index in board_mapping): + raise ProtocolError(f"DDR board mapping has no valid table at 0x{offset:X}") + if any(index != 0xFF and index >= table_count for index in board_mapping): + raise ProtocolError(f"DDR board mapping is out of range at 0x{offset:X}") + + table_offset = offset + structure_length + params_area_offset + params = _DdrParamsImage( + offset, + structure_length, + table_offset, + table_size, + table_count, + board_mapping, + ) + if params.tables_end > len(file_data): + raise ProtocolError(f"Truncated DDR tables at 0x{offset:X}") + return params + + def parse_cv6xx_boot(file_data: bytes) -> CV6xxBootParts: """Parse a CV6xx composite boot file into its constituent parts. - The file layout: - - Offset 2048: GSL (magic 0x4BB4D22D), length at offset 2084 - - After GSL + 1024: DDR params (magic 0x4B87A52D) - - After DDR params: U-Boot (magic 0x4BF01E2D) + Image-tool generations use different key and image-header sizes, placing + the GSL header at offsets including 0x800 and 0x1200. Locate a candidate + header, then accept it only when its declared payload is followed by a + structurally valid DDR parameter image and U-Boot image. """ - # 1. GSL - magic_gsl = struct.unpack(" 1: + raise ProtocolError("Ambiguous CV6xx boot image: multiple valid GSL layouts found") + return layouts[0] def wrap_cv6xx_payload(header_source: bytes, payload: bytes) -> bytes: @@ -164,11 +298,17 @@ def build_ddr_table(parts: CV6xxBootParts, board_id: int = 0) -> bytes: if mapped_index >= parts.table_count: mapped_index = 0 + params_structure_length = struct.unpack_from( + " bytes: """Build a synthetic CV6xx composite boot file for testing.""" - # GSL section starts at offset 2048 - gsl_size = gsl_len + 3072 - data = bytearray(gsl_size + 4096 + table_count * table_size + uboot_len + 4096) - - # GSL magic at offset 2048 - struct.pack_into(" 0 + assert len(parts.gsl_data) == gsl_header_offset + layout.get( + "gsl_structure_length", 0x400, + ) + 0x1000 assert parts.table_count == 2 - assert parts.table_size == 1024 - assert len(parts.uboot_data) > 0 + assert parts.table_size == 0x400 + assert len(parts.uboot_data) == layout.get("uboot_structure_length", 0x400) + 0x2000 def test_invalid_gsl_magic(self): firmware = bytearray(_build_cv6xx_firmware()) - struct.pack_into(" 0 + assert len(ddr_table) == 0x800 + 0x200 + assert ddr_table[-0x200:] == b"\x03" * 0x200 + + def test_build_ddr_table_for_0x1200_layout(self): + firmware = _build_cv6xx_firmware( + gsl_header_offset=0x1200, + gsl_structure_length=0x200, + ree_key_length=0x100, + params_structure_length=0x200, + params_area_offset=0x100, + table_count=1, + table_size=0x3000, + uboot_structure_length=0x200, + ) + parts = parse_cv6xx_boot(firmware) + + ddr_table = build_ddr_table(parts, board_id=7) + + assert len(ddr_table) == 0x400 + 0x3000 + assert ddr_table[-0x3000:] == b"\x01" * 0x3000 class TestCV6xxHandshake: @@ -174,6 +253,7 @@ async def test_send_firmware(self): assert result.success assert Stage.GSL in result.stages_completed + assert struct.unpack(">I", transport.tx_log[0][8:12])[0] == GSL_LOAD_ADDR def _transport_through_ddr_table(spl_output: bytes, then_acks: int = 0) -> MockTransport: From 3431b8d4e378b54ae65c14927e45a26e06196441 Mon Sep 17 00:00:00 2001 From: GuDongBin Date: Thu, 27 Aug 2026 10:11:36 +0800 Subject: [PATCH 2/2] cv6xx: keep browser recovery in sync --- docs/qemu_hisilicon_spec.md | 37 +++++---- web/index.html | 2 +- web/protocol.js | 140 ++++++++++++++++++++++++++-------- web/protocol.test.js | 145 ++++++++++++++++++++++-------------- 4 files changed, 224 insertions(+), 100 deletions(-) diff --git a/docs/qemu_hisilicon_spec.md b/docs/qemu_hisilicon_spec.md index ea131b5..eb5203e 100644 --- a/docs/qemu_hisilicon_spec.md +++ b/docs/qemu_hisilicon_spec.md @@ -215,29 +215,38 @@ but with per-chunk acknowledgment: The firmware file contains three concatenated sections: **GSL (Generic SoC Library)**: -- Magic: `0x4BB4D22D` at file offset 2048 (little-endian uint32) -- Length: at file offset 2084 (little-endian uint32) -- Total size: length + 3072 bytes +- Magic: `0x4BB4D22D` in an aligned image header near the start of the file +- Known image-tool layouts place this header at `0x800` or `0x1200` +- Structure length: at magic+8 (little-endian uint32) +- Payload length: at magic+36 (little-endian uint32) +- Total size: magic offset + structure length + payload length - Data: file bytes 0 through gsl_size-1 **DDR Parameters**: -- Magic: `0x4B87A52D` at offset (gsl_size + 1024) -- Table offset: at magic+32 (uint32 LE) +- Magic: `0x4B87A52D` in the REE-key/header area following the GSL +- Structure length: at magic+8 (uint32 LE) +- Parameter-area offset: at magic+32 (uint32 LE) - Table size: at magic+36 (uint32 LE) - Table count: at magic+40 (uint32 LE) - Board mapping: 8 bytes at magic+300 (maps board_id → table_index) -- DDR table data: header (2048 bytes from gsl_size) + selected table +- First table: magic + structure length + parameter-area offset +- DDR transfer data: bytes from gsl_size through the first table + selected table **U-Boot**: -- Magic: `0x4BF01E2D` (searched after DDR params section) -- Length: at magic+36 (uint32 LE) -- Total size: length + 1024 bytes +- Magic: `0x4BF01E2D` immediately after all declared DDR tables +- Structure length: at magic+8 (uint32 LE) +- Payload length: at magic+36 (uint32 LE) +- Total size: structure length + payload length + +The parser must validate the complete GSL/DDR/U-Boot chain before accepting +a magic match. This prevents magic-like payload bytes from being interpreted +as image headers. ### Phase 4: Transfer Sequence All data transfers use the V500-style HEAD/DATA/TAIL with per-chunk ACK. -1. **Send GSL** to address `0x04020000` +1. **Send GSL** to address `0x04021A00` (`CP_STEP1_ADDR`) 2. **Query Board ID** (Phase 2 above) 3. **Build DDR table**: select table using board_mapping[board_id] 4. **Send DDR table** to address `0x41000000` @@ -250,7 +259,7 @@ All data transfers use the V500-style HEAD/DATA/TAIL with per-chunk ACK. 1. Respond to `DEADBEEF` handshake with `uart ddr\n` 2. Respond to `CE` board ID query with appropriate CPU ID and board ID -3. Accept GSL transfer at 0x04020000 — this is the initial boot code +3. Accept GSL transfer at 0x04021A00 — this is the initial boot code 4. Accept DDR table at 0x41000000 — use this to configure emulated DDR 5. Output DDR training status text (e.g., "DDR training OK\n") 6. Accept U-Boot transfer at 0x41000000 @@ -260,9 +269,9 @@ All data transfers use the V500-style HEAD/DATA/TAIL with per-chunk ACK. | Magic | Location | Purpose | |-------|----------|---------| -| `0x4BB4D22D` | File offset 2048 | GSL section marker | -| `0x4B87A52D` | After GSL + 1024 | DDR params section marker | -| `0x4BF01E2D` | After DDR params | U-Boot section marker | +| `0x4BB4D22D` | Aligned header near file start | GSL section marker | +| `0x4B87A52D` | REE-key/header area after GSL | DDR params section marker | +| `0x4BF01E2D` | Immediately after DDR tables | U-Boot section marker | | `0xDEADBEEF` | Handshake frame (reversed as EF BE AD DE) | CV6xx handshake magic | --- diff --git a/web/index.html b/web/index.html index d5d4275..4e3cba7 100644 --- a/web/index.html +++ b/web/index.html @@ -457,7 +457,7 @@

Serial Console

setStage('ddr'); log(`Sending GSL (${parts.gslData.length} bytes)...`, 'info'); - if (!await sendDataToBootrom(parts.gslData, 0x04020000, 'GSL', (s,t) => setProgress(s,t,'GSL'))) return fail('GSL failed'); + if (!await sendDataToBootrom(parts.gslData, CV6XX_GSL_LOAD_ADDR, 'GSL', (s,t) => setProgress(s,t,'GSL'))) return fail('GSL failed'); log(`Sending DDR table (${parts.ddrTable.length} bytes)...`, 'info'); if (!await sendDataToBootrom(parts.ddrTable, 0x41000000, 'DDR', (s,t) => setProgress(s,t,'DDR'))) return fail('DDR table failed'); diff --git a/web/protocol.js b/web/protocol.js index fd3683a..8716768 100644 --- a/web/protocol.js +++ b/web/protocol.js @@ -100,38 +100,116 @@ function chunkData(data, size = 1024) { } // CV6xx boot file parser +const CV6XX_GSL_LOAD_ADDR = 0x04021A00; +const CV6XX_IMAGE_ALIGNMENT = 0x100; +const CV6XX_CODE_ALIGNMENT = 0x200; +const CV6XX_HEADER_FIELDS_SIZE = 40; +const CV6XX_DDR_FIELDS_SIZE = 308; +const CV6XX_MAX_GSL_HEADER_OFFSET = 0x10000; +const CV6XX_MAX_SECTION_GAP = 0x1000; +const CV6XX_MAX_DDR_TABLES = 8; + +function cv6xxMagicOffsets(view, magic, start, end) { + const offsets = []; + const limit = Math.min(end, view.byteLength) - 4; + let offset = Math.ceil(start / CV6XX_IMAGE_ALIGNMENT) * CV6XX_IMAGE_ALIGNMENT; + for (; offset <= limit; offset += CV6XX_IMAGE_ALIGNMENT) { + if (view.getUint32(offset, true) === magic) offsets.push(offset); + } + return offsets; +} + +function parseCv6xxCodeImage(view, offset, magic) { + if (offset + CV6XX_HEADER_FIELDS_SIZE > view.byteLength) return null; + if (view.getUint32(offset, true) !== magic) return null; + + const version = view.getUint32(offset + 4, true); + const structureLength = view.getUint32(offset + 8, true); + const signatureLength = view.getUint32(offset + 12, true); + const payloadLength = view.getUint32(offset + 36, true); + if (version === 0 || structureLength < CV6XX_HEADER_FIELDS_SIZE || + structureLength % CV6XX_IMAGE_ALIGNMENT !== 0 || signatureLength === 0 || + signatureLength > structureLength || payloadLength === 0 || + payloadLength % CV6XX_CODE_ALIGNMENT !== 0) return null; + + const imageEnd = offset + structureLength + payloadLength; + if (imageEnd > view.byteLength) return null; + return { offset, end: imageEnd }; +} + +function parseCv6xxDdrParams(data, view, offset) { + if (offset + CV6XX_DDR_FIELDS_SIZE > view.byteLength) return null; + if (view.getUint32(offset, true) !== 0x4B87A52D) return null; + + const version = view.getUint32(offset + 4, true); + const structureLength = view.getUint32(offset + 8, true); + const signatureLength = view.getUint32(offset + 12, true); + const paramsAreaOffset = view.getUint32(offset + 32, true); + const tableSize = view.getUint32(offset + 36, true); + const tableCount = view.getUint32(offset + 40, true); + if (version === 0 || structureLength < CV6XX_DDR_FIELDS_SIZE || + structureLength % CV6XX_IMAGE_ALIGNMENT !== 0 || signatureLength === 0 || + signatureLength > structureLength || + paramsAreaOffset % CV6XX_IMAGE_ALIGNMENT !== 0 || tableSize === 0 || + tableSize % CV6XX_IMAGE_ALIGNMENT !== 0 || tableCount < 1 || + tableCount > CV6XX_MAX_DDR_TABLES) return null; + + const boardMapping = data.slice(offset + 300, offset + 308); + if (![...boardMapping].some(index => index < tableCount)) return null; + if ([...boardMapping].some(index => index !== 0xff && index >= tableCount)) return null; + + const tableOffset = offset + structureLength + paramsAreaOffset; + const tablesEnd = tableOffset + tableSize * tableCount; + if (tablesEnd > view.byteLength) return null; + return { offset, structureLength, tableOffset, tablesEnd, + tableSize, tableCount, boardMapping }; +} + function parseCv6xxBoot(data) { - const view = new DataView(data.buffer, data.byteOffset); - // GSL - const gslMagic = view.getUint32(2048, true); - if (gslMagic !== 0x4BB4D22D) throw new Error(`Invalid GSL magic: 0x${gslMagic.toString(16)}`); - const gslLen = view.getUint32(2084, true); - const gslSize = gslLen + 3072; - const gslData = data.slice(0, gslSize); - // DDR params - const paramsStart = gslSize + 1024; - const paramsMagic = view.getUint32(paramsStart, true); - if (paramsMagic !== 0x4B87A52D) throw new Error(`Invalid DDR params magic: 0x${paramsMagic.toString(16)}`); - const offset32 = view.getUint32(paramsStart + 32, true); - const tableSize = view.getUint32(paramsStart + 36, true); - const tableCount = view.getUint32(paramsStart + 40, true); - const boardMapping = data.slice(paramsStart + 300, paramsStart + 308); - // U-Boot - let ubootOffset = -1; - for (let i = paramsStart; i < data.length - 4; i++) { - if (data[i]===0x2d && data[i+1]===0x1e && data[i+2]===0xf0 && data[i+3]===0x4b) { ubootOffset = i; break; } + const view = new DataView(data.buffer, data.byteOffset, data.byteLength); + const layouts = []; + const gslScanEnd = Math.min(data.length, CV6XX_MAX_GSL_HEADER_OFFSET + 4); + + for (const gslOffset of cv6xxMagicOffsets(view, 0x4BB4D22D, 0, gslScanEnd)) { + const gsl = parseCv6xxCodeImage(view, gslOffset, 0x4BB4D22D); + if (!gsl) continue; + + const paramsScanEnd = Math.min(data.length, gsl.end + CV6XX_MAX_SECTION_GAP + 4); + for (const paramsOffset of cv6xxMagicOffsets( + view, 0x4B87A52D, gsl.end, paramsScanEnd, + )) { + const params = parseCv6xxDdrParams(data, view, paramsOffset); + if (!params) continue; + const uboot = parseCv6xxCodeImage(view, params.tablesEnd, 0x4BF01E2D); + if (!uboot) continue; + layouts.push({ gsl, params, uboot }); + } + } + + if (layouts.length === 0) { + throw new Error('No structurally valid CV6xx GSL/DDR/U-Boot layout found'); } - if (ubootOffset < 0) throw new Error('U-Boot magic not found'); - const ubootLen = view.getUint32(ubootOffset + 36, true); - const ubootData = data.slice(ubootOffset, ubootOffset + ubootLen + 1024); - // DDR table for board_id=0 - const mappedIdx = Math.min(boardMapping[0], tableCount - 1); - const ddrBuf = new Uint8Array(2048 + tableSize); - ddrBuf.set(data.slice(gslSize, gslSize + 2048)); - const tblOff = gslSize + 2048 + offset32 + mappedIdx * tableSize; - ddrBuf.set(data.slice(tblOff, tblOff + tableSize), 2048); - - return { gslData, gslSize, ddrTable: ddrBuf, ubootData, tableCount, tableSize }; + if (layouts.length > 1) { + throw new Error('Ambiguous CV6xx boot image: multiple valid GSL layouts found'); + } + + const { gsl, params, uboot } = layouts[0]; + const mapped = params.boardMapping[0]; + const mappedIndex = mapped < params.tableCount ? mapped : 0; + const ddrPrefixLength = params.tableOffset - gsl.end; + const ddrTable = new Uint8Array(ddrPrefixLength + params.tableSize); + ddrTable.set(data.slice(gsl.end, params.tableOffset)); + const selectedTable = params.tableOffset + mappedIndex * params.tableSize; + ddrTable.set(data.slice(selectedTable, selectedTable + params.tableSize), ddrPrefixLength); + + return { + gslData: data.slice(0, gsl.end), + gslSize: gsl.end, + ddrTable, + ubootData: data.slice(uboot.offset, uboot.end), + tableCount: params.tableCount, + tableSize: params.tableSize, + }; } // V500/CV6xx SoC lists @@ -389,7 +467,7 @@ if (typeof module !== 'undefined' && module.exports) { module.exports = { CRC_TABLE, calcCrc, appendCrc, appendCrcLE, verifyCrc, buildHeadFrame, buildDataFrame, buildTailFrame, chunkData, - parseCv6xxBoot, V500_SOCS, CV6XX_SOCS, + parseCv6xxBoot, CV6XX_GSL_LOAD_ADDR, V500_SOCS, CV6XX_SOCS, FRAME_BLAST_SOCS, needsFrameBlast, FW_RELEASE_API, FW_DIRECT_BASE, FW_ASSET_RE, FW_PROXIES, CHIP_FW_ALIAS, fwNameForChip, parseReleaseAssets, parseDigest, diff --git a/web/protocol.test.js b/web/protocol.test.js index f85d38e..7a45d4b 100644 --- a/web/protocol.test.js +++ b/web/protocol.test.js @@ -10,7 +10,7 @@ const assert = require('node:assert/strict'); const { CRC_TABLE, calcCrc, appendCrc, appendCrcLE, verifyCrc, buildHeadFrame, buildDataFrame, buildTailFrame, chunkData, - parseCv6xxBoot, V500_SOCS, CV6XX_SOCS, + parseCv6xxBoot, CV6XX_GSL_LOAD_ADDR, V500_SOCS, CV6XX_SOCS, FW_DIRECT_BASE, FW_PROXIES, fwNameForChip, parseReleaseAssets, parseDigest, fwSourceUrls, bytesToHex, verifyFirmwareBytes, PROXY_WINDOW_SECONDS, proxySignatureMessage, hmacSha256Hex, @@ -283,76 +283,113 @@ describe('chunkData', () => { // CV6xx Boot File Parser Tests // ================================================================ describe('parseCv6xxBoot', () => { - function buildTestFirmware(gslLen = 4096, tableCount = 2, tableSize = 1024, ubootLen = 8192) { - const gslSize = gslLen + 3072; - const paramsStart = gslSize + 1024; - const ubootOffset = paramsStart + 1024; - const totalSize = ubootOffset + ubootLen + 1024 + 40; - const buf = new ArrayBuffer(totalSize); - const data = new Uint8Array(buf); - const view = new DataView(buf); - - // GSL magic at offset 2048 - view.setUint32(2048, 0x4BB4D22D, true); - view.setUint32(2084, gslLen, true); - - // DDR params + function buildTestFirmware(options = {}) { + const layout = { + gslHeaderOffset: 0x800, + gslStructureLength: 0x400, + gslLength: 0x1000, + reeKeyLength: 0x400, + paramsStructureLength: 0x400, + paramsAreaOffset: 0, + tableCount: 2, + tableSize: 0x400, + ubootStructureLength: 0x400, + ubootLength: 0x2000, + ...options, + }; + const gslEnd = layout.gslHeaderOffset + layout.gslStructureLength + layout.gslLength; + const paramsStart = gslEnd + layout.reeKeyLength; + const firstTable = paramsStart + layout.paramsStructureLength + layout.paramsAreaOffset; + const ubootOffset = firstTable + layout.tableCount * layout.tableSize; + const data = new Uint8Array( + ubootOffset + layout.ubootStructureLength + layout.ubootLength, + ); + const view = new DataView(data.buffer); + + view.setUint32(layout.gslHeaderOffset, 0x4BB4D22D, true); + view.setUint32(layout.gslHeaderOffset + 4, 0x100, true); + view.setUint32(layout.gslHeaderOffset + 8, layout.gslStructureLength, true); + view.setUint32(layout.gslHeaderOffset + 12, 0x40, true); + view.setUint32(layout.gslHeaderOffset + 36, layout.gslLength, true); + view.setUint32(paramsStart, 0x4B87A52D, true); - view.setUint32(paramsStart + 32, 0, true); // offset_32 - view.setUint32(paramsStart + 36, tableSize, true); - view.setUint32(paramsStart + 40, tableCount, true); - for (let i = 0; i < 8; i++) data[paramsStart + 300 + i] = Math.min(i, tableCount - 1); + view.setUint32(paramsStart + 4, 0x100, true); + view.setUint32(paramsStart + 8, layout.paramsStructureLength, true); + view.setUint32(paramsStart + 12, 0x40, true); + view.setUint32(paramsStart + 32, layout.paramsAreaOffset, true); + view.setUint32(paramsStart + 36, layout.tableSize, true); + view.setUint32(paramsStart + 40, layout.tableCount, true); + for (let i = 0; i < 8; i++) { + data[paramsStart + 300 + i] = i < layout.tableCount ? i : 0xff; + } + for (let i = 0; i < layout.tableCount; i++) { + data.fill(i + 1, firstTable + i * layout.tableSize, firstTable + (i + 1) * layout.tableSize); + } - // U-Boot magic view.setUint32(ubootOffset, 0x4BF01E2D, true); - view.setUint32(ubootOffset + 36, ubootLen, true); + view.setUint32(ubootOffset + 4, 0x100, true); + view.setUint32(ubootOffset + 8, layout.ubootStructureLength, true); + view.setUint32(ubootOffset + 12, 0x40, true); + view.setUint32(ubootOffset + 36, layout.ubootLength, true); + + return { data, layout, gslEnd, paramsStart, firstTable, ubootOffset }; + } - return data; + for (const [name, options] of [ + ['0x800', {}], + ['0x1200', { + gslHeaderOffset: 0x1200, + gslStructureLength: 0x200, + reeKeyLength: 0x100, + paramsStructureLength: 0x200, + paramsAreaOffset: 0x100, + ubootStructureLength: 0x200, + }], + ]) { + it(`parses valid ${name} firmware layout`, () => { + const { data, layout, gslEnd } = buildTestFirmware(options); + const parts = parseCv6xxBoot(data); + assert.equal(parts.gslData.length, gslEnd); + assert.equal(parts.tableCount, layout.tableCount); + assert.equal(parts.tableSize, layout.tableSize); + assert.equal(parts.ubootData.length, layout.ubootStructureLength + layout.ubootLength); + }); } - it('parses valid firmware', () => { - const fw = buildTestFirmware(); - const parts = parseCv6xxBoot(fw); - assert.ok(parts.gslData.length > 0); - assert.equal(parts.tableCount, 2); - assert.equal(parts.tableSize, 1024); - assert.ok(parts.ubootData.length > 0); - assert.ok(parts.ddrTable.length > 0); + it('exports the CP_STEP1 GSL load address', () => { + assert.equal(CV6XX_GSL_LOAD_ADDR, 0x04021A00); }); it('rejects invalid GSL magic', () => { - const fw = buildTestFirmware(); - const view = new DataView(fw.buffer); - view.setUint32(2048, 0xDEADBEEF, true); - assert.throws(() => parseCv6xxBoot(fw), /Invalid GSL magic/); + const { data, layout } = buildTestFirmware(); + new DataView(data.buffer).setUint32(layout.gslHeaderOffset, 0xDEADBEEF, true); + assert.throws(() => parseCv6xxBoot(data), /No structurally valid/); }); - it('rejects invalid DDR params magic', () => { - const fw = buildTestFirmware(); - const gslLen = new DataView(fw.buffer).getUint32(2084, true); - const gslSize = gslLen + 3072; - const paramsStart = gslSize + 1024; - new DataView(fw.buffer).setUint32(paramsStart, 0xBADBAD, true); - assert.throws(() => parseCv6xxBoot(fw), /Invalid DDR params magic/); + it('ignores a plausible false GSL header before a valid layout', () => { + const { data, gslEnd } = buildTestFirmware({ gslHeaderOffset: 0x1200 }); + const view = new DataView(data.buffer); + view.setUint32(0x800, 0x4BB4D22D, true); + view.setUint32(0x804, 0x100, true); + view.setUint32(0x808, 0x200, true); + view.setUint32(0x80c, 0x40, true); + view.setUint32(0x824, 0x200, true); + + assert.equal(parseCv6xxBoot(data).gslSize, gslEnd); }); - it('rejects missing U-Boot magic', () => { - // Create firmware with no U-Boot magic by zeroing it out - const fw = buildTestFirmware(); - const gslLen = new DataView(fw.buffer).getUint32(2084, true); - const gslSize = gslLen + 3072; - const paramsStart = gslSize + 1024; - const ubootOffset = paramsStart + 1024; - new DataView(fw.buffer).setUint32(ubootOffset, 0x00000000, true); - assert.throws(() => parseCv6xxBoot(fw), /U-Boot magic not found/); + it('rejects truncated DDR tables', () => { + const { data, paramsStart } = buildTestFirmware(); + new DataView(data.buffer).setUint32(paramsStart + 36, data.length, true); + assert.throws(() => parseCv6xxBoot(data), /No structurally valid/); }); it('returns correct DDR table size', () => { - const fw = buildTestFirmware(4096, 3, 512, 8192); - const parts = parseCv6xxBoot(fw); + const { data } = buildTestFirmware({ tableCount: 3, tableSize: 0x200 }); + const parts = parseCv6xxBoot(data); assert.equal(parts.tableCount, 3); - assert.equal(parts.tableSize, 512); - assert.equal(parts.ddrTable.length, 2048 + 512); + assert.equal(parts.tableSize, 0x200); + assert.equal(parts.ddrTable.length, 0x800 + 0x200); }); });