Source code for ska_low_cbf_fpga.describe_firmware

# -*- coding: utf-8 -*-
#
# Copyright (c) 2025 CSIRO Space and Astronomy.
#
# Distributed under the terms of the CSIRO Open Source Software Licence
# Agreement. See LICENSE for more info.

"""
Tools for describing the FPGA firmware in use or available on a card.
"""

import logging
from typing import Callable

from ska_low_cbf_fpga.args_ami_tool import ArgsAmi, BootDeviceType, PartitionSpec
from ska_low_cbf_fpga.args_fpga import ArgsFpgaDriver, FpgaRegisterError
from ska_low_cbf_fpga.args_map import ArgsMap
from ska_low_cbf_fpga.fpga_icl import FpgaPersonality


[docs] def describe_firmware( driver: ArgsFpgaDriver, map_dir: str = "./", personality_to_package: Callable[[str], str] = lambda x: x, ) -> dict[str, str | int | bool | None]: """ Describe the firmware in use by an FPGA driver. :param driver: An instance of ArgsFpgaDriver used to interact with the FPGA. :param map_dir: The directory to check for the ARGS FPGA register map file. Defaults to the current working directory. Note that subdirectories will be searched as well. :param personality_to_package: A callable that converts the FPGA personality four-letter code to the corresponding canonical package name. Defaults to a no-op lambda function that returns the input value unchanged. :return: A dictionary containing entries: - "available": A boolean indicating if the firmware is ready to use. - "map_build": The ARGS map build timestamp, as a hexadecimal string. This value identifies the corresponding FPGA map file, which is required to access FPGA registers. If the driver fails to read the map build register, this value will be ``None`` (suggests an error in the underlying FPGA driver or hardware). - "package": The package name for the firmware image, or ``None`` if unavailable (probably caused by a missing map file). - "version": The firmware version or ``None`` if unavailable (probably caused by a missing map file). """ firmware = {"available": False, "map_build": None, "package": None, "version": None} try: map_build = driver.get_map_build() # Report map build for easier debugging when map file not found firmware["map_build"] = f"{map_build:#010x}" except FpgaRegisterError as e: logging.warning("%s", e) return firmware # We need the right ARGS register map to be able to use the firmware. # Build date and Magic number are the only # registers with guaranteed addresses. # Try to load the map corresponding to our build date. try: args_map = ArgsMap.create_from_file(map_build, map_dir) except (FpgaRegisterError, FileNotFoundError) as e: logging.warning( "Failed to create ArgsMap for build %#010x: %s", map_build, e, ) return firmware try: fpga = FpgaPersonality(driver=driver, map_=args_map) personality = fpga.fw_personality.value version = fpga.fw_version.value logging.debug("This firmware: %s %s", personality, version) firmware.update( { "available": True, "package": personality_to_package(personality), "version": version, } ) except FpgaRegisterError as e: logging.warning("Failed to read firmware details from FPGA: %s", e) return firmware
[docs] def describe_partitions( device: str, map_dir: str = "./", personality_to_package: Callable[[str], str] = lambda x: x, ) -> list[dict[str, str | int | bool | None]]: """ Describe the firmware in each partition of a given AMI-based FPGA device. This function inspects the partitions of an FPGA device, attempting to read information about the firmware in each partition. It compiles this data into a list of dictionaries, with one dictionary representing each partition's details. .. warning:: **All partitions must be programmed with a valid ARGS firmware image.** This function will boot from each partition to inspect it, and booting from an un-programmed flash partition can make the card unusable. A host reboot will be required to recover in this circumstance. :param device: The PCIe device identifier of the FPGA to inspect. :param map_dir: The directory containing ARGS FPGA register map files. Defaults to the current working directory. :param personality_to_package: A callable that converts the FPGA personality four-letter code to the corresponding canonical package name. Defaults to a no-op lambda function that returns the input value unchanged. :returns: A list of dictionaries, each containing information about a partition on the device. Each dictionary contains entries: - "partition": The partition index. - "available": A boolean indicating if the firmware is ready to use. - "map_build": The ARGS map build timestamp, as a hexadecimal string. This value identifies the corresponding FPGA map file, which is required to access FPGA registers. If the driver fails to read the map build register, this value will be ``None`` (suggests an error in the underlying FPGA driver or hardware). - "package": The package name for the firmware image, or ``None`` if unavailable (probably caused by a missing map file). - "version": The firmware version or ``None`` if unavailable (probably caused by a missing map file). """ images = [] partition_spec = PartitionSpec(partition=0, boot_type=BootDeviceType.PRIMARY) driver = ArgsAmi(device=device, partition=partition_spec, firmware=None) for i in range(driver.n_partitions): partition = {"partition": i} images.append(partition) if i > 0: # for iteration 0 we already have our driver object partition_spec.partition = i driver = ArgsAmi(device=device, partition=partition_spec, firmware=None) partition.update(describe_firmware(driver, map_dir, personality_to_package)) return images