Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
12 changes: 12 additions & 0 deletions board_recovery.log
Original file line number Diff line number Diff line change
@@ -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
22 changes: 22 additions & 0 deletions docs/board_setup.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
23 changes: 22 additions & 1 deletion docs/fw_binaries.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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`
19 changes: 19 additions & 0 deletions docs/troubleshooting.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Copy link
Collaborator

Choose a reason for hiding this comment

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

Same comment as above, this applies to every board which relies on closed-source firmware binaries.


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
3 changes: 3 additions & 0 deletions src/snagrecover/50-snagboot.rules
Original file line number Diff line number Diff line change
Expand Up @@ -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"
130 changes: 130 additions & 0 deletions src/snagrecover/firmware/amba_fw.py
Original file line number Diff line number Diff line change
@@ -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
Copy link
Collaborator

Choose a reason for hiding this comment

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

These Firmware and FirmwareError classes aren't defined in firmware.py, are you missing some commits in your series?

+ same comment as for the previous commit regarding relative path imports.


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("<I", f.read(4))[0]

f.seek(FIRM_OFFSET_MEMFW_RESULT)
result_addr = struct.unpack("<I", f.read(4))[0]

f.seek(FIRM_OFFSET_MEMFW_CMD)
cmd_addr = struct.unpack("<I", f.read(4))[0]

f.seek(FIRM_OFFSET_MEMFW_PROG)
prog_addr = struct.unpack("<I", f.read(4))[0]

return AmbaFirmwareInfo(
version=version,
memfw_result_addr=result_addr,
memfw_cmd_addr=cmd_addr,
memfw_prog_addr=prog_addr,
)

except (OSError, struct.error) as e:
raise FirmwareError("Failed to extract firmware info") from e

@staticmethod
def pack_board_info() -> bytes:
return struct.pack(
"<IIII",
BOARD_INFO_MAGIC,
0x6F547541, # 'AuTo' in little endian
PTB_PTR,
0,
) # reserved

@staticmethod
def pack_firmware_info(fw_info: AmbaFirmwareInfo) -> bytes:
return struct.pack(
"<IIII",
FW_INFO_MAGIC,
fw_info.memfw_cmd_addr,
fw_info.memfw_result_addr,
0,
)
4 changes: 4 additions & 0 deletions src/snagrecover/firmware/firmware.py
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,10 @@ def run_firmware(port, fw_name: str, subfw_name: str = ""):
from snagrecover.firmware.bcm import bcm_run
Copy link
Collaborator

Choose a reason for hiding this comment

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

commit-wide comment: the commit history of Snagboot should be bisectable as much as possible, meaning each commit should allow Snagboot to run without errors.

In this commit, you're adding references to modules which don't exist yet.

I'd prefer if you reordered the commits so that each one is valid Python. So in this specific case, the commits adding the protocol and firmware code should come before this one.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Okay, will rearrange the code so it's self contained and does not require any future code.


bcm_run(port, fw_name, fw_blob, subfw_name)
elif soc_family == "ambarella":
from snagrecover.firmware.amba_fw import run_firmware as amba_run

amba_run(port, fw_name, subfw_name)
else:
raise Exception(f"Unsupported SoC family {soc_family}")
logger.info(f"Done installing firmware {fw_name}")
131 changes: 131 additions & 0 deletions src/snagrecover/protocols/amba.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
# This file is part of Snagboot
# Copyright (C) 2023 Bootlin
Copy link
Collaborator

Choose a reason for hiding this comment

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

The copyright holder is you, not Bootlin. The rest of the license header is OK.

#
# 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 enum import IntEnum
from typing import Tuple

from ..usb import UsbDevice, UsbError
Copy link
Collaborator

Choose a reason for hiding this comment

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

UsbDevice and UsbError are not defined in usb.py, are you missing some commits in your series?

Also, please use from snagrecover.etc when importing local modules, as I'm not sure how the relative path imports will behave with certain installation methods.


CMD_SIGNATURE = 0x55434D44
RSP_SIGNATURE = 0x55525350


class AmbaUsbError(Exception):
pass


class AmbaInquiryType(IntEnum):
CHIP = 0x00000001
ADDR = 0x00000002
REG = 0x00000003


class AmbaCommand(IntEnum):
RDY_TO_RCV = 0
RCV_DATA = 1
RDY_TO_SND = 2
SND_DATA = 3
INQUIRY_STATUS = 4


class AmbaResponse(IntEnum):
SUCCESS = 0
FAILED = 1
IN_BLD = 2


@dataclass
class AmbaDeviceInfo:
chip_type: int
dram_start: int


class AmbaProtocol:
def __init__(self, device: UsbDevice):
self.device = device
self._cmd_buf = bytearray(32)
self._rsp_buf = bytearray(16)

def _pack_command(self, cmd: int, *params) -> bytes:
struct.pack_into(
"<IIIIIIII",
self._cmd_buf,
0,
CMD_SIGNATURE,
cmd,
*(params + (0,) * (6 - len(params))),
)
return self._cmd_buf

def _unpack_response(self) -> Tuple[int, int, int, int]:
return struct.unpack_from("<IIII", self._rsp_buf)

def send_command(self, cmd: int, *params) -> 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")
Loading
Loading