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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ 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
* [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.MX91](https://www.nxp.com/products/i.MX91), [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)
* [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.MX91](https://www.nxp.com/products/i.MX91), [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), [i.MX95](https://www.nxp.com/products/i.MX95)
* [Texas Instruments](https://www.ti.com) [AM335x](https://www.ti.com/product/AM3358), [AM62x](https://www.ti.com/product/AM625), [AM62Lx](https://www.ti.com/product/AM62L), [AM64x](https://www.ti.com/product/AM6442), [AM654x](https://www.ti.com/product/AM6548)
* [Xilinx/AMD](https://www.amd.com/) [Zynq UltraScale+ MPSoC](https://www.amd.com/en/products/adaptive-socs-and-fpgas/soc/zynq-ultrascale-plus-mpsoc.html)
* [Intel](https://www.intel.com/) Keembay
Expand Down
2 changes: 1 addition & 1 deletion docs/developers/supporting_new_socs.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ quirks or particularities, it is recommended that you test them as well.
Here is a nonexhaustive list of i.MX SoCs supported by Snagrecover:

imx28, imx53, imx6q, imx6ull, imx7d, imx8mm, imx8qm, imx8qxp, imx91, imx93,
imx6d, imx6sl, imx6sll, ...
imx95, imx6d, imx6sl, imx6sll, ...

All of these devices are able to boot in USB recovery mode by exposing an HID
gadget to the host machine. A vendor-specific variety of the SDP protocol is
Expand Down
2 changes: 1 addition & 1 deletion docs/snagrecover.md
Original file line number Diff line number Diff line change
Expand Up @@ -350,7 +350,7 @@ You can generate this by compiling the u-boot.imx target in U-Boot.
configuration:
* path

### For NXP i.MX8, i.MX28 , i.MX91 and i.MX93 devices
### For NXP i.MX8, i.MX28 , i.MX91, i.MX93 and i.MX95 devices

[example](https://github.com/bootlin/snagboot/blob/main/src/snagrecover/templates/imx8-dart-mx8m-mini.yaml)

Expand Down
2 changes: 2 additions & 0 deletions src/snagrecover/50-snagboot.rules
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ SUBSYSTEM=="hidraw", SUBSYSTEMS=="usb", ATTRS{idVendor}=="0525", ATTRS{idProduct
SUBSYSTEM=="hidraw", SUBSYSTEMS=="usb", ATTRS{idVendor}=="1fc9", ATTRS{idProduct}=="0151", MODE="0660", TAG+="uaccess"
SUBSYSTEM=="hidraw", SUBSYSTEMS=="usb", ATTRS{idVendor}=="1fc9", ATTRS{idProduct}=="0152", MODE="0660", TAG+="uaccess"
SUBSYSTEM=="hidraw", SUBSYSTEMS=="usb", ATTRS{idVendor}=="1fc9", ATTRS{idProduct}=="0159", MODE="0660", TAG+="uaccess"
SUBSYSTEM=="hidraw", SUBSYSTEMS=="usb", ATTRS{idVendor}=="1fc9", ATTRS{idProduct}=="015d", MODE="0660", TAG+="uaccess"
SUBSYSTEM=="hidraw", SUBSYSTEMS=="usb", ATTRS{idVendor}=="3016", ATTRS{idProduct}=="0100", MODE="0660", TAG+="uaccess"

#NXP i.MX rules for libusb backend
Expand Down Expand Up @@ -66,6 +67,7 @@ SUBSYSTEM=="usb", ATTRS{idVendor}=="0525", ATTRS{idProduct}=="b4a4", MODE="0660"
SUBSYSTEM=="usb", ATTRS{idVendor}=="1fc9", ATTRS{idProduct}=="0151", MODE="0660", TAG+="uaccess"
SUBSYSTEM=="usb", ATTRS{idVendor}=="1fc9", ATTRS{idProduct}=="0152", MODE="0660", TAG+="uaccess"
SUBSYSTEM=="usb", ATTRS{idVendor}=="1fc9", ATTRS{idProduct}=="0159", MODE="0660", TAG+="uaccess"
SUBSYSTEM=="usb", ATTRS{idVendor}=="1fc9", ATTRS{idProduct}=="015d", MODE="0660", TAG+="uaccess"
SUBSYSTEM=="usb", ATTRS{idVendor}=="3016", ATTRS{idProduct}=="0100", MODE="0660", TAG+="uaccess"

#Allwinner SUNXI rules
Expand Down
1 change: 1 addition & 0 deletions src/snagrecover/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@
"imx865": "1fc9:0146",
"imx91": "1fc9:0159",
"imx93": "1fc9:014e",
"imx95": "1fc9:015d",
"imx7d": "15a2:0076",
"imx7s": "15a2:0076",
"imx6q": "15a2:0054",
Expand Down
42 changes: 27 additions & 15 deletions src/snagrecover/firmware/rom_container.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,9 @@
from snagrecover.config import recovery_config

CONTAINER_HDR_ALIGNMENT = 0x400
CONTAINER_HDR_ALIGNMENT_V2 = 0x4000
CONTAINER_TAG = 0x87
V2X_CONTAINER_TAG = 0x82
V2X_BOOTIMG_FLAG = 0x0B

ROM_CONTAINER_STRUCT_SIZE = 16
Expand All @@ -66,22 +68,32 @@ def get_container_size(boot_blob: bytes) -> int:
soc_model = recovery_config["soc_model"]
if soc_model in ["imx815", "imx865", "imx91", "imx93"]:
return len(boot_blob)
rom_container_tag = boot_blob[CONTAINER_HDR_ALIGNMENT + 3]
if rom_container_tag != CONTAINER_TAG:
return len(boot_blob)

cont_index = 1
romimg_offset = CONTAINER_HDR_ALIGNMENT + ROM_BOOTIMG_STRUCT_SIZE
romimg_flags = int.from_bytes(
boot_blob[romimg_offset + 24 : romimg_offset + 28], "little"
)
if romimg_flags & 0x0F == V2X_BOOTIMG_FLAG:
# skip V2X container
cont_index = 2
rom_container_tag = boot_blob[2 * CONTAINER_HDR_ALIGNMENT + 3]
if rom_container_tag != CONTAINER_TAG:
if boot_blob[3] == CONTAINER_TAG and boot_blob[0] >= 2:

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Please either define constants for the magic number offsets or assign them to explicitely named variables, so that it's clear what we're reading from the blob here.

# version >= 2 containers (e.g. i.MX95) use a larger alignment and a dedicated V2X tag
align = CONTAINER_HDR_ALIGNMENT_V2
cont_index = 1
if boot_blob[align + 3] == V2X_CONTAINER_TAG:
# skip V2X container
cont_index = 2
else:
align = CONTAINER_HDR_ALIGNMENT
if boot_blob[align + 3] != CONTAINER_TAG:
return len(boot_blob)
container_offset = cont_index * CONTAINER_HDR_ALIGNMENT
cont_index = 1
romimg_offset = align + ROM_BOOTIMG_STRUCT_SIZE
romimg_flags = int.from_bytes(
boot_blob[romimg_offset + 24 : romimg_offset + 28], "little"
)
if romimg_flags & 0x0F == V2X_BOOTIMG_FLAG:
# skip V2X container
cont_index = 2
if boot_blob[2 * align + 3] != CONTAINER_TAG:
return len(boot_blob)

container_offset = cont_index * align
if boot_blob[container_offset + 3] != CONTAINER_TAG:
return len(boot_blob)
num_images = int(boot_blob[container_offset + 11])
romimg_offset = (
container_offset
Expand All @@ -92,7 +104,7 @@ def get_container_size(boot_blob: bytes) -> int:
img_size = int.from_bytes(
boot_blob[romimg_offset + 4 : romimg_offset + 8], "little"
)
container_size = img_offset + img_size + cont_index * CONTAINER_HDR_ALIGNMENT
container_size = img_offset + img_size + container_offset
# round container size up
container_size = (
ceil(container_size / (1.0 * CONTAINER_HDR_ALIGNMENT)) * CONTAINER_HDR_ALIGNMENT
Expand Down
5 changes: 4 additions & 1 deletion src/snagrecover/protocols/fastboot.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,10 @@ def _is_cmd_supported(self, cmd: str) -> bool:
try:
self.cmd(cmd)
except FastbootError as e:
if e.data and FASTBOOT_UNRECOGNIZED_CMD_RESPONSE in e.data:
if e.data and (
FASTBOOT_UNSUPPORTED_CMD_RESPONSE in e.data
or FASTBOOT_UNRECOGNIZED_CMD_RESPONSE in e.data
):

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I disagree with this change, U-Boot returns "unrecognized" when the U-Boot command is unsupported, and "unsupported" when the Fastboot command is unsupported[1]. This check is meant to determine if the Fastboot "oem_run" command is supported or not, so we don't want to return False if the Fastboot command is supported but the U-Boot command isn't.

[1] https://elixir.bootlin.com/u-boot/v2026.04/source/drivers/fastboot/fb_command.c#L164

return False
return True

Expand Down
23 changes: 17 additions & 6 deletions src/snagrecover/protocols/hid.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ def get_hidraw_device(self):

return hidraw_path

def __init__(self, usb_dev: usb.core.Device):
def __init__(self, usb_dev: usb.core.Device, prefer_libusb: bool = False):
pretty_addr = prettify_usb_addr((usb_dev.bus, usb_dev.port_numbers))
if not is_hid(usb_dev):
raise IOError(f"Device {pretty_addr}, is USB but not HID!")
Expand All @@ -135,9 +135,11 @@ def __init__(self, usb_dev: usb.core.Device):
)
self.usb_dev.set_configuration(self.main_cfg.bConfigurationValue)

if platform.system() == "Linux" and self.usb_dev.is_kernel_driver_active(
self.main_intf.bInterfaceNumber
):
kernel_driver_active = platform.system() == "Linux" and (
self.usb_dev.is_kernel_driver_active(self.main_intf.bInterfaceNumber)
)

if kernel_driver_active and not prefer_libusb:
# The kernel driver in question should be usbhid
hidraw_path = self.get_hidraw_device()
if hidraw_path is None:
Expand All @@ -151,6 +153,9 @@ def __init__(self, usb_dev: usb.core.Device):
logger.info(f"HID device {pretty_addr} has hidraw dev {hidraw_path}")

else:
if kernel_driver_active:
self.usb_dev.detach_kernel_driver(self.main_intf.bInterfaceNumber)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Will this require root/admin privileges? Or just access rights to the USB device? Also, does it work properly on Windows?


# This case is for systems which don't
# have usbhid loaded for some reason.
# It is also suitable for Windows. if
Expand Down Expand Up @@ -230,7 +235,13 @@ def close(self):
if self.hidraw:
self.hidraw.close()
else:
usb.util.release_interface(self.usb_dev, self.main_intf.bInterfaceNumber)
try:
usb.util.release_interface(
self.usb_dev, self.main_intf.bInterfaceNumber
)
except usb.USBError:

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Please refine this catch with a check of the backend error code, so that we don't ignore errors related to access rights, target stalls etc. Something like this:

except usb.core.USBError as e:
    if e.errno != errno.ENODEV:
        raise e

# the device may already be gone (re-enumerated after jumping to firmware)
pass

def hidraw_read(self, length: int, timeout: int):
r, w, e = select.select([self.hidraw], [], [], timeout)
Expand All @@ -244,7 +255,7 @@ def hidraw_read(self, length: int, timeout: int):
def libusb_write(self, data: bytes):
# Use Interrupt OUT endpoint if available
if self.intr_out:
return self.usb_dev.write(self.intr_out, data)
return self.usb_dev.write(self.intr_out, data, timeout=5000)

report_id = data[0]
self.set_report(report_id, data)
Expand Down
26 changes: 23 additions & 3 deletions src/snagrecover/protocols/imx_sdp.py
Original file line number Diff line number Diff line change
Expand Up @@ -362,7 +362,15 @@ def sdps_write(self, blob: bytes, size: int) -> bool:
"""
logger.info(f"SDPS write with parameters size:0x{size:x} offset:0x00")
soc_model = recovery_config["soc_model"]
if soc_model not in ["imx8qm", "imx8qxp", "imx815", "imx865", "imx91", "imx93"]:
if soc_model not in [
"imx8qm",
"imx8qxp",
"imx815",
"imx865",
"imx91",
"imx93",
"imx95",
]:
# only some mpu models require a preliminary command before the report 2
# transfer
packet1_arr = bytearray(b"\x01") # report 1
Expand All @@ -376,14 +384,26 @@ def sdps_write(self, blob: bytes, size: int) -> bool:
packet1_arr += b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" # reserved
packet1 = bytes(packet1_arr)
self.dev.write(packet1)
if soc_model in ["imx815", "imx865", "imx91", "imx93"]:
if soc_model in ["imx815", "imx865", "imx91", "imx93", "imx95"]:
transfer_size = 1020
else:
transfer_size = 1024

sent = 0
for chunk in utils.dnload_iter(blob[0:size], transfer_size):
packet2 = b"\x02" + chunk
self.dev.write(packet2)
try:
self.dev.write(packet2)
except USBError:
# i.MX95's ROM re-enumerates once it has the first container, aborting the last transfer
if sent >= size - transfer_size:
logger.debug(
f"Device re-enumerated after {sent}/{size} bytes, "
"assuming first stage was accepted"
)
break
raise
sent += len(chunk)
"""
self.check_hab()
complete_status = self.dev.read(64, timeout=5)[:4]
Expand Down
14 changes: 9 additions & 5 deletions src/snagrecover/recoveries/imx.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@
"imx865",
"imx91",
"imx93",
"imx95",
]

# SoCs that use raw bulk endpoints rather than HID
Expand Down Expand Up @@ -145,18 +146,21 @@ def main():
soc_model = recovery_config["soc_model"]
usb_dev = get_usb(recovery_config["usb_path"])

# i.MX95 needs the libusb backend to reach its interrupt OUT endpoint
prefer_libusb = soc_model == "imx95"

if soc_model in raw_bulk_ep_socs:
sdp_cmd = SDPCommand(build_raw_ep_dev(usb_dev))
else:
sdp_cmd = SDPCommand(HIDDevice(usb_dev))
sdp_cmd = SDPCommand(HIDDevice(usb_dev, prefer_libusb=prefer_libusb))

rom_path = (usb_dev.bus, usb_dev.port_numbers)
rom_devnum = usb_dev.address

if soc_model in sdps_socs:
run_firmware(sdp_cmd, "flash-bin", "spl-sdps")
# On some SoCs (e.g.: i.MX8QM) we can have a second stage based on SPDV
if soc_model not in ["imx8qm", "imx8qxp"]:
# On some SoCs (e.g.: i.MX8QM, i.MX95) we can have a second stage based on SPDV
if soc_model not in ["imx8qm", "imx8qxp", "imx95"]:
return None
elif "u-boot-with-dcd" in recovery_config["firmware"]:
run_firmware(sdp_cmd, "u-boot-with-dcd")
Expand Down Expand Up @@ -185,10 +189,10 @@ def main():
if usb_dev is None:
access_error("SPL USB HID", f"{prettify_usb_addr(rom_path)}")

sdp_cmd = SDPCommand(HIDDevice(usb_dev))
sdp_cmd = SDPCommand(HIDDevice(usb_dev, prefer_libusb=prefer_libusb))
# MX8 boot images are more complicated to generate so we allow everything to be
# packaged in a single blob
if "imx8" in soc_model:
if "imx8" in soc_model or soc_model == "imx95":
if not dev_uses_sdpv(usb_dev):
raise Exception(
"Error: The installed SPL version does not support autofinding U-Boot"
Expand Down
2 changes: 2 additions & 0 deletions src/snagrecover/supported_socs.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ tested:
family: imx
imx93:
family: imx
imx95:
family: imx
keembay:
family: keembay
r8:
Expand Down
2 changes: 2 additions & 0 deletions src/snagrecover/templates/imx95-dart-mx95.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
flash-bin:
path: imx-boot