1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453
|
#!/usr/bin/env python3
#
# Copyright 2019 Ettus Research, a National Instruments Brand
#
# SPDX-License-Identifier: GPL-3.0-or-later
#
"""
Update the CPLD image for a ZBX daughterboard
"""
import argparse
import os
import re
import subprocess
import sys
import pyudev
from usrp_mpm.chips.max10_cpld_flash_ctrl import Max10CpldFlashCtrl
from usrp_mpm.mpmlog import get_logger, get_main_logger
from usrp_mpm.mpmutils import check_fpga_state
from usrp_mpm.periph_manager.x4xx_mb_cpld import make_mb_cpld_ctrl
from usrp_mpm.periph_manager.x4xx_periphs import CtrlportRegs
from usrp_mpm.sys_utils.sysfs_gpio import GPIOBank
from usrp_mpm.sys_utils.udev import dt_symbol_get_spidev
OPENOCD_DIR = "/usr/share/openocd/scripts"
AXI_BITQ_ADAPTER_SPEED = 5000
AXI_BITQ_BUS_CLK = 50000000
# The offsets are for JTAG_DB0 and JTAG_DB1 on the motherboard CPLD
DAUGHTERBOARD0_OFFSET = CtrlportRegs.MB_PL_CPLD + 0x60
DAUGHTERBOARD1_OFFSET = CtrlportRegs.MB_PL_CPLD + 0x80
# ZBX flash reconfiguration engine specific offsets
RECONFIG_ENGINE_OFFSET = 0x20
CPLD_MIN_REVISION = 0x20052016
ZBX_PID = 0x4002
def check_openocd_files(files, logger=None):
"""
Check if all file required by OpenOCD exist
:param logger: logger object
"""
for ocd_file in files:
if not os.path.exists(os.path.join(OPENOCD_DIR, ocd_file)):
if logger is not None:
logger.error("Missing file %s" % os.path.join(OPENOCD_DIR, ocd_file))
return False
return True
def find_offset(dboard):
"""
Find the AXI Bitq UIO device
:param dboard: the dboard, can be either 0 or 1
"""
assert dboard in (0, 1)
return DAUGHTERBOARD0_OFFSET if dboard == 0 else DAUGHTERBOARD1_OFFSET
def find_axi_bitq_uio():
"""
Find the AXI Bitq UIO device
"""
label = "ctrlport-mboard-regs"
logger = get_logger("update_cpld")
try:
context = pyudev.Context()
for uio in context.list_devices(subsystem="uio"):
uio_label = uio.attributes.asstring("maps/map0/name")
logger.trace(
"UIO label: {}, match: {} number: {}".format(
uio_label, uio_label == label, uio.sys_number
)
)
if uio_label == label:
return int(uio.sys_number)
return None
except OSError as ex:
logger.error("Error while looking for axi_bitq uio nodes: {}".format(ex))
return None
def do_update_cpld(dboard_update_settings):
"""
Carry out update process for the CPLD
:param dboard_update_settings: list of db and corresponding path (on device) to the new CPLD image
and updater mode to use
:return: True on success, False otherwise
"""
logger = get_logger("update_cpld")
if not dboard_update_settings:
logger.error("Invalid daughterboard selection.")
return False
if not check_fpga_state(logger=logger):
logger.error("CPLD lines are routed through fabric, FPGA is not programmed, giving up")
return False
for dboard_update_setting in dboard_update_settings:
dboard = dboard_update_setting[0]
filename = dboard_update_setting[1]
updater_mode = dboard_update_setting[2]
cpld_update_strategies = dboard_update_setting[3]
logger.info(
"Programming CPLD of dboard {} with image {} using {} mode".format(
dboard, filename, updater_mode
)
)
if not os.path.exists(filename):
logger.error("CPLD image file {} not found".format(filename))
return False
if updater_mode == "legacy":
success = jtag_cpld_update(filename, dboard, cpld_update_strategies, logger)
elif updater_mode == "flash":
success = flash_cpld_update(filename, dboard, cpld_update_strategies, logger)
else:
raise NotImplementedError("Unknown updater mode {}".format(updater_mode))
if not success:
return success
return True
def get_db_pid(slot):
assert slot in [0, 1]
cmd = ["eeprom-dump", "db{}".format(slot)]
output = subprocess.check_output(
cmd,
stderr=subprocess.STDOUT,
).decode("utf-8")
expression = re.compile(r"^usrp_eeprom_board_info \(0x..\) pid: 0x([0-9A-Fa-f]+)")
for line in output.splitlines():
match = expression.match(line)
if match:
pid = int(match.group(1), 16)
return pid
raise AssertionError("Cannot get pid from DB{} eeprom.: `{}'".format(slot, output))
def get_db_rev(slot):
assert slot in [0, 1]
cmd = ["eeprom-dump", "db{}".format(slot)]
output = subprocess.check_output(
cmd,
stderr=subprocess.STDOUT,
).decode("utf-8")
expression = re.compile("^usrp_eeprom_board_info.*rev: 0x([0-9A-Fa-f]+).*compat_rev:.*")
for line in output.splitlines():
match = expression.match(line)
if match:
rev = int(match.group(1), 16)
return rev
raise AssertionError("Cannot get rev from DB{} eeprom.: `{}'".format(slot, output))
def get_cpld_update_strategies(rev):
"""Determine the CPLD update strategies based on the rev"""
cpld_image_10m04_update_strategies = {
"cpld_model": "10m04",
"updaters": ["flash", "legacy"],
"default_updater": "flash",
"image_names": {
"flash": ["cpld-zbx-10m04.rpd", "usrp_zbx_cpld_10m04.rpd"],
"legacy": ["cpld-zbx-10m04.svf", "usrp_zbx_cpld_10m04.svf"],
},
"updater_config": {
"legacy": {
"files": ["fpga/altera-10m50.cfg"],
"cmd": [
"interface axi_bitq; axi_bitq_config %u %u %u; adapter_khz %u",
"init; svf -tap 10m50.tap %s -progress -quiet;exit",
],
}
},
}
cpld_image_xo3lf_update_strategies = {
"cpld_model": "xo3lf",
"updaters": ["legacy"],
"default_updater": "legacy",
"image_names": {"legacy": ["cpld-zbx-xo3lf.svf", "usrp_zbx_cpld_xo3lf.svf"]},
"updater_config": {
"legacy": {
"files": ["fpga/lattice-xo3lf.cfg"],
"cmd": [
"interface axi_bitq; axi_bitq_config %u %u %u; adapter_khz %u",
"init; svf -tap xo3lf.tap %s -progress -quiet;exit",
],
}
},
}
cpld_update_strategies = {
1: cpld_image_10m04_update_strategies, # revA
2: cpld_image_10m04_update_strategies, # revB
3: cpld_image_10m04_update_strategies, # revC
4: cpld_image_xo3lf_update_strategies, # revD
5: cpld_image_xo3lf_update_strategies, # revE
6: cpld_image_10m04_update_strategies, # revF
"10m04": cpld_image_10m04_update_strategies, # 10m04
"xo3lf": cpld_image_xo3lf_update_strategies, # xo3lf
}
if rev not in cpld_update_strategies:
raise NotImplementedError(
"The CPLD update strategy for rev or CPLD model {} is not available".format(rev)
)
return cpld_update_strategies[rev]
def flash_cpld_update(filename, dboard, cpld_update_strategies=None, logger=None):
"""
Carry out update process for the CPLD using flash mode
:param filename: path (on device) to the new CPLD image
:param dboard: dboard to update
:return: True on success, False otherwise
"""
dboard = int(dboard, 10)
logger.info(f"Updating daughterboard slot {dboard}...")
# enable required daughterboard clock
cpld_spi_node = dt_symbol_get_spidev("mb_cpld")
cpld_control = make_mb_cpld_ctrl(cpld_spi_node, logger)
cpld_control.enable_daughterboard_support_clock(dboard, enable=True)
# setup flash configuration engine and required register access
label = "ctrlport-mboard-regs"
ctrlport_regs = CtrlportRegs(label, logger)
regs = ctrlport_regs.get_db_cpld_iface(dboard)
flash_control = Max10CpldFlashCtrl(logger, regs, RECONFIG_ENGINE_OFFSET, CPLD_MIN_REVISION)
success = flash_control.update(filename)
# disable clock
cpld_control.enable_daughterboard_support_clock(dboard, enable=False)
if success:
logger.trace("Done programming CPLD...")
return success
def jtag_cpld_update(filename, dboard, cpld_update_strategies, logger=None):
"""
Carry out update process for the CPLD
:param filename: path (on device) to the new CPLD image
:param dboard: dboard to update
:param cpld_update_strategies: a data struct containing updaters, image names, metadata.
:return: True on success, False otherwise
"""
config = cpld_update_strategies["updater_config"]["legacy"]
if check_openocd_files(config["files"], logger=logger):
logger.trace("Found required OpenOCD files.")
else:
# check_openocd_files logs errors
return False
logger.info("Updating daughterboard slot {}...".format(dboard))
uio_id = find_axi_bitq_uio()
offset = find_offset(int(dboard, 10))
if uio_id is None or uio_id < 0:
logger.error("Failed to find axi_bitq uio devices. " "Make sure overlays are up to date")
return False
cmd = [
"openocd",
"-c",
config["cmd"][0] % (uio_id, AXI_BITQ_BUS_CLK, offset, AXI_BITQ_ADAPTER_SPEED),
"-f",
(config["files"][0]).strip(),
"-c",
config["cmd"][1] % filename,
]
logger.trace("Update CPLD CMD: {}".format(" ".join(cmd)))
subprocess.call(cmd)
logger.trace("Done programming CPLD...")
return True
def main():
"""
Go, go, go!
"""
# Do some setup
def parse_args():
"""Parse the command-line arguments"""
parser = argparse.ArgumentParser(description="Update the CPLD image on ZBX daughterboard")
parser.add_argument(
"--file",
help="Filename of CPLD image. Also specify the updater using"
" --updater arg when using this argument.",
default="",
)
parser.add_argument("--dboards", help="Slot name to program", default="0,1")
parser.add_argument(
"--updater",
help="The image updater method to use, either " " 'legacy' (uses openocd) or 'flash'",
default="",
)
parser.add_argument(
"-v", "--verbose", help="Increase verbosity level", action="count", default=1
)
parser.add_argument(
"-q", "--quiet", help="Decrease verbosity level", action="count", default=0
)
parser.add_argument(
"--force",
help="Force installing the CPLD image specified by the --file "
"argument if it does not match the name of the default CPLD image. "
"Using the wrong CPLD image may brick your device.",
action="store_true",
default=False,
required=False,
)
parser.add_argument(
"--cpld_type",
help="Specify the CPLD type. Currently supported types are '10m04' or 'xo3lf'."
" Use this argument to explicitly specify the CPLD hardware type and"
" skip internal revision compatibilty checks. Use this only if you"
" fully understand the hardware being used."
" e.g. zbx_update_cpld --cpld_type '10m04' ",
default=None,
)
args = parser.parse_args()
dboards = args.dboards.split(",")
if any([x not in ("0", "1") for x in dboards]):
log.error("Unsupported dboards requested: %s", dboards)
return False
if args.cpld_type:
# Hardware Specified. Skip all checks.
return args
if args.file and not args.updater:
parser.epilog = (
"\nERROR: When setting --file, please also specify the updater to use "
"using the --updater argument."
)
parser.print_help()
parser.epilog = None
sys.exit(1)
for dboard in dboards:
dboard_pid = get_db_pid(int(dboard, 10))
if dboard_pid != ZBX_PID:
parser.epilog = (
"\nERROR: Daughterboard in slot {} is not a ZBX daughterboard "
"(expected PID 0x{:04x} but found 0x{:04x})".format(dboard, ZBX_PID, dboard_pid)
)
parser.print_help()
sys.exit(1)
dboard_rev = get_db_rev(int(dboard, 10))
cpld_update_strategies = get_cpld_update_strategies(dboard_rev)
if args.updater and args.updater not in cpld_update_strategies["updaters"]:
parser.epilog = (
"\nERROR: Valid updaters for zbx rev {} are {}, "
"but you selected {}.".format(
dboard_rev, " and ".join(cpld_update_strategies["updaters"]), args.updater
)
)
parser.print_help()
parser.epilog = None
sys.exit(1)
default_image_names = []
for updater in cpld_update_strategies["updaters"]:
default_image_names += cpld_update_strategies["image_names"][updater]
if (
args.file
and (os.path.basename(args.file) not in default_image_names)
and not args.force
):
parser.epilog = (
"\nERROR: Valid CPLD image names for zbx rev {} are {}, "
"but you selected {}. Using the wrong CPLD image may brick your device. "
"Please use the --force option if you are really sure.".format(
dboard_rev, " and ".join(default_image_names), args.file
)
)
parser.print_help()
parser.epilog = None
sys.exit(1)
if (
args.file
and args.updater
and (
os.path.basename(args.file)
not in cpld_update_strategies["image_names"][args.updater]
)
and not args.force
):
parser.epilog = (
"\nERROR: Invalid CPLD image name and updater mode combination for zbx rev {}"
"Using the wrong CPLD image may brick your device. "
"Please use the --force option if you are really sure.".format(
dboard_rev,
)
)
parser.print_help()
parser.epilog = None
sys.exit(1)
return args
def get_dboard_update_settings(args, dboards):
"""Determine the dboard cpld image and updater mappings"""
dboard_update_settings = []
for dboard in dboards:
dboard_rev = get_db_rev(int(dboard, 10))
if args.cpld_type:
cpld_update_strategies = get_cpld_update_strategies(args.cpld_type)
else:
cpld_update_strategies = get_cpld_update_strategies(dboard_rev)
if args.updater:
updater = args.updater
else:
updater = cpld_update_strategies["default_updater"]
if args.file:
image_path = args.file
else:
image_names = cpld_update_strategies["image_names"][updater]
image_path = "/lib/firmware/ni/" + image_names[0]
dboard_update_settings.append([dboard, image_path, updater, cpld_update_strategies])
return dboard_update_settings
args = parse_args()
log = get_main_logger(log_default_delta=args.verbose - args.quiet)
dboards = args.dboards.split(",")
return do_update_cpld(get_dboard_update_settings(args, dboards))
if __name__ == "__main__":
sys.exit(not main())
|