diff --git a/README.md b/README.md index 50dc2b0..1e3b725 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,7 @@ ISP, UUU, and sunxi-fel. Snagboot is made of two separate parts: Snagboot currently supports the following families of System-On-Chips (SoCs): * [Allwinner sunxi](https://linux-sunxi.org/) A10, A10S, A13, A20, A23, A31, A33, A63, A64, A80, A83T, AF1C100S, H2+, R8, R16, R40, R329, R528, T113-S3, V3S, V5S, V536, V831, V853 + * [Ambarella](https://www.ambarella.com/products/consumer/) CV22, CV25, CV28, CV35 * [STMicroelectronics](http://st.com/) [STM32MP1](https://www.st.com/en/microcontrollers-microprocessors/stm32mp1-series.html) and [STM32MP2](https://www.st.com/en/microcontrollers-microprocessors/stm32mp2-series.html) * [Microchip](https://www.microchip.com/) [SAMA5](https://www.microchip.com/en-us/products/microprocessors/32-bit-mpus/sama5) * [NXP](https://www.nxp.com/) [i.MX6](https://www.nxp.com/products/processors-and-microcontrollers/arm-processors/i-mx-applications-processors/i-mx-6-processors:IMX6X_SERIES), [i.MX7](https://www.nxp.com/products/processors-and-microcontrollers/arm-processors/i-mx-applications-processors/i-mx-7-processors:IMX7-SERIES), [i.MX8](https://www.nxp.com/products/processors-and-microcontrollers/arm-processors/i-mx-applications-processors/i-mx-8-applications-processors:IMX8-SERIES), [i.MX93](https://www.nxp.com/products/processors-and-microcontrollers/arm-processors/i-mx-applications-processors/i-mx-9-processors/i-mx-93-applications-processor-family-arm-cortex-a55-ml-acceleration-power-efficient-mpu:i.MX93) diff --git a/board_recovery.log b/board_recovery.log new file mode 100644 index 0000000..77d3a1e --- /dev/null +++ b/board_recovery.log @@ -0,0 +1,12 @@ +2025-05-17 09:50:45,266 [DEBUG] Searching for USB device paths matching 4255:0013... +2025-05-17 09:50:45,282 [DEBUG] recovery_config:{'soc_model': 'cv22', 'soc_family': 'ambarella', 'usb_path': (1, (1, 2)), 'firmware': {'vid': 7054, 'pid': 49186, 'protocol': 'amba', 'firmware': {'dram_script': {'path': 'cv22_lpddr4_408MHz.ads', 'description': 'DRAM initialization script for CV22 LPDDR4 at 408MHz'}, 'bootloader': {'path': 'cv22_bld_linux_emmc.bin', 'description': 'CV22 bootloader for Linux eMMC boot'}}, 'steps': [{'name': 'Initialize DRAM', 'action': 'initialize_dram', 'description': 'Initialize device DRAM using ADS script'}, {'name': 'Load bootloader', 'action': 'load_bootloader', 'description': 'Load bootloader to device memory'}, {'name': 'Flash firmware', 'action': 'flash_firmware', 'description': 'Flash firmware image to device', 'args': {'firmware': 'firmware.bin'}}], 'description': 'Recovery process for Ambarella CV22 devices:\n1. Device enters USB recovery mode\n2. Initialize DRAM using ADS script\n3. Load bootloader to memory\n4. Flash firmware image\n5. Device boots from flashed firmware\n', 'notes': '- Ensure device is in USB recovery mode before starting\n- DRAM script and bootloader files must be in firmware directory\n- Firmware image should be compatible with CV22\n'}, 'args': {'soc': 'cv22', 'firmware_file': ['src/snagrecover/templates/ambarella-cv22.yaml'], 'firmware': None, 'uart': None, 'baudrate': 115200, 'netns': 'snagbootnet', 'loglevel': 'debug', 'logfile': 'board_recovery.log', 'rom_usb': None, 'usb_path': None, 'list_socs': False, 'version': False, 'template': None, 'udev': False, 'am335x_setup': False}} +2025-05-17 09:50:45,283 [INFO] Starting recovery of cv22 board +2025-05-17 09:50:45,289 [INFO] Done recovering cv22 board +2025-05-17 09:50:45,290 [INFO] Logs were appended to board_recovery.log +2025-05-17 09:50:45,290 [INFO] Done recovering cv22 board +2025-05-17 09:52:22,581 [DEBUG] Searching for USB device paths matching 4255:0013... +2025-05-17 09:52:22,594 [DEBUG] recovery_config:{'soc_model': 'cv22', 'soc_family': 'ambarella', 'usb_path': (1, (1, 2)), 'firmware': {'vid': 7054, 'pid': 49186, 'protocol': 'amba', 'firmware': {'dram_script': {'path': 'cv22_lpddr4_408MHz.ads', 'description': 'DRAM initialization script for CV22 LPDDR4 at 408MHz'}, 'bootloader': {'path': 'cv22_bld_linux_emmc.bin', 'description': 'CV22 bootloader for Linux eMMC boot'}}, 'steps': [{'name': 'Initialize DRAM', 'action': 'initialize_dram', 'description': 'Initialize device DRAM using ADS script'}, {'name': 'Load bootloader', 'action': 'load_bootloader', 'description': 'Load bootloader to device memory'}, {'name': 'Flash firmware', 'action': 'flash_firmware', 'description': 'Flash firmware image to device', 'args': {'firmware': 'firmware.bin'}}], 'description': 'Recovery process for Ambarella CV22 devices:\n1. Device enters USB recovery mode\n2. Initialize DRAM using ADS script\n3. Load bootloader to memory\n4. Flash firmware image\n5. Device boots from flashed firmware\n', 'notes': '- Ensure device is in USB recovery mode before starting\n- DRAM script and bootloader files must be in firmware directory\n- Firmware image should be compatible with CV22\n'}, 'args': {'soc': 'cv22', 'firmware_file': ['src/snagrecover/templates/ambarella-cv22.yaml'], 'firmware': None, 'uart': None, 'baudrate': 115200, 'netns': 'snagbootnet', 'loglevel': 'debug', 'logfile': 'board_recovery.log', 'rom_usb': None, 'usb_path': None, 'list_socs': False, 'version': False, 'template': None, 'udev': False, 'am335x_setup': False}} +2025-05-17 09:52:22,594 [INFO] Starting recovery of cv22 board +2025-05-17 09:52:22,601 [INFO] Done recovering cv22 board +2025-05-17 09:52:22,601 [INFO] Logs were appended to board_recovery.log +2025-05-17 09:52:22,601 [INFO] Done recovering cv22 board diff --git a/docs/board_setup.md b/docs/board_setup.md index 3767f4e..ec14567 100644 --- a/docs/board_setup.md +++ b/docs/board_setup.md @@ -71,6 +71,28 @@ for specific instructions. After powering on, the board should be detected as a USB device with VID:PID `8087:0b39`. +## Ambarella SoCs + +Ambarella SoCs have a USB boot mode that can be accessed by setting specific pins during reset. The exact configuration varies by board model, so refer to your board's documentation for specific instructions. + +1. Power off the board +2. Set the boot mode pins to USB boot mode (typically by setting a jumper or DIP switch) +3. Connect the USB cable to the USB port (usually the OTG port) +4. Power on the board + +After powering on, the board should be detected as a USB device. The recovery process involves: +1. Initializing DRAM using an ADS script +2. Loading a bootloader +3. Entering Amboot mode +4. Loading and executing U-Boot + +You will need the following files: +- DRAM initialization script (e.g., cv22_lpddr4_408MHz.ads) +- Bootloader binary (e.g., cv22_bld_linux_emmc.bin) +- U-Boot binary + +These files should be specified in your firmware configuration file. + ## The special case of TI AM335x devices During initialization, the ROM code will set up a boot device list and for each diff --git a/docs/fw_binaries.md b/docs/fw_binaries.md index d60eaf2..8a7dd39 100644 --- a/docs/fw_binaries.md +++ b/docs/fw_binaries.md @@ -306,7 +306,6 @@ configuration: - For M2 boards: `fip-m2.bin` - For HDDL2 boards: `fip-hddl2.bin` - ## For Broadcom BCM devices [example](../src/snagrecover/templates/bcm2711.yaml) @@ -352,3 +351,25 @@ configuration: configuration: * path + +## For Ambarella SoCs + +[example](../src/snagrecover/templates/ambarella-cv22.yaml) + +Ambarella SoCs use a multi-stage recovery process: +1. Initialize DRAM using an ADS script +2. Load a bootloader +3. Enter Amboot mode +4. Load and execute U-Boot + +The following files are required for Ambarella SoCs: + +**dram_init:** ADS script for DRAM initialization. This script contains commands to initialize the DRAM controller and configure the memory settings for the specific SoC and memory type. + +configuration: + * path: Path to the ADS script file, e.g., `cv22_lpddr4_408MHz.ads` + +**bootloader:** Bootloader binary that initializes the system and enters Amboot mode. This is typically a binary provided by Ambarella or your board vendor. + +configuration: + * path: Path to the bootloader binary, e.g., `cv22_bld_linux_emmc.bin` diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md index a3d2386..0682eef 100644 --- a/docs/troubleshooting.md +++ b/docs/troubleshooting.md @@ -47,3 +47,22 @@ ideal scenario, each board is plugged into a separate root hub port, but if an external hub is absolutely necessary, using one with a higher capacity or making sure that it is powered by an independent supply can help. +## Ambarella SoC recovery issues + +### USB device not detected + +If the Ambarella SoC is not detected as a USB device, make sure: +1. The board is properly set to USB boot mode using the appropriate jumpers or switches +2. The USB cable is connected to the correct port (usually the OTG port) +3. The board is powered on + +### DRAM initialization fails + +DRAM initialization can fail if the ADS script doesn't match the specific memory configuration of your board. Make sure you're using the correct ADS script for your specific board model and memory type. + +### Bootloader fails to enter Amboot mode + +If the bootloader fails to enter Amboot mode: +1. Check that you're using the correct bootloader binary for your SoC model +2. Ensure the board has enough power (some boards may require a powered USB hub) +3. Try resetting the board and starting the recovery process again diff --git a/src/snagrecover/50-snagboot.rules b/src/snagrecover/50-snagboot.rules index 7de5e66..1555b7a 100644 --- a/src/snagrecover/50-snagboot.rules +++ b/src/snagrecover/50-snagboot.rules @@ -85,3 +85,6 @@ SUBSYSTEM=="usb", ATTRS{idVendor}=="8087", ATTRS{idProduct}=="0b39", MODE="0660" #Broadcom rules SUBSYSTEM=="usb", ATTRS{idVendor}=="0a5c", ATTRS{idProduct}=="2711", MODE="0660", TAG+="uaccess" SUBSYSTEM=="usb", ATTRS{idVendor}=="0a5c", ATTRS{idProduct}=="2712", MODE="0660", TAG+="uaccess" + +#Ambarella rules +SUBSYSTEM=="usb", ATTRS{idVendor}=="1d6b", ATTRS{idProduct}=="0104", MODE="0660", TAG+="uaccess" diff --git a/src/snagrecover/firmware/amba_fw.py b/src/snagrecover/firmware/amba_fw.py new file mode 100644 index 0000000..c8bd4fc --- /dev/null +++ b/src/snagrecover/firmware/amba_fw.py @@ -0,0 +1,130 @@ +# This file is part of Snagboot +# Copyright (C) 2025 Petr Hodina +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# + +import struct +from dataclasses import dataclass +from pathlib import Path +from typing import Optional + +from .firmware import Firmware, FirmwareError + +FIRM_OFFSET_VERSION = 0x3C +FIRM_OFFSET_MEMFW_RESULT = 0x40 +FIRM_OFFSET_MEMFW_CMD = 0x50 +FIRM_OFFSET_MEMFW_PROG = 0x60 + +BOARD_INFO_MAGIC = 0x12345678 +BOARD_INFO_ADDR = 0x100000 +PTB_PTR = 0x200000 + +FW_INFO_MAGIC = 0x87654321 +FW_INFO_ADDR = 0x110000 + + +@dataclass +class AmbaFirmwareInfo: + version: int + memfw_result_addr: int + memfw_cmd_addr: int + memfw_prog_addr: int + + +class AmbaFirmware(Firmware): + def __init__( + self, + bootloader_path: Optional[Path] = None, + dram_script_path: Optional[Path] = None, + ): + super().__init__() + self.bootloader_path = bootloader_path + self.dram_script_path = dram_script_path + self._bootloader_data: Optional[bytes] = None + self._dram_script_data: Optional[str] = None + + def load(self) -> None: + if self.bootloader_path: + try: + with open(self.bootloader_path, "rb") as f: + self._bootloader_data = f.read() + except OSError as e: + raise FirmwareError("Failed to load bootloader") from e + + if self.dram_script_path: + try: + with open(self.dram_script_path, "r") as f: + self._dram_script_data = f.read() + except OSError as e: + raise FirmwareError("Failed to load DRAM script") from e + + @property + def bootloader(self) -> bytes: + if not self._bootloader_data: + raise FirmwareError("Bootloader not loaded") + return self._bootloader_data + + @property + def dram_script(self) -> str: + if not self._dram_script_data: + raise FirmwareError("DRAM script not loaded") + return self._dram_script_data + + @staticmethod + def get_firmware_info(firmware_path: Path) -> AmbaFirmwareInfo: + try: + with open(firmware_path, "rb") as f: + f.seek(FIRM_OFFSET_VERSION) + version = struct.unpack(" bytes: + return struct.pack( + " bytes: + return struct.pack( + " bytes: + struct.pack_into( + " Tuple[int, int, int, int]: + return struct.unpack_from(" Tuple[int, int, int]: + try: + cmd_buf = self._pack_command(cmd, *params) + self.device.write(cmd_buf) + + self.device.read(self._rsp_buf) + sig, rsp, p0, p1 = self._unpack_response() + + if sig != RSP_SIGNATURE: + raise AmbaUsbError("Invalid response signature") + + return rsp, p0, p1 + + except UsbError as e: + raise AmbaUsbError("USB communication error") from e + + def get_device_info(self) -> AmbaDeviceInfo: + rsp, chip, _ = self.send_command( + AmbaCommand.INQUIRY_STATUS, AmbaInquiryType.CHIP + ) + if rsp != AmbaResponse.SUCCESS: + raise AmbaUsbError("Failed to get chip type") + + rsp, dram_start, _ = self.send_command( + AmbaCommand.INQUIRY_STATUS, AmbaInquiryType.ADDR + ) + if rsp != AmbaResponse.SUCCESS: + raise AmbaUsbError("Failed to get DRAM start address") + + return AmbaDeviceInfo(chip, dram_start) + + def send_file(self, addr: int, data: bytes) -> None: + rsp, _, _ = self.send_command(AmbaCommand.RDY_TO_RCV, addr) + if rsp != AmbaResponse.SUCCESS: + raise AmbaUsbError("Device not ready to receive") + + rsp, _, _ = self.send_command(AmbaCommand.RCV_DATA) + if rsp != AmbaResponse.SUCCESS: + raise AmbaUsbError("Failed to initiate data transfer") + + try: + self.device.write(data) + except UsbError as e: + raise AmbaUsbError("Failed to send data") from e + + rsp, _, _ = self.send_command( + AmbaCommand.RDY_TO_RCV, + 0x80000000, + addr, + ) + if rsp != AmbaResponse.SUCCESS: + raise AmbaUsbError("Data transfer failed") diff --git a/src/snagrecover/recoveries/ambarella.py b/src/snagrecover/recoveries/ambarella.py new file mode 100644 index 0000000..dfd92ad --- /dev/null +++ b/src/snagrecover/recoveries/ambarella.py @@ -0,0 +1,156 @@ +# This file is part of Snagboot +# Copyright (C) 2025 Petr Hodina +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# + +import re +import time +from dataclasses import dataclass +from enum import IntEnum +from pathlib import Path +from typing import List, Optional + +from ..firmware.amba_fw import AmbaFirmware +from ..protocols.amba import AmbaCommand, AmbaProtocol +from ..usb import UsbDevice + + +class AdsCommandType(IntEnum): + INVALID = 0 + WRITE = 1 + READ = 2 + POLL = 3 + USLEEP = 4 + SLEEP = 5 + + +@dataclass +class AdsCommand: + type: AdsCommandType + addr: int = 0 + data: int = 0 + mask: int = 0 + + +class AdsParser: + def parse(self, script: str) -> List[AdsCommand]: + commands = [] + + for line in script.splitlines(): + line = line.strip() + + if not line or line.startswith("#"): + continue + + if cmd := self._parse_line(line): + commands.append(cmd) + + return commands + + def _parse_line(self, line: str) -> Optional[AdsCommand]: + # Write command: w addr data [mask] + if m := re.match( + r"w\s+0x([0-9a-f]+)\s+0x([0-9a-f]+)(?:\s+0x([0-9a-f]+))?", line, re.I + ): + addr = int(m.group(1), 16) + data = int(m.group(2), 16) + mask = int(m.group(3), 16) if m.group(3) else 0xFFFFFFFF + return AdsCommand(AdsCommandType.WRITE, addr, data, mask) + + # Read command: r addr + if m := re.match(r"r\s+0x([0-9a-f]+)", line, re.I): + addr = int(m.group(1), 16) + return AdsCommand(AdsCommandType.READ, addr) + + # Poll command: p addr data mask + if m := re.match( + r"p\s+0x([0-9a-f]+)\s+0x([0-9a-f]+)\s+0x([0-9a-f]+)", line, re.I + ): + addr = int(m.group(1), 16) + data = int(m.group(2), 16) + mask = int(m.group(3), 16) + return AdsCommand(AdsCommandType.POLL, addr, data, mask) + + # Sleep commands: sleep/usleep time + if m := re.match(r"(sleep|usleep)\s+(\d+)", line): + cmd_type = ( + AdsCommandType.SLEEP if m.group(1) == "sleep" else AdsCommandType.USLEEP + ) + time_val = int(m.group(2)) + return AdsCommand(cmd_type, data=time_val) + + return None + + +class AmbarellaRecovery: + def __init__(self, device: UsbDevice): + self.device = device + self.protocol = AmbaProtocol(device) + self.firmware = AmbaFirmware() + self._ads_parser = AdsParser() + + def _execute_ads_commands(self, commands: List[AdsCommand]) -> None: + for cmd in commands: + if cmd.type == AdsCommandType.WRITE: + self.protocol.send_command(AmbaCommand.RDY_TO_RCV, cmd.addr) + self.protocol.send_command(AmbaCommand.RCV_DATA, cmd.data) + + elif cmd.type == AdsCommandType.POLL: + timeout = time.time() + 5.0 # 5 second timeout + while time.time() < timeout: + rsp, val, _ = self.protocol.send_command( + AmbaCommand.INQUIRY_STATUS, cmd.addr + ) + if (val & cmd.mask) == (cmd.data & cmd.mask): + break + time.sleep(0.001) + + elif cmd.type in (AdsCommandType.SLEEP, AdsCommandType.USLEEP): + sleep_time = ( + cmd.data / 1_000_000 + if cmd.type == AdsCommandType.USLEEP + else cmd.data + ) + time.sleep(sleep_time) + + def initialize_dram(self, dram_script: Path) -> None: + self.firmware.dram_script_path = dram_script + self.firmware.load() + + commands = self._ads_parser.parse(self.firmware.dram_script) + self._execute_ads_commands(commands) + + def load_bootloader(self, bootloader: Path) -> None: + self.firmware.bootloader_path = bootloader + self.firmware.load() + + self.protocol.send_file(0x0, self.firmware.bootloader) + + board_info = AmbaFirmware.pack_board_info() + self.protocol.send_file(AmbaFirmware.BOARD_INFO_ADDR, board_info) + + def flash_firmware(self, firmware: Path) -> None: + fw_info = AmbaFirmware.get_firmware_info(firmware) + + fw_info_data = AmbaFirmware.pack_firmware_info(fw_info) + self.protocol.send_file(AmbaFirmware.FW_INFO_ADDR, fw_info_data) + + board_info = AmbaFirmware.pack_board_info() + self.protocol.send_file(AmbaFirmware.BOARD_INFO_ADDR, board_info) + + with open(firmware, "rb") as f: + fw_data = f.read() + self.protocol.send_file(fw_info.memfw_prog_addr, fw_data) diff --git a/src/snagrecover/supported_socs.yaml b/src/snagrecover/supported_socs.yaml index dc46191..a846235 100644 --- a/src/snagrecover/supported_socs.yaml +++ b/src/snagrecover/supported_socs.yaml @@ -67,6 +67,8 @@ tested: family: bcm bcm2712: family: bcm + cv22: + family: ambarella untested: a10: family: sunxi @@ -154,3 +156,11 @@ untested: family: sunxi v853: family: sunxi + cv22: + family: ambarella + cv25: + family: ambarella + cv28: + family: ambarella + cv35: + family: ambarella diff --git a/src/snagrecover/templates/ambarella-cv22.yaml b/src/snagrecover/templates/ambarella-cv22.yaml new file mode 100644 index 0000000..16fbbc3 --- /dev/null +++ b/src/snagrecover/templates/ambarella-cv22.yaml @@ -0,0 +1,51 @@ +# Ambarella CV22 recovery configuration + +# Device identification +vid: 0x1b8e # Ambarella vendor ID +pid: 0xc022 # CV22 product ID + +# Recovery protocol +protocol: amba + +# Required firmware files +firmware: + # DRAM initialization script + dram_script: + path: cv22_lpddr4_408MHz.ads + description: DRAM initialization script for CV22 LPDDR4 at 408MHz + + # Bootloader binary + bootloader: + path: cv22_bld_linux_emmc.bin + description: CV22 bootloader for Linux eMMC boot + +# Recovery steps +steps: + - name: Initialize DRAM + action: initialize_dram + description: Initialize device DRAM using ADS script + + - name: Load bootloader + action: load_bootloader + description: Load bootloader to device memory + + - name: Flash firmware + action: flash_firmware + description: Flash firmware image to device + args: + firmware: firmware.bin # Firmware image to flash + +# Recovery flow description +description: | + Recovery process for Ambarella CV22 devices: + 1. Device enters USB recovery mode + 2. Initialize DRAM using ADS script + 3. Load bootloader to memory + 4. Flash firmware image + 5. Device boots from flashed firmware + +# Additional notes +notes: | + - Ensure device is in USB recovery mode before starting + - DRAM script and bootloader files must be in firmware directory + - Firmware image should be compatible with CV22 diff --git a/src/snagrecover/utils.py b/src/snagrecover/utils.py index 94ae2fd..7f4b524 100644 --- a/src/snagrecover/utils.py +++ b/src/snagrecover/utils.py @@ -249,6 +249,10 @@ def get_recovery(soc_family: str): from snagrecover.recoveries.bcm import main as bcm_recovery return bcm_recovery + elif soc_family == "ambarella": + from snagrecover.recoveries.ambarella import main as ambarella_recovery + + return ambarella_recovery else: cli_error(f"unsupported board family {soc_family}")