From 0f6b7629a23525adbc6ea277ad9dd37f8e69c91f Mon Sep 17 00:00:00 2001 From: Romain Gantois Date: Wed, 29 May 2024 17:18:56 +0200 Subject: [PATCH] snagrecover: hide USB vid:pid addresses from recovery modules Some recovery modules currently take an additional "usb" argument for later bootloader stages. This is unnecessary, as the USB path is invariant during a recovery session. Deprecate the "rom-usb" CLI parameter in favor of a more aptly-named "usb-path" parameter. Add a method to convert vid:pid addresses to USB paths and use it to hide vid:pid addresses from recovery modules. Signed-off-by: Romain Gantois --- docs/fw_binaries.md | 19 +++------ src/snagflash/dfu.py | 7 +++- src/snagflash/fastboot.py | 9 ++++- src/snagrecover/cli.py | 29 ++++---------- src/snagrecover/config.py | 22 +++++------ src/snagrecover/recoveries/am62x.py | 6 +-- src/snagrecover/recoveries/imx.py | 2 +- src/snagrecover/recoveries/sama5.py | 2 +- src/snagrecover/recoveries/stm32mp1.py | 21 +++++----- src/snagrecover/recoveries/sunxi.py | 2 +- src/snagrecover/utils.py | 54 +++++++++++++++++++++++++- 11 files changed, 102 insertions(+), 71 deletions(-) diff --git a/docs/fw_binaries.md b/docs/fw_binaries.md index 68fa3b3..e174f50 100644 --- a/docs/fw_binaries.md +++ b/docs/fw_binaries.md @@ -39,16 +39,11 @@ when configuring U-Boot. after recovery e.g. if you want to get a U-Boot command line. - Sometimes U-Boot will try to load an environment from some memory device, which can cause issues. Setting `CONFIG_ENV_IS_NOWHERE=y` can help avoid this. -- If you change USB gadget VID:PID values - `CONFIG_USB_GADGET_VENDOR/PRODUCT_NUM`, this will also change SPL's gadget id, - so make sure to pass the "usb" firmware parameter when relevant. Note that you - should not do this with i.MX SoCs! This is due to USB IDs being used by - snagrecover to match protocols during i.MX recovery. - If you want to use snagflash after recovery, make sure to write down the - aforementioned `CONFIG_USB_GADGET_VENDOR/PRODUCT_NUM` values so that you can + `CONFIG_USB_GADGET_VENDOR/PRODUCT_NUM` values so that you can pass them to snagflash and setup proper udev rules so that you have rw access - rights to the corresponding USB device. See [snagflash docs](snagflash.md) for more - details. + rights to the corresponding USB device. See [snagflash docs](snagflash.md) + for more details. ## For ST STM32MP1 devices @@ -66,13 +61,11 @@ configuration: **tf-a:** Arm-trusted firmware BL2, with an stm32 image header. In typical build strategies, you have to pass your U-Boot binary to the tf-a build -process. If you change the USB VID/PID values used by tf-a, make sure to pass -the "usb" firmware parameter. For the secure firmware, use SP_MIN if available. +process. For the secure firmware, use SP_MIN if available. OPTEE can also work. configuration: * path - * usb vid:pid or bus-port1.port2.[...] (only if you have configured custom USB IDs in TF-A) ### Example build process for an stm32mp15-based board @@ -253,12 +246,10 @@ Snagrecover and SD Boot defconfig for Snagflash. AM62x/AM62Ax/AM62Px SoCs require multiple complex firmware images to boot. **tiboot3:** X.509 certificate container with U-Boot SPL for R5, TIFS, and a FIT -container with device tree blobs. If you change U-Boot's USB VID/PID, you should -specify them with the usb firmware parameter. SPL should support DFU. +container with device tree blobs. SPL should support DFU. configuration: * path - * usb vid:pid or bus-port1.port2.[...] (only if you have configured custom USB IDs in TF-A) **u-boot:** FIT container with U-Boot proper for A53 and device tree blobs diff --git a/src/snagflash/dfu.py b/src/snagflash/dfu.py index 1d23dc7..f362f3c 100644 --- a/src/snagflash/dfu.py +++ b/src/snagflash/dfu.py @@ -20,7 +20,7 @@ from snagrecover.protocols import dfu import logging logger = logging.getLogger("snagflash") -from snagrecover.utils import parse_usb_addr, get_usb, cli_error, reset_usb +from snagrecover.utils import parse_usb_addr, get_usb, cli_error, reset_usb, access_error from usb.core import Device def dfu_detach(dev: Device, altsetting: int = 0): @@ -52,7 +52,10 @@ def dfu_cli(args): cli_error("missing command line argument --dfu-config") if (args.port is None): cli_error("missing command line argument --port [vid:pid]") - dev = get_usb(parse_usb_addr(args.port)) + usb_addr = parse_usb_addr(args.port) + if usb_addr is None: + access_error("USB DFU", args.port) + dev = get_usb(usb_addr) dev.default_timeout = int(args.timeout) altsetting = 0 if args.dfu_config: diff --git a/src/snagflash/fastboot.py b/src/snagflash/fastboot.py index c32b18f..d46c3d9 100644 --- a/src/snagflash/fastboot.py +++ b/src/snagflash/fastboot.py @@ -18,7 +18,7 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. from snagrecover.protocols import fastboot as fb -from snagrecover.utils import parse_usb_addr, get_usb +from snagrecover.utils import parse_usb_addr, get_usb, access_error import sys import logging logger = logging.getLogger("snagflash") @@ -27,7 +27,12 @@ def fastboot(args): if (args.port is None): print("Error: Missing command line argument --port vid:pid|bus-port1.port2.[...]") sys.exit(-1) - dev = get_usb(parse_usb_addr(args.port)) + + usb_addr = parse_usb_addr(args.port) + if usb_addr is None: + access_error("USB Fastboot", args.port) + + dev = get_usb(usb_addr) dev.default_timeout = int(args.timeout) fast = fb.Fastboot(dev) # this is mostly there to dodge a linter error diff --git a/src/snagrecover/cli.py b/src/snagrecover/cli.py index cd4bea5..b7f9de6 100644 --- a/src/snagrecover/cli.py +++ b/src/snagrecover/cli.py @@ -20,7 +20,7 @@ import sys import argparse from snagrecover import __version__ -from snagrecover.utils import cli_error +from snagrecover.utils import cli_error, get_recovery import snagrecover.config as config import yaml import os @@ -50,7 +50,8 @@ def cli(): optional.add_argument("--netns", help="network namespace for AM335x USB recovery, defaults to 'snagbootnet'", default="snagbootnet") optional.add_argument("--loglevel", help="set loglevel", choices=["silent","info","debug"], default="silent") optional.add_argument("--logfile", help="set logfile", default="board_recovery.log") - optional.add_argument("--rom-usb", help="USB ID used by ROM code", metavar="vid:pid|bus-port1.port2.[...]") + optional.add_argument("--rom-usb", help="legacy, please use --usb-path") + optional.add_argument("--usb-path", help="address of recovery USB device", metavar="vid:pid|bus-port1.port2.[...]") utilargs = parser.add_argument_group("Utilities") utilargs.add_argument("--list-socs", help="list supported socs", action="store_true") utilargs.add_argument("--version", help="show version", action="store_true") @@ -125,26 +126,10 @@ def cli(): soc_family = config.recovery_config["soc_family"] print(f"Starting recovery of {soc_model} board") logger.info(f"Starting recovery of {soc_model} board") - if soc_family == "stm32mp1": - from snagrecover.recoveries.stm32mp1 import main as stm32_recovery - stm32_recovery() - elif soc_family == "sama5": - from snagrecover.recoveries.sama5 import main as sama5_recovery - sama5_recovery() - elif soc_family == "imx": - from snagrecover.recoveries.imx import main as imx_recovery - imx_recovery() - elif soc_family == "am335x": - from snagrecover.recoveries.am335x import main as am335x_recovery - am335x_recovery() - elif soc_family == "sunxi": - from snagrecover.recoveries.sunxi import main as sunxi_recovery - sunxi_recovery() - elif soc_family == "am62x": - from snagrecover.recoveries.am62x import main as am62x_recovery - am62x_recovery() - else: - cli_error(f"unsupported board family {soc_family}") + + recovery = get_recovery(soc_family) + recovery() + print(f"Done recovering {soc_model} board") if args.loglevel != "silent": print(f"Logs were appended to {args.logfile}") diff --git a/src/snagrecover/config.py b/src/snagrecover/config.py index 1eaa211..46f89b3 100644 --- a/src/snagrecover/config.py +++ b/src/snagrecover/config.py @@ -18,7 +18,7 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. import yaml -from snagrecover.utils import cli_error, parse_usb_addr +from snagrecover.utils import cli_error, parse_usb_addr, get_family, access_error import logging logger = logging.getLogger("snagrecover") import os @@ -55,12 +55,6 @@ recovery_config = {} # Global immutable config to be initialized with CLI args -def get_family(soc_model: str) -> str: - with open(os.path.dirname(__file__) + "/supported_socs.yaml", "r") as file: - socs = yaml.safe_load(file) - family = {**socs["tested"], **socs["untested"]}[soc_model]["family"] - return family - def check_soc_model(soc_model: str): with open(os.path.dirname(__file__) + "/supported_socs.yaml", "r") as file: socs = yaml.safe_load(file) @@ -76,14 +70,20 @@ def init_config(args: list): recovery_config.update({"soc_model": soc_model}) soc_family = get_family(soc_model) recovery_config.update({"soc_family": soc_family}) + + if args.rom_usb is not None and args.usb_path is None: + args.usb_path = args.rom_usb + if soc_family != "am335x": - if args.rom_usb is None: + if args.usb_path is None: if soc_family == "imx": - recovery_config["rom_usb"] = default_usb_ids["imx"][soc_model] + recovery_config["usb_path"] = default_usb_ids["imx"][soc_model] else: - recovery_config["rom_usb"] = default_usb_ids[soc_family] + recovery_config["usb_path"] = default_usb_ids[soc_family] else: - recovery_config["rom_usb"] = parse_usb_addr(args.rom_usb) + recovery_config["usb_path"] = parse_usb_addr(args.usb_path) + if recovery_config["usb_path"] is None: + access_error("USB", args.usb_path) fw_configs = {} if args.firmware: diff --git a/src/snagrecover/recoveries/am62x.py b/src/snagrecover/recoveries/am62x.py index f8a2b98..161fa3d 100644 --- a/src/snagrecover/recoveries/am62x.py +++ b/src/snagrecover/recoveries/am62x.py @@ -2,20 +2,18 @@ import logging logger = logging.getLogger("snagrecover") from snagrecover.firmware.firmware import run_firmware -from snagrecover.utils import parse_usb_addr, get_usb +from snagrecover.utils import get_usb from snagrecover.config import recovery_config import time def main(): - usb_addr = recovery_config["rom_usb"] + usb_addr = recovery_config["usb_path"] dev = get_usb(usb_addr) run_firmware(dev, "tiboot3") # USB device should re-enumerate at this point usb.util.dispose_resources(dev) # without this delay, USB device will be present but not ready time.sleep(1) - if "usb" in recovery_config["firmware"]["tiboot3"]: - usb_addr = parse_usb_addr(recovery_config["firmware"]["tiboot3"]["usb"]) dev = get_usb(usb_addr) diff --git a/src/snagrecover/recoveries/imx.py b/src/snagrecover/recoveries/imx.py index 97e4bf7..9e9e0d1 100644 --- a/src/snagrecover/recoveries/imx.py +++ b/src/snagrecover/recoveries/imx.py @@ -124,7 +124,7 @@ def write(self, data, timeout=None): def main(): soc_model = recovery_config["soc_model"] - usb_dev = get_usb(recovery_config["rom_usb"]) + usb_dev = get_usb(recovery_config["usb_path"]) if soc_model in raw_bulk_ep_socs: sdp_cmd = SDPCommand(build_raw_ep_dev(usb_dev)) diff --git a/src/snagrecover/recoveries/sama5.py b/src/snagrecover/recoveries/sama5.py index 54b55fb..c4961fd 100644 --- a/src/snagrecover/recoveries/sama5.py +++ b/src/snagrecover/recoveries/sama5.py @@ -97,7 +97,7 @@ def main(): print("Connecting to SAM-BA monitor...") soc_model = recovery_config["soc_model"] - dev = get_usb(recovery_config["rom_usb"]) + dev = get_usb(recovery_config["usb_path"]) # SAM-BA monitor needs a reset sometimes dev.reset() diff --git a/src/snagrecover/recoveries/stm32mp1.py b/src/snagrecover/recoveries/stm32mp1.py index 0d3d594..2515176 100644 --- a/src/snagrecover/recoveries/stm32mp1.py +++ b/src/snagrecover/recoveries/stm32mp1.py @@ -24,7 +24,7 @@ from snagrecover.recoveries import stm32_flashlayout as flashlayout from snagrecover.firmware.firmware import run_firmware from snagrecover.config import recovery_config -from snagrecover.utils import parse_usb_addr, get_usb +from snagrecover.utils import get_usb import logging logger = logging.getLogger("snagrecover") @@ -33,7 +33,7 @@ def main(): soc_model = recovery_config["soc_model"] # USB ENUMERATION - usb_addr = recovery_config["rom_usb"] + usb_addr = recovery_config["usb_path"] dev = get_usb(usb_addr) cfg = dev.get_active_configuration() logger.debug("USB config:") @@ -65,15 +65,14 @@ def main(): # DOWNLOAD U-BOOT if soc_model == "stm32mp13": time.sleep(1.5) - # We need to reset here, in the case where TF-A uses a different USB ID - if "usb" in recovery_config["firmware"]["tf-a"]: - usb_addr = parse_usb_addr(recovery_config["firmware"]["tf-a"]["usb"]) - try: - dev.reset() - except usb.core.USBError: - # this should actually fail - pass - time.sleep(0.5) + + try: + dev.reset() + except usb.core.USBError: + # this should actually fail + pass + time.sleep(0.5) + usb.util.dispose_resources(dev) dev = get_usb(usb_addr) cfg = dev.get_active_configuration() diff --git a/src/snagrecover/recoveries/sunxi.py b/src/snagrecover/recoveries/sunxi.py index 63033dd..cd593f1 100644 --- a/src/snagrecover/recoveries/sunxi.py +++ b/src/snagrecover/recoveries/sunxi.py @@ -30,7 +30,7 @@ def main(): # Try to reset device - usb_addr = recovery_config["rom_usb"] + usb_addr = recovery_config["usb_path"] if is_usb_path(usb_addr): find_usb = functools.partial(usb.core.find, bus=usb_addr[0], diff --git a/src/snagrecover/utils.py b/src/snagrecover/utils.py index 1acfb56..7bf4467 100644 --- a/src/snagrecover/utils.py +++ b/src/snagrecover/utils.py @@ -3,10 +3,17 @@ import usb import time import functools +import yaml +import os USB_RETRIES = 5 USB_INTERVAL = 1 +def get_family(soc_model: str) -> str: + with open(os.path.dirname(__file__) + "/supported_socs.yaml", "r") as file: + socs = yaml.safe_load(file) + family = {**socs["tested"], **socs["untested"]}[soc_model]["family"] + return family def is_usb_path(usb_addr) -> bool: return isinstance(usb_addr, tuple) and isinstance(usb_addr[1], tuple) @@ -41,13 +48,33 @@ def parse_usb_path(path: str) -> tuple: port_tuple = tuple([int(x) for x in port_numbers]) return (int(groups[0]), port_tuple) -def parse_usb_addr(usb_addr: str) -> tuple: +def find_usb_paths(usb_id: tuple) -> list: + (vid,pid) = usb_id + usb_paths = [] + + print(f"Searching for USB device paths matching {prettify_usb_addr((vid,pid))}...") + + devices = usb.core.find(idVendor=vid, idProduct=pid, find_all=True) + + for dev in devices: + usb_paths.append((dev.bus, dev.port_numbers)) + + return usb_paths + +def parse_usb_addr(usb_addr: str, find_all=False) -> tuple: """ parses vid:pid addresses into (vid,pid) and bus-port1.port2.[...] into (bus, (port1,port2,...)) """ if ":" in usb_addr: - return parse_usb_ids(usb_addr) + usb_id = parse_usb_ids(usb_addr) + usb_paths = find_usb_paths(usb_id) + if usb_paths == []: + return None + if find_all: + return usb_paths + else: + return usb_paths[0] else: return parse_usb_path(usb_addr) @@ -102,8 +129,31 @@ def dnload_iter(blob: bytes, chunk_size: int): L = len(blob) N = L // chunk_size R = L % chunk_size + for i in range(N): yield blob[chunk_size * i:chunk_size * (i + 1)] if R > 0: yield blob[chunk_size * N:chunk_size * N + R] +def get_recovery(soc_family: str): + if soc_family == "stm32mp1": + from snagrecover.recoveries.stm32mp1 import main as stm32_recovery + return stm32_recovery + elif soc_family == "sama5": + from snagrecover.recoveries.sama5 import main as sama5_recovery + return sama5_recovery + elif soc_family == "imx": + from snagrecover.recoveries.imx import main as imx_recovery + return imx_recovery + elif soc_family == "am335x": + from snagrecover.recoveries.am335x import main as am335x_recovery + return am335x_recovery + elif soc_family == "sunxi": + from snagrecover.recoveries.sunxi import main as sunxi_recovery + return sunxi_recovery + elif soc_family == "am62x": + from snagrecover.recoveries.am62x import main as am62x_recovery + return am62x_recovery + else: + cli_error(f"unsupported board family {soc_family}") +