Kévin Redon | e88be9e | 2020-01-14 18:19:48 +0100 | [diff] [blame] | 1 | #!/usr/bin/env python |
| 2 | # encoding: utf-8 |
| 3 | # python: 3.8.1 |
| 4 | |
| 5 | # library to enumerate USB devices |
| 6 | import usb.core |
| 7 | from usb.util import * |
| 8 | # more elegant structure |
| 9 | from typing import NamedTuple |
| 10 | # regular expressions utilities |
| 11 | import re |
| 12 | # open utilities to handle files |
| 13 | import os, sys |
| 14 | # to download the firmwares |
| 15 | import urllib.request |
| 16 | # to flash using DFU-util |
| 17 | import subprocess |
| 18 | |
| 19 | # SIMtrace 2 device information |
| 20 | class Device(NamedTuple): |
| 21 | usb_vendor_id: int |
| 22 | usb_product_id: int |
| 23 | name: str |
| 24 | url: dict # 1: sniff/trace firmware, 2: card emulation firmware |
| 25 | |
| 26 | # SIMtrace 2 devices definitions |
| 27 | DEVICE_SIMTRACE = Device(usb_vendor_id=0x1d50, usb_product_id=0x60e3, name="SIMtrace 2", url={"trace": "https://ftp.osmocom.org/binaries/simtrace2/firmware/latest/simtrace-trace-dfu-latest.bin", "cardem": "https://osmocom.org/attachments/download/3868/simtrace-cardem-dfu.bin"}) |
| 28 | DEVICE_QMOD = Device(usb_vendor_id=0x1d50, usb_product_id=0x4004, name="sysmoQMOD (Quad Modem)", url={"cardem": "https://ftp.osmocom.org/binaries/simtrace2/firmware/latest/qmod-cardem-dfu-latest.bin"}) |
| 29 | DEVICE_OWHW = Device(usb_vendor_id=0x1d50, usb_product_id=0x4001, name="OWHW", url={"cardem": "https://ftp.osmocom.org/binaries/simtrace2/firmware/latest/owhw-cardem-dfu-latest.bin"}) |
| 30 | DEVICES = [DEVICE_SIMTRACE, DEVICE_QMOD] |
| 31 | |
| 32 | # which firmware does the SIMtrace USN interface subclass correspond |
| 33 | FIRMWARE_SUBCLASS = {1: "trace", 2: "cardem"} |
| 34 | |
| 35 | def print_help(): |
| 36 | print("this script will flash SIMtrace 2 - based devices") |
| 37 | print("when no argument is provided, it will try to flash the application firmware of all SIMtrace 2 devices connected to USB with the latest version") |
| 38 | print("to flash a specific firmware, provide the name as argument") |
| 39 | print("the possible firmwares are: trace, cardem") |
| 40 | print("to list all devices connected to USB, provide the argument \"list\"") |
| 41 | |
| 42 | # the firmware to flash |
| 43 | to_flash = None |
| 44 | |
| 45 | # parse command line argument |
| 46 | if len(sys.argv) == 2: |
| 47 | to_flash = sys.argv[1] |
| 48 | if to_flash not in ["list", "trace", "cardem"] and len(sys.argv) > 1: |
| 49 | print_help() |
| 50 | exit(0) |
| 51 | |
| 52 | # get all USB devices |
| 53 | devices = [] |
| 54 | devices_nb = 0 |
| 55 | updated_nb = 0 |
| 56 | usb_devices = usb.core.find(find_all=True) |
| 57 | for usb_device in usb_devices: |
| 58 | # find SIMtrace devices |
| 59 | definitions = list(filter(lambda x: x.usb_vendor_id == usb_device.idVendor and x.usb_product_id == usb_device.idProduct, DEVICES)) |
| 60 | if 1 != len(definitions): |
| 61 | continue |
| 62 | devices_nb += 1 |
| 63 | definition = definitions[0] |
| 64 | serial = usb_device.serial_number or "unknown" |
| 65 | usb_path = str(usb_device.bus) + "-" + ".".join(map(str, usb_device.port_numbers)) |
| 66 | print("found " + definition.name + " device (chip ID " + serial + ") at USB path " + usb_path) |
| 67 | # determine if we are running DFU (in most cases the bootloader, but could also be the application) |
| 68 | dfu_interface = None |
| 69 | for configuration in usb_device: |
| 70 | # get DFU interface descriptor |
| 71 | dfu_interface = dfu_interface or find_descriptor(configuration, bInterfaceClass=254, bInterfaceSubClass=1) |
| 72 | if (None == dfu_interface): |
| 73 | print("no DFU USB interface found") |
| 74 | continue |
| 75 | dfu_mode = (2 == dfu_interface.bInterfaceProtocol) # InterfaceProtocol 1 is runtime mode, 2 is DFU mode |
| 76 | # determine firmware type (when not in DFU mode) |
| 77 | firmware = None |
| 78 | simtrace_interface = None |
| 79 | for configuration in usb_device: |
| 80 | simtrace_interface = simtrace_interface or find_descriptor(configuration, bInterfaceClass=255) |
| 81 | if simtrace_interface and simtrace_interface.bInterfaceSubClass in FIRMWARE_SUBCLASS: |
| 82 | firmware = firmware or FIRMWARE_SUBCLASS[simtrace_interface.bInterfaceSubClass] |
| 83 | if dfu_mode: |
| 84 | firmware = 'dfu' |
| 85 | if firmware: |
| 86 | print("installed firmware: " + firmware) |
| 87 | else: |
| 88 | print("unknown installed firmware") |
| 89 | continue |
| 90 | # determine version of the application/bootloader firmware |
| 91 | version = None |
| 92 | version_interface = None |
| 93 | for configuration in usb_device: |
| 94 | # get custom interface with string |
| 95 | version_interface = version_interface or find_descriptor(configuration, bInterfaceClass=255, bInterfaceSubClass=255) |
| 96 | if version_interface and version_interface.iInterface and version_interface.iInterface > 0 and get_string(usb_device, version_interface.iInterface): |
| 97 | version = get_string(usb_device, version_interface.iInterface) |
| 98 | if not version: |
| 99 | # the USB serial is set (in the application) since version 0.5.1.34-e026 from 2019-08-06 |
| 100 | # https://git.osmocom.org/simtrace2/commit/?id=e0265462d8c05ebfa133db2039c2fbe3ebbd286e |
| 101 | # the USB serial is set (in the bootloader) since version 0.5.1.45-ac7e from 2019-11-18 |
| 102 | # https://git.osmocom.org/simtrace2/commit/?id=5db9402a5f346e30288db228157f71c29aefce5a |
| 103 | # the firmware version is set (in the application) since version 0.5.1.37-ede8 from 2019-08-13 |
| 104 | # https://git.osmocom.org/simtrace2/commit/?id=ede87e067dadd07119f24e96261b66ac92b3af6f |
| 105 | # the firmware version is set (in the bootloader) since version 0.5.1.45-ac7e from 2019-11-18 |
| 106 | # https://git.osmocom.org/simtrace2/commit/?id=5db9402a5f346e30288db228157f71c29aefce5a |
| 107 | if dfu_mode: |
| 108 | if serial: |
| 109 | version = "< 0.5.1.45-ac7e" |
| 110 | else: |
| 111 | versoin = "< 0.5.1.45-ac7e" |
| 112 | else: |
| 113 | if serial: |
| 114 | version = "< 0.5.1.37-ede8" |
| 115 | else: |
| 116 | versoin = "< 0.5.1.34-e026" |
| 117 | print("device firmware version: " + version) |
| 118 | # flash latest firmware |
| 119 | if to_flash == "list": # we just want to list the devices, not flash them |
| 120 | continue |
| 121 | # check the firmware exists |
| 122 | if firmware == "dfu" and to_flash is None: |
| 123 | print("device is currently in DFU mode. you need to specify which firmware to flash") |
| 124 | continue |
| 125 | to_flash = to_flash or firmware |
| 126 | if to_flash not in definition.url.keys(): |
| 127 | print("no firmware image available for " + firmware + " firmware") |
| 128 | continue |
| 129 | # download firmware |
| 130 | try: |
| 131 | dl_path, header = urllib.request.urlretrieve(definition.url[to_flash]) |
| 132 | except: |
| 133 | print("could not download firmware " + definition.url[to_flash]) |
| 134 | continue |
| 135 | dl_file = open(dl_path, "rb") |
| 136 | dl_data = dl_file.read() |
| 137 | dl_file.close() |
| 138 | # compare versions |
| 139 | dl_version = re.search(b'firmware \d+\.\d+\.\d+\.\d+-[0-9a-fA-F]{4}', dl_data) |
| 140 | if dl_version is None: |
| 141 | print("could not get version from downloaded firmware image") |
| 142 | os.remove(dl_path) |
| 143 | continue |
| 144 | dl_version = dl_version.group(0).decode("utf-8").split(" ")[1] |
| 145 | print("latest firmware version: " + dl_version) |
| 146 | versions = list(map(lambda x: int(x), version.split(" ")[-1].split("-")[0].split("."))) |
| 147 | dl_versions = list(map(lambda x: int(x), dl_version.split("-")[0].split("."))) |
| 148 | dl_newer = (versions[0] < dl_versions[0] or (versions[0] == dl_versions[0] and versions[1] < dl_versions[1]) or (versions[0] == dl_versions[0] and versions[1] == dl_versions[1] and versions[2] < dl_versions[2]) or (versions[0] == dl_versions[0] and versions[1] == dl_versions[1] and versions[2] == dl_versions[2] and versions[3] < dl_versions[3])) |
| 149 | if not dl_newer: |
| 150 | print("no need to flash latest version") |
| 151 | os.remove(dl_path) |
| 152 | continue |
| 153 | print("flashing latest version") |
| 154 | dfu_result = subprocess.run(["dfu-util", "--device", hex(definition.usb_vendor_id) + ":" + hex(definition.usb_product_id), "--path", usb_path, "--cfg", "1", "--alt", "1", "--reset", "--download", dl_path]) |
| 155 | os.remove(dl_path) |
| 156 | if 0 != dfu_result.returncode: |
| 157 | printf("flashing firmware using dfu-util failed. ensure dfu-util is installed and you have the permissions to access this USB device") |
| 158 | continue |
| 159 | updated_nb += 1 |
| 160 | |
| 161 | print(str(devices_nb)+ " SIMtrace 2 device(s) found") |
| 162 | print(str(updated_nb)+ " SIMtrace 2 device(s) updated") |