diff --git a/README.md b/README.md index e808e84..47ebf5a 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/docs/developers/supporting_new_socs.md b/docs/developers/supporting_new_socs.md index f8d6525..b4fb604 100644 --- a/docs/developers/supporting_new_socs.md +++ b/docs/developers/supporting_new_socs.md @@ -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 diff --git a/docs/snagrecover.md b/docs/snagrecover.md index baa7506..e7c333f 100644 --- a/docs/snagrecover.md +++ b/docs/snagrecover.md @@ -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) diff --git a/src/snagrecover/50-snagboot.rules b/src/snagrecover/50-snagboot.rules index 01b74e1..0af1425 100644 --- a/src/snagrecover/50-snagboot.rules +++ b/src/snagrecover/50-snagboot.rules @@ -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 @@ -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 diff --git a/src/snagrecover/config.py b/src/snagrecover/config.py index 29e7c88..700d6e1 100644 --- a/src/snagrecover/config.py +++ b/src/snagrecover/config.py @@ -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", diff --git a/src/snagrecover/firmware/rom_container.py b/src/snagrecover/firmware/rom_container.py index 77c26b1..c362b56 100644 --- a/src/snagrecover/firmware/rom_container.py +++ b/src/snagrecover/firmware/rom_container.py @@ -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 @@ -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: + # 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 @@ -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 diff --git a/src/snagrecover/protocols/fastboot.py b/src/snagrecover/protocols/fastboot.py index b601f12..34b6144 100644 --- a/src/snagrecover/protocols/fastboot.py +++ b/src/snagrecover/protocols/fastboot.py @@ -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 + ): return False return True diff --git a/src/snagrecover/protocols/hid.py b/src/snagrecover/protocols/hid.py index ba6474b..a4d0db7 100644 --- a/src/snagrecover/protocols/hid.py +++ b/src/snagrecover/protocols/hid.py @@ -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!") @@ -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: @@ -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) + # This case is for systems which don't # have usbhid loaded for some reason. # It is also suitable for Windows. if @@ -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: + # 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) @@ -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) diff --git a/src/snagrecover/protocols/imx_sdp.py b/src/snagrecover/protocols/imx_sdp.py index 4ba58f1..ab7db75 100644 --- a/src/snagrecover/protocols/imx_sdp.py +++ b/src/snagrecover/protocols/imx_sdp.py @@ -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 @@ -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] diff --git a/src/snagrecover/recoveries/imx.py b/src/snagrecover/recoveries/imx.py index a327a2b..983bc9f 100644 --- a/src/snagrecover/recoveries/imx.py +++ b/src/snagrecover/recoveries/imx.py @@ -74,6 +74,7 @@ "imx865", "imx91", "imx93", + "imx95", ] # SoCs that use raw bulk endpoints rather than HID @@ -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") @@ -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" diff --git a/src/snagrecover/supported_socs.yaml b/src/snagrecover/supported_socs.yaml index b001f42..3f2a0b4 100644 --- a/src/snagrecover/supported_socs.yaml +++ b/src/snagrecover/supported_socs.yaml @@ -58,6 +58,8 @@ tested: family: imx imx93: family: imx + imx95: + family: imx keembay: family: keembay r8: diff --git a/src/snagrecover/templates/imx95-dart-mx95.yaml b/src/snagrecover/templates/imx95-dart-mx95.yaml new file mode 100644 index 0000000..02273b0 --- /dev/null +++ b/src/snagrecover/templates/imx95-dart-mx95.yaml @@ -0,0 +1,2 @@ +flash-bin: + path: imx-boot