Skip to content

Commit

Permalink
snagrecover: hide USB vid:pid addresses from recovery modules
Browse files Browse the repository at this point in the history
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 <romain.gantois@bootlin.com>
  • Loading branch information
rgantois committed Jun 8, 2024
1 parent ddb593b commit 0f6b762
Show file tree
Hide file tree
Showing 11 changed files with 102 additions and 71 deletions.
19 changes: 5 additions & 14 deletions docs/fw_binaries.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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

Expand Down Expand Up @@ -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

Expand Down
7 changes: 5 additions & 2 deletions src/snagflash/dfu.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -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:
Expand Down
9 changes: 7 additions & 2 deletions src/snagflash/fastboot.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand All @@ -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
Expand Down
29 changes: 7 additions & 22 deletions src/snagrecover/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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")
Expand Down Expand Up @@ -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}")
Expand Down
22 changes: 11 additions & 11 deletions src/snagrecover/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand All @@ -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:
Expand Down
6 changes: 2 additions & 4 deletions src/snagrecover/recoveries/am62x.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
2 changes: 1 addition & 1 deletion src/snagrecover/recoveries/imx.py
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down
2 changes: 1 addition & 1 deletion src/snagrecover/recoveries/sama5.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
21 changes: 10 additions & 11 deletions src/snagrecover/recoveries/stm32mp1.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")

Expand All @@ -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:")
Expand Down Expand Up @@ -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()
Expand Down
2 changes: 1 addition & 1 deletion src/snagrecover/recoveries/sunxi.py
Original file line number Diff line number Diff line change
Expand Up @@ -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],
Expand Down
54 changes: 52 additions & 2 deletions src/snagrecover/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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)

Expand Down Expand Up @@ -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}")

0 comments on commit 0f6b762

Please sign in to comment.